Low-level networking utilities for interacting with the MUD v2 framework in Unity.
UniMUD is under active development. Expect the API to change.
Add UniMUD to the Unity Package Manager via git url
https://github.com/emergenceland/unimud.git?path=Packages/UniMUD
1. Add a NetworkManager:
UniMUD looks for a NetworkManager instance in the scene. You should create an empty game object and attach the NetworkManager component (included with UniMUD) to it. The NetworkManager component has a few properties that you can must set:
- JSON RPC URL
- Websocket URL
- ChainID
- Contract Address (this can be auto-populated in a template)
As well as an optional property, Private Key (pk):
2. Generate Nethereum bindings:
You also need Nethereum bindings for your generated World contract. Bindings can be autogenerated for you in some templates, but you can also use any of these tools: Nethereum Code Generation
using IWorld.ContractDefinition;
using mud.Unity;
using UnityEngine;
async void Move(int x, int y)
{
// The MoveFunction type comes from your autogenerated bindings
// NetworkManager exposes a worldSend property that you can use to send transactions.
// It takes care of gas and nonce management for you.
// Make sure your MonoBehaviour is set up to handle async/await.
await NetworkManager.Instance.worldSend.TxExecute<MoveFunction>(x, y);
}UniMUD caches MUD v2 events in the client for you in a "datastore." You can access the datastore via the NetworkManager instance. The datastore keeps a multilevel index of tableId -> table -> records
class Record {
public string table;
public string key;
public Dictionary<string, object> value;
}For example, records for an entity's Position might look like:
[
{
"table": "Position",
"key": "0x1234",
"value": {
"x": 1,
"y": 2
}
},
{
"table": "Position",
"key": "0x5678",
"value": {
"x": 3,
"y": 4
}
}
]To fetch a record by key, use GetValue on the datastore:
Record? GetMonstersWithKey(string monsterKey) {
var ds = NetworkManager.Instance.ds;
var monstersTable = new TableId("", "Monsters");
return ds.GetValue(monstersTable, monsterKey);
}Use the Set method on the datastore:
void SetMonsterName(string monsterKey, string name) {
var ds = NetworkManager.Instance.ds;
var monstersTable = new TableId("", "Monsters");
ds.Set(monstersTable, monsterKey, new Dictionary<string, object> {
{ "name", name }
});
}For queries that are useful in an ECS context, you can use the Query class to build queries.
Get all records of entities that have Component X and Component Y
var hasHealthAndPosition = new Query().In(Health).In(Position)
// -> [ { table: "Position", key: "0x1234", value: { x: 1, y: 2 } },
// { table: "Health", key: "0x1234", value: { health: 100 } },
// { table: "Position", key: "0x456", value: {x: 2: y: 3} }, ...]Get all records of entities that have Component X and Component Y, but not Component Z
var notMonsters = new Query().In(Health).In(Position).Not(Monster)Get all records of entities that have Component X and Component Y, but only return rows from Component X
var allHealthRows = new Query().Select(HealthTable).In(Position).In(HealthTable)
// -> [ { table: "Health", key: "0x1234", value: { health: 100 } } ]Get all entities that have a value of Z for an attribute Y in component X
var allMonstersNamedChuck = new Query().In(MonsterTable, new Conditions[]{Condition.Has("name", "Chuck")})
// -> [ { table: "Monsters", key: "0x1234", value: { name: "Chuck", strength: 100 } } ]Make sure you actually run the query after building it, with NetworkManager.Instance.ds.RunQuery(yourQuery)
using mud.Client;
void RenderHealth() {
var hasHealth = new Query().Select(Health).In(InitialHealth).In(Health).In(TilePosition);
var recordsWithHealth = NetworkManager.Instance.ds.RunQuery(hasHealth); // don't forget
foreach (var record in recordsWithHealth) {
DrawHealthBar(record.value["healthValue"]);
// assumes the health table has an attribute called "healthValue"
}
}You can do reactive queries on the datastore, with the RxQuery(yourQuery) method.
using System;
using UniRx;
using mud.Client;
using mud.Unity;
using UnityEngine;
public class Health : MonoBehaviour
{
private IDisposable _disposable = new();
private int m_CurrentHealth = 0;
private void Awake()
{
// get the health table value from all entities that have health, initialHealth, and a position.
var healthValues = new Query().Select(Health).In(InitialHealth).In(Health).In(TilePosition);
_disposable = NetworkManager.Instance.ds
// RxQuery returns a tuple of 2 lists: (added, removed)
.RxQuery(healthValues).ObserveOnMainThread()
.Subscribe(OnHealthChange);
}
private void OnHealthChange((List<Record> SetRecords, List<Record> RemovedRecords) update)
{
var setValues = update.Item1;
var removedvalues = update.Item2;
// Values are Dictionaries mapping string attributes to their values.
// For example, if your mud config defines Health as:
// Health: {
// schema: {
// myHealthValue: "uint32",
// },
// }
// The value will be a Dictionary<string, object> with a single key named "myHealthValue".
foreach (var record in setValues)
{
var currentValue = record.value;
if (currentValue == null) return;
m_CurrentHealth = Convert.ToSingle(currentValue["myHealthValue"]);
SetHealthUI();
}
foreach (var record in removedValues)
{
// if health has been removed, then the entity has died.
var entityKey = record.key;
KillEntity(entityKey);
}
}
private void OnDestroy()
{
_disposable?.Dispose();
}
}Change the RPC and ChainID in NetworkManager. UniMUD currently does not implement a faucet service, so you must manually send funds to your address when deploying to a non-local chain (i.e. not Anvil).
- MODE integration
- Unity DOTS integration
- SQLite as a backend for persistence
MIT