Changing an objects material based on what is looked at in Unity - c#

I'm trying to change my objects material colour at run time based on if a ray I'm casting hits a certain object. When I quit my program I'm then wanting it to change back to its original material colour.
My menu with the material I want to switch between has a highlight function which lets the user know the material they are about to select. During the last few days I've been trying to get this to work I've had the following issues:
an instance of my desired material colour is attached to the gameobject but doesn't change the rendered material
the highlight material colour is placed on my game object as opposed to the actual material I want
I've ran out of ideas on how to fix this and I've gotten tunnel vision on this where I need a fresh pair of eyes to look over what I have so far.
Has anyone ever done this before? Could you please tell me what it is I'm doing wrong?
This is the current state of the code I have been working with:
My class storing the original material so that it can switch back to it once the program ends.
public GameObject targetMaterial;
public Color orignalMaterial;
//store GO original colour
void Awake()
{
orignalMaterial = targetMaterial.renderer.material.color;
}
//highlight code
public void ChangeObjectMaterialColour(Color materialColour)
{
targetMaterial.renderer.material.color = materialColour;
}
//
void OnApplicationQuit()
{
targetMaterial.renderer.material.color = orignalMaterial;
}
How I'm trying to change the material colours, I have three different options I want to switch between:
ChangeObjectColour new_colour1;
ChangeObjectColour new_colour2;
ChangeObjectColour new_colour3;
void Start ()
{
new_colour1 = GameObject.Find("Colour 1").GetComponent<ChangeObjectColour>();
new_colour2 = GameObject.Find("Colour 2").GetComponent<ChangeObjectColour>();
new_colour3 = GameObject.Find("Colour 3").GetComponent<ChangeObjectColour>();
}
void CastRay()
{
if (Physics.Raycast(transform.position, fwd, out hit))
{
foreach(string t in _tags)
{
if(hit.collider.gameObject.tag == t)
{
HighLight(hit.collider.gameObject);
hitTaggedObject = true;
}
}
if(hit.collider.gameObject.tag == "Colour1")
{ new_colour1.ChangeObjectMaterialColour(hit.collider.gameObject.renderer.material.color);
}
if(hit.collider.gameObject.tag == "Colour2")
{ new_colour2.ChangeObjectMaterialColour(hit.collider.gameObject.renderer.material.color);
}
if(hit.collider.gameObject.tag == "Colour3")
{
new_colour3.ChangeObjectMaterialColour(hit.collider.gameObject.renderer.material.color);
}
}
Can anyone see anything I'm doing wrong?

I think part of the problem is the way that your accessing the material properties. Here are two extension methods that change the color and material "Not permanently" Also you dont have to change the color back when the applications quits. It will revert back to its original state automatically on exit.
/// <summary>
/// Changes the material attached to the gameObject
/// </summary>
public static void ChangeMaterial(this GameObject go, Material mat)
{
go.renderer.material = mat;
}
/// <summary>
/// Changes the color of the material
/// </summary>
public static void ChangeColor(this Material mat, Color color)
{
mat.SetColor("_Color", color);
}
Ive used these methods hundreds of times in a single runtime session.
Put them in a static class and call it like its a member of its extended class.
For example:
gameObject.renderer.material.ChangeColor(Color.red);
gameObject.ChangeMaterial( /* Your Material */ );
The "_Color" is the name of the color component defined inside of the shader. All of unitys built in shaders have this property. But if you have a custom shader this might not be the case and it could be called something else. Unfortuantly there is no way to programmatically know this, you just have to keep track.

Related

Unity Right Click On Object

I am new to unity. Currently, I am working on a small project.
I am creating a solar system where the player can click on planets and the main camera will zoom in and follow that planet.
The player can also right-click on a particular planet and the name of that planet will be displayed. However, if the player re-clicked the right click again, the name will disappear (need help with that)
I used OnMouseOver with Input.GetMouseButton (see below). But I am stuck and have no idea how to stop displaying the text when the player re-click right-click.
Can anyone please help me :)
What I have used:
private void OnMouseOver()
{
if (Input.GetMouseButtonDown(1))
{
text.SetActive(true);
}
}
You can use 1 variable to storage the current state and switch on/off state.
bool active = false;
private void OnMouseOver()
{
if (Input.GetMouseButtonDown(1))
{
active = !active;
text.SetActive(active);
}
}
Using an additional field like in this answer isn't even necessary.
You can simply do
private void OnMouseOver()
{
if (Input.GetMouseButtonDown(1))
{
text.SetActive(!text.activeSelf);
}
}
See
GameObject.activeSelf

How can I save the material of the player with playerprefs?

I have a game wehre you are a cube and you dodge obstacles, I just implemented the ability to change the color of the cube like changing skin. I did that by assigning a different material to the player when he presses "2".
Here is the script:
void Update()
{
if (Input.GetKeyDown("2"))
{
Object.GetComponent<MeshRenderer>().material = Material1;
}
}
When you die the scene resets and when you win a new scene is loaded, I would like the game to remember the material change even after the scene is reset or a new scene is loaded. I have done some research and found something called "PlayerPrefs" and I have been playing around with it but nothing even got close to working and I didn't really understand what i was doing.
I really want to understand how this works becuase I know i will be using it alot when making games. Can someone help me understand?
Thanks.
Create one gameobject and apply this script
public class SavingMaterial : MonoBehaviour
{
public static SavingMaterial instance;
public Material mat;
void Awake()
{
if(instance == null)
{
instance = this;
DontDestroyOnLoad(base.gameObject);
}
else
{
Destroy(base.gameObject);
}
}
public void StoreMaterial(MeshRenderer mesh)
{
mat = mesh.material;
}
}
When you want to store material call this function like this
SavingMaterial.instance.StoreMaterial(you meshrenderer component);
and when you need this material just get from this class like this
material = SavingMaterial.instance.mat;
and note that if you quit the game you loose saved material as it is store in variable otherwise scene change and reset won't affect it.
you only save int,string and bool in playerprefs. you have to store values of materials into string and then save this string into playerprefs.
alternatively you can assign this material to local variable and set that script on Dontdestroyonload so it will not reset as scene is destroy or reset.

How to detect Collision on specific Object?

Let's suppose we have two cylinders in the scenes with red and blue materials on them. Also, we have two UI images with red and blue background. Now, what should I do to make the red image only draggable onto red cylinder and the blue image only draggable onto blue cylinder.
If I drag the red image onto the blue cylinder, then an error message should appear:
same for dragging blue image on red cylinder or vice versa.
See the picture below
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class wrench : MonoBehaviour {
public bool Dragging = false;
public bool collision = false;
Vector3 position;
public List<GameObject> UIimages;
public void BeginDrag() {
position = gameObject.transform.position;
Dragging = true;
foreach (GameObject obj in UIimages) {
obj.GetComponent<BoxCollider2D> ().enabled = false;
}
}
public void Drag() {
transform.position = Input.mousePosition;
}
public void Drop() {
if (!collision) {
gameObject.transform.position = position;
}
Dragging = false;
}
}
Assuming your dropps are already detected as you say and the question is more about how to differ between the objects I would use a simple enum e.g.
public enum WhichObject
{
red,
blue
}
the advantage is that later you can very easily add more options without having to deal with layers, tags etc.
On the GameObject you drag around add a component e.g.
public class ObjectInformation
{
public WhichObject IsObject;
}
and simply select in the Inspector (or if you spawn them via script) for each of your dragged objects which value the WhichObject variable shall have.
Than on your target objects where you detect the collision also add a WhichObject variable but this time configure the value you expect to be dropped here
public WhichObject ExpectedObject;
again set it via the inspector or script.
Than simply add a check
var info = droppedObject.GetComponent<ObjectInformation>();
// Something else dropped?
if(!info) return;
// Now check if the value matches
// the one this object expects
if(info.IsObject == ExpectedObject)
{
// Correct object
Debug.Log("Yeah!");
}
else
{
// Wrong object
Debug.LogFormat("Dropped object {0} does not match the expected object {1}!", info.IsObject, ExpectedObject);
}
Later you can also simply extend it using multiple enums like e.g. colors, forms etc.
And maybe you could as well make the check the other way round and check on the dragged object, if the values match and if the don't match not even allow to be dropped on that target object (this depends of course on your implementation).

EditorGUILayout.EnumPopup resetting value in custom editor

I have an editor script which is supposed to allow me to change the texture of a tile (I'm making a game board). It works by creating a drop down menu with different "tile types", and then when I select a new tile type from the menu it will change the texture to the appropriate type.
[CustomEditor(typeof(Tile))]
public class TileEditor : Editor {
enum Type {
Blank,
Portal
}
// Represents the type of tile this is
Type type;
public override void OnInspectorGUI() {
Tile tile = (Tile)target;
// Create a dropdown list of tile types
type = (Type)EditorGUILayout.EnumPopup ("Type", type);
switch (type) {
case Type.Blank:
tile.GetComponent<Renderer> ().material = Resources.Load ("Materials/Blank Tile", typeof(Material)) as Material;
break;
case Type.Portal:
tile.GetComponent<Renderer> ().material = Resources.Load("Materials/Portal Tile", typeof(Material)) as Material;
break;
}
}
}
If I remove the lines where I set the materials, the drop down menu works as expected, but with those lines included, when I select Portal from the drop down it will change the texture for just a moment, and then both the texture and the menu will reset themselves back to the first item in the enum (Blank).
What is going on here, and how do I make my selections persist?
Edit
I've narrowed down the issue slightly. When I select the Portal type it executes the Type.Portal case, but then immediately type changes back to Type.Blank and it executes the Type.Blank case. So it seems something is changing my type variable between calls to OnInspectorGUI().
There are just two problems:
It resets to the first enum right away and when you save the scene and load it, it also resets to the first enum when you select the GameObject Tile script is attached to.
Solution 1:
Make Type type static. Simply change Type type; to static Type type;.
This should fix the problem I described above.
But then, you will realize that when you save and re-load your project, the enum is not saved. It resets to index 0 (Type.Blank).
Solution 2:
I hardly work on Editor scripts but to my understanding, the type variable should be declared in the Tile script instead of the TileEditor script. Also, you should re-name that Type enum to something else. Type is part of C# standard API from the System namespace. Re-name it to something like TileType so that you won't confuse yourself in the future.
Declaring it in the Tile script should solve all your problems.
Tile script:
public class Tile : MonoBehaviour
{
public TileType type;
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
TileEditor script:
[CustomEditor(typeof(Tile))]
public class TileEditor : Editor
{
public override void OnInspectorGUI()
{
Tile tile = (Tile)target;
// Create a dropdown list of tile types
tile.type = (TileType)EditorGUILayout.EnumPopup("TileType", tile.type);
Debug.Log("OnInspectorGUI");
switch (tile.type)
{
case TileType.Blank:
tile.GetComponent<Renderer>().material = Resources.Load("Materials/Blank Tile", typeof(Material)) as Material;
Debug.Log("Blank");
break;
case TileType.Portal:
tile.GetComponent<Renderer>().material = Resources.Load("Materials/Portal Tile", typeof(Material)) as Material;
Debug.Log("Portal");
break;
}
}
}
public enum TileType
{
Blank,
Portal
}
You can also improve this by adding a class that loads the materials once then store them in static variable. This will prevent having to load it each time the dropdown is changed. You can learn more about Custom Inspector here.

Switch between multiple materials on a button press

I need to have an object in my scene change between two different materials at run time, when ever a button is pressed in my Unity project. However, I have never done this before and I'm having an issue getting my head around how to do this.
In my scene I have one game object I've called my controller. This script holds my material switching class and is looking like this:
public GameObject cupMesh;
bool isOn = true;
// Use this for initialization
void Start ()
{
cupMesh = GameObject.Find("CupMesh");
}
// Update is called once per frame
void Update ()
{
}
void OnGUI()
{
if(GUI.Button(new Rect(10,10, 100, 40), "Show mesh"))
{
renderer.enabled = false;
}
}
I know this doesn't change the material, but the above code does nothing. I've never modified anything on the mesh renderer before but I know there is a list of materials on it.
How can I access that list so I can have my program switch between the two materials found there?
To show or hide a gameObject, rather than using render.enabled property, you should use this:
// Unactivates the game object.
gameObject.SetActive (false);
However it's not clear from the code if you want to adjust the material of the object the script is attached to, or the cupMesh game object.
If you wanted to make the cupMesh disappear, you would use:
cupMesh.SetActive (false);
Or if you wanted to access the material component of the cupMesh, this is
cupMesh.renderer.material

Categories

Resources