Skip to content

Commit 550e2a7

Browse files
committed
Update README.md
1 parent 2571359 commit 550e2a7

File tree

1 file changed

+96
-51
lines changed

1 file changed

+96
-51
lines changed

README.md

Lines changed: 96 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[🕹️ CLICK HERE TO PLAY 🕹️](https://croquet.github.io/multiblaster-tutorial/step9.html)_then scan the QR code or share the generated session URL to invite other players._
66

77
Each HTML file in [this repository](https://github.com/croquet/multiblaster-tutorial/)
8-
contains an increasingly complete multiplayer game built using [Croquet](https://croquet.io/docs/).
8+
contains an increasingly complete multiplayer game built using [Croquet](https://github.com/croquet/croquet).
99

1010
It's a 2D game, and its visuals are intentionally kept simple so that the code is more understandable.
1111

@@ -16,7 +16,7 @@ They can also shoot blasts which cause asteroids to break up and vanish.
1616
Successful blasts increase the player's score, while colliding with an asteroid
1717
causes a ship to be destroyed and lose all points.
1818

19-
**📖 Please use our [Documentation](https://croquet.io/docs/croquet/) alongside this tutorial, and join our [Discord](https://croquet.io/discord) for questions 🤔**
19+
**📖 Please use our [Documentation](https://multisynq.io/docs/croquet/) alongside this tutorial, and join our [Discord](https://multisynq.io/discord) for questions 🤔**
2020

2121
## Step 0: Asteroids floating without Croquet 🪨≠🪨
2222

@@ -84,19 +84,28 @@ by drawing the asteroids on a canvas. These parts are subclassed from
8484
The last few lines instruct Croquet to join a session for a particular model and view class
8585
via `Croquet.Session.join()`. The name and password for this session are taken from
8686
the current URL, or generated automatically using `autoSession()` and `autoPassword`.
87-
It also needs an API key. You should fetch your own key from [croquet.io/keys](https://croquet.io/keys/).
87+
It also needs an API key. You should fetch your own key from [multisynq.io/coder](https://multisynq.io/coder/).
8888

8989
This version has only 20 lines more than the non-Croquet one from step 0.
9090

91-
Notice that the computation looks exactly the same, no special data structures need to be used,
92-
all models are synchronized between machines without any special markup.
91+
Notice that the computation looks exactly the same.
92+
_No special data structures need to be used._
93+
All models are synchronized between machines without any special markup.
9394

9495
```js
95-
move() {
96-
this.x = (this.x + this.dx + 1000) % 1000;
97-
this.y = (this.y + this.dy + 1000) % 1000;
98-
this.a = (this.a + this.da + Math.PI) % Math.PI;
99-
this.future(50).move();
96+
class Asteroid extends Croquet.Model {
97+
98+
...
99+
100+
move() {
101+
this.x = (this.x + this.dx + 1000) % 1000;
102+
this.y = (this.y + this.dy + 1000) % 1000;
103+
this.a = (this.a + this.da + Math.PI) % Math.PI;
104+
this.future(50).move();
105+
}
106+
107+
...
108+
100109
}
101110
```
102111

@@ -105,31 +114,38 @@ The only new construct is the line
105114
this.future(50).move();
106115
```
107116
inside of the `move()` method. This causes `move()` to be called again 50 ms in the future,
108-
similarly to the `timeout()` call in step 0. Future messages are how you define an object's behavior over
109-
time in Croquet.
117+
similarly to the `timeout()` call in step 0.
118+
_Future messages are how you define an object's behavior over time in Croquet._
110119

111120
Drawing happens exactly the same as in the non-Croquet case:
112121

113122
```js
114-
for (const asteroid of this.model.asteroids) {
115-
const { x, y, a, size } = asteroid;
116-
this.context.save();
117-
this.context.translate(x, y);
118-
this.context.rotate(a);
119-
this.context.beginPath();
120-
this.context.moveTo(+size, 0);
121-
this.context.lineTo( 0, +size);
122-
this.context.lineTo(-size, 0);
123-
this.context.lineTo( 0, -size);
124-
this.context.closePath();
125-
this.context.stroke();
126-
this.context.restore();
123+
class Display extends Croquet.View {
124+
125+
...
126+
127+
update() {
128+
...
129+
for (const asteroid of this.model.asteroids) {
130+
const { x, y, a, size } = asteroid;
131+
this.context.save();
132+
this.context.translate(x, y);
133+
this.context.rotate(a);
134+
this.context.beginPath();
135+
this.context.moveTo(+size, 0);
136+
this.context.lineTo( 0, +size);
137+
this.context.lineTo(-size, 0);
138+
this.context.lineTo( 0, -size);
139+
this.context.closePath();
140+
this.context.stroke();
141+
this.context.restore();
142+
}
143+
}
127144
}
128145
```
129146

130-
Notice that the view's `update()` method can read the asteroid positions directly from the model
131-
for drawing. Unlike in server-client computing, these positions do not need to be transmitted
132-
via the network. They are already available locally.
147+
Notice that the view's `update()` method can read the asteroid positions directly from the model for drawing.
148+
_Unlike in server-client computing, these positions do not need to be transmitted via the network._ They are already available locally.
133149

134150
However, you must take care to not accidentally modify any model properties directly,
135151
because that would break the synchronization. See the next step for how to interact with the model.
@@ -145,6 +161,8 @@ For each player joining, another spaceship is created by subscribing to the sess
145161
`view-join` and `view-exit` events:
146162

147163
```js
164+
class Game extends Croquet.Model {
165+
148166
init() {
149167
...
150168
this.ships = new Map();
@@ -162,33 +180,39 @@ For each player joining, another spaceship is created by subscribing to the sess
162180
this.ships.delete(viewId);
163181
ship.destroy();
164182
}
183+
184+
...
165185
```
166186
Each ship subscribes to that player's input only, using the player's `viewId` as an event scope.
167187
This is how the shared model can distinguish events sent from different user's views:
168188
169189
```js
170-
init({ viewId }) {
171-
...
172-
this.left = false;
173-
this.right = false;
174-
this.forward = false;
175-
this.subscribe(viewId, "left-thruster", this.leftThruster);
176-
this.subscribe(viewId, "right-thruster", this.rightThruster);
177-
this.subscribe(viewId, "forward-thruster", this.forwardThruster);
178-
this.move();
179-
}
190+
class Ship extends Croquet.Model {
180191

181-
leftThruster(active) {
182-
this.left = active;
183-
}
192+
init({ viewId }) {
193+
...
194+
this.left = false;
195+
this.right = false;
196+
this.forward = false;
197+
this.subscribe(viewId, "left-thruster", this.leftThruster);
198+
this.subscribe(viewId, "right-thruster", this.rightThruster);
199+
this.subscribe(viewId, "forward-thruster", this.forwardThruster);
200+
this.move();
201+
}
184202

185-
rightThruster(active) {
186-
this.right = active;
187-
}
203+
leftThruster(active) {
204+
this.left = active;
205+
}
188206

189-
forwardThruster(active) {
190-
this.forward = active;
191-
}
207+
rightThruster(active) {
208+
this.right = active;
209+
}
210+
211+
forwardThruster(active) {
212+
this.forward = active;
213+
}
214+
215+
...
192216
```
193217
194218
The ship's `move()` method uses the stored thruster values to accelerate or rotate the ship:
@@ -198,10 +222,12 @@ move() {
198222
if (this.forward) this.accelerate(0.5);
199223
if (this.left) this.a -= 0.2;
200224
if (this.right) this.a += 0.2;
201-
...
202-
}
225+
this.x = ...
226+
this.y = ...
203227
```
204228
229+
Again, the ship's new rotation `a` and position `x,y` _do not need to be published to other players._ This computation happens synchronized on each player's machine, based on the `left`, `right`, and `forward` properties that were set via the following thruster events.
230+
205231
In the local view, key up and down events of the arrow keys publish the events to enable and disable the thrusters:
206232
207233
```js
@@ -292,6 +318,21 @@ mainLoop() {
292318
this.checkCollisions();
293319
this.future(50).mainLoop();
294320
}
321+
322+
checkCollisions() {
323+
for (const asteroid of this.asteroids) {
324+
const minx = asteroid.x - asteroid.size;
325+
const maxx = asteroid.x + asteroid.size;
326+
const miny = asteroid.y - asteroid.size;
327+
const maxy = asteroid.y + asteroid.size;
328+
for (const blast of this.blasts) {
329+
if (blast.x > minx && blast.x < maxx && blast.y > miny && blast.y < maxy) {
330+
asteroid.hitBy(blast);
331+
break;
332+
}
333+
}
334+
}
335+
}
295336
```
296337
297338
When an asteroid was hit by a blast, it shrinks itself and changes direction perpendicular to the shot.
@@ -313,6 +354,10 @@ hitBy(blast) {
313354
}
314355
```
315356
357+
The remarkable thing about this code is how unremarkable it is.
358+
There is nothing "fancy" required of the programmer, it reads almost the same as if it were a single-player game.
359+
And there is no network congestion even if hundreds of blasts are moving because their positions are never sent over the network.
360+
316361
## Step 5: Turn ship into debris after colliding with asteroids 🚀➡💥
317362
318363
([full source code](https://github.com/croquet/multiblaster-tutorial/blob/main/step5.html))
@@ -614,8 +659,8 @@ There's an even more polished game with some gimmicks at
614659
615660
One of its gimmicks is that if the initials contain an emoji, it will be used for shooting. The trickiest part of that is properly parsing out the emoji, which can be composed of many code points 😉
616661
617-
You can play it online at [croquet.io/multiblaster](https://croquet.io/multiblaster/).
662+
You can play it online at [apps.multisynq.io/multiblaster](https://apps.multisynq.io/multiblaster/).
618663
619664
## Further Information 👀
620665
621-
Please use our [Documentation](https://croquet.io/docs), and join our [Discord](https://croquet.io/discord) for questions!
666+
Please use our [Documentation](https://multisynq.io/docs/croquet/) alongside this tutorial, and join our [Discord](https://multisynq.io/discord) for questions!

0 commit comments

Comments
 (0)