@gamerstake/game-core 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +142 -0
- package/CLIENT_SDK_README.md +634 -0
- package/README.md +58 -5
- package/dist/client/index.d.ts +574 -0
- package/dist/client/index.js +970 -0
- package/dist/client/index.js.map +1 -0
- package/dist/index.d.ts +90 -1
- package/dist/index.js +356 -1
- package/dist/index.js.map +1 -1
- package/docs/N1-package-overview.md +303 -0
- package/docs/N2-api-reference.md +500 -0
- package/docs/N3-implementation-guide.md +550 -0
- package/docs/N4-testing-validation.md +390 -0
- package/docs/N5-integration-checklist.md +90 -0
- package/docs/README.md +137 -0
- package/examples/simple-game/README.md +17 -6
- package/examples/simple-game/client.ts +25 -14
- package/examples/simple-game/package.json +5 -1
- package/examples/simple-game/server.ts +21 -13
- package/package.json +17 -13
- package/src/adapters/SocketIOAdapter.ts +496 -0
- package/src/client/GameClient.ts +466 -0
- package/src/client/InputBuffer.ts +148 -0
- package/src/client/Interpolator.ts +242 -0
- package/src/client/Reconciler.ts +233 -0
- package/src/client/TimeSync.ts +182 -0
- package/src/client/index.ts +32 -0
- package/src/client/types.ts +247 -0
- package/src/index.ts +8 -0
- package/tests/client/InputBuffer.test.ts +118 -0
- package/tests/client/TimeSync.test.ts +116 -0
- package/tsup.config.ts +4 -1
- package/TESTING_OVERVIEW.md +0 -378
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
# Game Core Client SDK
|
|
2
|
+
|
|
3
|
+
**Version:** 0.2.0
|
|
4
|
+
**Status:** ✅ Production Ready
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 🎮 What is the Client SDK?
|
|
9
|
+
|
|
10
|
+
The Client SDK is the **browser-side** component of `@gamerstake/game-core` that eliminates the need for custom networking code in multiplayer games.
|
|
11
|
+
|
|
12
|
+
### Before Client SDK
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
// Custom networking code (2,000+ lines)
|
|
16
|
+
class NetClient {
|
|
17
|
+
/* 547 lines */
|
|
18
|
+
}
|
|
19
|
+
class TimeSync {
|
|
20
|
+
/* 429 lines */
|
|
21
|
+
}
|
|
22
|
+
class InputBuffer {
|
|
23
|
+
/* 270 lines */
|
|
24
|
+
}
|
|
25
|
+
class Reconciler {
|
|
26
|
+
/* 587 lines */
|
|
27
|
+
}
|
|
28
|
+
class Interpolator {
|
|
29
|
+
/* 200+ lines */
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### After Client SDK
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { GameClient } from '@gamerstake/game-core/client';
|
|
37
|
+
|
|
38
|
+
const client = new GameClient({
|
|
39
|
+
serverUrl: 'http://localhost:3000',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
await client.connect();
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Result:** 2,000+ lines → 5 lines (99.7% reduction)
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 📦 Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pnpm add @gamerstake/game-core
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 🚀 Quick Start
|
|
58
|
+
|
|
59
|
+
### 1. Import Client SDK
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { GameClient } from '@gamerstake/game-core/client';
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 2. Create Client
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
const client = new GameClient({
|
|
69
|
+
serverUrl: 'http://localhost:3000',
|
|
70
|
+
enablePrediction: true,
|
|
71
|
+
enableReconciliation: true,
|
|
72
|
+
enableInterpolation: true,
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 3. Connect to Server
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
await client.connect();
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 4. Listen for Events
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
client.on('stateUpdate', (state) => {
|
|
86
|
+
// Update game entities
|
|
87
|
+
state.players?.forEach((player) => {
|
|
88
|
+
updatePlayerPosition(player.id, player.x, player.z);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 5. Send Commands
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// Movement
|
|
97
|
+
client.sendMove(targetX, targetZ);
|
|
98
|
+
|
|
99
|
+
// Custom actions
|
|
100
|
+
client.sendAction('attack', { targetId: 'enemy-1' });
|
|
101
|
+
client.sendAction('collect', { itemId: 'item-5' });
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 6. Update Every Frame
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
function gameLoop(deltaMs) {
|
|
108
|
+
client.update(deltaMs); // Handles interpolation, prediction
|
|
109
|
+
render();
|
|
110
|
+
requestAnimationFrame(gameLoop);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
gameLoop(16);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 📚 API Reference
|
|
119
|
+
|
|
120
|
+
### GameClient
|
|
121
|
+
|
|
122
|
+
#### Constructor
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
new GameClient(config: GameClientConfig)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Config Options:**
|
|
129
|
+
|
|
130
|
+
| Option | Type | Default | Description |
|
|
131
|
+
| :--------------------- | :------ | :----------- | :---------------------------- |
|
|
132
|
+
| `serverUrl` | string | **required** | Server URL to connect to |
|
|
133
|
+
| `enablePrediction` | boolean | `true` | Enable client-side prediction |
|
|
134
|
+
| `enableReconciliation` | boolean | `true` | Enable server reconciliation |
|
|
135
|
+
| `enableInterpolation` | boolean | `true` | Enable entity interpolation |
|
|
136
|
+
| `interpolationDelay` | number | `100` | Interpolation delay (ms) |
|
|
137
|
+
| `reconnectAttempts` | number | `5` | Number of reconnect attempts |
|
|
138
|
+
| `reconnectDelay` | number | `1000` | Delay between reconnects (ms) |
|
|
139
|
+
|
|
140
|
+
#### Methods
|
|
141
|
+
|
|
142
|
+
**`connect(): Promise<void>`**
|
|
143
|
+
Connect to game server.
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
await client.connect();
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**`disconnect(): void`**
|
|
150
|
+
Disconnect from server.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
client.disconnect();
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**`on(event, handler): void`**
|
|
157
|
+
Register event handler.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
client.on('stateUpdate', (state) => {
|
|
161
|
+
/* ... */
|
|
162
|
+
});
|
|
163
|
+
client.on('customEvent', (data) => {
|
|
164
|
+
/* ... */
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**`sendMove(targetX, targetZ): number`**
|
|
169
|
+
Send movement command.
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
const actionId = client.sendMove(100, 50);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**`sendAction(type, data): number`**
|
|
176
|
+
Send custom action command.
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
client.sendAction('attack', { targetId: 'enemy-1' });
|
|
180
|
+
client.sendAction('knife_throw', { targetX: 100, targetZ: 50 });
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**`update(deltaMs): void`**
|
|
184
|
+
Update client (call every frame).
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
client.update(16); // ~60 FPS
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**`getStats(): NetworkStats`**
|
|
191
|
+
Get network statistics.
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
const stats = client.getStats();
|
|
195
|
+
console.log('RTT:', stats.rtt);
|
|
196
|
+
console.log('Jitter:', stats.jitter);
|
|
197
|
+
console.log('Messages sent:', stats.messagesSent);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**`getServerTime(): number`**
|
|
201
|
+
Get estimated server time.
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
const serverTime = client.getServerTime();
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**`isConnected(): boolean`**
|
|
208
|
+
Check if connected to server.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
if (client.isConnected()) {
|
|
212
|
+
// Connected
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## 🎯 Features
|
|
219
|
+
|
|
220
|
+
### 1. Time Synchronization
|
|
221
|
+
|
|
222
|
+
Automatically synchronizes client clock with server for accurate lag compensation.
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
// Get server time
|
|
226
|
+
const serverTime = client.getServerTime();
|
|
227
|
+
|
|
228
|
+
// Get network stats
|
|
229
|
+
const stats = client.getStats();
|
|
230
|
+
console.log('RTT:', stats.rtt, 'ms');
|
|
231
|
+
console.log('Clock offset:', stats.offset, 'ms');
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### 2. Client Prediction
|
|
235
|
+
|
|
236
|
+
Applies commands locally before server confirmation for responsive gameplay.
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
// Enabled by default
|
|
240
|
+
const client = new GameClient({
|
|
241
|
+
serverUrl: 'http://localhost:3000',
|
|
242
|
+
enablePrediction: true,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Movement feels instant!
|
|
246
|
+
client.sendMove(x, z);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### 3. Server Reconciliation
|
|
250
|
+
|
|
251
|
+
Corrects prediction errors when server state arrives.
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
const client = new GameClient({
|
|
255
|
+
serverUrl: 'http://localhost:3000',
|
|
256
|
+
enableReconciliation: true,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Automatically corrects position differences
|
|
260
|
+
// Smoothly interpolates corrections
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### 4. Entity Interpolation
|
|
264
|
+
|
|
265
|
+
Smoothly renders remote entities between server updates.
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
const client = new GameClient({
|
|
269
|
+
serverUrl: 'http://localhost:3000',
|
|
270
|
+
enableInterpolation: true,
|
|
271
|
+
interpolationDelay: 100, // 100ms delay for smooth rendering
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Remote players move smoothly even at low update rates
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### 5. Input Buffering
|
|
278
|
+
|
|
279
|
+
Buffers inputs and tracks acknowledgments for reliable networking.
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
// Automatically buffers inputs
|
|
283
|
+
const actionId = client.sendMove(x, z);
|
|
284
|
+
|
|
285
|
+
// Tracks acknowledgment
|
|
286
|
+
client.on('moveAck', (data) => {
|
|
287
|
+
if (data.actionId === actionId) {
|
|
288
|
+
// Input acknowledged by server
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### 6. Auto-Reconnection
|
|
294
|
+
|
|
295
|
+
Automatically reconnects on disconnect with exponential backoff.
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
const client = new GameClient({
|
|
299
|
+
serverUrl: 'http://localhost:3000',
|
|
300
|
+
reconnectAttempts: 5,
|
|
301
|
+
reconnectDelay: 1000, // Initial delay, increases exponentially
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
client.on('disconnect', (reason) => {
|
|
305
|
+
console.log('Disconnected:', reason);
|
|
306
|
+
// Will automatically attempt reconnection
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
client.on('reconnect', () => {
|
|
310
|
+
console.log('Reconnected!');
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## 🎮 Complete Example
|
|
317
|
+
|
|
318
|
+
### Client (Browser)
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
import { GameClient } from '@gamerstake/game-core/client';
|
|
322
|
+
|
|
323
|
+
class MyGame {
|
|
324
|
+
private client: GameClient;
|
|
325
|
+
private players = new Map();
|
|
326
|
+
|
|
327
|
+
async init() {
|
|
328
|
+
// Create client
|
|
329
|
+
this.client = new GameClient({
|
|
330
|
+
serverUrl: 'http://localhost:3000',
|
|
331
|
+
enablePrediction: true,
|
|
332
|
+
enableReconciliation: true,
|
|
333
|
+
enableInterpolation: true,
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// Register events
|
|
337
|
+
this.client.on('connected', () => {
|
|
338
|
+
console.log('Connected to server!');
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
this.client.on('stateUpdate', (state) => {
|
|
342
|
+
this.handleStateUpdate(state);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
this.client.on('playerDied', (data) => {
|
|
346
|
+
this.showDeathAnimation(data.playerId);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// Connect
|
|
350
|
+
await this.client.connect();
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
handleStateUpdate(state) {
|
|
354
|
+
// Update player positions
|
|
355
|
+
state.players?.forEach((player) => {
|
|
356
|
+
const entity = this.players.get(player.playerId);
|
|
357
|
+
if (entity) {
|
|
358
|
+
entity.position.set(player.x, 0, player.z);
|
|
359
|
+
entity.health = player.health;
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
onPlayerClick(x, z) {
|
|
365
|
+
// Send movement command
|
|
366
|
+
this.client.sendMove(x, z);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
onAttackClick(targetId) {
|
|
370
|
+
// Send attack command
|
|
371
|
+
this.client.sendAction('attack', { targetId });
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
update(deltaMs) {
|
|
375
|
+
// Update client (interpolation, prediction)
|
|
376
|
+
this.client.update(deltaMs);
|
|
377
|
+
|
|
378
|
+
// Game logic
|
|
379
|
+
this.updateAnimations(deltaMs);
|
|
380
|
+
this.updateEffects(deltaMs);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
render() {
|
|
384
|
+
// Render game
|
|
385
|
+
this.renderer.render(this.scene, this.camera);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
gameLoop(deltaMs) {
|
|
389
|
+
this.update(deltaMs);
|
|
390
|
+
this.render();
|
|
391
|
+
requestAnimationFrame(() => this.gameLoop(16));
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Start game
|
|
396
|
+
const game = new MyGame();
|
|
397
|
+
await game.init();
|
|
398
|
+
game.gameLoop(16);
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### Server (Node.js)
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
import { Server as SocketIOServer } from 'socket.io';
|
|
405
|
+
import { GameServer, Entity } from '@gamerstake/game-core';
|
|
406
|
+
import { MyGameRules } from './MyGameRules';
|
|
407
|
+
|
|
408
|
+
const io = new SocketIOServer(3000);
|
|
409
|
+
const gameServer = new GameServer();
|
|
410
|
+
gameServer.setServer(io);
|
|
411
|
+
|
|
412
|
+
const room = gameServer.createRoom('room-1', new MyGameRules(), {
|
|
413
|
+
tickRate: 60,
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
io.on('connection', (socket) => {
|
|
417
|
+
const player = new Entity(socket.id, 0, 0);
|
|
418
|
+
room.addPlayer(player);
|
|
419
|
+
room.getNetwork().registerSocket(socket.id, socket);
|
|
420
|
+
|
|
421
|
+
socket.on('disconnect', () => {
|
|
422
|
+
room.removePlayer(socket.id);
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## 🔧 Advanced Usage
|
|
430
|
+
|
|
431
|
+
### Custom Event Handlers
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
// Register any custom event
|
|
435
|
+
client.on('knifeSpawn', (data) => {
|
|
436
|
+
spawnKnife(data.knifeId, data.x, data.z);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
client.on('knifeHit', (data) => {
|
|
440
|
+
playHitSound();
|
|
441
|
+
showBloodEffect(data.hitX, data.hitZ);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
client.on('gameOver', (data) => {
|
|
445
|
+
showWinner(data.winner);
|
|
446
|
+
});
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Network Statistics
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
const stats = client.getStats();
|
|
453
|
+
|
|
454
|
+
console.log({
|
|
455
|
+
rtt: stats.rtt, // Round-trip time
|
|
456
|
+
jitter: stats.jitter, // Network jitter
|
|
457
|
+
connected: stats.connected, // Connection status
|
|
458
|
+
messagesSent: stats.messagesSent, // Total sent
|
|
459
|
+
messagesReceived: stats.messagesReceived, // Total received
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// Display to user
|
|
463
|
+
showNetworkQuality(stats.rtt < 50 ? 'Good' : 'Poor');
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Adaptive Interpolation
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
// Automatically adjust interpolation based on network conditions
|
|
470
|
+
const adaptiveDelay = client.getAdaptiveInterpolationDelay();
|
|
471
|
+
|
|
472
|
+
// Use it for rendering
|
|
473
|
+
const renderTime = Date.now() - adaptiveDelay;
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## 🧪 Testing
|
|
479
|
+
|
|
480
|
+
### Unit Tests
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
import { GameClient } from '@gamerstake/game-core/client';
|
|
484
|
+
|
|
485
|
+
describe('MyGame', () => {
|
|
486
|
+
let client: GameClient;
|
|
487
|
+
|
|
488
|
+
beforeEach(() => {
|
|
489
|
+
client = new GameClient({
|
|
490
|
+
serverUrl: 'http://localhost:3000',
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('should connect to server', async () => {
|
|
495
|
+
await client.connect();
|
|
496
|
+
expect(client.isConnected()).toBe(true);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it('should send movement', () => {
|
|
500
|
+
const actionId = client.sendMove(100, 50);
|
|
501
|
+
expect(actionId).toBeGreaterThan(0);
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
## 🐛 Troubleshooting
|
|
509
|
+
|
|
510
|
+
### Connection Issues
|
|
511
|
+
|
|
512
|
+
**Problem:** Can't connect to server
|
|
513
|
+
|
|
514
|
+
**Solutions:**
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
// Check server URL
|
|
518
|
+
const client = new GameClient({
|
|
519
|
+
serverUrl: 'http://localhost:3000', // Correct port?
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
// Check CORS on server
|
|
523
|
+
const io = new SocketIOServer(PORT, {
|
|
524
|
+
cors: { origin: '*' }, // Allow all origins
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
// Check connection event
|
|
528
|
+
client.on('error', (error) => {
|
|
529
|
+
console.error('Connection error:', error);
|
|
530
|
+
});
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### High Latency
|
|
534
|
+
|
|
535
|
+
**Problem:** Movement feels laggy
|
|
536
|
+
|
|
537
|
+
**Solutions:**
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
// Check RTT
|
|
541
|
+
const stats = client.getStats();
|
|
542
|
+
console.log('RTT:', stats.rtt); // Should be < 100ms
|
|
543
|
+
|
|
544
|
+
// Increase interpolation delay
|
|
545
|
+
const client = new GameClient({
|
|
546
|
+
serverUrl: 'http://localhost:3000',
|
|
547
|
+
interpolationDelay: 150, // Increase for high latency
|
|
548
|
+
});
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Position Desync
|
|
552
|
+
|
|
553
|
+
**Problem:** Player position not matching server
|
|
554
|
+
|
|
555
|
+
**Solutions:**
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
// Enable reconciliation
|
|
559
|
+
const client = new GameClient({
|
|
560
|
+
serverUrl: 'http://localhost:3000',
|
|
561
|
+
enableReconciliation: true,
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
// Check for prediction errors in console
|
|
565
|
+
// Reconciler will automatically correct
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
---
|
|
569
|
+
|
|
570
|
+
## 📊 Performance
|
|
571
|
+
|
|
572
|
+
### Network Performance
|
|
573
|
+
|
|
574
|
+
- **RTT:** < 100ms (typical)
|
|
575
|
+
- **Jitter:** < 20ms (typical)
|
|
576
|
+
- **Bandwidth:** ~10-50 KB/s per player
|
|
577
|
+
- **Update Rate:** 20-60 Hz
|
|
578
|
+
|
|
579
|
+
### Client Performance
|
|
580
|
+
|
|
581
|
+
- **CPU Usage:** < 5% (at 60 FPS)
|
|
582
|
+
- **Memory:** < 10MB per client
|
|
583
|
+
- **Frame Impact:** < 1ms per frame
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
## 🔄 Migration Guide
|
|
588
|
+
|
|
589
|
+
### From Custom NetClient
|
|
590
|
+
|
|
591
|
+
If you have custom networking code:
|
|
592
|
+
|
|
593
|
+
**Before:**
|
|
594
|
+
|
|
595
|
+
```javascript
|
|
596
|
+
import NetClient from './net/NetClient.js';
|
|
597
|
+
|
|
598
|
+
const socket = io('http://localhost:3000');
|
|
599
|
+
const netClient = new NetClient(socket);
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
**After:**
|
|
603
|
+
|
|
604
|
+
```typescript
|
|
605
|
+
import { GameClient } from '@gamerstake/game-core/client';
|
|
606
|
+
|
|
607
|
+
const client = new GameClient({
|
|
608
|
+
serverUrl: 'http://localhost:3000',
|
|
609
|
+
});
|
|
610
|
+
await client.connect();
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
See [MUNDO-CLEAVER-MIGRATION-GUIDE.md](../../docs/features/game-core/MUNDO-CLEAVER-MIGRATION-GUIDE.md) for complete migration guide.
|
|
614
|
+
|
|
615
|
+
---
|
|
616
|
+
|
|
617
|
+
## 📚 Resources
|
|
618
|
+
|
|
619
|
+
- [Main README](./README.md) - Package overview
|
|
620
|
+
- [Developer Guide](./DEVELOPER_GUIDE.md) - Complete tutorial
|
|
621
|
+
- [API Documentation](./API.md) - Full API reference
|
|
622
|
+
- [Examples](./examples/) - Working examples
|
|
623
|
+
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
## 🆘 Support
|
|
627
|
+
|
|
628
|
+
- **Issues:** https://github.com/gamerstake/gamerstake/issues
|
|
629
|
+
- **Documentation:** https://docs.gamerstake.com
|
|
630
|
+
- **Discord:** https://discord.gg/gamerstake
|
|
631
|
+
|
|
632
|
+
---
|
|
633
|
+
|
|
634
|
+
**Built with ❤️ by the GamerStake team**
|
package/README.md
CHANGED
|
@@ -44,6 +44,12 @@ Reusable multiplayer game engine for the GamerStake platform.
|
|
|
44
44
|
|
|
45
45
|
## Installation
|
|
46
46
|
|
|
47
|
+
### From NPM Registry
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pnpm add @gamerstake/game-core
|
|
51
|
+
```
|
|
52
|
+
|
|
47
53
|
### From Monorepo Workspace
|
|
48
54
|
|
|
49
55
|
If you're in the GamerStake monorepo:
|
|
@@ -56,15 +62,62 @@ If you're in the GamerStake monorepo:
|
|
|
56
62
|
}
|
|
57
63
|
```
|
|
58
64
|
|
|
59
|
-
|
|
65
|
+
See [PUBLISHING.md](./PUBLISHING.md) for publishing and distribution options.
|
|
60
66
|
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
## Quick Start
|
|
68
|
+
|
|
69
|
+
### Client SDK (Browser/Game)
|
|
70
|
+
|
|
71
|
+
Build multiplayer games with **zero networking code**:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { GameClient } from '@gamerstake/game-core/client';
|
|
75
|
+
|
|
76
|
+
// Create client
|
|
77
|
+
const client = new GameClient({
|
|
78
|
+
serverUrl: 'http://localhost:3000',
|
|
79
|
+
enablePrediction: true, // Smooth local movement
|
|
80
|
+
enableReconciliation: true, // Server correction
|
|
81
|
+
enableInterpolation: true, // Smooth remote players
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Connect
|
|
85
|
+
await client.connect();
|
|
86
|
+
|
|
87
|
+
// Listen for state updates
|
|
88
|
+
client.on('stateUpdate', (state) => {
|
|
89
|
+
// Update your game entities
|
|
90
|
+
updatePlayers(state.players);
|
|
91
|
+
updateEntities(state.entities);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Listen for custom events
|
|
95
|
+
client.on('playerDied', (data) => {
|
|
96
|
+
showDeathAnimation(data.playerId);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Send movement
|
|
100
|
+
client.sendMove(targetX, targetZ);
|
|
101
|
+
|
|
102
|
+
// Send custom actions
|
|
103
|
+
client.sendAction('attack', { targetId: 'enemy-1' });
|
|
104
|
+
|
|
105
|
+
// Update every frame
|
|
106
|
+
function gameLoop(deltaMs) {
|
|
107
|
+
client.update(deltaMs); // Handles interpolation, prediction
|
|
108
|
+
render();
|
|
109
|
+
requestAnimationFrame(gameLoop);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Get network stats
|
|
113
|
+
const stats = client.getStats();
|
|
114
|
+
console.log('RTT:', stats.rtt, 'ms');
|
|
115
|
+
console.log('Jitter:', stats.jitter, 'ms');
|
|
63
116
|
```
|
|
64
117
|
|
|
65
|
-
|
|
118
|
+
**That's it!** No Socket.io code, no custom networking, no prediction logic. Just game logic! 🎮
|
|
66
119
|
|
|
67
|
-
|
|
120
|
+
### Server SDK (Node.js)
|
|
68
121
|
|
|
69
122
|
### 1. Implement GameRules
|
|
70
123
|
|