using System; using System.Collections.Generic; using Unity.Properties; using Uigenx.Binding; using UnityEngine; using UnityEngine.UIElements; namespace Game.UI.Samples { /// /// A hand written data source. `[UgxDataSource]` puts it in the studio's schema, `[CreateProperty]` /// makes a property bindable by Unity's own runtime binding, and calling Notify from the setters /// is what tells the bindings to refresh. /// /// /// The paths the generated binding table uses are exactly the member names reported by /// `UgxSchemaWalker`: `hp`, `maxHp`, `hpNormalised`, `warningOpacity`. Prefixing them in the /// studio without a matching nested member here would make every binding fail to resolve. /// [UgxDataSource("Player")] public sealed class HudDataSource : UgxDataSource { int _hp = 100; int _maxHp = 100; [CreateProperty] public int hp { get => _hp; set { if (_hp == value) return; _hp = value; Notify(); Notify(nameof(hpNormalised)); Notify(nameof(lowHealth)); Notify(nameof(warningOpacity)); } } [CreateProperty] public int maxHp { get => _maxHp; set { if (_maxHp == value) return; _maxHp = value; Notify(); Notify(nameof(hpNormalised)); Notify(nameof(lowHealth)); Notify(nameof(warningOpacity)); } } [CreateProperty(ReadOnly = true)] public float hpNormalised => _maxHp <= 0 ? 0f : Mathf.Clamp01((float)_hp / _maxHp); [CreateProperty(ReadOnly = true)] public bool lowHealth => hpNormalised < 0.25f; /// /// Style bindings need a value the style parser understands, so the boolean is exposed as /// the number the `opacity` binding actually wants. /// [CreateProperty(ReadOnly = true)] public float warningOpacity => lowHealth ? 1f : 0f; } /// /// Drives the player's health so the sample HUD visibly reacts. It forwards the whole data /// source contract, change notification included: a component that implemented only /// IUgxDataSource would render once and then freeze. /// [AddComponentMenu("UI Creator/Samples/Minimal HUD Driver")] public sealed class MinimalHudDriver : MonoBehaviour, IUgxDataSource, INotifyBindablePropertyChanged, IDataSourceViewHashProvider { [SerializeField] float damagePerSecond = 8f; public HudDataSource Data { get; } = new HudDataSource(); float _accumulated; public event EventHandler propertyChanged { add => Data.propertyChanged += value; remove => Data.propertyChanged -= value; } public long GetViewHashCode() => Data.GetViewHashCode(); public bool TryGet(string path, out object value) => Data.TryGet(path, out value); public bool TrySet(string path, object value) => Data.TrySet(path, value); public IEnumerable Paths => Data.Paths; void Update() { _accumulated += damagePerSecond * Time.deltaTime; if (_accumulated < 1f) return; int damage = Mathf.FloorToInt(_accumulated); _accumulated -= damage; Data.hp = Mathf.Max(0, Data.hp - damage); } } }