Custom Inspector Array/List not Staying in play mode - c#

First of all i want to say Sorry for my bad english and bad grammar
i have a problem and that is when i press play in the editor my array i made in my custom editor disapares(also does that when i update the script)!
First i got a script called “ColorChangerSingle” which is the script i declare varibles
using UnityEngine;
public class ColorChangerSingle
{
public GameObject gameObjectToChange;
public Color color;
}
then i have a script called “ColorChanger” which is the script i make a custom inspector for and all it got is a static list of “ColorChangerSingle”
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ColorChanger : MonoBehaviour {
public static List<ColorChangerSingle> single = new List();
}
and i have the custom inspector script called “CustomChangeColorInspector” which is the custom inspector script.
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(ColorChanger))]
public class CustomColorChangerInspector : Editor
{
public override void OnInspectorGUI()
{
for (int i = 0; i < ColorChanger.single.Count; i++)
{
EditorGUILayout.BeginHorizontal();
ColorChanger.single[i].gameObjectToChange = (GameObject)EditorGUILayout.ObjectField(ColorChanger.single[i].gameObjectToChange, typeof(GameObject));
ColorChanger.single[i].color = EditorGUILayout.ColorField(ColorChanger.single[i].color);
EditorGUILayout.EndHorizontal();
if (ColorChanger.single[i].gameObjectToChange != null)
if (ColorChanger.single[i].gameObjectToChange.GetComponent() != null)
ColorChanger.single[i].gameObjectToChange.GetComponent().material.color = ColorChanger.single[i].color;
}
EditorGUILayout.BeginHorizontal("box");
if (GUILayout.Button("Add To Array"))
{
ColorChanger.single.Add(new ColorChangerSingle());
}
if (GUILayout.Button("Remove Object In Array"))
{
ColorChanger.single.RemoveAt(ColorChanger.single.Count - 1);
}
EditorGUILayout.EndHorizontal();
}
}
when i add arrays in “not play mode” everything works(setting objects / changing the color of them) but when i press play the array gets “reset”, i think it has to do with the “ColorChanger” script where i set the list equal to a new list of ColorChangerSingle :/
any help is greatly appreciated!
Pictures:
https://gyazo.com/167ab826b6d578ec5a66d9d2586479e8
https://gyazo.com/847a063f9885478200c5a504be1dae2a
thanks for your time and have a great day! //Jrp0h
btw i hope the catagory is good and i know i can clean up the code alot but i made this really quick becuse im working on a secret project and did not want to use that code :)

I don't think your problem comes from the public static List<ColorChangerSingle> single = new List(); line.
What I'd recommend is adding [SerializeField] attributes to your single field and [System.Serializable] to your ColorChangerSingle class. Also are you sure your scene is saved before entering Play mode (this is a common mistake I used to do earlier on) ? If not you can add something like this at the end of the OnInspectorGUI() method :
if(GUI.changed && !Application.isPlaying)
{
EditorUtility.SetDirty(m_Target);
EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
}
EDIT : Also you have to give your custom inspector script a reference to the instance of the script you want to edit (think of many of your GameObjects holding a ColorChanger script), when you call ColorChanger.single[i].gameObjectToChange = [...]; your CustomColorChangerInspector inspector script doesn't know which of your GameObject you refer too.
This is why you have to reference it. The way I usually do it for quick custom inspetocrs (there is more than one way to do it, using serialization for example) is :
[CustomEditor(typeof(ColorChanger))]
public class CustomColorChangerInspector : Editor
{
// I like to declare it once for all but you can also call "(ColorChanger)target" each time to refer to the target
private ColorChanger m_Target;
public override void OnInspectorGUI()
{
m_Target = target as ColorChanger;
for (int i = 0; i < ColorChanger.single.Count; i++)
{
EditorGUILayout.BeginHorizontal();
m_Target.single[i].gameObjectToChange = (GameObject)EditorGUILayout.ObjectField(m_Target.single[i].gameObjectToChange, typeof(GameObject));
[...]
}
}
}

Related

How can I make an object accessible?

I am new to coding and I've been trying to solve this problem.
I want to get the user's input from this input box and write it here. I managed to write this code
This is the first script
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SubmitButton : MonoBehaviour
{
object myObject = new Object();
public Button btnClick;
public InputField inputUser;
void Start()
{
btnClick.onClick.AddListener(GetInputOnClickHandler);
}
public void GetInputOnClickHandler()
{
Debug.Log("Input is " + inputUser.text);
inputUser.text = myObject.ToString();
}
}
And this is the second script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class txtScript : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
Text txtMy = GameObject.Find("Canvas/Text").GetComponent<Text>();
txtMy.text = "Test" + ToString(SubmitButton.myObject);
}
// Update is called once per frame
void Update()
{
}
}
As you can see, I tried to acces myObject from SubmitButton but I got this error:
Assets\Scripts\txtScript.cs(12,53): error CS0122: 'SubmitButton.myObject' is inaccessible due to its protection level
I've tried to solve this but I can't manage to do it.
I used static modifiers/Getters & Setters but maybe I was doing something wrong.
It's probably easiest, at least while you're still familiarising yourself with coding, to simplify this down and just use one script which references the text input, submit button and text display.
This can be achieved by adding a line and adjusting a line in your first script SubmitButton, and removing or disabling the second txtScript (as it is not needed).
Declare an additional field for the displayText (this should reference the GameObject shown in your second screenshot)
public Text displayText;
Then on the submit action, directly update the displayText text to match the inputUser text
public void GetInputOnClickHandler()
{
Debug.Log("Input is " + inputUser.text);
displayText.text = inputUser.text;
}
Also derHugo mentions, it is not possible to create or meaningfully use the base Object class directly. Most objects you create & access via script in Unity are Components (e.g. Text, Button, Rigidbody, Renderer, etc.), Monobehaviour scripts which are attached to GameObjects (e.g. these scripts), or regular C# classes and structs which you have written to work in tandem with the former two (this last one being the only type you would usually explicitly instantiate using the new keyword).

How do I correctly create a reference to a Unity object?

I am new to C# and Unity and am having trouble finding a clear answer to my problem.
I am trying to create a simple TextMeshProUGUI text log buffer in a panel. The buffer itself works fine until I try to access it from another class - I believe because I am not creating the reference to the panel correctly.
Here is my code for the TextMeshProUGUI object collector:
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class TextLogControl : MonoBehaviour
{
public TextMeshProUGUI textPrefab; // Unity prefab
public List<TextMeshProUGUI> textItems = new List<TextMeshProUGUI>();
[SerializeField]
public int maxItems = 100;
public void LogText(string newTextString, Color newColor)
{
Instantiate(textPrefab, transform);
textPrefab.text = newTextString;
if (textItems.Count >= maxItems)
{
textItems.RemoveAt(0); // I should probably be destroying something, but that's another question
}
textPrefab.gameObject.SetActive(true);
textItems.Add(textPrefab);
}
// The above function works correctly if I write a test function within this same class
}
Here is the code for the class that is trying to access the LogText() function:
using System;
using UnityEngine;
public class World : MonoBehaviour
{
Color defaultColor = Color.black;
public TextLogControl textLog;
public void Init()
{
// I need to create a reference here somewhere, but nothing I am trying is working
textLog.LogText("Welcome - you made it!", defaultColor);
}
}
I am putting the TextLogControl script on the Unity GameObject that is holding the TMP objects, and that works on its own.
I thought that I was creating a reference to the holder GameObject by dragging it onto my World object in Unity as below, but I am still getting an NRE when I call World.Init(), which means that I'm doing something wrong, but I cannot figure out what.
I thought this would create the reference that is not being created
Edit: The error I'm receiving is
NullReferenceException: Object reference not set to an instance of an object
When trying to run World.Init() - specifically, textLog is null, even though I have got it dragged onto the appropriate spot in Unity (I believe).
As this is so long for comment,
A null reference means that it is trying to access something that doesn't exist. You either forgot to drag something in the editor, or you are a step ahead and have something un-commented that should still be commented. Your code is using something that isn't there. I recommend you to add this piece of code to your files to check either the error is coming from NullRefrence of class or the else code.
TextMeshProUGUIs = textPrefab.GetComponent<TextMeshProUGUI>();
if (TextMeshProUGUIs == null)
{
Debug.LogError("No TextMeshProUGUI component found.");
}

Unity, editor-time script, On GameObject Added to Scene Event

Say you have a trivial prefab, "Box", which we'll say is nothing more than a standard meter cube.
1 - The prefab Box is in your Project panel
2 - Drag it to the Scene
3 - Obviously it now appears in the Hierarchy panel also, and probably selected and shown in Inspector
To be clear, game is NOT Play when you do this, you're only in ordinary Editor mode.
Is it possible to make a script (an "Editor script"?) so that,
when you do "1" and "2" above, (again this is in Editor mode, not during a game)
when 3 happens, we can affect the new Box item in the scene
So, simple example: we will set the Z position to "2" always, no matter where you drop it.
In short: Editor code so that every time you drag a prefab P to the scene, it sets the position z to 2.0.
Is this possible in Unity? I know nothing of "editor scripts".
It seems very obvious this should be possible.
You can add a custom window editor which implements OnHierarchyChange to handle all the changes in the hierarchy window. This script must be inside the Editor folder. To make it work automatically make sure you have this window opened first.
using System.Linq;
using UnityEditor;
using UnityEngine;
public class HierarchyMonitorWindow : EditorWindow
{
[MenuItem("Window/Hierarchy Monitor")]
static void CreateWindow()
{
EditorWindow.GetWindow<HierarchyMonitorWindow>();
}
void OnHierarchyChange()
{
var addedObjects = Resources.FindObjectsOfTypeAll<MyScript>()
.Where(x => x.isAdded < 2);
foreach (var item in addedObjects)
{
//if (item.isAdded == 0) early setup
if (item.isAdded == 1) {
// do setup here,
// will happen just after user releases mouse
// will only happen once
Vector3 p = transform.position;
item.transform.position = new Vector3(p.x, 2f, p.z);
}
// finish with this:
item.isAdded++;
}
}
}
I attached the following script to the box:
public class MyScript : MonoBehaviour {
public int isAdded { get; set; }
}
Note that OnHierarchyChange is called twice (once when you start dragging the box onto the scene, and once when you release the mouse button) so isAdded is defined as an int to enable its comparison with 2. So you can also have initialization logic when x.isAdded < 1
You could thy this:
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
[ExecuteInEditMode]
public class PrintAwake : MonoBehaviour
{
#if UNITY_EDITOR
void Awake() .. Start() also works perfectly
{
if(!EditorApplication.isPlaying)
Debug.Log("Editor causes this Awake");
}
#endif
}
See https://docs.unity3d.com/ScriptReference/ExecuteInEditMode.html
Analysis:
This does in fact work!
One problem! It happens when the object starts to exist, so, when you are dragging it to the scene, but before you let go. So in fact, if you specifically want to adjust the position in some way (snapping to a grid - whatever) it is not possible using this technique!
So, for example, this will work perfectly:
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
[ExecuteInEditMode]
public class PrintAwake : MonoBehaviour
{
#if UNITY_EDITOR
void Start() {
if(!EditorApplication.isPlaying) {
Debug.Log("Editor causes this START!!");
RandomSpinSetup();
}
}
#endif
private void RandomSpinSetup() {
float r = Random.Range(3,8) * 10f;
transform.eulerAngles = new Vector3(0f, r, 0f);
name = "Cube: " + r + "°";
}
}
Note that this works correctly, that is to say it does "not run" when you actually Play the game. If you hit "Play" it won't then again set random spins :)
Great stuff
Have a similar issue - wanted to do some stuff after object was dragged into scene (or scene was opened with already existed object). But in my case gameobject was disabled. So I couldn't use neither Awake, nor Start.
Solved via akin of dirty trick - just used constructor for my MonoBehaviour class. Unity blocks any attempts to use most of API inside MonoBehaviour constructors, but we could just wait for some time, for example via EditorApplication.delayedCall. So code looks like this:
public class ExampleClass: MonoBehaviour
{
//...
// some runtime logic
//...
#if UNITY_EDITOR
//Constructor
ExampleClass()
{
EditorApplication.delayCall += DoSomeStuffForDisabledObjectAfterCreation;
}
void DoSomeStuffForDisabledObjectAfterCreation()
{
if (!isActiveAndEnabled)
{
//Some usefull stuff
}
}
#endif
}
Monobehaviours have a Reset method, that only gets called in Editor mode, whenever you reset or first instantiate an object.
public class Example : MonoBehaviour
{
void Reset()
{
transform.position =
new Vector3(transform.position.x, 2.22f, transform.position.z);
Debug.Log("Hi ...");
}
}

GameObject prefab is not showing in scene when activated in my script

I have 2 classes attached to 2 different GUIs, PlayerInfoGUI and DiplomacyGUI.
I am trying to have a method in PlayerInfoGUI create a list of buttons dynamically which, when clicked will then pull up DiplomacyGUI. (Both GUIs as well as the dynamic buttons have their own prefabs created)
My PlayerInfoGUI class dynamically populates a panel with buttons using the PopulatePlayerList method.
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
using System.Collections.Generic;
public class PlayerInfoGUI : MonoBehaviour
{
public Image[] guiElements;
public GameObject playerInfoButtonPrefab, canvasParent;
public GameObject diplomacyMenu;
void Awake ()
{
PopulatePlayerList (GameEngine.competingPlayers);
}
void PopulatePlayerList (List<CompetingPlayer> players)
{
for (int i = 0; i < players.Count; i++) {
GameObject go = (GameObject)Instantiate
(playerInfoButtonPrefab);
Button playerInfoButton = go.GetComponent<UnityEngine.UI.Button>
();
CompetingPlayer receivingPlayer = players [i];
playerInfoButton.onClick.AddListener (() => handleDiplomacyMenu
(receivingPlayer));
go.transform.SetParent (canvasParent.transform, false);
}
}
public void handleDiplomacyMenu (CompetingPlayer receivingPlayer)
{
diplomacyMenu.SetActive (true);
}
}
The listener on the PlayerInfo Button is firing when clicked, but the diplomacyMenu GameObject is not showing up in the scene. Most of the research I have read says this should be a simple diplomacyMenu.SetActive(true), or a diplomacy.gameObject.SetActive(true), but this doesn't work.
I have confirmed that the code is being run, but the object cannot be seen.
Thank you in Advance!
PlayerInfoPrefab
PlayerInfoButtonPrefab
DiplomacyPrefab
You're using diplomacyMenu.gameObject when diplomacyMenu is already a game object, this might cause issues. Also make sure the prefab that is represented by diplomacyMenu has its children enabled. I think SetActive() just sets the top level parent to enabled, not any nested objects.

Change color of multiple game objects in Unity 5

I would like to change the color of multiple gameobjects in Unity using a single script. I'm kinda lost in the way how to do it. I'm new to Unity and this is some sort of basic training for me.
Unity version: 5.3.4
Observed Behavior:
Added the same script to the other gameobjects and all change to the same color
Expected Behavior:
Change the color of the gameobjects individually
List of things tried:
Using the -FindGameObject-
Tried to acces the materials using the -GameObject-
Tried both at the same time
Thinking in multiple scripts to achieve the results I want
Here's the code
C#:
using UnityEngine;
using System.Collections;
public class ChangeColor : MonoBehaviour
{
//If I change these variables to -GameObject-
//It blocks me to access the renderer
//Making the variables public doesn't work either
private Renderer cube;
private Renderer sphere;
void Start ()
{
//Tried here the -FindGameObjectWithTag-
cube = GetComponent<Renderer>();
sphere = GetComponent<Renderer>();
}
void Update ()
{
if(Input.GetKeyDown(KeyCode.A))
{
//Tried here the -FindGameObjectWithTag-
cube.material.color = Color.red;
}
if(Input.GetKeyDown(KeyCode.S))
{
//Tried here the -FindGameObjectWithTag-
sphere.material.color = Color.green;
}
}
}
Maybe I'm doing something wrong, as I said I'm new to Unity, I kindly accept any help, if it is noobfriendly the better.
Thanks
There are a couple different ways you can go about doing this.
If they are all under the same parent without other objects there too you could loop through the children and change their colors
GameObject Parent = GameObject.Find("ParentObject");
for( int x = 0; x > Parent.transform.childCount; x++);
{
Parent.transform.GetChild(x).GetComponent<Renderer>().material.color = Color.red;
}
If there are less than say 20ish, you could create a list and simply drag and drop each transform into the inspector after changing the list's size to the number of objects you have.
/*Make sure you have Using "System.Collections.Generic" at the top */
//put this outside function so that the inspector can see it
public List<Transform> Objs;
// in your function put this (when changing the color)
foreach(Transform Tform in Objs){
Tform.GetComponent<Renderer>().material.color = Color.red;
}
Similarly to 2, if you want to do it all in code you can do
//Outside function
public List<Transform> Objs;
//inside function
Objs.Add(GameObject.Find("FirstObject").transform);
Objs.Add(GameObject.Find("SecondObject").transform);
//... keep doing this
//now do the same foreach loop as in 2
You could search by tag (if you have a lot of objects) (i would imagine that this would take a bit longer just because it is going through each component but I have no evidence to back that up)
//Outside function
public GameObject[] Objs;
//inside function
Objs = GameObject.FindGameObjectsWithTag("ATagToChangeColor");
foreach(Transform Tform in Objs){
Tform.GetComponent<Renderer>().material.color = Color.red();
}
And about this comment:
//If I change these variables to -GameObject-
//It blocks me to access the renderer
//Making the variables public doesn't work either
If you make them of type GameObject then you should easily be able to access the renderer simply by doing this:
public GameObject cube;
public void Start(){
cube.GetComponent<Renderer>().material.color = Color.red();
}
And making the variables public allows unity's inspector to see the variables so that you can change them in the unity editor without having to open the script.

Categories