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 ( + + {/* +
+ select signer:{" "} + +
*/} + {/* */} +
+ ); +}; \ 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" } }