diff --git a/bun.lockb b/bun.lockb
index d8553b12..98aac85b 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/examples/react-app/bun.lockb b/examples/react-app/bun.lockb
deleted file mode 100755
index a3c4a488..00000000
Binary files a/examples/react-app/bun.lockb and /dev/null differ
diff --git a/examples/react-phaser-example/.env b/examples/react-phaser-example/.env
new file mode 100644
index 00000000..49b085e6
--- /dev/null
+++ b/examples/react-phaser-example/.env
@@ -0,0 +1,7 @@
+VITE_PUBLIC_ETH_CONTRACT_ADDRESS=0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7
+VITE_PUBLIC_ACCOUNT_CLASS_HASH=0x04d07e40e93398ed3c76981e72dd1fd22557a78ce36c0515f679e27f0bb5bc5f
+VITE_PUBLIC_MASTER_ADDRESS=0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973
+VITE_PUBLIC_MASTER_PRIVATE_KEY=0x1800000000300000180000000000030000000000003006001800006600
+VITE_PUBLIC_WORLD_ADDRESS=0x534692277764b04cfc469858891b825c799d1da550d2509fdd5be2f32abdaa0
+VITE_PUBLIC_NODE_URL=http://localhost:5050
+VITE_PUBLIC_TORII=http://localhost:8080
\ No newline at end of file
diff --git a/examples/react-phaser-example/.eslintrc.cjs b/examples/react-phaser-example/.eslintrc.cjs
new file mode 100644
index 00000000..4020bcbf
--- /dev/null
+++ b/examples/react-phaser-example/.eslintrc.cjs
@@ -0,0 +1,14 @@
+module.exports = {
+ env: { browser: true, es2020: true },
+ extends: [
+ 'eslint:recommended',
+ 'plugin:@typescript-eslint/recommended',
+ 'plugin:react-hooks/recommended',
+ ],
+ parser: '@typescript-eslint/parser',
+ parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
+ plugins: ['react-refresh'],
+ rules: {
+ 'react-refresh/only-export-components': 'warn',
+ },
+}
diff --git a/examples/react-phaser-example/.gitignore b/examples/react-phaser-example/.gitignore
new file mode 100644
index 00000000..54f07af5
--- /dev/null
+++ b/examples/react-phaser-example/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
\ No newline at end of file
diff --git a/examples/react-phaser-example/LICENSE b/examples/react-phaser-example/LICENSE
new file mode 100644
index 00000000..d29b8597
--- /dev/null
+++ b/examples/react-phaser-example/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Dojo
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/examples/react-phaser-example/codegen.ts b/examples/react-phaser-example/codegen.ts
new file mode 100644
index 00000000..7df216eb
--- /dev/null
+++ b/examples/react-phaser-example/codegen.ts
@@ -0,0 +1,15 @@
+import type { CodegenConfig } from '@graphql-codegen/cli';
+
+const config: CodegenConfig = {
+ schema: 'http://localhost:8080',
+ documents: 'src/**/*.graphql',
+ generates: {
+ 'src/generated/graphql.ts': {
+ plugins: ['typescript', 'typescript-operations', 'typescript-graphql-request'],
+ config: {
+ rawRequest: true
+ },
+ },
+ },
+};
+export default config;
\ No newline at end of file
diff --git a/examples/react-phaser-example/index.html b/examples/react-phaser-example/index.html
new file mode 100644
index 00000000..e0d1c840
--- /dev/null
+++ b/examples/react-phaser-example/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Vite + React + TS
+
+
+
+
+
+
diff --git a/examples/react-phaser-example/package.json b/examples/react-phaser-example/package.json
new file mode 100644
index 00000000..ee37f9ff
--- /dev/null
+++ b/examples/react-phaser-example/package.json
@@ -0,0 +1,55 @@
+{
+ "name": "react-phaser-example",
+ "private": true,
+ "version": "0.1.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
+ "preview": "vite preview",
+ "codegen": "graphql-codegen",
+ "components": "npx @dojoengine/core ../dojo-starter/target/dev/manifest.json src/dojo/contractComponents.ts http://localhost:5050 0x534692277764b04cfc469858891b825c799d1da550d2509fdd5be2f32abdaa0"
+ },
+ "dependencies": {
+ "@dojoengine/core": "link:dojo-packages/packages/core",
+ "@dojoengine/create-burner": "link:dojo-packages/packages/create-burner",
+ "@dojoengine/torii-client": "link:dojo-packages/packages/torii-client",
+ "@dojoengine/utils": "link:dojo-packages/packages/utils",
+ "@dojoengine/react": "link:dojo-packages/packages/react",
+ "@dojoengine/recs": "0.1.35",
+ "@latticexyz/utils": "^2.0.0-next.11",
+ "@latticexyz/phaserx": "^2.0.0-next.11",
+ "ethers": "^5.7.2",
+ "events": "^3.3.0",
+ "graphql": "^16.7.1",
+ "graphql-request": "^6.1.0",
+ "mobx": "^6.9.0",
+ "phaser": "3.60.0-beta.14",
+ "proxy-deep": "^3.1.1",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "rxjs": "^7.8.1",
+ "simplex-noise": "^4.0.1",
+ "starknet": "^5.19.5",
+ "styled-components": "^6.0.7",
+ "zustand": "^4.4.1"
+ },
+ "devDependencies": {
+ "@graphql-codegen/cli": "^5.0.0",
+ "@graphql-codegen/typescript": "^4.0.1",
+ "@graphql-codegen/typescript-graphql-request": "^5.0.0",
+ "@graphql-codegen/typescript-operations": "^4.0.1",
+ "@types/node": "^20.4.8",
+ "@types/react": "^18.0.37",
+ "@types/react-dom": "^18.0.11",
+ "@typescript-eslint/eslint-plugin": "^5.59.0",
+ "@typescript-eslint/parser": "^5.59.0",
+ "@vitejs/plugin-react": "^4.0.0",
+ "eslint": "^8.38.0",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "eslint-plugin-react-refresh": "^0.3.4",
+ "typescript": "^5.0.2",
+ "vite": "^4.3.9"
+ }
+}
diff --git a/examples/react-phaser-example/public/vite.svg b/examples/react-phaser-example/public/vite.svg
new file mode 100644
index 00000000..e7b8dfb1
--- /dev/null
+++ b/examples/react-phaser-example/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/react-phaser-example/readme.md b/examples/react-phaser-example/readme.md
new file mode 100644
index 00000000..630c96d2
--- /dev/null
+++ b/examples/react-phaser-example/readme.md
@@ -0,0 +1,21 @@
+## Dojo create-phaser-app
+
+The pattern here is inherited from mud.dev and is a simple example of how to use the dojo engine with create-phaser-app.
+
+### Steps to Execute the Example
+
+Firstly, clone the [dojo-starter](https://github.com/dojoengine/dojo-starter) project and follow the given instructions to ensure it's functioning correctly. This step is crucial as this client displays data obtained from the project.
+
+Subsequently, clone this project and execute the following commands in the terminal:
+
+```console
+yarn
+
+yarn generate
+
+yarn dev
+```
+
+### Notes
+
+This is just a simple example of how to setup a project, but in by no means is the only way.
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/App.css b/examples/react-phaser-example/src/App.css
new file mode 100644
index 00000000..e69de29b
diff --git a/examples/react-phaser-example/src/App.tsx b/examples/react-phaser-example/src/App.tsx
new file mode 100644
index 00000000..4f2a4011
--- /dev/null
+++ b/examples/react-phaser-example/src/App.tsx
@@ -0,0 +1,28 @@
+import "./App.css";
+import { useEffect } from "react";
+import { useNetworkLayer } from "./hooks/useNetworkLayer";
+import { PhaserLayer } from "./phaser/phaserLayer";
+import { store } from "./store/store";
+import { UI } from "./ui";
+
+function App() {
+ const networkLayer = useNetworkLayer();
+
+ useEffect(() => {
+ if (!networkLayer) return;
+
+ console.log("Setting network layer");
+
+ store.setState({ networkLayer });
+ }, [networkLayer]);
+
+ return (
+
+ );
+}
+
+export default App;
diff --git a/examples/react-phaser-example/src/artTypes/world.ts b/examples/react-phaser-example/src/artTypes/world.ts
new file mode 100644
index 00000000..1e819ca2
--- /dev/null
+++ b/examples/react-phaser-example/src/artTypes/world.ts
@@ -0,0 +1,9 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+export enum Tileset {
+ Grass = 0,
+ Mountains = 1,
+ Forest = 2,
+}
+export enum TileAnimationKey { }
+export const TileAnimations: { [key in TileAnimationKey]: number[] } = {};
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/assets/atlases/atlas.json b/examples/react-phaser-example/src/assets/atlases/atlas.json
new file mode 100644
index 00000000..1052ddd4
--- /dev/null
+++ b/examples/react-phaser-example/src/assets/atlases/atlas.json
@@ -0,0 +1,103 @@
+{
+ "meta": {
+ "app": "",
+ "version": "1.0.0"
+ },
+ "textures": [
+ {
+ "image": "atlas.png?timestamp=1678825051140",
+ "format": "RGBA8888",
+ "size": {
+ "w": 2048,
+ "h": 2048
+ },
+ "scale": 1,
+ "frames": [
+ {
+ "filename": "sprites/soldier/idle/0.png",
+ "rotated": false,
+ "trimmed": false,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 0,
+ "y": 0,
+ "w": 32,
+ "h": 32
+ },
+ "frame": {
+ "x": 0,
+ "y": 0,
+ "w": 32,
+ "h": 32
+ }
+ },
+ {
+ "filename": "sprites/soldier/idle/1.png",
+ "rotated": false,
+ "trimmed": false,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 0,
+ "y": 0,
+ "w": 32,
+ "h": 32
+ },
+ "frame": {
+ "x": 32,
+ "y": 0,
+ "w": 32,
+ "h": 32
+ }
+ },
+ {
+ "filename": "sprites/soldier/idle/2.png",
+ "rotated": false,
+ "trimmed": false,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 0,
+ "y": 0,
+ "w": 32,
+ "h": 32
+ },
+ "frame": {
+ "x": 0,
+ "y": 32,
+ "w": 32,
+ "h": 32
+ }
+ },
+ {
+ "filename": "sprites/soldier/idle/3.png",
+ "rotated": false,
+ "trimmed": false,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 0,
+ "y": 0,
+ "w": 32,
+ "h": 32
+ },
+ "frame": {
+ "x": 32,
+ "y": 32,
+ "w": 32,
+ "h": 32
+ }
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/assets/atlases/atlas.png b/examples/react-phaser-example/src/assets/atlases/atlas.png
new file mode 100644
index 00000000..67ad935c
Binary files /dev/null and b/examples/react-phaser-example/src/assets/atlases/atlas.png differ
diff --git a/examples/react-phaser-example/src/assets/react.svg b/examples/react-phaser-example/src/assets/react.svg
new file mode 100644
index 00000000..6c87de9b
--- /dev/null
+++ b/examples/react-phaser-example/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/assets/tilesets/world.png b/examples/react-phaser-example/src/assets/tilesets/world.png
new file mode 100644
index 00000000..75e8fa22
Binary files /dev/null and b/examples/react-phaser-example/src/assets/tilesets/world.png differ
diff --git a/examples/react-phaser-example/src/dojo/contractComponents.ts b/examples/react-phaser-example/src/dojo/contractComponents.ts
new file mode 100644
index 00000000..12e2cf77
--- /dev/null
+++ b/examples/react-phaser-example/src/dojo/contractComponents.ts
@@ -0,0 +1,39 @@
+/* Autogenerated file. Do not edit manually. */
+
+import { defineComponent, Type as RecsType, World } from "@dojoengine/recs";
+
+export function defineContractComponents(world: World) {
+ return {
+ Moves: (() => {
+ return defineComponent(
+ world,
+ {
+ player: RecsType.BigInt,
+ remaining: RecsType.Number,
+ last_direction: RecsType.Number,
+ },
+ {
+ metadata: {
+ name: "Moves",
+ types: ["Direction"],
+ },
+ }
+ );
+ })(),
+ Position: (() => {
+ return defineComponent(
+ world,
+ {
+ player: RecsType.BigInt,
+ vec: { x: RecsType.Number, y: RecsType.Number },
+ },
+ {
+ metadata: {
+ name: "Position",
+ types: ["Vec2"],
+ },
+ }
+ );
+ })(),
+ };
+}
diff --git a/examples/react-phaser-example/src/dojo/createClientComponents.ts b/examples/react-phaser-example/src/dojo/createClientComponents.ts
new file mode 100644
index 00000000..fba197bc
--- /dev/null
+++ b/examples/react-phaser-example/src/dojo/createClientComponents.ts
@@ -0,0 +1,13 @@
+import { overridableComponent } from "@dojoengine/recs";
+import { SetupNetworkResult } from "./setupNetwork";
+
+
+export type ClientComponents = ReturnType;
+
+export function createClientComponents({ contractComponents }: SetupNetworkResult) {
+ return {
+ ...contractComponents,
+ Position: overridableComponent(contractComponents.Position),
+ Moves: overridableComponent(contractComponents.Moves),
+ };
+}
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/dojo/createNetworkLayer.ts b/examples/react-phaser-example/src/dojo/createNetworkLayer.ts
new file mode 100644
index 00000000..0a1194ff
--- /dev/null
+++ b/examples/react-phaser-example/src/dojo/createNetworkLayer.ts
@@ -0,0 +1,28 @@
+import { world } from "./world";
+import { setup } from "./setup";
+import { Account, RpcProvider } from "starknet";
+
+export type NetworkLayer = Awaited>;
+
+export const createNetworkLayer = async () => {
+ const { components, systemCalls, network } = await setup();
+
+ const provider = new RpcProvider({
+ nodeUrl: import.meta.env.VITE_PUBLIC_NODE_URL!,
+ });
+
+ // TODO: Make Burner System
+ const account = new Account(
+ provider,
+ import.meta.env.VITE_PUBLIC_MASTER_ADDRESS!,
+ import.meta.env.VITE_PUBLIC_MASTER_PRIVATE_KEY!
+ );
+
+ return {
+ world,
+ components,
+ systemCalls,
+ network,
+ account,
+ };
+};
diff --git a/examples/react-phaser-example/src/dojo/createSystemCalls.ts b/examples/react-phaser-example/src/dojo/createSystemCalls.ts
new file mode 100644
index 00000000..e358db45
--- /dev/null
+++ b/examples/react-phaser-example/src/dojo/createSystemCalls.ts
@@ -0,0 +1,116 @@
+import { SetupNetworkResult } from "./setupNetwork";
+import { Account } from "starknet";
+import { Entity, getComponentValue } from "@dojoengine/recs";
+import { uuid } from "@latticexyz/utils";
+import { ClientComponents } from "./createClientComponents";
+import { Direction, updatePositionWithDirection } from "../utils";
+import { getEvents, setComponentsFromEvents } from "@dojoengine/utils";
+
+export type SystemCalls = ReturnType;
+
+export function createSystemCalls(
+ { execute, contractComponents }: SetupNetworkResult,
+ { Position, Moves }: ClientComponents
+) {
+ const spawn = async (signer: Account) => {
+ const entityId = signer.address.toString() as Entity;
+
+ const positionId = uuid();
+ Position.addOverride(positionId, {
+ entity: entityId,
+ value: { player: BigInt(entityId), vec: { x: 10, y: 10 } },
+ });
+
+ const movesId = uuid();
+ Moves.addOverride(movesId, {
+ entity: entityId,
+ value: {
+ player: BigInt(entityId),
+ remaining: 10,
+ last_direction: 0,
+ },
+ });
+
+ try {
+ const { transaction_hash } = await execute(
+ signer,
+ "actions",
+ "spawn",
+ []
+ );
+
+ setComponentsFromEvents(
+ contractComponents,
+ getEvents(
+ await signer.waitForTransaction(transaction_hash, {
+ retryInterval: 100,
+ })
+ )
+ );
+ } catch (e) {
+ console.log(e);
+ Position.removeOverride(positionId);
+ Moves.removeOverride(movesId);
+ } finally {
+ Position.removeOverride(positionId);
+ Moves.removeOverride(movesId);
+ }
+ };
+
+ const move = async (signer: Account, direction: Direction) => {
+ const entityId = signer.address.toString() as Entity;
+
+ const positionId = uuid();
+ Position.addOverride(positionId, {
+ entity: entityId,
+ value: {
+ player: BigInt(entityId),
+ vec: updatePositionWithDirection(
+ direction,
+ // currently recs does not support nested values so we use any here
+ getComponentValue(Position, entityId) as any
+ ).vec,
+ },
+ });
+
+ const movesId = uuid();
+ Moves.addOverride(movesId, {
+ entity: entityId,
+ value: {
+ player: BigInt(entityId),
+ remaining:
+ (getComponentValue(Moves, entityId)?.remaining || 0) - 1,
+ },
+ });
+
+ try {
+ const { transaction_hash } = await execute(
+ signer,
+ "actions",
+ "move",
+ [direction]
+ );
+
+ setComponentsFromEvents(
+ contractComponents,
+ getEvents(
+ await signer.waitForTransaction(transaction_hash, {
+ retryInterval: 100,
+ })
+ )
+ );
+ } catch (e) {
+ console.log(e);
+ Position.removeOverride(positionId);
+ Moves.removeOverride(movesId);
+ } finally {
+ Position.removeOverride(positionId);
+ Moves.removeOverride(movesId);
+ }
+ };
+
+ return {
+ spawn,
+ move,
+ };
+}
diff --git a/examples/react-phaser-example/src/dojo/setup.ts b/examples/react-phaser-example/src/dojo/setup.ts
new file mode 100644
index 00000000..1fb9e411
--- /dev/null
+++ b/examples/react-phaser-example/src/dojo/setup.ts
@@ -0,0 +1,16 @@
+import { createClientComponents } from "./createClientComponents";
+import { createSystemCalls } from "./createSystemCalls";
+import { setupNetwork } from "./setupNetwork";
+
+export type SetupResult = Awaited>;
+
+export async function setup() {
+ const network = await setupNetwork();
+ const components = createClientComponents(network);
+ const systemCalls = createSystemCalls(network, components);
+ return {
+ network,
+ components,
+ systemCalls,
+ };
+}
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/dojo/setupNetwork.ts b/examples/react-phaser-example/src/dojo/setupNetwork.ts
new file mode 100644
index 00000000..98e32933
--- /dev/null
+++ b/examples/react-phaser-example/src/dojo/setupNetwork.ts
@@ -0,0 +1,34 @@
+import { defineContractComponents } from "./contractComponents";
+import { world } from "./world";
+import { RPCProvider } from "@dojoengine/core";
+import { Account, num } from "starknet";
+import { GraphQLClient } from 'graphql-request';
+import { getSdk } from '../generated/graphql';
+import manifest from '../../../dojo-starter/target/dev/manifest.json'
+
+export type SetupNetworkResult = Awaited>;
+
+export async function setupNetwork() {
+ // Extract environment variables for better readability.
+ const { VITE_PUBLIC_WORLD_ADDRESS, VITE_PUBLIC_NODE_URL, VITE_PUBLIC_TORII } = import.meta.env;
+
+ // Create a new RPCProvider instance.
+ const provider = new RPCProvider(VITE_PUBLIC_WORLD_ADDRESS, manifest, VITE_PUBLIC_NODE_URL);
+
+ // Return the setup object.
+ return {
+ provider,
+ world,
+
+ // Define contract components for the world.
+ contractComponents: defineContractComponents(world),
+
+ // Define the graph SDK instance.
+ graphSdk: () => getSdk(new GraphQLClient(VITE_PUBLIC_TORII)),
+
+ // Execute function.
+ execute: async (signer: Account, contract: string, system: string, call_data: num.BigNumberish[]) => {
+ return provider.execute(signer, contract, system, call_data);
+ },
+ };
+}
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/dojo/world.ts b/examples/react-phaser-example/src/dojo/world.ts
new file mode 100644
index 00000000..19bf1912
--- /dev/null
+++ b/examples/react-phaser-example/src/dojo/world.ts
@@ -0,0 +1,3 @@
+import { createWorld } from "@dojoengine/recs";
+
+export const world = createWorld();
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/generated/graphql.ts b/examples/react-phaser-example/src/generated/graphql.ts
new file mode 100644
index 00000000..8ff86c1c
--- /dev/null
+++ b/examples/react-phaser-example/src/generated/graphql.ts
@@ -0,0 +1,252 @@
+import { GraphQLClient } from 'graphql-request';
+import { GraphQLClientRequestHeaders } from 'graphql-request/build/cjs/types';
+import { print } from 'graphql'
+import gql from 'graphql-tag';
+export type Maybe = T | null;
+export type InputMaybe = Maybe;
+export type Exact = { [K in keyof T]: T[K] };
+export type MakeOptional = Omit & { [SubKey in K]?: Maybe };
+export type MakeMaybe = Omit & { [SubKey in K]: Maybe };
+export type MakeEmpty = { [_ in K]?: never };
+export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
+/** All built-in and custom scalars, mapped to their actual values */
+export type Scalars = {
+ ID: { input: string; output: string; }
+ String: { input: string; output: string; }
+ Boolean: { input: boolean; output: boolean; }
+ Int: { input: number; output: number; }
+ Float: { input: number; output: number; }
+ Cursor: { input: any; output: any; }
+ DateTime: { input: any; output: any; }
+ felt252: { input: any; output: any; }
+ u8: { input: any; output: any; }
+ u32: { input: any; output: any; }
+};
+
+export type ComponentUnion = Moves | Position;
+
+export type Entity = {
+ __typename?: 'Entity';
+ componentNames?: Maybe;
+ components?: Maybe>>;
+ createdAt?: Maybe;
+ id?: Maybe;
+ keys?: Maybe;
+ updatedAt?: Maybe;
+};
+
+export type EntityConnection = {
+ __typename?: 'EntityConnection';
+ edges?: Maybe>>;
+ totalCount: Scalars['Int']['output'];
+};
+
+export type EntityEdge = {
+ __typename?: 'EntityEdge';
+ cursor: Scalars['Cursor']['output'];
+ node?: Maybe;
+};
+
+export type Event = {
+ __typename?: 'Event';
+ createdAt?: Maybe;
+ data?: Maybe;
+ id?: Maybe;
+ keys?: Maybe;
+ systemCall: SystemCall;
+ systemCallId?: Maybe;
+};
+
+export type EventConnection = {
+ __typename?: 'EventConnection';
+ edges?: Maybe>>;
+ totalCount: Scalars['Int']['output'];
+};
+
+export type EventEdge = {
+ __typename?: 'EventEdge';
+ cursor: Scalars['Cursor']['output'];
+ node?: Maybe;
+};
+
+export type Moves = {
+ __typename?: 'Moves';
+ entity?: Maybe;
+ remaining?: Maybe;
+};
+
+export type MovesConnection = {
+ __typename?: 'MovesConnection';
+ edges?: Maybe>>;
+ totalCount: Scalars['Int']['output'];
+};
+
+export type MovesEdge = {
+ __typename?: 'MovesEdge';
+ cursor: Scalars['Cursor']['output'];
+ node?: Maybe;
+};
+
+export type Position = {
+ __typename?: 'Position';
+ entity?: Maybe;
+ x?: Maybe;
+ y?: Maybe;
+};
+
+export type PositionConnection = {
+ __typename?: 'PositionConnection';
+ edges?: Maybe>>;
+ totalCount: Scalars['Int']['output'];
+};
+
+export type PositionEdge = {
+ __typename?: 'PositionEdge';
+ cursor: Scalars['Cursor']['output'];
+ node?: Maybe;
+};
+
+export type Query = {
+ __typename?: 'Query';
+ entities?: Maybe;
+ entity: Entity;
+ event: Event;
+ events?: Maybe;
+ movesComponents?: Maybe;
+ positionComponents?: Maybe;
+ system: System;
+ systemCall: SystemCall;
+ systemCalls?: Maybe;
+ systems?: Maybe;
+};
+
+
+export type QueryEntitiesArgs = {
+ after?: InputMaybe;
+ before?: InputMaybe;
+ first?: InputMaybe;
+ keys: Array;
+ last?: InputMaybe;
+};
+
+
+export type QueryEntityArgs = {
+ id: Scalars['ID']['input'];
+};
+
+
+export type QueryEventArgs = {
+ id: Scalars['ID']['input'];
+};
+
+
+export type QueryMovesComponentsArgs = {
+ after?: InputMaybe;
+ before?: InputMaybe;
+ first?: InputMaybe;
+ last?: InputMaybe;
+};
+
+
+export type QueryPositionComponentsArgs = {
+ after?: InputMaybe;
+ before?: InputMaybe;
+ first?: InputMaybe;
+ last?: InputMaybe;
+};
+
+
+export type QuerySystemArgs = {
+ id: Scalars['ID']['input'];
+};
+
+
+export type QuerySystemCallArgs = {
+ id: Scalars['Int']['input'];
+};
+
+export type System = {
+ __typename?: 'System';
+ classHash?: Maybe;
+ createdAt?: Maybe;
+ id?: Maybe;
+ name?: Maybe;
+ systemCalls: Array;
+ transactionHash?: Maybe;
+};
+
+export type SystemCall = {
+ __typename?: 'SystemCall';
+ createdAt?: Maybe;
+ data?: Maybe;
+ id?: Maybe;
+ system: System;
+ systemId?: Maybe;
+ transactionHash?: Maybe;
+};
+
+export type SystemCallConnection = {
+ __typename?: 'SystemCallConnection';
+ edges?: Maybe>>;
+ totalCount: Scalars['Int']['output'];
+};
+
+export type SystemCallEdge = {
+ __typename?: 'SystemCallEdge';
+ cursor: Scalars['Cursor']['output'];
+ node?: Maybe;
+};
+
+export type SystemConnection = {
+ __typename?: 'SystemConnection';
+ edges?: Maybe>>;
+ totalCount: Scalars['Int']['output'];
+};
+
+export type SystemEdge = {
+ __typename?: 'SystemEdge';
+ cursor: Scalars['Cursor']['output'];
+ node?: Maybe;
+};
+
+export type GetEntitiesQueryVariables = Exact<{ [key: string]: never; }>;
+
+
+export type GetEntitiesQuery = { __typename?: 'Query', entities?: { __typename?: 'EntityConnection', edges?: Array<{ __typename?: 'EntityEdge', node?: { __typename?: 'Entity', keys?: string | null, components?: Array<{ __typename: 'Moves', remaining?: any | null } | { __typename: 'Position', x?: any | null, y?: any | null } | null> | null } | null } | null> | null } | null };
+
+
+export const GetEntitiesDocument = gql`
+ query getEntities {
+ entities(keys: ["%"]) {
+ edges {
+ node {
+ keys
+ components {
+ __typename
+ ... on Moves {
+ remaining
+ }
+ ... on Position {
+ x
+ y
+ }
+ }
+ }
+ }
+ }
+}
+ `;
+
+export type SdkFunctionWrapper = (action: (requestHeaders?:Record) => Promise, operationName: string, operationType?: string) => Promise;
+
+
+const defaultWrapper: SdkFunctionWrapper = (action, _operationName, _operationType) => action();
+const GetEntitiesDocumentString = print(GetEntitiesDocument);
+export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = defaultWrapper) {
+ return {
+ getEntities(variables?: GetEntitiesQueryVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise<{ data: GetEntitiesQuery; extensions?: any; headers: Dom.Headers; status: number; }> {
+ return withWrapper((wrappedRequestHeaders) => client.rawRequest(GetEntitiesDocumentString, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'getEntities', 'query');
+ }
+ };
+}
+export type Sdk = ReturnType;
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/graphql/schema.graphql b/examples/react-phaser-example/src/graphql/schema.graphql
new file mode 100644
index 00000000..3c9bd8a8
--- /dev/null
+++ b/examples/react-phaser-example/src/graphql/schema.graphql
@@ -0,0 +1,19 @@
+query getEntities {
+ entities(keys: ["%"]) {
+ edges {
+ node {
+ keys
+ components {
+ __typename
+ ... on Moves {
+ remaining
+ }
+ ... on Position {
+ x
+ y
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/hooks/useDojo.tsx b/examples/react-phaser-example/src/hooks/useDojo.tsx
new file mode 100644
index 00000000..180133ad
--- /dev/null
+++ b/examples/react-phaser-example/src/hooks/useDojo.tsx
@@ -0,0 +1,37 @@
+import { Account, RpcProvider } from "starknet";
+import { NetworkLayer } from "../dojo/createNetworkLayer";
+import { PhaserLayer } from "../phaser";
+import { store } from "../store/store";
+import { useBurner } from "@dojoengine/create-burner";
+
+export type UIStore = ReturnType;
+
+export const useDojo = () => {
+ const { networkLayer, phaserLayer } = store();
+
+ const provider = new RpcProvider({
+ nodeUrl: import.meta.env.VITE_PUBLIC_NODE_URL,
+ });
+
+ // todo: allow connection with wallet providers
+ const masterAccount = new Account(provider, import.meta.env.VITE_PUBLIC_MASTER_ADDRESS!, import.meta.env.VITE_PUBLIC_MASTER_PRIVATE_KEY!)
+
+ const { create, list, get, account, select, isDeploying } = useBurner();
+
+ if (phaserLayer === null) {
+ throw new Error("Store not initialized");
+ }
+
+ return {
+ networkLayer: networkLayer as NetworkLayer,
+ phaserLayer: phaserLayer as PhaserLayer,
+ account: {
+ create,
+ list,
+ get,
+ account: account ? account : masterAccount,
+ select,
+ isDeploying
+ }
+ }
+};
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/hooks/useNetworkLayer.tsx b/examples/react-phaser-example/src/hooks/useNetworkLayer.tsx
new file mode 100644
index 00000000..796598b5
--- /dev/null
+++ b/examples/react-phaser-example/src/hooks/useNetworkLayer.tsx
@@ -0,0 +1,17 @@
+import { useEffect, useMemo } from "react";
+import { createNetworkLayer } from "../dojo/createNetworkLayer";
+import { usePromiseValue } from "./usePromiseValue";
+
+export const useNetworkLayer = () => {
+ const networkLayerPromise = useMemo(() => {
+ return createNetworkLayer();
+ }, []);
+
+ useEffect(() => {
+ return () => {
+ networkLayerPromise.then((networkLayer) => networkLayer.world.dispose());
+ };
+ }, [networkLayerPromise]);
+
+ return usePromiseValue(networkLayerPromise);
+};
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/hooks/usePhaserLayer.tsx b/examples/react-phaser-example/src/hooks/usePhaserLayer.tsx
new file mode 100644
index 00000000..0877c572
--- /dev/null
+++ b/examples/react-phaser-example/src/hooks/usePhaserLayer.tsx
@@ -0,0 +1,74 @@
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
+import { createPhaserLayer } from "../phaser";
+import { NetworkLayer } from "../dojo/createNetworkLayer";
+import { phaserConfig } from "../phaser/configurePhaser";
+import { usePromiseValue } from "./usePromiseValue";
+
+type Props = {
+ networkLayer: NetworkLayer | null;
+};
+
+const createContainer = () => {
+ const container = document.createElement("div");
+ container.style.width = "100%";
+ container.style.height = "100%";
+ container.style.pointerEvents = "all";
+ container.style.overflow = "hidden";
+ return container;
+};
+
+
+export const usePhaserLayer = ({ networkLayer }: Props) => {
+ const parentRef = useRef(null);
+ const [{ width, height }, setSize] = useState({ width: 0, height: 0 });
+
+ const { phaserLayerPromise, container } = useMemo(() => {
+ if (!networkLayer) return { container: null, phaserLayerPromise: null };
+
+ const container = createContainer();
+ if (parentRef.current) {
+ parentRef.current.appendChild(container);
+ }
+
+ return {
+ container,
+ phaserLayerPromise: createPhaserLayer(networkLayer, {
+ ...phaserConfig,
+ scale: {
+ ...phaserConfig.scale,
+ parent: container,
+ mode: Phaser.Scale.NONE,
+ width,
+ height,
+ },
+ }),
+ };
+
+ // We don't want width/height to recreate phaser layer, so we ignore linter
+ }, [networkLayer]);
+
+ useEffect(() => {
+ return () => {
+ phaserLayerPromise?.then((phaserLayer) => phaserLayer.world.dispose());
+ container?.remove();
+ };
+ }, [container, phaserLayerPromise]);
+
+ const phaserLayer = usePromiseValue(phaserLayerPromise);
+
+ const ref = useCallback(
+ (el: HTMLElement | null) => {
+ parentRef.current = el;
+ if (container) {
+ if (parentRef.current) {
+ parentRef.current.appendChild(container);
+ } else {
+ container.remove();
+ }
+ }
+ },
+ [container]
+ );
+
+ return useMemo(() => ({ ref, phaserLayer }), [ref, phaserLayer]);
+}
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/hooks/usePromiseValue.ts b/examples/react-phaser-example/src/hooks/usePromiseValue.ts
new file mode 100644
index 00000000..37fa4e5f
--- /dev/null
+++ b/examples/react-phaser-example/src/hooks/usePromiseValue.ts
@@ -0,0 +1,24 @@
+import { useEffect, useState, useRef } from "react";
+
+export const usePromiseValue = (promise: Promise | null) => {
+ const promiseRef = useRef(promise);
+ const [value, setValue] = useState(null);
+ useEffect(() => {
+ if (!promise) return;
+ let isMounted = true;
+ promiseRef.current = promise;
+ // TODO: do something with promise errors?
+ promise.then((resolvedValue) => {
+ // skip if unmounted (state changes will cause errors otherwise)
+ if (!isMounted) return;
+ // If our promise was replaced before it resolved, ignore the result
+ if (promiseRef.current !== promise) return;
+
+ setValue(resolvedValue);
+ });
+ return () => {
+ isMounted = false;
+ };
+ }, [promise]);
+ return value;
+};
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/main.tsx b/examples/react-phaser-example/src/main.tsx
new file mode 100644
index 00000000..883bfbb1
--- /dev/null
+++ b/examples/react-phaser-example/src/main.tsx
@@ -0,0 +1,8 @@
+import ReactDOM from 'react-dom/client';
+import App from './App.tsx';
+
+const rootElement = document.getElementById("root");
+if (!rootElement) throw new Error("React root not found");
+const root = ReactDOM.createRoot(rootElement);
+
+root.render( );
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/phaser/configurePhaser.ts b/examples/react-phaser-example/src/phaser/configurePhaser.ts
new file mode 100644
index 00000000..97d93e4e
--- /dev/null
+++ b/examples/react-phaser-example/src/phaser/configurePhaser.ts
@@ -0,0 +1,92 @@
+import {
+ defineSceneConfig,
+ AssetType,
+ defineScaleConfig,
+ defineMapConfig,
+ defineCameraConfig,
+} from "@latticexyz/phaserx";
+import worldTileset from "../assets/tilesets/world.png";
+import { TileAnimations, Tileset } from "../artTypes/world";
+import { Sprites, Assets, Maps, Scenes, TILE_HEIGHT, TILE_WIDTH, Animations } from "./constants";
+
+const ANIMATION_INTERVAL = 200;
+
+const mainMap = defineMapConfig({
+ chunkSize: TILE_WIDTH * 64, // tile size * tile amount
+ tileWidth: TILE_WIDTH,
+ tileHeight: TILE_HEIGHT,
+ backgroundTile: [Tileset.Grass],
+ animationInterval: ANIMATION_INTERVAL,
+ tileAnimations: TileAnimations,
+ layers: {
+ layers: {
+ Background: { tilesets: ["Default"] },
+ Foreground: { tilesets: ["Default"] },
+ },
+ defaultLayer: "Background",
+ },
+});
+
+export const phaserConfig = {
+ sceneConfig: {
+ [Scenes.Main]: defineSceneConfig({
+
+ assets: {
+ [Assets.Tileset]: {
+ type: AssetType.Image,
+ key: Assets.Tileset,
+ path: worldTileset,
+ },
+ [Assets.MainAtlas]: {
+ type: AssetType.MultiAtlas,
+ key: Assets.MainAtlas,
+ // Add a timestamp to the end of the path to prevent caching
+ path: `src/assets/atlases/atlas.json?timestamp=${Date.now()}`,
+ options: {
+ imagePath: "src/assets/atlases/",
+ },
+ },
+ },
+ maps: {
+ [Maps.Main]: mainMap,
+ },
+ sprites: {
+ [Sprites.Soldier]: {
+ assetKey: Assets.MainAtlas,
+ frame: "sprites/soldier/idle/0.png",
+ },
+ },
+ animations: [
+ {
+ key: Animations.SwordsmanIdle,
+ assetKey: Assets.MainAtlas,
+ startFrame: 0,
+ endFrame: 3,
+ frameRate: 6,
+ repeat: -1,
+ prefix: "sprites/soldier/idle/",
+ suffix: ".png",
+ },
+ ],
+ tilesets: {
+ Default: {
+ assetKey: Assets.Tileset,
+ tileWidth: TILE_WIDTH,
+ tileHeight: TILE_HEIGHT,
+ },
+ },
+ }),
+ },
+ scale: defineScaleConfig({
+ parent: "phaser-game",
+ zoom: 1,
+ mode: Phaser.Scale.NONE,
+ }),
+ cameraConfig: defineCameraConfig({
+ pinchSpeed: 1,
+ wheelSpeed: 1,
+ maxZoom: 3,
+ minZoom: 1,
+ }),
+ cullingChunkSize: TILE_HEIGHT * 16,
+};
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/phaser/constants.ts b/examples/react-phaser-example/src/phaser/constants.ts
new file mode 100644
index 00000000..0ff006b0
--- /dev/null
+++ b/examples/react-phaser-example/src/phaser/constants.ts
@@ -0,0 +1,33 @@
+export enum Scenes {
+ Main = "Main",
+}
+
+export enum Maps {
+ Main = "Main",
+}
+
+export enum Animations {
+ SwordsmanIdle = "SwordsmanIdle",
+}
+export enum Sprites {
+ Soldier,
+}
+
+export enum Assets {
+ MainAtlas = "MainAtlas",
+ Tileset = "Tileset",
+}
+
+export enum Direction {
+ Unknown,
+ Up,
+ Down,
+ Left,
+ Right
+}
+
+export const TILE_HEIGHT = 32;
+export const TILE_WIDTH = 32;
+
+// contract offset so we don't overflow
+export const POSITION_OFFSET = 1000;
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/phaser/index.ts b/examples/react-phaser-example/src/phaser/index.ts
new file mode 100644
index 00000000..6c5e4c3b
--- /dev/null
+++ b/examples/react-phaser-example/src/phaser/index.ts
@@ -0,0 +1,33 @@
+import { createPhaserEngine } from "@latticexyz/phaserx";
+import { NetworkLayer } from '../dojo/createNetworkLayer';
+import { registerSystems } from './systems/registerSystems';
+import { namespaceWorld } from '@dojoengine/recs';
+
+export type PhaserLayer = Awaited>;
+type PhaserEngineConfig = Parameters[0];
+
+export const createPhaserLayer = async (networkLayer: NetworkLayer, phaserConfig: PhaserEngineConfig) => {
+
+ const world = namespaceWorld(networkLayer.world, "phaser");
+ const { game, scenes, dispose: disposePhaser } = await createPhaserEngine(phaserConfig);
+ world.registerDisposer(disposePhaser);
+
+ const { camera } = scenes.Main;
+
+ camera.phaserCamera.setBounds(-1000, -1000, 2000, 2000);
+ camera.phaserCamera.centerOn(0, 0);
+
+ const components = {};
+
+ const layer = {
+ networkLayer,
+ world,
+ game,
+ scenes,
+ components,
+ }
+
+ registerSystems(layer);
+
+ return layer
+}
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/phaser/phaserLayer.tsx b/examples/react-phaser-example/src/phaser/phaserLayer.tsx
new file mode 100644
index 00000000..1d8b850c
--- /dev/null
+++ b/examples/react-phaser-example/src/phaser/phaserLayer.tsx
@@ -0,0 +1,35 @@
+import { useEffect } from "react";
+import { NetworkLayer } from "../dojo/createNetworkLayer";
+import { store } from "../store/store";
+import { usePhaserLayer } from "../hooks/usePhaserLayer";
+
+type Props = {
+ networkLayer: NetworkLayer | null;
+};
+
+// TODO: this is where we need to set the burner account from local storage.
+export const PhaserLayer = ({ networkLayer }: Props) => {
+
+ const { phaserLayer, ref } = usePhaserLayer({ networkLayer });
+
+ useEffect(() => {
+ if (phaserLayer) {
+ store.setState({ phaserLayer });
+
+ console.log("Setting phaser layer");
+ }
+ }, [phaserLayer]);
+
+ return (
+
+ );
+};
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/phaser/systems/controls.ts b/examples/react-phaser-example/src/phaser/systems/controls.ts
new file mode 100644
index 00000000..788ee38c
--- /dev/null
+++ b/examples/react-phaser-example/src/phaser/systems/controls.ts
@@ -0,0 +1,42 @@
+import { PhaserLayer } from "..";
+import { Direction } from "../../utils";
+
+export const controls = (layer: PhaserLayer) => {
+
+ const {
+ scenes: {
+ Main: { input },
+ },
+ networkLayer: {
+ systemCalls: { move },
+ account
+ },
+ } = layer;
+
+ input.onKeyPress(
+ keys => keys.has("W"),
+ () => {
+ move(account, Direction.Up);
+ });
+
+ input.onKeyPress(
+ keys => keys.has("A"),
+ () => {
+ move(account, Direction.Left);
+ }
+ );
+
+ input.onKeyPress(
+ keys => keys.has("S"),
+ () => {
+ move(account, Direction.Down);
+ }
+ );
+
+ input.onKeyPress(
+ keys => keys.has("D"),
+ () => {
+ move(account, Direction.Right);
+ }
+ );
+};
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/phaser/systems/mapSystem.ts b/examples/react-phaser-example/src/phaser/systems/mapSystem.ts
new file mode 100644
index 00000000..7d558365
--- /dev/null
+++ b/examples/react-phaser-example/src/phaser/systems/mapSystem.ts
@@ -0,0 +1,32 @@
+import { Tileset } from "../../artTypes/world";
+import { PhaserLayer } from "..";
+import { createNoise2D } from "simplex-noise";
+
+export function mapSystem(layer: PhaserLayer) {
+ const {
+ scenes: {
+ Main: {
+ maps: {
+ Main: { putTileAt },
+ },
+ },
+ },
+ } = layer;
+
+ const noise = createNoise2D();
+
+ for (let x = -500; x < 500; x++) {
+ for (let y = -500; y < 500; y++) {
+ const coord = { x, y };
+ const seed = noise(x, y);
+
+ putTileAt(coord, Tileset.Grass, "Background");
+
+ if (seed >= 0.45) {
+ putTileAt(coord, Tileset.Mountains, "Foreground");
+ } else if (seed < -0.45) {
+ putTileAt(coord, Tileset.Forest, "Foreground");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/phaser/systems/move.ts b/examples/react-phaser-example/src/phaser/systems/move.ts
new file mode 100644
index 00000000..0f6dff86
--- /dev/null
+++ b/examples/react-phaser-example/src/phaser/systems/move.ts
@@ -0,0 +1,54 @@
+import { Entity, Has, defineEnterSystem, defineSystem, getComponentValueStrict } from "@dojoengine/recs";
+import { PhaserLayer } from "..";
+import { tileCoordToPixelCoord } from "@latticexyz/phaserx";
+import { Animations, POSITION_OFFSET, TILE_HEIGHT, TILE_WIDTH } from "../constants";
+
+export const move = (layer: PhaserLayer) => {
+
+ const {
+ world,
+ scenes: {
+ Main: { objectPool, camera },
+ },
+ networkLayer: {
+ components: { Position }
+ },
+ } = layer;
+
+ defineEnterSystem(world, [Has(Position)], ({ entity }: any) => {
+ const playerObj = objectPool.get(entity.toString(), "Sprite");
+
+ console.log(playerObj)
+
+ playerObj.setComponent({
+ id: 'animation',
+ once: (sprite: any) => {
+
+ console.log(sprite)
+ sprite.play(Animations.SwordsmanIdle);
+ }
+ });
+ });
+
+ defineSystem(world, [Has(Position)], ({ entity }: any) => {
+
+ console.log(entity)
+
+ const position = getComponentValueStrict(Position, entity.toString() as Entity);
+
+ const offsetPosition = { x: position?.vec.x, y: position?.vec.y };
+
+ const pixelPosition = tileCoordToPixelCoord(offsetPosition, TILE_WIDTH, TILE_HEIGHT);
+
+ const player = objectPool.get(entity, "Sprite")
+
+ player.setComponent({
+ id: 'position',
+ once: (sprite: any) => {
+ sprite.setPosition(pixelPosition?.x, pixelPosition?.y);
+ camera.centerOn(pixelPosition?.x, pixelPosition?.y);
+ }
+ })
+
+ });
+};
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/phaser/systems/registerSystems.ts b/examples/react-phaser-example/src/phaser/systems/registerSystems.ts
new file mode 100644
index 00000000..7e9a95da
--- /dev/null
+++ b/examples/react-phaser-example/src/phaser/systems/registerSystems.ts
@@ -0,0 +1,12 @@
+import { PhaserLayer } from "..";
+import { move } from "./move";
+import { spawn } from "./spawn";
+import { controls } from "./controls";
+import { mapSystem } from "./mapSystem";
+
+export const registerSystems = (layer: PhaserLayer) => {
+ move(layer);
+ spawn(layer);
+ controls(layer);
+ mapSystem(layer)
+};
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/phaser/systems/spawn.ts b/examples/react-phaser-example/src/phaser/systems/spawn.ts
new file mode 100644
index 00000000..67276b35
--- /dev/null
+++ b/examples/react-phaser-example/src/phaser/systems/spawn.ts
@@ -0,0 +1,20 @@
+import { PhaserLayer } from "..";
+
+export const spawn = (layer: PhaserLayer) => {
+
+ const {
+ scenes: {
+ Main: { input },
+ },
+ networkLayer: {
+ systemCalls: { spawn },
+ account
+ },
+ } = layer;
+
+ input.onKeyPress(
+ keys => keys.has("SPACE"),
+ () => {
+ spawn(account);
+ });
+};
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/store/store.ts b/examples/react-phaser-example/src/store/store.ts
new file mode 100644
index 00000000..05794064
--- /dev/null
+++ b/examples/react-phaser-example/src/store/store.ts
@@ -0,0 +1,14 @@
+import { create } from "zustand";
+import { NetworkLayer } from "../dojo/createNetworkLayer";
+import { PhaserLayer } from "../phaser";
+
+export type Store = {
+ networkLayer: NetworkLayer | null;
+ phaserLayer: PhaserLayer | null;
+};
+
+export const store = create(() => ({
+ networkLayer: null,
+ phaserLayer: null
+}));
+
diff --git a/examples/react-phaser-example/src/ui/clickWrapper.tsx b/examples/react-phaser-example/src/ui/clickWrapper.tsx
new file mode 100644
index 00000000..5e219b82
--- /dev/null
+++ b/examples/react-phaser-example/src/ui/clickWrapper.tsx
@@ -0,0 +1,18 @@
+
+import { DetailedHTMLProps, HTMLAttributes } from "react";
+
+type Props = DetailedHTMLProps, HTMLDivElement>;
+
+/**
+ * Wrap any piece of UI that needs to receive click events with this.
+ * Make sure it is unmounted when the click events are no longer needed.
+ */
+export const ClickWrapper = (props: Props) => {
+ const { children, style } = props;
+
+ return (
+
+ {children}
+
+ );
+};
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/ui/index.tsx b/examples/react-phaser-example/src/ui/index.tsx
new file mode 100644
index 00000000..1b2cf105
--- /dev/null
+++ b/examples/react-phaser-example/src/ui/index.tsx
@@ -0,0 +1,35 @@
+import styled from "styled-components";
+import { store } from "../store/store";
+import { Wrapper } from "./wrapper";
+import { SpawnBtn } from "./spawnbtn";
+
+export const UI = () => {
+ const layers = store((state) => {
+ return {
+ networkLayer: state.networkLayer,
+ phaserLayer: state.phaserLayer,
+ };
+ });
+
+ if (!layers.networkLayer || !layers.phaserLayer) return <>>;
+
+ return (
+
+
+
+
+
+
+ );
+};
+
+const HeaderContainer = styled.div`
+ position: absolute;
+ top: 5%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ color: white;
+ display: flex;
+ flex-direaction: row;
+ gap: 20px;
+`;
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/ui/spawnbtn.tsx b/examples/react-phaser-example/src/ui/spawnbtn.tsx
new file mode 100644
index 00000000..61018ae6
--- /dev/null
+++ b/examples/react-phaser-example/src/ui/spawnbtn.tsx
@@ -0,0 +1,39 @@
+import { useDojo } from "../hooks/useDojo";
+import { ClickWrapper } from "./clickWrapper";
+
+export const SpawnBtn = () => {
+ // const {
+ // account: {
+ // create,
+ // list,
+ // get,
+ // account,
+ // select,
+ // isDeploying
+ // },
+ // networkLayer: {
+ // systemCalls: { spawn },
+ // },
+ // } = useDojo();
+
+ return (
+
+ {/* {isDeploying ? "deploying burner" : "create burner"}
+
+ select signer:{" "}
+ select(e.target.value)}>
+ {list().map((account, index) => {
+ return {account.address}
+ })}
+
+
*/}
+ {/* {
+ spawn(account);
+ }}
+ >
+ Spawn
+ */}
+
+ );
+};
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/ui/wrapper.tsx b/examples/react-phaser-example/src/ui/wrapper.tsx
new file mode 100644
index 00000000..8f145635
--- /dev/null
+++ b/examples/react-phaser-example/src/ui/wrapper.tsx
@@ -0,0 +1,7 @@
+import styled from "styled-components";
+
+export const Wrapper = styled.div`
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+`;
\ No newline at end of file
diff --git a/examples/react-phaser-example/src/utils/index.ts b/examples/react-phaser-example/src/utils/index.ts
new file mode 100644
index 00000000..1ec8b000
--- /dev/null
+++ b/examples/react-phaser-example/src/utils/index.ts
@@ -0,0 +1,29 @@
+export enum Direction {
+ Left = 1,
+ Right = 2,
+ Up = 3,
+ Down = 4,
+}
+
+export function updatePositionWithDirection(
+ direction: Direction,
+ value: { vec: { x: number; y: number } }
+) {
+ switch (direction) {
+ case Direction.Left:
+ value.vec.x--;
+ break;
+ case Direction.Right:
+ value.vec.x++;
+ break;
+ case Direction.Up:
+ value.vec.y--;
+ break;
+ case Direction.Down:
+ value.vec.y++;
+ break;
+ default:
+ throw new Error("Invalid direction provided");
+ }
+ return value;
+}
diff --git a/examples/react-phaser-example/src/vite-env.d.ts b/examples/react-phaser-example/src/vite-env.d.ts
new file mode 100644
index 00000000..11f02fe2
--- /dev/null
+++ b/examples/react-phaser-example/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/examples/react-phaser-example/tsconfig.json b/examples/react-phaser-example/tsconfig.json
new file mode 100644
index 00000000..22a01748
--- /dev/null
+++ b/examples/react-phaser-example/tsconfig.json
@@ -0,0 +1,34 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": [
+ "ES2020",
+ "DOM",
+ "DOM.Iterable"
+ ],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ /* Bundler mode */
+ "moduleResolution": "node",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "allowSyntheticDefaultImports": true
+ },
+ "include": [
+ "src"
+ ],
+ "references": [
+ {
+ "path": "./tsconfig.node.json"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/examples/react-phaser-example/tsconfig.node.json b/examples/react-phaser-example/tsconfig.node.json
new file mode 100644
index 00000000..42872c59
--- /dev/null
+++ b/examples/react-phaser-example/tsconfig.node.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/examples/react-phaser-example/vite.config.ts b/examples/react-phaser-example/vite.config.ts
new file mode 100644
index 00000000..5a33944a
--- /dev/null
+++ b/examples/react-phaser-example/vite.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+})
diff --git a/package.json b/package.json
index 891c7fe1..212570c8 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,7 @@
"version": "1.0.0",
"workspaces": [
"examples/react-app",
+ "examples/react-phaser-example",
"packages/create-burner",
"packages/core",
"packages/utils",
@@ -20,7 +21,8 @@
"build-torii-client": "bun run --cwd packages/torii-client build",
"build-torii-wasm": "bun run --cwd packages/torii-wasm build",
"build-react": "bun run --cwd packages/react build",
+ "build-phaser": "bun run --cwd examples/react-phaser-example build",
"build": "bun run build-core && bun run build-create-burner && bun run build-utils && bun run build-torii-client && bun run build-torii-wasm && bun run build-react",
- "clean": "rm -rf node_modules packages/create-burner/node_modules packages/core/node_modules packages/utils/node_modules packages/torii-client/node_modules packages/torii-wasm/node_modules packages/react/node_modules bun.lockb packages/create-burner/bun.lockb packages/core/bun.lockb packages/utils/bun.lockb packages/torii-client/bun.lockb packages/torii-wasm/bun.lockb packages/react/bun.lockb && rm -rf examples/react-app/node_modules examples/react-app/bun.lockb"
+ "clean": "rm -rf node_modules packages/create-burner/node_modules packages/core/node_modules packages/utils/node_modules packages/torii-client/node_modules packages/torii-wasm/node_modules packages/react/node_modules bun.lockb packages/create-burner/bun.lockb packages/core/bun.lockb packages/utils/bun.lockb packages/torii-client/bun.lockb packages/torii-wasm/bun.lockb packages/react/bun.lockb && rm -rf examples/react-app/node_modules examples/react-app/bun.lockb examples/react-phaser-example/node_modules examples/react-phaser-example/bun.lockb"
}
}