Skip to content

Instantly share code, notes, and snippets.

@mandarinx
Created December 28, 2014 13:09
Show Gist options
  • Save mandarinx/00e59ad6a7cd3e82cb47 to your computer and use it in GitHub Desktop.
Save mandarinx/00e59ad6a7cd3e82cb47 to your computer and use it in GitHub Desktop.
Listen for changes on int, float and bool variables
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace MyGame.Core {
[System.Serializable]
public class Bool : System.Object {
public delegate void UpdateDelegate(bool val);
public UpdateDelegate OnUpdate;
public bool Value;
public Bool(bool val) {
Value = val;
}
public static implicit operator bool(Bool val) {
return val.Value;
}
public static implicit operator Bool(bool val) {
return new Bool(val);
}
override public string ToString() {
return Value.ToString();
}
public static implicit operator string(Bool x) {
return x.Value ? "True" : "False";
}
public virtual bool Set(bool val) {
Value = val;
if (OnUpdate != null) {
OnUpdate(Value);
}
return Value;
}
public static bool operator ==(Bool x, Bool y) {
return x.Value == y.Value ? true : false;
}
public static bool operator !=(Bool x, Bool y) {
return x.Value != y.Value ? true : false;
}
public static Bool operator !(Bool x) {
return new Bool(x.Value ? false : true);
}
public override bool Equals(object o) {
try {
return (bool) (this == (Bool) o);
} catch {
return false;
}
}
public override int GetHashCode() {
return Value ? 1 : 0;
}
public void ClearListeners() {
if (OnUpdate == null) {
return;
}
foreach (UpdateDelegate d in OnUpdate.GetInvocationList()) {
OnUpdate -= (UpdateDelegate)d;
}
}
}
}
using UnityEngine;
using UnityEditor;
using MyGame.Core;
using System.Collections;
using System.Collections.Generic;
namespace MyGame {
[CustomPropertyDrawer(typeof(Bool))]
public class VariablePropertyDrawer : PropertyDrawer {
override public void OnGUI(Rect pos,
SerializedProperty prop,
GUIContent label) {
EditorGUI.BeginProperty(pos, label, prop);
int ctrlID = GUIUtility.GetControlID(FocusType.Passive);
pos = EditorGUI.PrefixLabel(pos, ctrlID, label);
int indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
Rect r = new Rect(pos.x, pos.y, pos.width, pos.height);
EditorGUI.PropertyField(r,
prop.FindPropertyRelative("Value"),
GUIContent.none);
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
}
}
@mandarinx
Copy link
Author

Bool.cs is a wrapper for a boolean that also adds an OnUpdate delegate that one can use for subscribing to changes in the boolean's value.

You use it almost like a float. You can set the value directly by using health = 5, but since it's not possible to add operator overloading to the equal (=) operator, you have to use the Set function to trigger the OnUpdate delegate.

OnUpdate passes the new value, after the changes has been made, to it's delegates.

This is particularly handy to use for updating the UI without having to write lots of UI specific code in the functions that updates the value. By creating a class that only handles the bindings between UI elements and their data source, one separates the code into more easily maintainable chunks. The code for updating e.g. the player's health only updates the player's health property. A separate class listens for changes in the player's health and keeps the UI updated.

You can easily create separate classes for floats and ints by copying and changing Bool.cs. The first version was made using generics, but since Unity's PropertyDrawer doesn't support generic classes, I had to create separate classes for each value type. It would have been a more elegant solution.

@mandarinx
Copy link
Author

Here's an example of how to use this setup.

You would setup the player class with a health property like this:

using UnityEngine;
using MyGame.Core;
using System.Collections;

namespace MyGame.Entities {

public class Player : MonoBehaviour {

    public Float health = 100.0f;

    public void TakeDamage(float damage) {
        health.Set(health - damage);
    }
}
}

Notice the capital F in Float. This is the wrapper class and NOT the default float value type.

In a separate class you would add bindings between the UI and the player's health property like this:

using UnityEngine;
using UnityEngine.UI;
using MyGame.Core;
using System.Collections;

namespace MyGame.UI {

public class UIBindings : MonoBehaviour {

    public Player   player;
    public Text     healthLabel;

    void Awake() {
        player.health.OnUpdate += HealthUpdate;
    }

    private void HealthUpdate(float health) {
        healthLabel.text = "Health: " + health;
    }
}
}

The UIBindings class hooks up the data source to the UI element by itself. As you can see, the code for updating the player's health and the player class is kept nice and clean with code only relevant for the player's state. UIBindings listens for changes in the Float and keeps the UI updated.

Note that you can force and update of the UI before changes has been made in the player's health by calling HealthUpdate(player.health);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment