-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtutorial-MyFirstGame.html
348 lines (292 loc) · 25.8 KB
/
tutorial-MyFirstGame.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>My First Game: Pong - Documentation</title>
<link rel="shortcut icon" href="http://lance.gg/favicon.ico">
<link rel="icon" sizes="16x16 32x32 64x64" href="http://lance.gg/favicon.ico">
<link rel="icon" type="image/png" sizes="196x196" href="http://lance.gg/favicon-192.png">
<link rel="icon" type="image/png" sizes="160x160" href="http://lance.gg/favicon-160.png">
<link rel="icon" type="image/png" sizes="96x96" href="http://lance.gg/favicon-96.png">
<link rel="icon" type="image/png" sizes="64x64" href="http://lance.gg/favicon-64.png">
<link rel="icon" type="image/png" sizes="32x32" href="http://lance.gg/favicon-32.png">
<link rel="icon" type="image/png" sizes="16x16" href="http://lance.gg/favicon-16.png">
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link href="https://file.myfontastic.com/DeXq9523CzrFERZkXSzP7D/icons.css" rel="stylesheet">
<link type="text/css" rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
<link rel="stylesheet" href="//cdn.jsdelivr.net/highlight.js/9.8.0/styles/atelier-sulphurpool-light.min.css">
</head>
<body>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<nav>
<h2 class="home"><a href="index.html">Lance</a></h2><h2>Concepts</h2><ul class="tutorials"><li><span class='category'>Introduction<ul><li><a href="tutorial-introduction.html">Introduction</a></li><li><a href="tutorial-introduction_prologue.html">Prologue</a></li><li><a href="tutorial-introduction_community.html">Community</a></li><li><a href="tutorial-introduction_roadmap.html">Development Roadmap</a></li><li><a href="tutorial-introduction_faq.html">Frequently Asked Questions</a></li></ul></li><li><span class='category'>Overview<ul><li><a href="tutorial-overview_architecture.html">Architecture of a Multiplayer Game</a></li><li><a href="tutorial-choosing_a_physics_engine.html">Choosing a Physics Engine</a></li><li><a href="tutorial-guide_gameengine.html">Game Engine</a></li><li><a href="tutorial-guide_serverengine.html">Server Engine</a></li><li><a href="tutorial-guide_clientengine.html">Client Engine</a></li><li><a href="tutorial-guide_renderer.html">Renderer</a></li><li><a href="tutorial-guide_gameworld.html">Game World and Game Objects</a></li><li><a href="tutorial-guide_serialization.html">Serialization and Communication</a></li></ul></li><li><span class='category'>Synchronization Methods<ul><li><a href="tutorial-guide_syncinterpolation.html">Interpolation</a></li><li><a href="tutorial-guide_syncextrapolation.html">Extrapolation</a></li></ul></li><li><span class='category'>Tutorials<ul><li><a href="tutorial-MyFirstGame.html">My First Game: Pong</a></li><li><a href="tutorial-spaceships.html">Spaaace</a></li></ul></li><li><span class='category'>Extras<ul><li><a href="tutorial-guide_tuningdebugging.html">Fine Tuning and Debugging</a></li><li><a href="tutorial-furtherreading.html">Further Reading</a></li></ul></li></ul><h2>API Reference</h2><h3 class="classes">Classes</h3><ul><li><a href="AFrameRenderer.html">AFrameRenderer</a><ul class='methods'><li data-type='method'><a href="AFrameRenderer.html#draw">draw</a></li><li data-type='method'><a href="AFrameRenderer.html#init">init</a></li></ul></li><li><a href="BaseTypes.html">BaseTypes</a></li><li><a href="CannonPhysicsEngine.html">CannonPhysicsEngine</a></li><li><a href="ClientEngine.html">ClientEngine</a><ul class='methods'><li data-type='method'><a href="ClientEngine.html#connect">connect</a></li><li data-type='method'><a href="ClientEngine.html#disconnect">disconnect</a></li><li data-type='method'><a href="ClientEngine.html#sendInput">sendInput</a></li><li data-type='method'><a href="ClientEngine.html#start">start</a></li></ul></li><li><a href="DynamicObject.html">DynamicObject</a><ul class='methods'><li data-type='method'><a href="DynamicObject.html#bendingToString">bendingToString</a></li><li data-type='method'><a href="DynamicObject.html#toString">toString</a></li></ul></li><li><a href="GameEngine.html">GameEngine</a><ul class='methods'><li data-type='method'><a href="GameEngine.html#addObjectToWorld">addObjectToWorld</a></li><li data-type='method'><a href="GameEngine.html#getPlayerGameOverResult">getPlayerGameOverResult</a></li><li data-type='method'><a href="GameEngine.html#isOwnedByPlayer">isOwnedByPlayer</a></li><li data-type='method'><a href="GameEngine.html#on">on</a></li><li data-type='method'><a href="GameEngine.html#once">once</a></li><li data-type='method'><a href="GameEngine.html#processInput">processInput</a></li><li data-type='method'><a href="GameEngine.html#registerClasses">registerClasses</a></li><li data-type='method'><a href="GameEngine.html#removeListener">removeListener</a></li><li data-type='method'><a href="GameEngine.html#removeObjectFromWorld">removeObjectFromWorld</a></li><li data-type='method'><a href="GameEngine.html#start">start</a></li><li data-type='method'><a href="GameEngine.html#step">step</a></li></ul></li><li><a href="GameObject.html">GameObject</a><ul class='methods'><li data-type='method'><a href="GameObject.html#bendingToString">bendingToString</a></li><li data-type='method'><a href="GameObject.html#hasComponent">hasComponent</a></li><li data-type='method'><a href="GameObject.html#onAddToWorld">onAddToWorld</a></li><li data-type='method'><a href="GameObject.html#onRemoveFromWorld">onRemoveFromWorld</a></li><li data-type='method'><a href="GameObject.html#syncTo">syncTo</a></li><li data-type='method'><a href="GameObject.html#toString">toString</a></li></ul></li><li><a href="GameWorld.html">GameWorld</a><ul class='methods'><li data-type='method'><a href="GameWorld.html#addObject">addObject</a></li><li data-type='method'><a href="GameWorld.html#forEachObject">forEachObject</a></li><li data-type='method'><a href="GameWorld.html#getNewId">getNewId</a></li><li data-type='method'><a href="GameWorld.html#queryObject">queryObject</a></li><li data-type='method'><a href="GameWorld.html#queryObjects">queryObjects</a></li><li data-type='method'><a href="GameWorld.html#removeObject">removeObject</a></li></ul></li><li><a href="KeyboardControls.html">KeyboardControls</a></li><li><a href="PhysicalObject2D.html">PhysicalObject2D</a><ul class='methods'><li data-type='method'><a href="PhysicalObject2D.html#onAddToWorld">onAddToWorld</a></li><li data-type='method'><a href="PhysicalObject2D.html#toString">toString</a></li></ul></li><li><a href="PhysicalObject3D.html">PhysicalObject3D</a><ul class='methods'><li data-type='method'><a href="PhysicalObject3D.html#toString">toString</a></li></ul></li><li><a href="Quaternion.html">Quaternion</a><ul class='methods'><li data-type='method'><a href="Quaternion.html#conjugate">conjugate</a></li><li data-type='method'><a href="Quaternion.html#copy">copy</a></li><li data-type='method'><a href="Quaternion.html#multiply">multiply</a></li><li data-type='method'><a href="Quaternion.html#set">set</a></li><li data-type='method'><a href="Quaternion.html#setFromAxisAngle">setFromAxisAngle</a></li><li data-type='method'><a href="Quaternion.html#slerp">slerp</a></li><li data-type='method'><a href="Quaternion.html#toAxisAngle">toAxisAngle</a></li><li data-type='method'><a href="Quaternion.html#toString">toString</a></li></ul></li><li><a href="Renderer.html">Renderer</a><ul class='methods'><li data-type='method'><a href="Renderer.html#addObject">addObject</a></li><li data-type='method'><a href="Renderer.html#draw">draw</a></li><li data-type='method'><a href="Renderer.html#init">init</a></li><li data-type='method'><a href="Renderer.html#removeObject">removeObject</a></li><li data-type='method'><a href="Renderer.html#runClientStep">runClientStep</a></li><li data-type='method'><a href="Renderer.html#stop">stop</a></li></ul></li><li><a href="Serializer.html">Serializer</a><ul class='methods'><li data-type='method'><a href="Serializer.html#.typeCanAssign">typeCanAssign</a></li><li data-type='method'><a href="Serializer.html#addCustomType">addCustomType</a></li><li data-type='method'><a href="Serializer.html#registerClass">registerClass</a></li></ul></li><li><a href="ServerEngine.html">ServerEngine</a><ul class='methods'><li data-type='method'><a href="ServerEngine.html#gameStatus">gameStatus</a></li></ul></li><li><a href="SimplePhysicsEngine.html">SimplePhysicsEngine</a></li><li><a href="ThreeVector.html">ThreeVector</a><ul class='methods'><li data-type='method'><a href="ThreeVector.html#add">add</a></li><li data-type='method'><a href="ThreeVector.html#clone">clone</a></li><li data-type='method'><a href="ThreeVector.html#copy">copy</a></li><li data-type='method'><a href="ThreeVector.html#getBendingDelta">getBendingDelta</a></li><li data-type='method'><a href="ThreeVector.html#length">length</a></li><li data-type='method'><a href="ThreeVector.html#lerp">lerp</a></li><li data-type='method'><a href="ThreeVector.html#multiplyScalar">multiplyScalar</a></li><li data-type='method'><a href="ThreeVector.html#normalize">normalize</a></li><li data-type='method'><a href="ThreeVector.html#set">set</a></li><li data-type='method'><a href="ThreeVector.html#subtract">subtract</a></li><li data-type='method'><a href="ThreeVector.html#toString">toString</a></li></ul></li><li><a href="Trace.html">Trace</a></li><li><a href="TwoVector.html">TwoVector</a><ul class='methods'><li data-type='method'><a href="TwoVector.html#add">add</a></li><li data-type='method'><a href="TwoVector.html#clone">clone</a></li><li data-type='method'><a href="TwoVector.html#copy">copy</a></li><li data-type='method'><a href="TwoVector.html#getBendingDelta">getBendingDelta</a></li><li data-type='method'><a href="TwoVector.html#length">length</a></li><li data-type='method'><a href="TwoVector.html#lerp">lerp</a></li><li data-type='method'><a href="TwoVector.html#multiplyScalar">multiplyScalar</a></li><li data-type='method'><a href="TwoVector.html#normalize">normalize</a></li><li data-type='method'><a href="TwoVector.html#set">set</a></li><li data-type='method'><a href="TwoVector.html#subtract">subtract</a></li><li data-type='method'><a href="TwoVector.html#toString">toString</a></li></ul></li></ul><h3 class="events">Events</h3><ul><li><a href="GameEngine.html#event:client__postStep">client__postStep</a></li><li><a href="GameEngine.html#event:client__preStep">client__preStep</a></li><li><a href="GameEngine.html#event:client__processInput">client__processInput</a></li><li><a href="GameEngine.html#event:client__slowFrameRate">client__slowFrameRate</a></li><li><a href="GameEngine.html#event:client__stepReset">client__stepReset</a></li><li><a href="GameEngine.html#event:client__syncReceived">client__syncReceived</a></li><li><a href="GameEngine.html#event:objectAdded">objectAdded</a></li><li><a href="GameEngine.html#event:objectDestroyed">objectDestroyed</a></li><li><a href="GameEngine.html#event:playerDisconnected">playerDisconnected</a></li><li><a href="GameEngine.html#event:playerJoined">playerJoined</a></li><li><a href="GameEngine.html#event:postStep">postStep</a></li><li><a href="GameEngine.html#event:preStep">preStep</a></li><li><a href="GameEngine.html#event:processInput">processInput</a></li><li><a href="GameEngine.html#event:server__inputReceived">server__inputReceived</a></li><li><a href="GameEngine.html#event:server__playerDisconnected">server__playerDisconnected</a></li><li><a href="GameEngine.html#event:server__playerJoined">server__playerJoined</a></li><li><a href="GameEngine.html#event:server__postStep">server__postStep</a></li><li><a href="GameEngine.html#event:server__preStep">server__preStep</a></li><li><a href="GameEngine.html#event:server__processInput">server__processInput</a></li><li><a href="GameEngine.html#event:start">start</a></li><li><a href="GameEngine.html#event:syncReceived">syncReceived</a></li></ul>
</nav>
<div id="main">
<h1 class="page-title">My First Game: Pong</h1>
<section>
<header>
</header>
<article>
<p>This tutorial takes about 30 minutes. It will guide you in building the simplest
JavaScript networked game, <a href="https://en.wikipedia.org/wiki/Pong">Pong</a>. It starts with a walk-through of environment setup,
then proceeds with the writing of client code, server
code, and game logic.</p>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/f/f8/Pong.png/220px-Pong.png" alt="Pong"></p>
<h2>Setting up the Environment</h2><p>The creation of a new game starts by cloning boilerplate code:</p>
<pre class="prettyprint source lang-shell"><code>git clone https://github.com/lance-gg/lancegame.git
cd lancegame
npm install</code></pre><p>You now have the basic directory structure of a game. Look around.
The boilerplate includes an <code>index.html</code> file, which will be served
to the clients, and a <code>main.js</code> file, which is the entry point of the node.js server.
The game code is inside the <code>src</code> directory, divided into
sub-directories <code>client</code>, <code>server</code>, and <code>common</code>.</p>
<p>Take a look at <code>webpack.config.js</code> which shows how the game is packaged, and <code>.babelrc</code> shows how the game loads the lance library.</p>
<h2>Step 1: Create the Game Object Classes</h2><p>There are two kinds of objects in every Pong game: the paddle and the ball.
These files extend the <code>DynamicObject</code> class, but are quite simple.
The boilerplate includes a sample game object class, in the file
<code>src/common/PlayerAvatar.js</code></p>
<p>Create the following two classes in the <code>src/common</code> directory:</p>
<h3>src/common/Paddle.js</h3><p>The Paddle class is similar to
the sample <code>PlayerAvatar.js</code> object, except that it is called <code>Paddle</code> and it asks the renderer to create a sprite.</p>
<pre class="prettyprint source lang-javascript"><code>'use strict';
import DynamicObject from 'lance/serialize/DynamicObject';
export default class Paddle extends DynamicObject {
constructor(gameEngine, options, props) {
super(gameEngine, options, props);
if (props && props.playerId)
this.playerId = props.playerId;
this.class = Paddle;
}
onAddToWorld(gameEngine) {
if (gameEngine.renderer) {
gameEngine.renderer.addSprite(this, 'paddle');
}
}
}</code></pre><h3>src/common/Ball.js</h3><p>The Ball class is only slightly more complicated than the Paddle
class. It adds two getters, which define "bending" properties.
The bending properties below indicate that the client object's position should
gradually <em>bend</em> towards the server object's position at a rate of 80% each time the server sends position updates. The client object's
velocity should not bend at all, because the ball's velocity can change
suddenly as it hits a wall or a paddle.
We also give the Ball an initial velocity when it is created.</p>
<pre class="prettyprint source lang-javascript"><code>'use strict';
import DynamicObject from 'lance/serialize/DynamicObject';
export default class Ball extends DynamicObject {
get bendingMultiple() { return 0.8; }
get bendingVelocityMultiple() { return 0; }
constructor(gameEngine, options, props) {
super(gameEngine, options, props);
this.class = Ball;
this.velocity.set(2, 2);
}
onAddToWorld(gameEngine) {
if (gameEngine.renderer) {
gameEngine.renderer.addSprite(this, 'ball');
}
}
}</code></pre><h2>Step 2: Implement the MyGameEngine class</h2><p>The game engine class runs on both the server and the client,
and executes the game's logic. The client runs the game engine to
predict what will happen, but the server execution is the true
game progress, overriding what the clients might have predicted.</p>
<p>For Pong, we will need to bounce the ball around the board, and check if it
hit a paddle. We will also need to respond to the user's up/down inputs.</p>
<h3>src/common/MyGameEngine.js</h3><p>The MyGameEngine class implements the actual logic of the game. First add the objects we created, and some constants, at the top of the file:</p>
<pre class="prettyprint source lang-javascript"><code>import TwoVector from 'lance/serialize/TwoVector';
import Paddle from './Paddle';
import Ball from './Ball';
const PADDING = 20;
const WIDTH = 400;
const HEIGHT = 400;
const PADDLE_WIDTH = 10;
const PADDLE_HEIGHT = 50;</code></pre><ul>
<li><strong>start()</strong>: registers the game logic to run as a post-step function, and keep references to the game objects. Modify the start method to match the following:</li>
</ul>
<pre class="prettyprint source lang-javascript"><code>start() {
super.start();
this.on('postStep', () => { this.postStepHandleBall(); });
this.on('objectAdded', (object) => {
if (object.class === Ball) {
this.ball = object;
} else if (object.playerId === 1) {
this.paddle1 = object;
} else if (object.playerId === 2) {
this.paddle2 = object;
}
});
}</code></pre><ul>
<li><strong>registerClasses</strong>: register all the game objects on the serializer. Add this function:</li>
</ul>
<pre class="prettyprint source lang-javascript"><code>registerClasses(serializer) {
serializer.registerClass(Paddle);
serializer.registerClass(Ball);
}</code></pre><ul>
<li><strong>processInput</strong>: handle user inputs by moving the paddle up or down.
Modify the processInput method to match the following:</li>
</ul>
<pre class="prettyprint source lang-javascript"><code>processInput(inputData, playerId) {
super.processInput(inputData, playerId);
// get the player paddle tied to the player socket
let playerPaddle = this.world.queryObject({ playerId });
if (playerPaddle) {
if (inputData.input === 'up') {
playerPaddle.position.y -= 5;
} else if (inputData.input === 'down') {
playerPaddle.position.y += 5;
}
}
}</code></pre><ul>
<li><strong>initGame</strong>: create two paddles, a ball, and add these objects to the game world. This method will be called only on the server. Add the following <code>initGame()</code> method:</li>
</ul>
<pre class="prettyprint source lang-javascript"><code>initGame() {
// create the paddle objects
this.addObjectToWorld(new Paddle(this, null, { position: new TwoVector(PADDING, 0), playerId: 1 }));
this.addObjectToWorld(new Paddle(this, null, { position: new TwoVector(WIDTH - PADDING, 0), playerId: 2 }));
this.addObjectToWorld(new Ball(this, null, { position: new TwoVector(WIDTH /2, HEIGHT / 2) }));
}</code></pre><ul>
<li><strong>postStepHandleBall</strong>: this method is executed after the ball has moved. It contains <strong>all the core pong game logic</strong>: it checks if the ball has hit any wall, or any paddle, and decides if a player has scored. </li>
</ul>
<pre class="prettyprint source lang-javascript"><code>postStepHandleBall() {
if (!this.ball)
return;
// CHECK LEFT EDGE:
if (this.ball.position.x <= PADDING + PADDLE_WIDTH &&
this.ball.position.y >= this.paddle1.y && this.ball.position.y <= this.paddle1.position.y + PADDLE_HEIGHT &&
this.ball.velocity.x < 0) {
// ball moving left hit player 1 paddle
this.ball.velocity.x *= -1;
this.ball.position.x = PADDING + PADDLE_WIDTH + 1;
} else if (this.ball.position.x <= 0) {
// ball hit left wall
this.ball.velocity.x *= -1;
this.ball.position.x = 0;
console.log(`player 2 scored`);
}
// CHECK RIGHT EDGE:
if (this.ball.position.x >= WIDTH - PADDING - PADDLE_WIDTH &&
this.ball.position.y >= this.paddle2.position.y && this.ball.position.y <= this.paddle2.position.y + PADDLE_HEIGHT &&
this.ball.velocity.x > 0) {
// ball moving right hits player 2 paddle
this.ball.velocity.x *= -1;
this.ball.position.x = WIDTH - PADDING - PADDLE_WIDTH - 1;
} else if (this.ball.position.x >= WIDTH ) {
// ball hit right wall
this.ball.velocity.x *= -1;
this.ball.position.x = WIDTH - 1;
console.log(`player 1 scored`);
}
// ball hits top
if (this.ball.position.y <= 0) {
this.ball.position.y = 1;
this.ball.velocity.y *= -1;
} else if (this.ball.position.y >= HEIGHT) {
// ball hits bottom
this.ball.position.y = HEIGHT - 1;
this.ball.velocity.y *= -1;
}
}</code></pre><h2>Step 3: Extend the MyServerEngine Class</h2><p>The server engine will initialize the game engine when the game is started, and handle player connections and "disconnections".</p>
<h3>src/server/MyServerEngine.js</h3><pre class="prettyprint source lang-javascript"><code>'use strict';
import ServerEngine from 'lance/ServerEngine';
import PlayerAvatar from '../common/PlayerAvatar';
export default class MyServerEngine extends ServerEngine {
constructor(io, gameEngine, inputOptions) {
super(io, gameEngine, inputOptions);
}
start() {
super.start();
this.gameEngine.initGame();
this.players = {
player1: null,
player2: null
};
}
onPlayerConnected(socket) {
super.onPlayerConnected(socket);
// attach newly connected player an available paddle
if (this.players.player1 === null) {
this.players.player1 = socket.id;
this.gameEngine.paddle1.playerId = socket.playerId;
} else if (this.players.player2 === null) {
this.players.player2 = socket.id;
this.gameEngine.paddle2.playerId = socket.playerId;
}
}
onPlayerDisconnected(socketId, playerId) {
super.onPlayerDisconnected(socketId, playerId);
if (this.players.player1 == socketId) {
console.log('Player 1 disconnected');
this.players.player1 = null;
} else if (this.players.player2 == socketId) {
console.log('Player 2 disconnected');
this.players.player2 = null;
}
}
}</code></pre><h2>Step 4: the Client Code</h2><p>The client-side code must implement a renderer, and a client engine.</p>
<p>The renderer, in our case, will update HTML elements created for
each paddle and the ball:</p>
<h3>src/client/MyRenderer.js</h3><p>Change the <code>draw()</code> method and add the <code>addSprite()</code> methods as shown here:</p>
<pre class="prettyprint source lang-javascript"><code>draw(t, dt) {
super.draw(t, dt);
for (let objId of Object.keys(this.sprites)) {
if (this.sprites[objId].el) {
this.sprites[objId].el.style.top = this.gameEngine.world.objects[objId].position.y + 'px';
this.sprites[objId].el.style.left = this.gameEngine.world.objects[objId].position.x + 'px';
}
}
}
addSprite(obj, objName) {
if (objName === 'paddle') objName += obj.playerId;
this.sprites[obj.id] = {
el: document.querySelector('.' + objName)
};
}</code></pre><h2>Step 4: the Client Visuals</h2><p>The client visuals code are simple HTML so we don't discuss them in detail.
In file <code>index.html</code>, change the <code><body></code> DOM element contain just the following snippet.</p>
<pre class="prettyprint source lang-HTML"><code><div style="width: 400px; height: 400px; background: black">
<div style="position:absolute;width:10px;height:50px;background:white" class="paddle1"></div>
<div style="position:absolute;width:10px;height:50px;background:white" class="paddle2"></div>
<div style="position:absolute;width:5px; height:5px;background:white" class="ball"></div>
</div></code></pre><h2>Step 5: Running the Game</h2><p>Once everything has been put together the end result should look like
the pong branch of the repository.</p>
<p>NOTE: If you prefer to get a clean working copy, you can run:</p>
<pre class="prettyprint source lang-shell"><code>git clone https://github.com/lance-gg/lancegame.git pong
cd pong
git checkout pong
npm install</code></pre><p>To run the game you must first build the JavaScript bundle. The <code>npm install</code>
command above already did this for you, but we changed the code, so you must rebuild by
executing:</p>
<pre class="prettyprint source lang-shell"><code>npm run build</code></pre><p>To run the game, type:</p>
<pre class="prettyprint source lang-shell"><code>npm start</code></pre><p>Open two browser windows and point them to the local host. The URL is
<code>http://127.0.0.1:3000/</code> on windows, and <code>http://localhost:3000/</code>
on a Mac.</p>
<h2>Next Steps</h2><p>Your next steps might be to get a deeper understanding by going through
the Spaceships Tutorial, which introduces the concepts and components
of an Lance networked game.</p>
</article>
</section>
</div>
<br class="clear">
<footer>
Did you find a mistake? Do you have an improvement? <a href="https://github.com/lance-gg/lance/issues">Let us know!</a>
</footer>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-88335360-1', 'auto');
ga('send', 'pageview');
</script>
<script src="https://use.typekit.net/lai1bbe.js"></script>
<script>try{Typekit.load({ async: true });}catch(e){}</script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.8.0/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script src="scripts/linenumber.js"></script>
</body>
</html>