State
UI Patterns
Wire Unity UI controls, paired sliders, derived status values, and a complete view-model example.
- Slider binding
- Two sliders
- Derived status
- Complete example
These examples show common Unity UI patterns that become clearer when views subscribe to state.
Common Unity UI Patterns
Slider to State
private readonly AzState<float> _volume = new(0.5f);
private readonly AzEventTokenBag _bindings = new();
private void OnEnable()
{
volumeSlider.onValueChanged.AddListener(OnSliderChanged);
_volume.BindTo(this, static (self, value) => self.SetSliderWithoutNotify(value)).AddTo(_bindings);
}
private void OnDisable()
{
volumeSlider.onValueChanged.RemoveListener(OnSliderChanged);
_bindings.DisposeAll();
}
private void OnSliderChanged(float value)
{
_volume.SourceSet(value);
}
private void SetSliderWithoutNotify(float value)
{
volumeSlider.SetValueWithoutNotify(value);
}Two Sliders Bound Together
private readonly AzState<float> _modelVolume = new(0.35f);
private readonly AzState<float> _uiVolume = new(0.35f);
private IAzPausableToken _twoWay;
private void OnEnable()
{
_twoWay = _modelVolume.BindTwoWay(_uiVolume, snapToThisOnBind: true);
}
private void OnDisable()
{
_twoWay?.Dispose();
_twoWay = null;
}Derived Status State
private readonly AzState<int> _score = new(0);
private readonly AzState<string> _status = new("Ready");
private readonly AzEventTokenBag _bindings = new();
private void OnEnable()
{
_score.SubscribeMap(
DescribeScore,
value => _status.SourceSet(value),
notifyImmediately: false).AddTo(_bindings);
_status.BindTo(SetStatusText).AddTo(_bindings);
}
private static string DescribeScore(int score)
{
if (score >= 10) return "Score is hot";
if (score > 0) return "Score is waking up";
return "Score is idle";
}Complete Example
using Azkar.Eda;
using UnityEngine;
using UnityEngine.UIElements;
public sealed class PlayerViewModel
{
private readonly AzState<int> _health = new(100);
private readonly AzState<int> _score = new(0);
private readonly AzState<string> _status = new("Ready");
private readonly AzEventTokenBag _internalBindings = new();
public IReadOnlyAzState<int> Health => _health.AsReadOnly();
public IReadOnlyAzState<int> Score => _score.AsReadOnly();
public IReadOnlyAzState<string> Status => _status.AsReadOnly();
public PlayerViewModel()
{
_health.Validator = value => Mathf.Clamp(value, 0, 100);
_score.SubscribeMap(
DescribeScore,
value => _status.SourceSet(value),
notifyImmediately: false).AddTo(_internalBindings);
}
public void Dispose()
{
_internalBindings.Dispose();
}
public void TakeDamage(int amount)
{
_health.SourceSet(_health.Value - amount);
}
public void AddScore(int amount)
{
_score.SourceSet(_score.Value + amount);
}
private static string DescribeScore(int score)
{
if (score >= 10) return "Score is hot";
if (score > 0) return "Score is waking up";
return "Score is idle";
}
}
public sealed class PlayerHud : MonoBehaviour
{
[SerializeField] private UIDocument document;
private readonly AzEventTokenBag _bindings = new();
private PlayerViewModel _model;
private Label _healthText;
private Label _scoreText;
private Label _statusText;
private void Awake()
{
_model = new PlayerViewModel();
}
private void OnEnable()
{
var root = document.rootVisualElement;
_healthText = root.Q<Label>("healthText");
_scoreText = root.Q<Label>("scoreText");
_statusText = root.Q<Label>("statusText");
_model.Health.SubscribeMap(
value => $"HP: {value}",
text => _healthText.text = text).AddTo(_bindings);
_model.Score.SubscribeMap(
value => $"Score: {value}",
text => _scoreText.text = text).AddTo(_bindings);
_model.Status.BindTo(
text => _statusText.text = text).AddTo(_bindings);
}
private void OnDisable()
{
_bindings.DisposeAll();
}
private void OnDestroy()
{
_model?.Dispose();
}
public void DebugDamage()
{
_model.TakeDamage(10);
}
public void DebugScore()
{
_model.AddScore(1);
}
}This example shows the core AzState workflow:
- the view model owns writable
AzState<T> - public consumers receive read-only state
- state mutation uses
SourceSet - derived status uses
SubscribeMap - UI uses
SubscribeMapandBindTo - subscriptions are owned by an
AzEventTokenBag
