Unity API Reference
MorphynEntity
Attach to any GameObject to give it its own .morph script. The entity is registered into the shared runtime context automatically when the scene starts. Fields and events are visible and editable in the Inspector.
Settings
| Setting | Description |
|---|---|
| Script | .morph file to load (TextAsset) |
| Custom Entity Name | Override the entity name (defaults to file name) |
| Auto Save On Destroy | Write entity state to disk when the GameObject is destroyed |
Inspector
In Edit mode, the Inspector shows all has fields with their current values. Editing a field directly patches the .morph file on disk. In Play mode, fields reflect live runtime values and can be edited in real time. All events are listed with an Emit button to fire them manually — useful for testing without writing C#.
API Methods
MorphynEntity entity = GetComponent<MorphynEntity>();
// Get field — generic with default
float hp = entity.Get<float>("hp", 100f);
bool alive = entity.Get<bool>("alive", true);
// Get raw MorphynValue
MorphynValue raw = entity.GetField("hp");
// Set field — typed overloads
entity.SetField("hp", 50f);
entity.SetField("alive", true);
entity.SetField("name", "Hero");
entity.SetField("hp", MorphynValue.FromDouble(50.0)); // explicit MorphynValue
// Trigger event
entity.Emit("damage", 25);
entity.Emit("die");
// Watch field for changes — typed
entity.Watch<float>("hp", (old, now) => {
hpBar.fillAmount = now / maxHp;
});
// Watch field for changes — raw MorphynValue
entity.Watch("hp", (oldVal, newVal) => {
Debug.Log($"hp changed: {oldVal.NumVal} -> {newVal.NumVal}");
});
// Listen to another entity's events
entity.ListenTo("Boss", "phase_change", args => {
int phase = System.Convert.ToInt32(args[0].ToObject());
UpdateBossUI(phase);
});
// Entity name (resolved after registration)
string name = entity.EntityName;
MorphynController
The main runtime component. ONE instance per scene manages the shared context. Required even when using MorphynEntity — it hosts the tick loop, hot reload, and save/load.
Settings
| Setting | Description |
|---|---|
| Morphyn Scripts | .morph files to load globally |
| Run On Start | Auto-load on Start() |
| Enable Tick | Send tick(dt) every frame |
| Enable Hot Reload | Edit files during Play mode |
| Auto Save | Save on quit |
| Save Folder | Where to save state |
API Methods
MorphynController morphyn = MorphynController.Instance;
// Get field — typed helpers
float hp = morphyn.GetFloat("Player", "hp");
bool alive = morphyn.GetBool("Player", "alive");
double speed = morphyn.GetDouble("Player", "speed");
string nickname = morphyn.GetString("Player", "nickname");
// Get field — generic (auto-converts to T)
float hp = morphyn.Get<float>("Player", "hp");
bool alive = morphyn.Get<bool>("Player", "alive");
// Get field — dynamic (returns double, bool, string, MorphynPool or null)
object val = morphyn.Get("Player", "hp");
// Get raw MorphynValue
MorphynValue raw = morphyn.GetField("Player", "hp");
// Set field — accepts bool, float, double, string or MorphynValue directly
morphyn.SetField("Player", "hp", 50.0);
morphyn.SetField("Player", "alive", true);
morphyn.SetField("Player", "name", "Hero");
// Get all fields
Dictionary<string, MorphynValue> fields = morphyn.GetAllFields("Player");
// Trigger event
morphyn.Emit("Player", "damage", 25);
// Trigger event and get return value
MorphynValue result = morphyn.EmitSync("Player", "calculate", 25.0, 10.0);
// Subscribe Morphyn entity to another entity's event
morphyn.Subscribe("Logger", "Player", "death", "onPlayerDeath");
// Unsubscribe
morphyn.Unsubscribe("Logger", "Player", "death", "onPlayerDeath");
// Subscribe C# method to a Morphyn event
morphyn.When("Player", "death", args => Debug.Log("Player died!"));
// Unsubscribe C# method
morphyn.Unwhen("Player", "death", myHandler);
// Watch a field for changes — raw MorphynValue
morphyn.Watch("Player", "hp", (oldVal, newVal) => {
Debug.Log($"hp changed: {oldVal.NumVal} -> {newVal.NumVal}");
});
// Watch a field for changes — typed (auto-converts to T)
morphyn.Watch<float>("Player", "hp", (old, now) => {
hpBar.fillAmount = now / maxHp;
});
// Unwatch
morphyn.Unwatch("Player", "hp", myCallback);
// Save / Load
morphyn.SaveState();
morphyn.LoadState("Player");
morphyn.LoadAllStates();
C# Listeners
Subscribe any C# method directly to a Morphyn entity event using When and Unwhen.
When
The handler receives the same arguments the event was fired with as object?[].
Example:
void Start()
{
MorphynController.Instance.When("Player", "death", OnPlayerDeath);
MorphynController.Instance.When("Player", "levelUp", args => {
double level = Convert.ToDouble(args[0]);
levelUpScreen.Show((int)level);
});
}
void OnPlayerDeath(object?[] args)
{
deathScreen.SetActive(true);
respawnButton.interactable = true;
}
void OnDestroy()
{
// Always unsubscribe to avoid memory leaks
MorphynController.Instance.Unwhen("Player", "death", OnPlayerDeath);
}
Unwhen
Note
Always call Unwhen in OnDestroy to avoid memory leaks and calls on destroyed objects.
Difference from Subscribe
When / Unwhen |
Subscribe / Unsubscribe |
|
|---|---|---|
| Subscriber | C# Action<object?[]> |
Morphyn entity event |
| Use case | Unity UI, audio, effects | Morphyn-to-Morphyn logic |
| Handler | Any C# lambda or method | Morphyn event name |
// C# reacts to Morphyn event
morphyn.When("Enemy", "death", args => {
Instantiate(explosionPrefab, transform.position, Quaternion.identity);
});
// Morphyn entity reacts to Morphyn event
morphyn.Subscribe("Logger", "Enemy", "death", "onEnemyDeath");
Field Watchers
Subscribe a C# callback to changes of a specific field on a Morphyn entity. The callback fires immediately after the field is written, before the next tick. It only fires when the value actually changes — assigning the same value is a no-op.
Watch
// Raw MorphynValue
morphyn.Watch(entityName, fieldName, (oldVal, newVal) => { });
// Typed — auto-converts to T (supports float, double, int, bool, string)
morphyn.Watch<T>(entityName, fieldName, (old, now) => { });
// MorphynEntity shorthand — no entity name needed
entity.Watch<T>(fieldName, (old, now) => { });
Example:
void Start()
{
MorphynController.Instance.Watch<float>("Player", "hp", OnHpChanged);
MorphynController.Instance.Watch("Enemy", "alive", (old, now) => {
if (!System.Convert.ToBoolean(now.ToObject()))
ShowDeathEffect();
});
}
void OnHpChanged(float old, float now)
{
hpBar.fillAmount = now / maxHp;
if (now <= 0) deathScreen.SetActive(true);
}
void OnDestroy()
{
MorphynController.Instance.Unwatch("Player", "hp", OnHpChanged);
}
Unwatch
Pass the same delegate instance used in Watch. Always call Unwatch in OnDestroy.
Watch vs When
Watch / Unwatch |
When / Unwhen |
|
|---|---|---|
| Trigger | Field value changes | Entity fires an event |
| Arguments | (oldValue, newValue) |
Event arguments |
| Use case | UI sync, visual feedback | Reactions to game events |
Event Subscriptions from C
You can manage Morphyn entity subscriptions directly from C# using Subscribe and Unsubscribe.
Subscribe
// subscriberEntity will receive handlerEvent when targetEntity fires targetEvent
morphyn.Subscribe(subscriberEntityName, targetEntityName, targetEvent, handlerEvent);
Example — Logger reacts to Player death:
This is equivalent to writing inside a .morph file:
Unsubscribe
Full Example
logger.morph:
entity Logger {
has count: 0
event onPlayerDeath {
count + 1 -> count
emit unity("Log", "Player died! Total deaths:", count)
}
event onPlayerLevelUp(level) {
emit unity("Log", "Player reached level:", level)
}
}
GameManager.cs:
using UnityEngine;
public class GameManager : MonoBehaviour
{
void Start()
{
var morphyn = MorphynController.Instance;
morphyn.Subscribe("Logger", "Player", "death", "onPlayerDeath");
morphyn.Subscribe("Logger", "Player", "levelUp", "onPlayerLevelUp");
}
public void OnBossFightEnd()
{
MorphynController.Instance.Unsubscribe("Logger", "Player", "death", "onPlayerDeath");
}
}
Notes
- Subscriptions set from C# behave exactly like
when/unwheninside.morphfiles - An entity cannot subscribe to its own instance —
Subscribe("Player", "Player", ...)will log a warning and do nothing - Duplicate subscriptions are ignored
- Dead entities are cleaned up automatically during garbage collection
Unity Bridge
Call Unity functions from .morph files.
Setup
using UnityEngine;
using Morphyn.Unity;
public class GameManager : MonoBehaviour
{
void Start()
{
// Register callbacks BEFORE MorphynController.Start()
UnityBridge.Instance.RegisterCallback("PlaySound", args => {
string soundName = args[0]?.ToString() ?? "";
AudioSource.PlayOneShot(Resources.Load<AudioClip>(soundName));
});
UnityBridge.Instance.RegisterCallback("ShowMessage", args => {
Debug.Log($"Message: {string.Join(" ", args)}");
});
}
}
Calling from Morphyn
entity Enemy {
has hp: 50
event damage(amount) {
hp - amount -> hp
emit unity("PlaySound", "hit")
check hp <= 0: {
emit unity("PlaySound", "explosion")
emit unity("ShowMessage", "Enemy destroyed!")
}
}
}
Built-in Callbacks
MorphynController registers these automatically:
emit unity("Log", "Hello", "World") # Debug.Log
emit unity("Move", 1, 0, 0) # moves MorphynController's transform
emit unity("Rotate", 45) # rotates MorphynController's transform
Hot Reload
Edit .morph files during Play mode — changes apply instantly.
Enable: Check Enable Hot Reload on MorphynController.
- Start Play mode
- Open
player.morph - Change
has damage: 10tohas damage: 999 - Save
- Damage updates immediately — no restart needed
Save / Load
Morphyn saves entity state as readable .morph files.
// Save all entities
MorphynController.Instance.SaveState();
// Saved to: Application.persistentDataPath/MorphynData/
// Load single entity
MorphynController.Instance.LoadState("Player");
// Auto-save on quit — set in Inspector
Per-file save policy is set on each entry in the MorphynController Morphyn Scripts array:
| Mode | Behavior |
|---|---|
None |
Never save or load |
Auto |
Load on startup, save on quit |
ManualOnly |
Only when you call SaveState() / LoadState() from code |
MorphynEntity has its own Auto Save On Destroy toggle. When enabled, the entity's fields are written to disk when the GameObject is destroyed.
Saved file example:
Best Practices
1. Prefer MorphynEntity over dumping everything into MorphynController
PlayerObject → MorphynEntity → player.morph
EnemyPrefab → MorphynEntity → enemy.morph
ShopManager → MorphynEntity → shop.morph
One MorphynController still needs to exist in the scene for the runtime, but it doesn't need to hold every script.
2. One MorphynController per scene
3. Register Unity callbacks early
[DefaultExecutionOrder(-100)]
public class Setup : MonoBehaviour
{
void Awake() // Use Awake, not Start
{
UnityBridge.Instance.RegisterCallback("MyCallback", args => {});
}
}
4. Always unsubscribe in OnDestroy
void OnDestroy()
{
MorphynController.Instance.Unwhen("Player", "death", myHandler);
MorphynController.Instance.Unwatch("Player", "hp", myWatcher);
}
5. Use for configs and logic, not heavy simulation
// GOOD
entity ShopPrices { has swordCost: 100 }
// BAD
entity ComplexAI { /* keep heavy simulation in C# */ }
Troubleshooting
"MorphynController not found"
"Callback not found"
- Register callbacks in
Awake(), notStart() - Register before MorphynController loads scripts
Inspector shows "Failed to parse"
- Check that the entity name in the
.morphfile matches the file name (or set Custom Entity Name) - Check the Console for parser errors
- Make sure the
.morphfile has valid syntax
Hot reload not working
- Editor only — not available in builds
- File must be inside the
Assetsfolder - Check
Enable Hot Reloadis ticked
Values not updating
Watch not firing
- Check the field name matches exactly (case-sensitive)
- Watch fires only on value change — setting the same value does nothing
- Make sure
Watchis called afterLoadAndRun()completes