EditorGUILayout.EnumPopup resetting value in custom editor - c#

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.

Related

Dynamically add a Component from a list of components

I will break it down to give an understanding.
I have a parent class Item.
I have child classes for each type of Item (Weapon, Armor, etc).
I have a list of all the items.
I'm trying to randomly grab an item from that list, and then add it as a component.
My items each randomly generate their own names, stats, etc, but they are all specific to the type of item they are and so I needed them to be generated in their own classes and not just generating "Item".
So here is what I have so far.
public Item RandomList(List<Item> list)
{
return list[random.Next(list.Count)];
}
This returns a random item from my list.
public void GenerateLoot(GameObject gameObject, List<Item> list)
{
gameObject.AddComponent(randomManager.RandomList(list));
}
This is where I'm running into problems. It says : "cannot conver from Item to System.Type". I've tried various things such as using typeof, etc.
This is something I'm fairly new to.
AddComponent seems to not support parameters other than type of a script, and if I understood you correctly, your items are all customizable in many aspects that should be applied when you add component in runtime. I advise adding method AddToGameobject in class Item:
abstract class Item : Monobehaviour {
public abstract void AddToGameObject (GameObject obj);
}
class Weapon : Item {
int damage;
int cooldown;
public override void AddToGameObject (GameObject obj) {
var copy = obj.AddComponent <Weapon> ();
copy.damage = damage;
copy.cooldown = cooldown;
}
}
class Armor : Item {
int protection;
public override void AddToGameObject (GameObject obj) {
var copy = obj.AddComponent <Armor> ();
copy.protection = protection;
}
}
And your loot generation function will become
public void GenerateLoot (GameObject gameObject, List <Item> list) {
randomManager.RandomList (list).AddToGameObject (gameObject);
}
If you want to attach existing component itself instead of a copy, you can't (explanations: 1, 2).
As MSL says, you need to give it a Type argument to AddComponent, as the documentation suggest:
https://docs.unity3d.com/ScriptReference/GameObject.AddComponent.html
So my suggestion is to do it that way:
1.First you call and add the component (similar as you do)
gameObject.AddComponent(typeof(Item)) as Item;
or my prefered way:
gameObject.AddComponent<Item>();
2.Then add the item values and properties:
gameObject.GetComponent<Item>().value = item.value;
You are trying to set a GameObject Component of type Item. If you try to Add Component Item in the Inspector of any GameObject, there is none (unless your script is called Item, but that won't do what you're trying to do).
I'm assuming in your example, you may want to generate a random item upon opening a chest. There is a couple of steps to create a fully functional item, so I can't really give it all to you, so I'll give you an idea of how you'd do it.
The following will randomly change the image of the GameObject by Adding Component SpriteRenderer and randomly change the Sprite value of this Component.
public class RandomSpawn : MonoBehaviour {
GameObject gameObject;
public Sprite[] weapons; // assign an array of weapon sprites in Inspector
void Start() {
gameObject = new GameObject();
SpriteRenderer gameObjectSR = // add SpriteRenderer component
gameObject.AddComponent(typeof(SpriteRenderer)) as SpriteRenderer;
int randomIndex = Random.Range(0, weapons.Length); // random index within weapons
gameObjectSR.sprite = weapons[randomIndex]; // assign Sprite from weapons
// instantiate object
Instantiate(gameObject, transform.position, transform.rotation);
}
}
So you begin with Sprite[] weapons, an array of weapon images (basically). In the Start() function, create a new GameObject, and add a SpriteRenderer (this doesn't have to be random because you want to change what the item looks like). The random part begins with obtaining a random int (the index which is inside the length of the weapons). Then assigning a random weapons Sprite onto the GameObject, and instantiating it. This would work as compared to your attempt because SpriteRenderer is a Unity GameObject Component.
For this GameObject, you may want to attach your Weapon script (since it's derived from the Item class) and assuming you have a Weapon function SetDamage, like this:
Weapon w = gameObject.AddComponent<Weapon>();
w.SetDamage(Random.Range(5,10)); // set weapon damage between 5 and 10
This will then change the value of the Weapon damage, which can be accessed by script for when you are damaging an enemy, for example.
Note: There is probably a way to create your own components, if the normal library of components doesn't satisfy your needs.

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).

How do I use the inspector to set PlayerPrefs on button click which will switch scenes?

I have two scenes MAP and GAME.
On the MAP, I want to use the variables (in the inspector) on a button to build the level in the GAME.
I have three game modes {1, 2, 3}
I also have a score that needs to be achieved {I want to be able to set this score}
Once the buttons is clicked I want it to save to PlayerPrefs and then in the GAME it will read which PlayerPrefs were saved and then load that data.
Button Script
public class LevelButton : MonoBehaviour {
public float GameScoreNeeded;
public int GameMode;
}
public void MakeLevel()
{
PlayerPrefs.SetInt("GameMode") = GameMode;
PlayerPrefs.SetFloat("GameScoreNeedeed") = GameScoreNeeded;
}
On the GAME
void Start(){
BuildLevel();
}
public void BuildLevel(){
gameMode = PlayersPrefs.GetInt("GameMode");
gameScoreNeeded = PlayersPrefs.GetFloat("GameScoreNeeded");
}
//Rest of code
This is what I think needs to happen, but I can't seem to get the int from the inspector to save.
but I can't seem to get the int from the inspector to save.
PlayerPrefs.SetInt("GameMode") = GameMode;
PlayerPrefs.SetFloat("GameScoreNeedeed") = GameScoreNeeded;
You are not using PlayerPrefs properly. It takes two parameters. The key and the value. The loading code seems right but the saving code is not. Below is the correct way to save your values:
PlayerPrefs.SetInt("GameMode", GameMode);
PlayerPrefs.SetFloat("GameScoreNeedeed", GameScoreNeeded);
It would make sense to call PlayerPrefs.Save(); after the code above. You can then use PlayersPrefs.GetInt or PlayersPrefs.GetFloat to read the saved value later on.

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

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

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.

Categories

Resources