diff --git a/docs/developer/typescript/01_gettingStarted/00_intro.md b/docs/developer/typescript/01_gettingStarted/00_intro.md index 73a38e2469cd..9fd89e26d12c 100644 --- a/docs/developer/typescript/01_gettingStarted/00_intro.md +++ b/docs/developer/typescript/01_gettingStarted/00_intro.md @@ -1,216 +1,2 @@ -# Getting Started with Typescript - -_This guide will teach you how to get started programming with Ethereal Engine using Typescript._ -_Visit the [Typescript: Introduction](/docs/manual/developer/typescript/intro) page for more details._ - - -## Overview - -We're going to look at 'Pong', a multiplayer game that we've built in Ethereal Engine using Typescript. It is an example of best practices for developers. - -## Installation and Running Pong - -1) Ethereal Engine scans for projects mounted in the /packages/projects/projects sub-folder of Ethereal Engine. From scratch we can install Ethereal Engine itself and also register a sub project using the following: - -``` -gh repo clone EtherealEngine/EtherealEngine -cd EtherealEngine -cd packages/projects/projects -gh repo clone EtherealEngine/ee-tutorial-pong -cd ../../../ -``` - -2) A fresh install of Ethereal Engine can be run like so: - -``` -npm install -npm run dev -``` - -3) Once Ethereal Engine itself is running, from the web admin panel of Ethereal Engine create a 'location' for the project. See https://localhost:3000/admin . Map the project to the name 'pong'. - -4) Run the project on the web by visiting it with the URL you created. See https://localhost:3000/location/pong - -## Understanding Pong - -### A 10000 foot overview - -Pong has several files which are roughly represent parts of the experience: - -- PaddleSystem -> a paddle 'system' for managing the player paddles specifically. -- PlateComponent -> each pong game has one or more plates that represent player positions in a game. In our case we have 4 player games. -- PongComponent -> each pong game in a larger pong world has a concept of a game starting or stopping; this is managed here. -- PongGameSystem -> a game 'system' for managing each game instance; starting and stopping play and dealing with players. -- PongPhysicsSystem -> a ball spawner and scoring 'system' for the pong game overall -- worldinjection -> bootstraps the game - -A 'Pong' world can have several separate pong tables at once. Each pong table has four pong plates and can handle four players at once. - -### World Injection - -Ethereal Engine projects typically have a worldinjection.ts file to bootstrap a project. In Pong this file registers and starts three 'systems', and does little else: - -``` -import './PaddleSystem' -import './PongGameSystem' -import './PongPhysicsSystem' -export default async function worldInjection() {} -``` - -### Entities, Components and Systems - -Ethereal Engine uses an ECS (Entity Component System). Entities in a game are entirely defined by their Components or 'capabilities', and systems driven the state of the entire application over time by running every frame over those components. One benefit of an ECS is that it allows a highly granular 'vocabulary' for designers to express game ideas with. - -The ECS we use currently BitECS. Another good ECS is Flecs: - -- https://github.com/NateTheGreatt/bitECS -- https://news.ycombinator.com/item?id=35434374 - -In Pong there are three systems imported via worldInjection above. Each is responsible for a different part of the experience: - -1) PaddleSystem -2) PongPhysicsSystem -3) PongGameSystem - -We will look at the PaddleSystem.ts first. - -### PaddleSystem: Introduction to State and Reactivity - -Ethereal Engine uses the React pattern of allowing state observers to 'react' to state changes. This is done for a couple of different reasons. Philosophically it separates the 'what' from the 'how', and technically it helps decouple game components from each other, allowing developers to scale work horizontally, reducing dependencies. - -The React website has several good discussions on reactivity as a whole: - -- https://react.dev/learn/reacting-to-input-with-state - -In Pong each active player has two paddles (one for the left hand and one for the right hand), and paddles are spawned or destroyed as players come and go. In this case the 'what' is that there are some set of paddles. And the 'how' (which we will get to later) is that the paddles happen to be 3d objects with collision hulls. But at this level of scope we can separate our concerns and we don't have to think about how the paddles are manifested. - -In PaddleSystem.ts we see a good example of this reactive state pattern. The approach we've taken here is to track the enumeration of active paddles like so: - -``` -export const PaddleState = defineState({ - name: 'ee.pong.PaddleState', - initial: {} as Record< - EntityUUID, - { - owner: UserID - handedness: 'left' | 'right' - gameEntityUUID: EntityUUID - } - > - ... -``` - -The defineState() method registers a collection of Record objects. A Record is a schema in a third party runtime schema definition language that Ethereal Engine uses. - -### PaddleSystem: Introduction to Event Sourced State - -Ethereal Engine uses an event sourced state paradigm. Sourcing state and responding to that state is asynchronous but a single 'effect' or outcome results, rather than having to propagate potentially thousands of successive state changes. - -A good discussion of Event Sourcing can be found here: - -https://domaincentric.net/blog/event-sourcing-snapshotting - -In an Event Sourced system, the current state of an aggregate is usually reconstituted from the full history of events. It means that before handling a command we need to do a full read of a single fine-grained stream and transport the events over the network. This allows late joiners to synchronize with the overall game state. - -In PaddleSystem we define a set of actions explicitly like so: - -``` -export class PaddleActions { - static spawnPaddle = defineAction({ - ...WorldNetworkAction.spawnObject.actionShape, - prefab: 'ee.pong.paddle', - gameEntityUUID: matchesEntityUUID, - handedness: matches.literals('left', 'right'), - owner: matchesUserId, - $topic: NetworkTopics.world - }) -} -``` - -And we then allow the registration of 'receptors' on state objects to catch dispatched events over the network, and in this case we're entirely focused on updating the state records above: - -``` - ... - receptors: [ - [ - PaddleActions.spawnPaddle, - (state, action: typeof PaddleActions.spawnPaddle.matches._TYPE) => { - state[action.entityUUID].merge({ - owner: action.owner, - handedness: action.handedness, - gameEntityUUID: action.gameEntityUUID - }) - } - ], - [ - WorldNetworkAction.destroyObject, - (state, action: typeof WorldNetworkAction.destroyObject.matches._TYPE) => { - state[action.entityUUID].set(none) - } - ] - ] -}) -``` - -The WorldNetworkAction.destroyObject is an observer we've injected here to catch here to make sure that we update our state tables appropriately. Although we have custom state on the object creation, we don't have any custom state on paddle destruction. - -### PaddleState: Introduction to Components - -With the state management out of the way, now we're left with the details of making sure our visual representations reflect our state. - -PaddleReactor defines a React component that has a useEffect() to observe state changes on a given PaddleState entry. When the PaddleState changes it sets up an entity to reflect that owner. Inside the useEffect() we see several typical 3d and game related components being setup: - - - UUIDComponent - - TransformComponent - - VisibleComponent - - DistanceFromCameraComponent - - FrustrumCullComponent - - NameComponent - - PrimitiveGeometryComponent - - ColliderComponent - - GrabbableComponent - -Most of these components are self descriptive, and this typically reflects the core set of components you'll see in many Ethereal Engine 3d entities that represent objects in a game. - -The GrabbableComponent is notable in that it's a good example of where components are more than just 'state'; they can be used to form an expressive 'vocabulary' of high level intentions. In this case we want the paddle to stay attached to the owner avatar at a specified attachment point. If we didn't have this concept we would have to fire rigid body physics target position events every frame to keep the paddle synchronized with the player. - -### PaddleState: Introduction to Reactors - -Both PaddleReactor and Reactor in PaddleSystem demonstrate reactivity to state. The reactor is updated whenever state changes, and the game entities that exist are a reflection of that larger state. - -### PaddleState: System - -Tying everything together in PaddleSystem is the PaddleSystem itself. It registers and runs an execute() handler every frame and it also registers the reactor: - -``` -export const PaddleSystem = defineSystem({ - uuid: 'pong.paddle-system', - execute, - reactor, - insert: { after: PhysicsSystem } -}) -``` - -### PaddleState: Overall Flow - -The general flow is like so: - -1) The execute handler catches and handles PaddleActions using ```receiveActions(PaddleState)``` - -2) The PaddleActions respond to network events and applies them to the PaddleState. - -3) The reactor reacts to any state changes on PaddleState. - -## PlateComponent - - -### PongComponent, PongGameSystem and PongPhysicsSystem - - -### Summary - +# Topic 1 +Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis. diff --git a/docs/developer/typescript/02_topic1/00_intro.md b/docs/developer/typescript/02_topic1/00_intro.md deleted file mode 100644 index 9fd89e26d12c..000000000000 --- a/docs/developer/typescript/02_topic1/00_intro.md +++ /dev/null @@ -1,2 +0,0 @@ -# Topic 1 -Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis. diff --git a/docs/developer/typescript/02_topic1/01_file.md b/docs/developer/typescript/02_topic1/01_file.md deleted file mode 100644 index 7db4cab12c85..000000000000 --- a/docs/developer/typescript/02_topic1/01_file.md +++ /dev/null @@ -1,2 +0,0 @@ -# Topic -Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis. diff --git a/docs/developer/typescript/02_topic1/02_file2.md b/docs/developer/typescript/02_topic1/02_file2.md deleted file mode 100644 index 7db4cab12c85..000000000000 --- a/docs/developer/typescript/02_topic1/02_file2.md +++ /dev/null @@ -1,2 +0,0 @@ -# Topic -Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis. diff --git a/docs/developer/typescript/02_topic1/_category_.json b/docs/developer/typescript/02_topic1/_category_.json deleted file mode 100644 index 2faf0119c19e..000000000000 --- a/docs/developer/typescript/02_topic1/_category_.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "Topic1", - "position": 02, - "link": { - "type": "doc", - "id": "developer/typescript/topic1/intro" - } -} diff --git a/docs/developer/typescript/50_bestPractices/00_intro.md b/docs/developer/typescript/50_bestPractices/00_intro.md new file mode 100644 index 000000000000..0fc9858f7433 --- /dev/null +++ b/docs/developer/typescript/50_bestPractices/00_intro.md @@ -0,0 +1,55 @@ +--- +sidebar_label: Typescript +--- +# Typescript: Best Practices Guide + + +:::important +This guide teaches some of the Ethereal Engine recommended best practices for developers. +As such, the level of knowledge required will be higher than if it was just an introductory guide. + +_Visit the [Developer Manual](/docs/manual/developer/intro) page for in-depth information about programming with Ethereal Engine._ +::: + +This guide will teach you how to create **Pong**, a multiplayer game built with Ethereal Engine using Typescript. + +## Installation and Running the project +### 1. Install Pong + +Ethereal Engine scans for projects in its `/packages/projects/projects` sub-folder. +We can install Ethereal Engine from scratch and also register a sub project using the following commands: +```bash +git clone https://github.com/EtherealEngine/etherealengine +cd etherealengine +cd packages/projects/projects +git clone https://github.com/EtherealEngine/ee-tutorial-pong +cd ../../../ +``` + +### 2. Run the engine +A fresh install of Ethereal Engine can be run with: +```bash +npm install +npm run dev +``` + +### 3. Configure the Location +Go to the Admin Panel page, create a `Location` for the project and change the project's name to `pong`. +:::note[Admin Panel] +https://localhost:3000/admin +::: + +:::important +Ethereal Engine must be running for this step and the rest of this guide to work. +::: + +### 4. Run Pong +Run the project on the web by visiting it with the URL you just created. +:::note[Pong] +https://localhost:3000/location/pong +::: diff --git a/docs/developer/typescript/50_bestPractices/01_understanding.md b/docs/developer/typescript/50_bestPractices/01_understanding.md new file mode 100644 index 000000000000..a67d42dc6f92 --- /dev/null +++ b/docs/developer/typescript/50_bestPractices/01_understanding.md @@ -0,0 +1,189 @@ +# Understanding Pong + +## High Level Overview +A Pong world can have several separate pong tables at once. +Each pong table has four pong plates and can handle four players at once. + +Pong has several files which represent different parts of the experience: +- **PaddleSystem**: A paddle 'system' for managing the player paddles. +- **PlateComponent**: Each pong game has one or more plates that represent player positions in a game. This example creates 4 player games. +- **PongComponent**: Each pong game in has the concept of a game starting or stopping; this is managed here. +- **PongGameSystem**: A game system for managing each game instance; starting and stopping play and dealing with players. +- **PongPhysicsSystem**: A ball spawner and scoring 'system' for the pong game overall +- **worldinjection**: Connects the project's code with the engine's code. + +### World Injection +Ethereal Engine projects have a `worldinjection.ts` file to connect the project code to with the engine so it can be run _(aka bootstrapping / injection)_. +For Pong this file registers and starts three systems, and does little else: +```ts +import './PaddleSystem' +import './PongGameSystem' +import './PongPhysicsSystem' +export default async function worldInjection() {} +``` + +## Entities, Components and Systems +Ethereal Engine uses an ECS (Entity Component System). +**Entities** in a game are entirely defined by their **Components** or 'capabilities'. +**Systems** drive the state of the entire application over time by running every frame over those components. + +A great benefit of the ECS pattern is that it allows a highly granular 'vocabulary' for designers to express game ideas with. + +:::info +Ethereal Engine currently uses [bitECS](https://github.com/NateTheGreatt/bitECS) for its ECS implementation. +Another great ECS library to look into is [Flecs](https://news.ycombinator.com/item?id=35434374). +::: + +Pong imports three systems via worldInjection. Each one is responsible for a different part of the Pong experience: +1. PaddleSystem +2. PongPhysicsSystem +3. PongGameSystem + +We will look at `PaddleSystem.ts` first. + +## PaddleSystem: Introduction to State and Reactivity +In Pong each active player has two paddles (one for the left hand and one for the right hand), and paddles are spawned or destroyed as players come and go. + +We will use Ethereal Engine's best practices and separate our concerns. +What this means for us, at this level of abstraction, is that we don't yet need to think about how the paddles are manifested. + +We will separate the 'what' from the 'how' of the paddles: +- **what**: There are some set of paddles. _(Entities)_ +- **how**: The paddles happen to be 3D objects with collision hulls. _(Components)_ + > We will get into the details of how this works later. + +:::info +Ethereal Engine uses the React pattern of allowing state observers to 'react' to state changes. +You can read more in depth about this in the [ECS section](/docs/manual/developer/ecs) of the manual. +::: + + +In `PaddleSystem.ts` we can see a good example of the reactive state pattern that Ethereal Engine follows. +The basic idea here is to track the list of active paddles: +```ts +export const PaddleState = defineState({ + name: 'ee.pong.PaddleState', + initial: {} as Record< + EntityUUID, + { + owner: UserID + handedness: 'left' | 'right' + gameEntityUUID: EntityUUID + } + > + ... +``` + +:::info +The `defineState()` method registers a collection of Record objects. +A `Record` is a schema in a third party runtime schema definition language that Ethereal Engine uses. +::: + +## PaddleSystem: Introduction to Event Sourced State +Ethereal Engine uses an event sourced state paradigm. + +:::info +A good discussion about Event Sourcing can be found here: +https://domaincentric.net/blog/event-sourcing-snapshotting +::: +Sourcing state and responding to that state is asynchronous. +But, rather than having to propagate potentially thousands of successive state changes, a single 'effect' or outcome will result from this process. + +In an Event Sourced system, the current state of an aggregate is usually reconstituted from the full history of events. +This means that, before handling a command, we need to do a full read of a single fine-grained stream and transport the events over the network. + +This design allows late joiners of the game to synchronize with the overall game state. + +We define a set of actions explicitly in `PaddleSystem.ts`: +```ts +export class PaddleActions { + static spawnPaddle = defineAction({ + ...WorldNetworkAction.spawnObject.actionShape, + prefab: 'ee.pong.paddle', + gameEntityUUID: matchesEntityUUID, + handedness: matches.literals('left', 'right'), + owner: matchesUserId, + $topic: NetworkTopics.world + }) +} +``` + +We then allow the registration of 'receptors' on state objects to catch dispatched events over the network. +In this case, we're entirely focused on updating the state records defined above: +```ts + ... + receptors: [ + [ + PaddleActions.spawnPaddle, + (state, action: typeof PaddleActions.spawnPaddle.matches._TYPE) => { + state[action.entityUUID].merge({ + owner: action.owner, + handedness: action.handedness, + gameEntityUUID: action.gameEntityUUID + }) + } + ], + [ + WorldNetworkAction.destroyObject, + (state, action: typeof WorldNetworkAction.destroyObject.matches._TYPE) => { + state[action.entityUUID].set(none) + } + ] + ] +}) +``` +`WorldNetworkAction.destroyObject` is an observer that we have injected to make sure that we update our state tables appropriately. + +Although we have custom state on the object creation, we don't have any custom state on paddle destruction. + + +## PaddleState: Introduction to Components +With the state management out of the way, we are now left with the details of making sure that our visual representations reflect our state. + +`PaddleReactor.ts` defines a React component that has a `useEffect()` to observe state changes on a given `PaddleState` entry. The reactor sets up an entity to reflect that owner when PaddleState changes. + +Inside `useEffect()` we see several typical 3D and game related components being setup: +- UUIDComponent +- TransformComponent +- VisibleComponent +- DistanceFromCameraComponent +- FrustrumCullComponent +- NameComponent +- PrimitiveGeometryComponent +- ColliderComponent +- GrabbableComponent + +These components reflect the core set of components that you will find in many Ethereal Engine 3D entities used to represent objects in a game. + +:::important +The `GrabbableComponent` is notable because it is a good example of components being more than just 'state'. +They can also be used to form an expressive 'vocabulary' of the high level intentions of the developer. + +In this case, we want the paddle to stay attached to the owner's avatar at a specified attachment point. +If we didn't have this concept we would have to fire rigid body physics target position events every frame to keep the paddle synchronized with the player. +::: + +## PaddleState: Introduction to Reactors +Both PaddleReactor and Reactor in `PaddleSystem.ts` demonstrate reactivity to state: +- The reactor is updated whenever state changes +- Game entities that exist are a reflection of that larger state. + + +## PaddleState: System +Tying everything together in `PaddleSystem.ts` is the PaddleSystem itself. +It registers and runs an execute() handler every frame and it also registers the reactor: +```ts +export const PaddleSystem = defineSystem({ + uuid: 'pong.paddle-system', + execute, + reactor, + insert: { after: PhysicsSystem } +}) +``` + +## PaddleState: Overall Flow +The general flow of PaddleState is: +1. The execute handler catches and handles PaddleActions using `receiveActions(PaddleState)` +2. PaddleActions respond to network events and applies them to the PaddleState. +3. The reactor executes its process based on changes to PaddleState. + diff --git a/docs/developer/typescript/50_bestPractices/02_step2.md b/docs/developer/typescript/50_bestPractices/02_step2.md new file mode 100644 index 000000000000..d7416984bbcf --- /dev/null +++ b/docs/developer/typescript/50_bestPractices/02_step2.md @@ -0,0 +1,5 @@ +# PlateComponent + +## PongComponent, PongGameSystem and PongPhysicsSystem + +## Summary diff --git a/docs/developer/typescript/50_bestPractices/03_step3.md b/docs/developer/typescript/50_bestPractices/03_step3.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/docs/developer/typescript/50_bestPractices/_category_.json b/docs/developer/typescript/50_bestPractices/_category_.json new file mode 100644 index 000000000000..caf587abc223 --- /dev/null +++ b/docs/developer/typescript/50_bestPractices/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Best Practices", + "position": 50, + "link": { + "type": "doc", + "id": "developer/typescript/bestPractices/intro" + } +} diff --git a/readme.md b/readme.md index 7fb1d13abd68..639b17555b3a 100644 --- a/readme.md +++ b/readme.md @@ -23,6 +23,11 @@ - Underscore `_` for italics - End lines with double space ` ` for newline breaks within the same paragraph. - Always assign a valid coding language to code blocks _(necessary for syntax highlighting)_. +- Always use `N. Text` for numbered bullet points. (eg: 1. First point) +- Align newline subtext of bullet points to the text of the bullet point, so that the subtext becomes a part of the bullet point alignment rules. +- Align the first paragraph after a title right next to their title, without an empty newline in between the text and the title. + (preferable, not mandatory. easier to pattern/block skim-read internally, and doesn't affect the rendered html) + ## Docusaurus: Routing - Always name files/folder with the format: `NN_theFile.md`