I am adding a set of array values through inspector window. I am trying to achieve to trigger a function, when I change my array values. That is, my script should check if the new values are not equal to old values and then call this function.
I do not want to use Update as it will take more memory and processing power, so what could be the alternative?
public class SetValues : MonoBehaviour {
[Serializable]
public struct SetValues
{
public float Position;
public float Value;
}
public SetValues[] setValues;
void Function_ValuesChanged()
{
Debug.Log("The Value is changed");
//Do Something
}
}
Try MonoBehavior.OnValidate()
Example:
public class SetValues : MonoBehaviour {
[Serializable]
public struct SetValues
{
public float Position;
public float Value;
}
public SetValues[] setValues;
void OnValidate()
{
Debug.Log("The Value is changed");
//Do Something
}
}
If something happens in game and you want to notify other scripts use events. Events are lightweight and easy to implement.
In class that contain array, create property and change array value, only from property. Never directly interact with array field.
// Create Event Arguments
public class OnArrayValueChangedEventArgs : EventArgs
{
public float Position;
public float Value;
}
// Crate Event
public event EventHandler<OnArrayValueChangedEventArgs> OnArrayValueChanged;
Array[] myArray;
// Change Array value only from this property, so when you change value, event will be called
public Array[] MyArray
{
get { return myArray; }
set { myArray = value; OnArrayValueChangedEvent(this, new OnArrayValueChangedEventArgs() { Position = myArray.Position, Value = myArray.Value };}
}
From second class you should just subscribe to this event and do the thing. I will call this event from my GameManager Singleton class.
private void Awake()
{
GameManager.Instance.OnArrayValueChanged += Instance_OnArrayValueChanged;
}
private void Instance_OnArrayValueChanged(object sender, GameManager.OnArrayValueChangedEventArgs e)
{
// Do the thing
}
Related
For example, I have a variable "Wisps" that I want to change when the player picks up an object. But I don't know how to do it. I tried to add a WispDisplay object to call the classes, like in Java, but it doesn't seem to work.
public class WispCode : MonoBehaviour
{
WispDisplay wd = new WispDisplay();
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
wd.setWisp(wd.getWisp()+1);
Destroy(gameObject);
}
}
}
public class WispDisplay : MonoBehaviour
{
public int Wisp = 5;
public Text WispText;
void Start()
{
}
void Update()
{
WispText.text = "Wisp: " + Wisp.ToString();
}
public int getWisp()
{
return Wisp;
}
public void setWisp(int newWisp)
{
Wisp = newWisp;
}
}
Easiest (a tiny bit dirty) way is to use a static variable. Downside: you can only have exactly ONE.
Example:
public class MyClass: MonoBehaviour {
public static int wisps;
}
Then, in ANY class, just use this to access it:
MyClass.wisps = 1234;
The more elegant way, working with multiple class instances, is using references.
Example:
public class PlayerClass: MonoBehaviour {
public int wisps = 0;
}
public class MyClass: MonoBehaviour {
public PlayerClass player;
void Update(){
player.wisps += 1;
}
}
Then, you need to drag-drop (aka "assign") the "PlayerClass" Component (attached to the player) to the the Gameobject that should increase the Wisps count. You can duplicate these objects after assigning the reference.
Now, if you actually want to have some sort of collectible, I'd suggest this approach:
You Have a Player "PlayerClass" and some Objects that are collectible, which have Trigger Colliders.
The objects have this code:
public class Example : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
// probably a good idea to check for player tag:
// other.compareTag("Player");
// but you need to create the "Player" Tag and assign it to Player Collider Object.
if(TryGetComponent(out PlayerClass player))
{
player.wisps += 1;
}
}
}
how i can pass a simple boolean variable between two different object?
I can try this but didn't work...
First script:
public class CollisionController : MonoBehaviour
{
public PlayerMovement movement;
public bool active = false;
private void OnCollisionEnter(Collision collision)
{
if(collision.collider.tag == "Obstacle")
{
active = true;
}
}
}
Second script (that read the boolean variable "active")
public class EmptyControllerColl : MonoBehaviour
{
public CollisionController controller;
public PlayerMovement movement;
public bool activeLocal = false;
private void Start()
{
GetComponentInChildren<CollisionController>();
}
void Update()
{
activeLocal = controller.active;
if(activeLocal == false)
{
Debug.Log("Nothing...");
}
if(activeLocal == true)
{
Debug.Log("Game over");
}
}
}
When the variable bool "Active" change its status, the variable "activeLocal" don't change status.. How can I resolve this problem?
Collision Controller is "connect" to Cube Object.
EmptyControllerColl is "connect" to emptyGameObject (parent of Cube).
This line
_ = GameObject.Find("cubo Variant").GetComponent<CollisionController>().active;
makes no sense. First of all there is no field or variable declared with the name _ so this shouldn't even compile at all. And secondly what do you need this for? Rather store the according reference once in the controller field and reuse it later.
Then for your usecase there is no need at all to store the value in a local variable ... this makes things only more complicated. Simply where you need it get the value from controller.active.
Also do not use == for tags. Rather check via CompareTag. The problem is that == silently fails if you have any typo or the tag doesn't exist at all. CompareTag rather throws an error that the given tag is not valid.
public class EmptyControllerColl : MonoBehaviour
{
// Best already drag this in via the Inspector in Unity
[SerializeField] private CollisionController controller;
public PlayerMovement movement;
// As fallback get it ONCE on runtime
private void Awake()
{
// since you say the cube is a child of this empty object you do not use
// Find at all but can simply use GetComponentInChildren
if(!controller) controller = GetComponentInChildren<CollisionController>(true);
}
void Update()
{
// No need to store this in a local field at all
if(!controller.active)
{
Debug.Log("Nothing...");
}
// use if else since both cases are exclusive and you don't even need to check the value twice
else
{
Debug.Log("Game over");
}
}
}
Event Driven - part A
In general you should avoid poll checks for a bool value in Update and rather come up with a more event driven solution! An example could look like:
public class CollisionController : MonoBehaviour
{
public PlayerMovement movement;
// Here everyone who wants can add listeners that get called as soon as
// we invoke this event. We will do it everytime the 'active' value is changed
public event Action<bool> OnActiveStateChanged;
// Backing field for 'active'
private bool _active;
// Property that reads and writes '_active'
// Everytime it is assigned it also invokes 'OnActiveStateChanged'
private bool active
{
get { return _active; }
set
{
_active = value;
OnActiveStateChanged?.Invoke(_active);
}
}
private void OnCollisionEnter(Collision collision)
{
if(collision.collider.CompareTag("Obstacle"))
{
active = true;
}
}
}
Now you would register a listener for this event like
public class EmptyControllerColl : MonoBehaviour
{
// Best already drag this in via the Inspector in Unity
[SerializeField] private CollisionController controller;
public PlayerMovement movement;
// As fallback get it ONCE on runtime
private void Awake()
{
// since you say the cube is a child of this empty object you do not use
// Find at all but can simply use GetComponentInChildren
if(!controller) controller = GetComponentInChildren<CollisionController>(true);
// register a callback. It is allowed an save to unregister first
// which makes sure this is only registered exactly once
controller.OnActiveStateChanged -= HandleControlerActiveStateChanged;
controller.OnActiveStateChanged += HandleControlerActiveStateChanged;
}
private void HandleGameOver()
{
Debug.Log("Game over");
}
private void HandleControlerActiveStateChanged(bool value)
{
if(!value)
{
Debug.Log("Nothing...");
}
else
{
Debug.Log("Game over");
}
}
private void OnDestroy()
{
// always clean up listeners
controller.OnActiveStateChanged -= HandleControlerActiveStateChanged;
}
}
This now is way more efficient since you don't all time run an Update method. Instead the HandleControlerActiveStateChanged is only called when the value of active is actually changed.
Event Driven - part B
And then actually in your case there is need to use a bool at all you could use a simple event Action instead and remove all the bools entirely:
public class CollisionController : MonoBehaviour
{
public PlayerMovement movement;
public event Action OnGameOver;
private void OnCollisionEnter(Collision collision)
{
if(collision.collider.CompareTag("Obstacle"))
{
OnGameOver?.Invoke();
}
}
}
Now you would register a listener for this event like
public class EmptyControllerColl : MonoBehaviour
{
[SerializeField] private CollisionController controller;
public PlayerMovement movement;
private void Awake()
{
if(!controller) controller = GetComponentInChildren<CollisionController>(true);
controller.OnGameOver -= HandleGameOver;
controller.OnGameOver += HandleGameOver;
}
private void HandleGameOver()
{
Debug.Log("Game over");
}
private void OnDestroy()
{
controller.OnGameOver -= HandleGameOver;
}
}
using UnityEngine;
public class CollisionController : MonoBehaviour
{
void Start()
{
// Calls the function ApplyDamage with a value of 5
// Every script attached to the game object
// that has an ApplyDamage function will be called.
gameObject.SendMessage("ApplyDamage", 5.0);
}
}
public class EmptyControllerColl : MonoBehaviour
{
public void ApplyDamage(float damage)
{
print(damage);
}
}
https://docs.unity3d.com/ScriptReference/GameObject.SendMessage.html
I'm using a static variable in a class but the problem is that it needs another value from another class for it's initial value(See the code snippet). I thought initializing it in Start function. But (correct me if I'm wrong) that means it will be reinitialized for every instance of the object which is something redundant since I want this variable to be initialized for just once at the creation of very first Unit which has UnitManager on.
So my question is at what place would be considered as a good practice to initialize this variable?
Thanks!
Code:
public class UnitManager : MonoBehaviour
{
// Distance in terms of Unity Unity from the target position to stop for units
static float distanceToStop;
private void Start()
{
if (WorldCoordController.OneUnityMeterToRealWorld < 10)
{
distanceToStop = 1 / WorldCoordController.OneUnityMeterToRealWorld;
}
else
{
distanceToStop = 0.1f;
}
}
}
public class UnitManager : MonoBehaviour
{
// Distance in terms of Unity Unity from the target position to stop for units
static float distanceToStop;
static bool distanceSet = false;
private void Start()
{
// If the distance is not set
if(!this.distanceSet)
{
if (WorldCoordController.OneUnityMeterToRealWorld < 10)
{
distanceToStop = 1 / WorldCoordController.OneUnityMeterToRealWorld;
} else {
distanceToStop = 0.1f;
}
this.distanceSet = true;
}
}
The "distanceSet" bool will be shared between the instances so you will only set the distance on the first one :D
Maybe consider calling UnitManager with an Init(); with the WorldCoordController value it needs.
You can create a custom class that will have a static reference to its self and be initialized only once (the first time it gets called).
Example:
public class ExampleClass
{
//Static Functionality
private static ExampleClass _inst;
public static ExampleClass Instance
{
get
{
if (_inst is null)
{
_inst = new ExampleClass();
_inst.Init();
}
return _inst;
}
}
//Class Values
public static int MyValue;
public int Value1;
//private Constructor
private ExampleClass()
{
}
//initialize values here
private void Init()
{
}
}
And then you can access the values like this:
//This will return the Value1 int
ExampleClass.Instance.Value1
or
//This will return the static MyValue int
ExampleClass.MyValue
From what you are asking, you can use only the Value1 from the above example and have it initialized only once in the init. If you want the value to be accessible only for read you can set it as property with "private set" operator.
The advantage of this is you dont need Start or Monobehaviour so it can work anywhere without having it in gameobjects.
Hope this helps, and happy coding!
I've got a class holding a List of classes, derived from an abstract superclass.
[System.Serializable]
public class ClassWithList: MonoBehaviour
{
[SerializeField]
private List<Element> elements;
public List<Element> Elements
{
get
{
if (elements == null)
elements= new List<Element>();
return elements;
}
}
}
[System.Serializable]
public abstract class Element: MonoBehaviour
{
[SerializeField]
protected bool isClickedInList;
public bool IsClickedInList
{
get { return isClickedInList; }
set { isClickedInList = value; }
}
}
I use a custom Editor for ClassWithList with a ReorderableList to modify the Element entities' values.
[CustomEditor(typeof(ClassWithList))]
public class Editor_ClassWithList : Editor
{
private ReorderableList elementList;
void OnEnable()
{
if (elementList== null)
elementList=
new ReorderableList(
serializedObject, serializedObject.FindProperty("elements"),
true, true, true, true);
elementList.drawElementCallback += DrawElement;
}
private void OnDisable() { elementList.drawElementCallback -= DrawElement; }
private void DrawElement(Rect r, int i, bool active, bool focused)
{
Element e = elementList.serializedProperty.GetArrayElementAtIndex(i).objectReferenceValue as Element;
e.IsClickedInList = EditorGUI.Toggle(new Rect(r.x, r.y, r.width, r.height - 1), e.IsClickedInList);
}
public override void OnInspectorGUI()
{
serializedObject.Update();
elementList.DoLayoutList();
serializedObject.ApplyModifiedProperties();
}
}
The list is correctly displayed and shows all elements. Clicking on the toggle button results in a correct change of the Elements isClickedInList value. But as soon as I press "Play" this value is overwritten as if it wouldn't be serialized.
Any ideas where things went wrong? Might the usage of serializedObject.FindProperty("elements") cause this problem?
Further testing with the provided example and comparision with my code revealed, that the issue was the GameObject being a prefab. Somehow this results in non-saving behaviour using serialized properties, although I couldn't reproduce the error.
Weird question here. I have a main class which manages a bunch of event classes...I want one of the events to alter a value in the main class.
Normally, I would do something like this by using the ref keyword...but in this case, I want to pass in the reference in the constructor, and have any further modifications by the class be reflected in the main class. Is this possible? Right now I have the following:
class MainClass {
float transparency = 0.0f;
List<Events> listOfEvents;
listOfEvents.Add(new FadeInEvent(ref float transparency));
}
class FadeInEvent {
float transparency;
public FadeInEvent(ref float t) {
transparency = t;
}
public void Update() //Occurs every frame
{
transparency += 0.01f;
}
}
This does not work; the transparency in the main class is not updated by the FadeInEvent class. How can I work this out?
How about creating a class for the variable as classes are pass by reference automatically:
class Transparency
{
public float Value = 0.0f;
}
class MainClass
{
Transparency transparency = new Transparency();
// this List<Events> doesn't match class, but I'm sure this was just a sample of a larger problem
List<Events> listOfEvents = new List<Events>();
listOfEvents.Add(new FadeInEvent(transparency));
}
class FadeInEvent{
Transparency transparency;
public FadeInEvent(Transparency t) {
transparency = t;
}
public void Update() //Occurs every frame
{
transparency.Value += 0.01f;
}
}
You cannot keep a reference to a value type, such as float. What you can do is have a FadeInEvent constructor that takes a MainClass and modify it's public Transparency property:
class MainClass
{
float transparency = 0.0f;
public float Transparency
{
get { return transparency;}
set { transparency = value;}
}
List<Events> listOfEvents;
public void AddFadeInEvent()
{
listOfEvents.Add(new FadeInEvent(this));
}
}
class FadeInEvent
{
MainClass mainClass;
public FadeInEvent(MainClass mainClass)
{
this.mainClass = mainClass;
}
public void Update() //Occurs every frame
{
mainClass.Transparency += 0.01f;
}
}