グローバルのハッカソンではAutonomous Worldというテーマがよく聞かれます。フルオンチェーンゲームの解釈を進めたオンチェーンの自律的な世界のことだと認識していますが、どのように作られているのか詳しくないので一緒に見ていきましょう!
Autonomous Worldとは
Autonmous Worldが何か、という話については以下の記事に詳しくまとまっていたのでぜひ参照してみてください。
またEthereum naviさんの以下の記事もMUDに限らずオンチェーンゲーム全般の趣旨や課題・可能性について触れていて必読です!
オンチェーンゲームの誕生背景と概要、事例紹介、そして現状の課題から今後の発展可能性までを網羅的に解説
ETH Globalの「Autonomous Worldsハッカソン」でファイナリストに選出された10個のプロジェクトについて概観し、今後の可能性と課題について探る
チュートリアル
今回はこちらのETHGlobalのAutonomous HackathonでのMUD開発者のチュートリアルを進めていきます。
まず https://github.com/latticexyz/react-workshop-starter のレポジトリをgit cloneします。
pnpm install, pnpm devで立ち上げます。
Foundryのlocal chainに繋がったフロントエンドが立ち上がります。Reactのコンポーネントを作るチュートリアルにならないために、コンポーネントは用意してくれています。
チェーンデータをhookしてReact Appに反映・インタラクティブにしていく方法について学んでいきます。
今回はTODOリストを作っていきます。
まずデータストラクチャを作ります。packages/contracts/mud.config.tsを開きます。以下のようにToDoのschemaを追加します。
import { mudConfig } from "@latticexyz/world/register";
export default mudConfig({
tables: {
ToDo: {
schema: {
done: "bool",
body: "string",
},
},
},
modules: [
{
name: "UniqueEntityModule",
root: true,
args: [],
},
],
});
自動でコントラクトが再デプロイされるようです。
次にpackages/contracts/src/systems/ToDoSystem.solを開いて編集していきます。
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import { System } from "@latticexyz/world/src/System.sol";
import { addressToEntity } from "../Utils.sol";
import { getUniqueEntity } from "@latticexyz/world/src/modules/uniqueentity/getUniqueEntity.sol";
import { ToDo, ToDoData } from "../codegen/Tables.sol";
contract ToDoSystem is System {
function addToDo(string memory body) public {
bytes32 id = getUniqueEntity();
ToDo.set(id, ToDoData({body: body, done: false}));
}
}
todoを追加するためのaddToDoという関数を作成しました。
フロントエンドからこの関数を実行していきます。
コンポーネントは作成されているので、インポートします。
packages/client/src/App.tsx
import React from "react";
import "./index.css";
import {
AppContainer,
Container,
Card,
HeaderDiv,
Subtitle,
Title,
Footer,
TextLink,
} from "./theme";
import { ToDoForm } from "./ToDoForm";
export const App = () => {
return (
<Container>
<AppContainer>
<HeaderDiv>
<Title>MUD x React Workshop</Title>
<Subtitle>Creating a todo list using MUD</Subtitle>
</HeaderDiv>
<Card>
<ToDoForm />
</Card>
<Footer>
<TextLink href="https://v2.mud.dev">MUD docs</TextLink>
</Footer>
</AppContainer>
</Container>
);
};
上記の+ボタンを押しても何も起きないのでロジックを実装していきます。
ToDoForm.tsx
import { useState } from "react";
import { FormButton, FormField, FormFieldWrapper } from "./theme";
import { Plus } from "./theme/Plus";
import { useMUD } from "./MUDContext";
export function ToDoForm() {
const [newToDo, setNewToDo] = useState("");
const {
systemCalls: { addToDo },
} = useMUD();
return (
<FormFieldWrapper>
<FormField
type="text"
placeholder="new todo"
value={newToDo}
onChange={(e) => {
setNewToDo(e.target.value);
}}
/>
<FormButton
onClick={() => {
addToDo(newToDo);
setNewToDo("");
}}
>
<Plus />
</FormButton>
</FormFieldWrapper>
);
}
状態を確認するためにフロントに表示します。
import React from "react";
import "./index.css";
import {
AppContainer,
Container,
Card,
HeaderDiv,
Subtitle,
Title,
Footer,
TextLink,
} from "./theme";
import { ToDoForm } from "./ToDoForm";
import { useMUD } from "./MUDContext";
import { useEntityQuery } from "@latticexyz/react";
import { Has } from "@latticexyz/recs";
export const App = () => {
const {
components: { ToDo },
} = useMUD();
const toDoIds = useEntityQuery([Has(ToDo)]);
return (
<Container>
<AppContainer>
<HeaderDiv>
<Title>MUD x React Workshop</Title>
<Subtitle>Creating a todo list using MUD</Subtitle>
</HeaderDiv>
<Card>
{toDoIds}
<ToDoForm />
</Card>
<Footer>
<TextLink href="https://v2.mud.dev">MUD docs</TextLink>
</Footer>
</AppContainer>
</Container>
);
};
id 0x01が表示されました。idからbodyに入れたstringのデータを取得して表示させます。
<Card>
{[...toDoIds].map((id) => {
const toDoData = getComponentValueStrict(ToDo, id);
return <ToDoItem key={id} id={id} {...toDoData} />;
})}
<ToDoForm />
</Card>
ここまででaddToDoは完成です。次にチェックボックスを入れたらToDoをDoneにする必要があります。またSolidityから編集していきます。
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import { System } from "@latticexyz/world/src/System.sol";
import { addressToEntity } from "../Utils.sol";
import { getUniqueEntity } from "@latticexyz/world/src/modules/uniqueentity/getUniqueEntity.sol";
import { ToDo, ToDoData } from "../codegen/Tables.sol";
contract ToDoSystem is System {
function addToDo(string memory body) public {
bytes32 id = getUniqueEntity();
ToDo.set(id, ToDoData({body: body, done: false}));
}
function toggleDone(bytes32 toDoId) public {
ToDo.setDone(toDoId, !ToDo.getDone(toDoId));
}
}
保存するとコントラクトが自動デプロイされフロントエンドは新しいコントラクトに接続されています(既存のToDoは無くなって見えます)。新しいworldに切り替わったので、古いworldに戻したければ戻すこともできるみたいです。
createSystemCalls.ts
const toggleDone = (id: string) => {
worldSend("toggleDone", [entityToBytes32(id)]);
};
ToDoItem.tsx
export function ToDoItem({ id, body, done }: Props) {
const {
systemCalls: { toggleDone },
} = useMUD();
return (
<ToDoItemWrapper>
<span>{body}</span>
<input
type="checkbox"
checked={done}
onChange={(e) => {
toggleDone(id);
}}
/>
</ToDoItemWrapper>
);
}
TodoをDoneにすることができました。
次にチームでToDoを管理することを念頭に、TodoにOwnerをつけていきます。
function addToDo(string memory body) public {
bytes32 id = getUniqueEntity();
bytes32 owner = addressToEntity(_msgSender());
ToDo.set(id, ToDoData({body: body, done: false, owner: owner}));
}
MUDではmsg.sender()ではなく_msgSender()を使う必要があります。
ここで少しエラーが出てしまって今回のチュートリアルはここまでとなりました。。。
ただ全体的な流れと基本的な操作はつかめたと思います。
まとめ
これでゲーム全体を作るのはなかなか根気のいる作業だなという所感ですが、Autonomous Worldを作るにあたってMUDは欠かせないツールだなと思いました。今後の開発体験の向上に期待です。
弊社Pontechはweb3に関わる開発を得意とするテック企業です。サービス開発に関するご相談はこちらのフォームからお願いいたします。
また、受託開発案件に共に取り組むメンバーを募集しています!ご興味のある方はぜひお話させてください!