Skip to content

kids.kapish.entityreact

unity

Data-driven entity system with reactive views. Entities hold typed properties; views react to changes via reflection binding.

Installation

Add to your Unity project's package manifest:

json
{
  "kids.kapish.entityreact": "file:../../src/unity/kids.kapish.entityreact"
}

Requires Unity 2022.3+.

Architecture

Entity

Entity is a ScriptableObject asset. It holds:

  • Properties (EntityProperties) -- a typed key-value bag (case-insensitive string keys, LateCastObject values).
  • Tags (List<EntityTag>) -- named property sets that extend EntityProperties and participate in the same change notification system.
  • Actions (List<EntityAction>) -- named, triggerable actions with an Executed signal (Signal<EntityAction>) and execution count.
  • Views -- registered View MonoBehaviours that receive property change broadcasts.

When a property is set via Properties.Set<T>(key, value), the entity broadcasts a SendMessage(key, oldValue, newValue) to all registered views. View registration and deregistration are deferred during broadcast to prevent collection modification.

Entities are created as assets via Create > Carrot > EntityReact > Entity.

Properties

EntityProperties stores runtime state cloned from serialized defaults on ManagedEnable. Each value is a LateCastObject -- a type-tagged object wrapper with a LateCastObjectType discriminator. Deep cloning is performed for Gradient and AnimationCurve to ensure instance independence.

Supported property types: string, float, int, bool, Color, Gradient, Vector2, Vector2Int, Vector3, Vector3Int, Vector4, Sprite, AudioClip, AnimationCurve, and generic object.

Tags

EntityTag extends EntityProperties. Subclasses define DefaultValues which are applied on enable without triggering view broadcasts (the entity reference is temporarily nulled). Tags let you compose entity capabilities -- attach a "Destructible" tag to add Health and MaxHealth properties with defaults, and any view that listens for Health_Changed will react.

Actions

EntityAction is a named, serializable action. Subscribe via action.Executed.Add(handler) (returns an unsubscribe Action) to handle triggers. Actions track their ExecutionCount. The entity exposes StartAction(name), GetAction(name), HasAction(name), and TryGetAction(name, out action).

Views

View is a MonoBehaviour that registers with an entity on Start and deregisters on OnDisable. When a property changes, the view receives it via ReceiveMessage which dispatches through two reflection channels:

  1. Property setter -- if the view has a C# property matching the key name, its setter is called with the new value.
  2. Change method -- if the view has a method named {Key}_Changed(T oldValue, T newValue), it is invoked.

On initialization (first registration), views receive all current property values with the key suffixed _Initialize for property setters.

Reflection lookups are cached per (viewType, key, valueType) in ViewReflection to avoid per-frame reflection overhead.

ViewFallback

ViewFallback is a MonoBehaviour that provides a shared entity reference for multiple View components on the same GameObject. If a view has no explicit entity assigned, it falls back to the ViewFallback on its GameObject.

File Structure

Runtime/
├── Entity.cs              # ScriptableObject entity asset
├── EntityProperties.cs    # Typed key-value property bag + EntityProperty
├── EntityTag.cs           # Composable property set with defaults
├── EntityAction.cs        # Named triggerable action
├── LateCastObject.cs      # Type-tagged value wrapper + enum + extensions
└── Views/
    ├── View.cs            # Reactive MonoBehaviour view base
    ├── ViewFallback.cs    # Shared entity reference fallback
    └── ViewReflection.cs  # Cached reflection dispatch

Dependencies

DependencyKind
kids.kapishruntime (Carrot core Unity package)

Build

Import via Unity Package Manager. Requires Unity 2022.3+.


Usage Guide

Data-driven entity system with reactive views. Entities hold typed properties; views react to changes via reflection binding.

Setup

Add kids.kapish.entityreact to your Unity project manifest. Requires Unity 2022.3+.

Common Patterns

1. Create an entity asset

Right-click in the Project window: Create > Carrot > EntityReact > Entity. Set a UniqueName and configure default properties and tags in the inspector.

2. Set and read properties at runtime

csharp
using Carrot.EntityReact;

// Set a property -- broadcasts to all views
entity.Properties.Set("Health", 100);
entity.Properties.Set("Name", "Hero");
entity.Properties.Set("IsAlive", true);

// Read properties
int health = entity.Properties.Get<int>("Health");
bool alive = entity.Properties.TryGet<bool>("IsAlive", out bool value);
bool hasHealth = entity.Properties.Has("Health");

3. Build a reactive view

Subclass View and add matching properties or _Changed methods. The reflection system dispatches automatically -- no manual subscription needed.

csharp
using Carrot.EntityReact.Views;
using UnityEngine;
using UnityEngine.UI;

public class HealthBarView : View
{
    [SerializeField] private Slider slider;

    // Called when "Health" is set on the entity (property setter)
    public int Health
    {
        set => slider.value = value;
    }

    // Called when "Health" changes (method handler -- receives old and new)
    private void Health_Changed(int oldValue, int newValue)
    {
        if (newValue < oldValue)
        {
            Debug.Log($"Took {oldValue - newValue} damage!");
        }
    }

    // Called once on registration with current value
    public int Health_Initialize
    {
        set => slider.maxValue = value;
    }
}

4. Wire a view to an entity

In the inspector, assign the Entity field on your View component. Alternatively, add a ViewFallback component to the GameObject and assign the entity there -- all sibling views without an explicit entity will use the fallback.

5. Create a custom tag

Tags compose reusable property sets with defaults:

csharp
using System.Collections.Generic;
using Carrot.EntityReact;

public class DestructibleTag : EntityTag
{
    public override IEnumerable<KeyValuePair<string, object>> DefaultValues
    {
        get
        {
            yield return new KeyValuePair<string, object>("Health", 100);
            yield return new KeyValuePair<string, object>("MaxHealth", 100);
            yield return new KeyValuePair<string, object>("IsIndestructible", false);
        }
    }
}

Add the tag to an entity at runtime:

csharp
entity.AddTag<DestructibleTag>();
// or
entity.AddTag(typeof(DestructibleTag));

Tag properties are broadcast to views on registration, just like entity properties.

6. Define and trigger actions

csharp
using Carrot.EntityReact;
using UnityEngine;

// Create an action at runtime
EntityAction interact = EntityAction.Create(entity, "Interact", interactIcon);
interact.Executed.Add(action =>
{
    Debug.Log($"Interacted with {action.Entity.UniqueName} ({action.ExecutionCount} times)");
});
// .Add() returns an unsubscribe Action — capture it if you need to detach later.
entity.AddAction(interact);

// If you need to detach later:
// var unsub = interact.Executed.Add(Handler);
// unsub();

// Trigger it
entity.StartAction("Interact");

// Query
if (entity.TryGetAction("Interact", out EntityAction found))
{
    Debug.Log($"Action found, executed {found.ExecutionCount} times");
}

Add kids.kapish.signals to your asmdef references if using EntityAction.Executed from your own code.

7. Query tags

csharp
if (entity.HasTag<DestructibleTag>())
{
    DestructibleTag tag = entity.GetTag<DestructibleTag>();
    int health = tag.Get<int>("Health");
}

// By name (matches on type name)
EntityTag tag = entity.GetTag("DestructibleTag");

8. Multiple views, shared entity

When several view components on the same GameObject share one entity, use ViewFallback to avoid duplicating the entity reference:

csharp
// On the GameObject:
// - ViewFallback (Entity = myEntity)
// - HealthBarView (Entity = null -- falls back to ViewFallback)
// - NameplateView (Entity = null -- falls back to ViewFallback)

Tips

  • Properties are case-insensitive -- Health, health, and HEALTH all resolve to the same property.
  • Dots become underscores -- a property key like stats.health dispatches as stats_health for reflection matching.
  • Views register on Start -- properties set before Start are delivered as initialization messages on registration.
  • Tag defaults don't broadcast -- default values are applied silently. Only subsequent Set calls trigger view updates.
  • Reflection is cached -- the first lookup per (viewType, key, valueType) uses reflection; subsequent calls hit the cache.
  • Deferred view changes -- registering or deregistering a view during a broadcast is safe; changes are flushed after the broadcast completes.

Carrot