Lattice Workshop - Step 6.4

Add particles on spawn/mine and pending action

Next step

Let's add some more particles. Using the same explode utility as before, we can add particles every time a new entity (including mined tiles) is spawned.

Additionally we add a PendingAction component to mark an entity currently waiting for a transaction. Based on this component we can then add a particle emitter using the rangeEmitter utility from @latticexyz/phaser-middleware.

Files changed (2) hide show
  1. client/src/Game.ts +27 -1
  2. client/src/systems/ParticleSystem.ts +51 -0
client/src/Game.ts CHANGED
@@ -1,4 +1,12 @@
1
- import { createWorld, Entity, exists, getComponentValue, Has } from "@latticexyz/mobx-ecs";
1
+ import {
2
+ createWorld,
3
+ Entity,
4
+ exists,
5
+ getComponentValue,
6
+ Has,
7
+ removeComponent,
8
+ setComponent,
9
+ } from "@latticexyz/mobx-ecs";
2
10
  import { createMapping, loadEvents, setupContracts, setupMappings } from "../packages/lattice-eth-middleware";
3
11
  import { setupPhaser } from "@latticexyz/phaser-middleware";
4
12
  import {
@@ -22,6 +30,7 @@ import { Coord } from "./types";
22
30
  import { createInputSystem } from "./systems/InputSystem";
23
31
  import { Directions } from "./constants";
24
32
  import { createLifeSystem } from "./systems/LifeSystem";
33
+ import { createParticleSystem } from "./systems/ParticleSystem";
25
34
 
26
35
  export async function createGame(contractAddress: string, privateKey: string, chainId: number, personaId: number) {
27
36
  const world = createWorld();
@@ -53,6 +62,7 @@ export async function createGame(contractAddress: string, privateKey: string, ch
53
62
  const Attack = createUintComponent(world, "Attack");
54
63
  const Life = createTupleComponent(world, "Life");
55
64
  const Selected = createBoolComponent(world, "Selected");
65
+ const PendingAction = createCoordComponent(world, "PendingAction");
56
66
 
57
67
  const components = {
58
68
  Position,
@@ -66,6 +76,7 @@ export async function createGame(contractAddress: string, privateKey: string, ch
66
76
  Attack,
67
77
  Life,
68
78
  Selected,
79
+ PendingAction,
69
80
  };
70
81
 
71
82
  /*****************************************
@@ -93,7 +104,21 @@ export async function createGame(contractAddress: string, privateKey: string, ch
93
104
  }
94
105
 
95
106
  async function action(entity: Entity, target: Coord) {
107
+ // Don't act if an action is pending
108
+ const pendingAction = exists([Has(PendingAction)]);
109
+ if (pendingAction != undefined) {
110
+ console.warn("Action in progress");
111
+ return;
112
+ }
113
+
114
+ // Add an action indicator
115
+ setComponent(PendingAction, entity, target);
116
+
117
+ // Execute the action
96
118
  await txExecutor.sendTx((contract) => contract.action(entity, target));
119
+
120
+ // Remove the action indicator
121
+ removeComponent(PendingAction, entity);
97
122
  }
98
123
  function actionDirection(direction: keyof typeof Directions) {
99
124
  // Get the currently selected entity
@@ -135,6 +160,7 @@ export async function createGame(contractAddress: string, privateKey: string, ch
135
160
  createAppearanceSystem(context);
136
161
  createInputSystem(context);
137
162
  createLifeSystem(context);
163
+ createParticleSystem(context);
138
164
 
139
165
  return context;
140
166
  }
client/src/systems/ParticleSystem.ts ADDED
@@ -0,0 +1,51 @@
1
+ import {
2
+ defineEnterQuery,
3
+ defineReactionSystem,
4
+ defineUpdateQuery,
5
+ getComponentValue,
6
+ getComponentValueStrict,
7
+ Has,
8
+ } from "@latticexyz/mobx-ecs";
9
+ import { explode, rangeEmitter, removeEmitter } from "@latticexyz/phaser-middleware";
10
+ import { Context } from "../types";
11
+
12
+ export function createParticleSystem(context: Context) {
13
+ const {
14
+ world,
15
+ components: { Position, PendingAction },
16
+ phaser: { map, particles },
17
+ } = context;
18
+
19
+ // Explosion when entities first appear
20
+ const positionEnterQuery = defineEnterQuery(world, [Has(Position)]);
21
+ defineReactionSystem(
22
+ world,
23
+ () => positionEnterQuery.get(),
24
+ (entities) => {
25
+ for (const entity of entities) {
26
+ const coord = getComponentValueStrict(Position, entity);
27
+ explode(coord, particles, map);
28
+ }
29
+ }
30
+ );
31
+
32
+ // Emitter as action indicator
33
+ const pendingActionQuery = defineUpdateQuery(world, [Has(PendingAction), Has(Position)]);
34
+ defineReactionSystem(
35
+ world,
36
+ () => pendingActionQuery.get(),
37
+ (entities) => {
38
+ for (const entity of entities) {
39
+ const pendingCoord = getComponentValue(PendingAction, entity);
40
+ const currentCoord = getComponentValue(Position, entity);
41
+
42
+ if (!pendingCoord || !currentCoord) {
43
+ removeEmitter(String(entity), particles);
44
+ continue;
45
+ }
46
+
47
+ rangeEmitter(currentCoord, pendingCoord, particles, map, String(entity));
48
+ }
49
+ }
50
+ );
51
+ }