using UnityEngine;
using Morphyn.Unity;
using Morphyn.Parser;
// ── SETUP: TWO APPROACHES ─────────────────────────────────────────────────────
// APPROACH A: MorphynEntity per GameObject (recommended for per-object logic)
// 1. Add MorphynEntity component to a GameObject
// 2. Drag a .morph file into the Script slot
// 3. Press Play — entity is registered automatically
MorphynEntity entity = GetComponent<MorphynEntity>();
// APPROACH B: MorphynController (global, shared scripts)
// 1. Add MorphynController component to a scene GameObject
// 2. Drag .morph files into the Morphyn Scripts array
// 3. Press Play
MorphynController morphyn = MorphynController.Instance;
// ── READ FIELDS (via MorphynEntity) ──────────────────────────────────────────
float hp = entity.Get<float>("hp", 100f);
bool alive = entity.Get<bool>("alive", true);
string nickname = entity.Get<string>("nickname", "unknown");
// Raw MorphynValue
MorphynValue raw = entity.GetField("hp");
if (!raw.IsNull) float hp = System.Convert.ToSingle(raw.ToObject());
// ── READ FIELDS (via MorphynController) ──────────────────────────────────────
// Typed helpers — cleanest option when you know the type
float hp = morphyn.GetFloat("Player", "hp");
float hp = morphyn.GetFloat("Player", "hp", 100f); // with default
bool alive = morphyn.GetBool("Player", "alive");
double speed = morphyn.GetDouble("Player", "speed");
string nickname = morphyn.GetString("Player", "nickname", "unknown");
// Generic — auto-converts to T
float hp = morphyn.Get<float>("Player", "hp", 100f);
bool alive = morphyn.Get<bool>("Player", "alive");
int level = morphyn.Get<int>("Player", "level");
// Dynamic — returns whatever is stored (double, bool, string, MorphynPool or null)
object val = morphyn.Get("Player", "hp");
// Raw MorphynValue struct
MorphynValue raw = morphyn.GetField("Player", "hp");
if (!raw.IsNull) float hp = System.Convert.ToSingle(raw.ToObject());
// All fields at once
Dictionary<string, MorphynValue> fields = morphyn.GetAllFields("Player");
// ── WRITE FIELDS ──────────────────────────────────────────────────────────────
// Via MorphynEntity
entity.SetField("hp", 50f);
entity.SetField("alive", true);
entity.SetField("name", "Hero");
// Via MorphynController — overloads accept primitives directly
morphyn.SetField("Player", "hp", 50.0);
morphyn.SetField("Player", "alive", true);
morphyn.SetField("Player", "name", "Hero");
morphyn.SetField("Player", "speed", 3.5f);
// Or pass MorphynValue explicitly
morphyn.SetField("Player", "hp", MorphynValue.FromDouble(50.0));
// ── TRIGGER EVENTS ────────────────────────────────────────────────────────────
// Via MorphynEntity
entity.Emit("damage", 25);
entity.Emit("heal", 20);
// Via MorphynController
morphyn.Emit("Player", "damage", 25); // fire and forget
morphyn.Emit("Player", "heal", 20);
morphyn.Emit("Enemy", "take_damage", 10, true); // multiple args
// ── SYNC EMIT (returns a value) ───────────────────────────────────────────────
// Executes immediately and returns the last assigned value inside the event
// Primitive overloads
MorphynValue result = morphyn.EmitSync("MathLib", "clamp", 150.0);
// Multiple args via MorphynValue
MorphynValue result = morphyn.EmitSync("MathLib", "clamp",
MorphynValue.FromDouble(150.0),
MorphynValue.FromDouble(0.0),
MorphynValue.FromDouble(100.0));
float clamped = System.Convert.ToSingle(result.ToObject()); // 100
// ── C# LISTENERS ──────────────────────────────────────────────────────────────
// Subscribe a C# method to a Morphyn entity event.
// Handler receives event args as object?[].
Action<object?[]> onDeath = args => {
Debug.Log("Player died");
deathScreen.SetActive(true);
};
morphyn.When("Player", "death", onDeath);
morphyn.When("Player", "levelUp", args => {
int level = System.Convert.ToInt32(args[0]);
levelUpUI.Show(level);
});
// Always unsubscribe in OnDestroy to avoid memory leaks
morphyn.Unwhen("Player", "death", onDeath);
public class MyComponent : MonoBehaviour
{
void Start()
{
MorphynController.Instance.When("Player", "death", OnPlayerDeath);
}
void OnPlayerDeath(object?[] args) { /* ... */ }
void OnDestroy()
{
MorphynController.Instance.Unwhen("Player", "death", OnPlayerDeath);
}
}
// ── FIELD WATCHERS ────────────────────────────────────────────────────────────
// Subscribe to changes of a specific field.
// Fires only when the value actually changes — same-value assignment is a no-op.
// Callback receives (oldValue, newValue).
// Via MorphynEntity — typed
entity.Watch<float>("hp", (old, now) => {
hpBar.fillAmount = now / maxHp;
});
// Via MorphynController — raw MorphynValue
morphyn.Watch("Player", "hp", (oldVal, newVal) => {
Debug.Log($"hp: {oldVal.NumVal} -> {newVal.NumVal}");
});
// Via MorphynController — typed, auto-converts to T (supports float, double, int, bool, string)
morphyn.Watch<float>("Player", "hp", (old, now) => {
hpBar.fillAmount = now / maxHp;
});
morphyn.Watch<bool>("Player", "alive", (old, now) => {
if (!now) deathScreen.SetActive(true);
});
// Unwatch — pass the same delegate instance used in Watch
morphyn.Unwatch("Player", "hp", myCallback);
public class HpBar : MonoBehaviour
{
void Start()
{
MorphynController.Instance.Watch<float>("Player", "hp", OnHpChanged);
}
void OnHpChanged(float old, float now)
{
hpBar.fillAmount = now / 100f;
}
void OnDestroy()
{
MorphynController.Instance.Unwatch("Player", "hp", OnHpChanged);
}
}
// Watch vs When:
// Watch — fires when a field VALUE changes, receives (old, new)
// When — fires when an entity sends a named EVENT, receives event args
// ── MORPHYN-TO-MORPHYN SUBSCRIPTIONS FROM C# ─────────────────────────────────
// Equivalent to writing `when Player.death : onPlayerDeath` in .morph
morphyn.Subscribe("Logger", "Player", "death", "onPlayerDeath");
// subscriberEntity, targetEntity, targetEvent, handlerEvent
morphyn.Unsubscribe("Logger", "Player", "death", "onPlayerDeath");
// When vs Subscribe:
// When / Unwhen — C# method reacts to Morphyn event
// Subscribe — Morphyn entity reacts to another Morphyn entity's event
// ── UNITY BRIDGE: call Unity from .morph ──────────────────────────────────────
// In .morph:
// emit unity("PlaySound", "explosion")
// emit unity("SpawnVFX", x, y, z)
// Register callbacks BEFORE MorphynController loads (use Awake, not Start)
[DefaultExecutionOrder(-100)]
public class Setup : MonoBehaviour
{
void Awake()
{
UnityBridge.Instance.RegisterCallback("PlaySound", args => {
string clip = args[0]?.ToString() ?? "";
AudioSource.PlayClipAtPoint(Resources.Load<AudioClip>(clip), Vector3.zero);
});
UnityBridge.Instance.RegisterCallback("SpawnVFX", args => {
float x = System.Convert.ToSingle(args[0]);
float y = System.Convert.ToSingle(args[1]);
float z = System.Convert.ToSingle(args[2]);
Instantiate(vfxPrefab, new Vector3(x, y, z), Quaternion.identity);
});
}
}
// Built-in callbacks registered by MorphynController automatically:
// emit unity("Log", "msg") → Debug.Log
// emit unity("Move", x, y, z) → moves MorphynController's transform
// emit unity("Rotate", angle) → rotates MorphynController's transform
// ── SAVE / LOAD ───────────────────────────────────────────────────────────────
morphyn.SaveState(); // saves all entities to persistentDataPath/MorphynData/
morphyn.LoadState("Player"); // loads fields for one entity
morphyn.LoadAllStates(); // loads all
// Per-file save policy — set in Inspector on each MorphynScriptEntry:
// None — never save/load automatically
// Auto — load on startup, save on quit
// ManualOnly — only when you call SaveState() / LoadState() from code
// MorphynEntity has its own autoSaveOnDestroy toggle (set in Inspector).
// When enabled, entity state is written to disk when the GameObject is destroyed.
// Saved files are plain .morph — human-readable, version-controllable:
// entity Player {
// has hp: 75
// has level: 5
// }
// ── HOT RELOAD ────────────────────────────────────────────────────────────────
// Enable "Enable Hot Reload" on MorphynController (Editor only)
// Edit any .morph file → save → logic updates instantly in running game
// Entity field values are preserved across reloads
// ── FULL EXAMPLE: ENTITY-PER-OBJECT ──────────────────────────────────────────
// enemy.morph
/*
entity Enemy {
has hp: 50
has reward: 100
event take_damage(amount) {
hp - amount -> hp
check hp <= 0: emit self.die
}
event die {
emit unity("OnEnemyDied", reward)
emit self.destroy
}
}
*/
// Attach MorphynEntity to EnemyPrefab and drag enemy.morph into the Script slot.
public class EnemyController : MonoBehaviour
{
MorphynEntity _entity;
void Start()
{
_entity = GetComponent<MorphynEntity>();
// React to event
_entity.Watch<float>("hp", (old, now) => {
hpBar.fillAmount = now / 50f;
});
MorphynController.Instance.When("Enemy", "die", args => {
int reward = System.Convert.ToInt32(args[0]);
ScoreManager.Add(reward);
Destroy(gameObject);
});
}
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Bullet"))
_entity.Emit("take_damage", 25);
}
void OnDestroy()
{
MorphynController.Instance.Unwhen("Enemy", "die", myHandler);
}
}