Package for interacting with the MUD framework in Unity.
- git (download)
- foundry (forge, anvil, cast) (download, make sure to foundryup at least once)
- node.js (v16+) (download)
- pnpm (after installing node: npm install --global pnpm)
- Unity (download)
- The .NET SDK (7.0) (download)
If you are using Windows:
- Install Git Bash (gitforwindows.org)
- Install nodejs, including “native modules” (nodejs.org/en/download) (re native modules: just keep the checkmark, it’s enabled by default in the installer)
- Use Git Bash for all terminal commands
Follow this guide to make a basic MUD game with Unity.
git clone https://github.com/emergenceland/UniMUD.git
- Open terminal and enter the project root
cd UniMUD/templates/counter
- Install MUD
pnpm install
- Enter contract folder
cd packages/contracts
- Deploy contracts locally
pnpm dev
- Open a second terminal window to contracts
cd UniMUD/templates/counter/packages/contracts
- Run codegen for C# scripts and link to the deploy
pnpm dev:unity
- Open the project in Unity Hub
UniMUD/templates/counter/packages/client
- Enter play mode!
To fetch a table by key, use GetTable
on MUDTable:
MonsterTable monstersTable = MUDTable.GetTable<MonsterTable>(key);
using IWorld.ContractDefinition;
using mud;
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.World.Write<MoveFunction>(System.Convert.ToInt32(x), System.Convert.ToInt32(y));
}
UniMUD caches MUD v2 events in the client for you in a "datastore." You can access the datastore via the NetworkManager. The datastore keeps a multilevel index of tableId -> table -> records
class RxRecord {
public string TableId { get; set; }
public string Key { get; set; }
public Property RawValue { get; set; }
}
For example, records for an entity's Position might look like:
[
{
"tableId": "Position",
"key": "0x1234",
"x": 1,
"y": 2
},
{
"tableId": "Position",
"key": "0x5678",
"x": 3,
"y": 4
}
]
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
RxTable Health = ds.tableNameIndex["Health"];
RxTable Position ds.tableNameIndex["Position"];
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 monsters that have the name Chuck
var allMonstersNamedChuck = new Query().In(MonsterTable).In(MonsterTable, new Condition[]{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.Datastore.RunQuery(yourQuery)
using mud;
void RenderHealth() {
var hasHealth = new Query().Select(Health).In(InitialHealth).In(Health).In(TilePosition);
var recordsWithHealth = NetworkManager.Datastore.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 MUDTable.GetUpdates<YourTable>()
method.
using System;
using UniRx;
using mud;
using UnityEngine;
public class Counter : MonoBehaviour {
private IDisposable _disposable = new();
void Start() {
net = NetworkManager.Instance;
net.OnNetworkInitialized += SubscribeToCounter;
}
private void SubscribeToCounter(NetworkManager _) {
_counterSub = MUDTable.GetUpdates<CounterTable>().ObserveOnMainThread().Subscribe(OnIncrement);
}
private void OnIncrement(RecordUpdate update) {
if (update.Type != UpdateType.DeleteRecord) {
var currentValue = update.CurrentRecordValue;
if (currentValue == null) return;
Debug.Log("Counter is now: " + JsonConvert.SerializeObject(currentValue));
Instantiate(prefab, Vector3.up, Quaternion.identity);
}
}
}
Select the Testnet NetworkType on the NetworkManager or create your own NetworkData ScriptableObject in the project window and link it.
- Does not support setters for individual table values on contracts (must set the entire table every time)
- Does not support pop or push array functions on contracts
- Indexer
- Caching/persistence
MIT