MUD client

Interacting with MUD from a client

MUD is client agnostic: any client -- a browser, a game engine, or an iOS app -- can implement the synchronization protocol and a client-side cache to replicate Store tables, along with the necessary infrastructure to send transactions to the World.

However, currently we only officially support the browser and Node. We provide tooling for representing the onchain state and replicate it from an RPC or MODE via a network stack.

There also exists tooling for Unity (opens in a new tab) from the folks at emergence.land (opens in a new tab).

Representing state in the browser

Currently the way to query the Store and react to changes is recs. This package is designed to represent and query ECS data from a Store. If your MUD tables are set up with a single bytes32 key (see the ECS modelling guide), and you plan to represent an entity and its associated components as the same bytes32 key on multiple tables, then recs will let you query your data in a natural way.

To read data and react to changes with recs, there exists two APIs:

  1. Reading component value directly
  2. Subscribing to a query that returns a set of matching entities

For the examples below, let's assume this MUD config. Notice that each of these tables have an (implicit) bytes32 key; which is recommended when modelling your MUD data using ECS.

const config = mudConfig({
  tables: {
    NameComponent: "string",
    PlayerComponent: "bool",
    PositionComponent: { schema: { x: "int32", y: "int32" } },
  },
});

Reading component value directly

If you have a specific bytes32 key and a reference to a component, you can query its value directly, or use a React hook that will re-render when the corresponding component value updates.

Reading component value in vanilla javascript

import { getComponentValueStrict } from "@latticexyz/recs";
// note: NameComponent is an recs component; components could come from the `setup` function of MUD.
const { NameComponent } = components
// get a reference to the recs world; as an example from the `setup` function of MUD.
const world = [...]
// you need a reference to an `Entity` from recs. For the sake of the example, let's say we know that the bytes32 key is "0xDEAD"
// you wouldn't do normally: entities would be found via queries.
const entityID = "0xDEAD" as EntityID;
const entity = world.registerEntity({ id: "0xDEAD" as EntityI })
// now we can fetch the value of the NameComponent on our entity
const name = getComponentValueStrict(NameComponent, entity)
// -> "John Doe"

Reading component value in react with @latticexyz/react

import { useComponentValue } from "@latticexyz/react";
function ExampleComponent() {
  const { components, world } = useMUD;
  const { NameComponent } = components;
  // you need a reference to an `Entity` from recs. For the sake of the example, let's say we know that the bytes32 key is "0xDEAD"
  // you wouldn't do this normally: entities would be found via queries.
  const entityID = "0xDEAD" as EntityID;
  const entity = world.registerEntity({ id: "0xDEAD" });
  // now we can fetch the value of the NameComponent on our entity
  const name = useComponentValue(NameComponent, entity);
  // -> "John Doe"
  // this will re-render when the component value changes!
  return <p>{name ?? "<no name>"}</p>;
}

Running queries

A common way of working with ECS data is by using queries. A query is a function that returns a list of entities that match a set of predicates called query fragments.

There are 4 types of query fragments:

  1. Has: matches entities that have a specific component.
  2. Not: matches entities that do not have a specific component.
  3. HasValue: matches entities that have a specific component with a set value.
  4. NotValue: matches entities that do not have a component with a set value (will also match if it doesn't have the component at all)

Running query with vanilla javascript

import { runQuery, Has, HasValue, getComponentValueStrict } from "@latticexyz/recs";
const { PlayerComponent, PositionComponent, NameComponent } = components
// query for all named players at the center of the universe
const matchingEntities = runQuery([
  Has(PlayerComponent),
  Has(Name),
  HasValue(PositionCompoennt, {x: 0, y: 0})
])
// now you can map these to their name as an example
const names = matchingEntities.map(
  playerEntity => getComponentValueStrict(NameComponent, playerEntity
)
// -> ["Bob", "Alice", "Eve"]

Reacting to queries with vanilla javascript

import { defineSystem, Has, HasValue, getComponentValueStrict, UpdateType } from "@latticexyz/recs";
const { PlayerComponent, PositionComponent, NameComponent } = components
// get a reference to the recs world; as an example from the `setup` function of MUD.
const world = [...]
defineSystem(world, [Has(PlayerComponent), Has(Name), HasValue(PositionComponent, {x: 0, y: 0}], ({entity, component, value, type}) => {
  // every time an entity enter, exit, or get updated within a query; this callback with fire
  // the "type" will match these: UpdateType.Enter, UpdateType.Exit, UpdateType.Update
  // as an example, if a new entity is named, has a player component, and is at the center of the universe; the callback fires
  // if the name of a player at the center of the universe changes, the callback will also fire for that entity
  // let's only log when a new entity matches this query
  // every time a named player reaches {0, 0}; we want to log their name
  if(type !== UpdateType.Enter) return
  console.log(getComponentValueStrict(NameComponent, entity) + " reached the center!")
})

Reacting to queries with React and @latticexyz/react

import { useEntityQuery } from "@latticexyz/react";
import { Has, HasValue, getComponentValueStrict } from "@latticexyz/recs";
function ExampleComponent() {
  const { components, world } = useMUD;
  const { NameComponent, PlayerComponent, PositionCompoennt } = components
  // get a list of all entities that are named, players, and at the center of the universe
  // it is reactive and will trigger a re-render when the set of matching queries update
  const entities = useEntityQuery([Has(PlayerComponent), Has(Name), HasValue(PositionComponent, {x: 0, y: 0}])
  // [entity1, entity2, ...]
  return(
    <div>
      <span>Players at the center:</span>
      <ul>
      {entities.map(entity => (
        <li>{getComponentValueStrict(NameComponent, entity)}</li>
      ))}
      </ul>
    </div>
  )
}