omgkit 2.13.0 → 2.15.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/README.md +93 -10
- package/package.json +2 -2
- package/plugin/agents/api-designer.md +5 -0
- package/plugin/agents/architect.md +8 -0
- package/plugin/agents/brainstormer.md +4 -0
- package/plugin/agents/cicd-manager.md +6 -0
- package/plugin/agents/code-reviewer.md +6 -0
- package/plugin/agents/copywriter.md +2 -0
- package/plugin/agents/data-engineer.md +255 -0
- package/plugin/agents/database-admin.md +10 -0
- package/plugin/agents/debugger.md +10 -0
- package/plugin/agents/devsecops.md +314 -0
- package/plugin/agents/docs-manager.md +4 -0
- package/plugin/agents/domain-decomposer.md +181 -0
- package/plugin/agents/embedded-systems.md +397 -0
- package/plugin/agents/fullstack-developer.md +12 -0
- package/plugin/agents/game-systems-designer.md +375 -0
- package/plugin/agents/git-manager.md +10 -0
- package/plugin/agents/journal-writer.md +2 -0
- package/plugin/agents/ml-engineer.md +284 -0
- package/plugin/agents/observability-engineer.md +353 -0
- package/plugin/agents/oracle.md +9 -0
- package/plugin/agents/performance-engineer.md +290 -0
- package/plugin/agents/pipeline-architect.md +6 -0
- package/plugin/agents/planner.md +12 -0
- package/plugin/agents/platform-engineer.md +325 -0
- package/plugin/agents/project-manager.md +3 -0
- package/plugin/agents/researcher.md +5 -0
- package/plugin/agents/scientific-computing.md +426 -0
- package/plugin/agents/scout.md +3 -0
- package/plugin/agents/security-auditor.md +7 -0
- package/plugin/agents/sprint-master.md +17 -0
- package/plugin/agents/tester.md +10 -0
- package/plugin/agents/ui-ux-designer.md +12 -0
- package/plugin/agents/vulnerability-scanner.md +6 -0
- package/plugin/commands/data/pipeline.md +47 -0
- package/plugin/commands/data/quality.md +49 -0
- package/plugin/commands/domain/analyze.md +34 -0
- package/plugin/commands/domain/map.md +41 -0
- package/plugin/commands/game/balance.md +56 -0
- package/plugin/commands/game/optimize.md +62 -0
- package/plugin/commands/iot/provision.md +58 -0
- package/plugin/commands/ml/evaluate.md +47 -0
- package/plugin/commands/ml/train.md +48 -0
- package/plugin/commands/perf/benchmark.md +54 -0
- package/plugin/commands/perf/profile.md +49 -0
- package/plugin/commands/platform/blueprint.md +56 -0
- package/plugin/commands/security/audit.md +54 -0
- package/plugin/commands/security/scan.md +55 -0
- package/plugin/commands/sre/dashboard.md +53 -0
- package/plugin/registry.yaml +711 -0
- package/plugin/skills/ai-ml/experiment-tracking/SKILL.md +338 -0
- package/plugin/skills/ai-ml/feature-stores/SKILL.md +340 -0
- package/plugin/skills/ai-ml/llm-ops/SKILL.md +454 -0
- package/plugin/skills/ai-ml/ml-pipelines/SKILL.md +390 -0
- package/plugin/skills/ai-ml/model-monitoring/SKILL.md +398 -0
- package/plugin/skills/ai-ml/model-serving/SKILL.md +386 -0
- package/plugin/skills/event-driven/cqrs-patterns/SKILL.md +348 -0
- package/plugin/skills/event-driven/event-sourcing/SKILL.md +334 -0
- package/plugin/skills/event-driven/kafka-deep/SKILL.md +252 -0
- package/plugin/skills/event-driven/saga-orchestration/SKILL.md +335 -0
- package/plugin/skills/event-driven/schema-registry/SKILL.md +328 -0
- package/plugin/skills/event-driven/stream-processing/SKILL.md +313 -0
- package/plugin/skills/game/game-audio/SKILL.md +446 -0
- package/plugin/skills/game/game-networking/SKILL.md +490 -0
- package/plugin/skills/game/godot-patterns/SKILL.md +413 -0
- package/plugin/skills/game/shader-programming/SKILL.md +492 -0
- package/plugin/skills/game/unity-patterns/SKILL.md +488 -0
- package/plugin/skills/iot/device-provisioning/SKILL.md +405 -0
- package/plugin/skills/iot/edge-computing/SKILL.md +369 -0
- package/plugin/skills/iot/industrial-protocols/SKILL.md +438 -0
- package/plugin/skills/iot/mqtt-deep/SKILL.md +418 -0
- package/plugin/skills/iot/ota-updates/SKILL.md +426 -0
- package/plugin/skills/microservices/api-gateway-patterns/SKILL.md +201 -0
- package/plugin/skills/microservices/circuit-breaker-patterns/SKILL.md +246 -0
- package/plugin/skills/microservices/contract-testing/SKILL.md +284 -0
- package/plugin/skills/microservices/distributed-tracing/SKILL.md +246 -0
- package/plugin/skills/microservices/service-discovery/SKILL.md +304 -0
- package/plugin/skills/microservices/service-mesh/SKILL.md +181 -0
- package/plugin/skills/mobile-advanced/mobile-ci-cd/SKILL.md +407 -0
- package/plugin/skills/mobile-advanced/mobile-security/SKILL.md +403 -0
- package/plugin/skills/mobile-advanced/offline-first/SKILL.md +473 -0
- package/plugin/skills/mobile-advanced/push-notifications/SKILL.md +494 -0
- package/plugin/skills/mobile-advanced/react-native-deep/SKILL.md +374 -0
- package/plugin/skills/simulation/numerical-methods/SKILL.md +434 -0
- package/plugin/skills/simulation/parallel-computing/SKILL.md +382 -0
- package/plugin/skills/simulation/physics-engines/SKILL.md +377 -0
- package/plugin/skills/simulation/validation-verification/SKILL.md +479 -0
- package/plugin/skills/simulation/visualization-scientific/SKILL.md +365 -0
- package/plugin/workflows/ai-engineering/agent-development.md +3 -3
- package/plugin/workflows/ai-engineering/fine-tuning.md +3 -3
- package/plugin/workflows/ai-engineering/model-evaluation.md +3 -3
- package/plugin/workflows/ai-engineering/prompt-engineering.md +2 -2
- package/plugin/workflows/ai-engineering/rag-development.md +4 -4
- package/plugin/workflows/ai-ml/data-pipeline.md +188 -0
- package/plugin/workflows/ai-ml/experiment-cycle.md +203 -0
- package/plugin/workflows/ai-ml/feature-engineering.md +208 -0
- package/plugin/workflows/ai-ml/model-deployment.md +199 -0
- package/plugin/workflows/ai-ml/monitoring-setup.md +227 -0
- package/plugin/workflows/api/api-design.md +1 -1
- package/plugin/workflows/api/api-testing.md +2 -2
- package/plugin/workflows/content/technical-docs.md +1 -1
- package/plugin/workflows/database/migration.md +1 -1
- package/plugin/workflows/database/optimization.md +1 -1
- package/plugin/workflows/database/schema-design.md +3 -3
- package/plugin/workflows/development/bug-fix.md +3 -3
- package/plugin/workflows/development/code-review.md +2 -1
- package/plugin/workflows/development/feature.md +3 -3
- package/plugin/workflows/development/refactor.md +2 -2
- package/plugin/workflows/event-driven/consumer-groups.md +190 -0
- package/plugin/workflows/event-driven/event-storming.md +172 -0
- package/plugin/workflows/event-driven/replay-testing.md +186 -0
- package/plugin/workflows/event-driven/saga-implementation.md +206 -0
- package/plugin/workflows/event-driven/schema-evolution.md +173 -0
- package/plugin/workflows/fullstack/authentication.md +4 -4
- package/plugin/workflows/fullstack/full-feature.md +4 -4
- package/plugin/workflows/game-dev/content-pipeline.md +218 -0
- package/plugin/workflows/game-dev/platform-submission.md +263 -0
- package/plugin/workflows/game-dev/playtesting.md +237 -0
- package/plugin/workflows/game-dev/prototype-to-production.md +205 -0
- package/plugin/workflows/microservices/contract-first.md +151 -0
- package/plugin/workflows/microservices/distributed-tracing.md +166 -0
- package/plugin/workflows/microservices/domain-decomposition.md +123 -0
- package/plugin/workflows/microservices/integration-testing.md +149 -0
- package/plugin/workflows/microservices/service-mesh-setup.md +153 -0
- package/plugin/workflows/microservices/service-scaffolding.md +151 -0
- package/plugin/workflows/omega/1000x-innovation.md +2 -2
- package/plugin/workflows/omega/100x-architecture.md +2 -2
- package/plugin/workflows/omega/10x-improvement.md +2 -2
- package/plugin/workflows/quality/performance-optimization.md +2 -2
- package/plugin/workflows/research/best-practices.md +1 -1
- package/plugin/workflows/research/technology-research.md +1 -1
- package/plugin/workflows/security/penetration-testing.md +3 -3
- package/plugin/workflows/security/security-audit.md +3 -3
- package/plugin/workflows/sprint/sprint-execution.md +2 -2
- package/plugin/workflows/sprint/sprint-retrospective.md +1 -1
- package/plugin/workflows/sprint/sprint-setup.md +1 -1
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
# Game Networking
|
|
2
|
+
|
|
3
|
+
Multiplayer game networking including client-server architecture, state synchronization, lag compensation, and matchmaking.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Game networking enables real-time multiplayer experiences through efficient state synchronization, prediction, and latency management.
|
|
8
|
+
|
|
9
|
+
## Core Concepts
|
|
10
|
+
|
|
11
|
+
### Network Models
|
|
12
|
+
- **Client-Server**: Authoritative server
|
|
13
|
+
- **Peer-to-Peer**: Direct player connections
|
|
14
|
+
- **Relay**: Server-mediated P2P
|
|
15
|
+
- **Hybrid**: Mixed approaches
|
|
16
|
+
|
|
17
|
+
### Synchronization Methods
|
|
18
|
+
- **Lockstep**: All clients wait for all inputs
|
|
19
|
+
- **State Sync**: Server sends world state
|
|
20
|
+
- **Input Sync**: Server processes inputs
|
|
21
|
+
- **Snapshot Interpolation**: Buffered state replay
|
|
22
|
+
|
|
23
|
+
## Client-Server Architecture
|
|
24
|
+
|
|
25
|
+
### Server-Authoritative Model
|
|
26
|
+
```typescript
|
|
27
|
+
// server.ts
|
|
28
|
+
import { Server } from 'socket.io';
|
|
29
|
+
|
|
30
|
+
interface GameState {
|
|
31
|
+
players: Map<string, PlayerState>;
|
|
32
|
+
projectiles: ProjectileState[];
|
|
33
|
+
tick: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface PlayerState {
|
|
37
|
+
id: string;
|
|
38
|
+
position: Vector3;
|
|
39
|
+
rotation: number;
|
|
40
|
+
velocity: Vector3;
|
|
41
|
+
health: number;
|
|
42
|
+
lastProcessedInput: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface PlayerInput {
|
|
46
|
+
tick: number;
|
|
47
|
+
movement: Vector2;
|
|
48
|
+
rotation: number;
|
|
49
|
+
actions: string[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
class GameServer {
|
|
53
|
+
private state: GameState;
|
|
54
|
+
private tickRate = 60;
|
|
55
|
+
private inputBuffer: Map<string, PlayerInput[]> = new Map();
|
|
56
|
+
|
|
57
|
+
constructor(private io: Server) {
|
|
58
|
+
this.state = {
|
|
59
|
+
players: new Map(),
|
|
60
|
+
projectiles: [],
|
|
61
|
+
tick: 0
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
this.setupHandlers();
|
|
65
|
+
this.startGameLoop();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private setupHandlers(): void {
|
|
69
|
+
this.io.on('connection', (socket) => {
|
|
70
|
+
this.handlePlayerJoin(socket);
|
|
71
|
+
|
|
72
|
+
socket.on('input', (input: PlayerInput) => {
|
|
73
|
+
this.bufferInput(socket.id, input);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
socket.on('disconnect', () => {
|
|
77
|
+
this.handlePlayerLeave(socket.id);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private bufferInput(playerId: string, input: PlayerInput): void {
|
|
83
|
+
if (!this.inputBuffer.has(playerId)) {
|
|
84
|
+
this.inputBuffer.set(playerId, []);
|
|
85
|
+
}
|
|
86
|
+
this.inputBuffer.get(playerId)!.push(input);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private startGameLoop(): void {
|
|
90
|
+
const tickInterval = 1000 / this.tickRate;
|
|
91
|
+
|
|
92
|
+
setInterval(() => {
|
|
93
|
+
this.processInputs();
|
|
94
|
+
this.updatePhysics();
|
|
95
|
+
this.broadcastState();
|
|
96
|
+
this.state.tick++;
|
|
97
|
+
}, tickInterval);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private processInputs(): void {
|
|
101
|
+
for (const [playerId, inputs] of this.inputBuffer) {
|
|
102
|
+
const player = this.state.players.get(playerId);
|
|
103
|
+
if (!player) continue;
|
|
104
|
+
|
|
105
|
+
for (const input of inputs) {
|
|
106
|
+
this.applyInput(player, input);
|
|
107
|
+
player.lastProcessedInput = input.tick;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
this.inputBuffer.clear();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private applyInput(player: PlayerState, input: PlayerInput): void {
|
|
114
|
+
// Apply movement
|
|
115
|
+
const moveDir = normalize(input.movement);
|
|
116
|
+
player.velocity.x = moveDir.x * MOVE_SPEED;
|
|
117
|
+
player.velocity.z = moveDir.y * MOVE_SPEED;
|
|
118
|
+
player.rotation = input.rotation;
|
|
119
|
+
|
|
120
|
+
// Process actions
|
|
121
|
+
for (const action of input.actions) {
|
|
122
|
+
if (action === 'shoot') {
|
|
123
|
+
this.spawnProjectile(player);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private broadcastState(): void {
|
|
129
|
+
const snapshot = this.createSnapshot();
|
|
130
|
+
this.io.emit('state', snapshot);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private createSnapshot(): GameSnapshot {
|
|
134
|
+
return {
|
|
135
|
+
tick: this.state.tick,
|
|
136
|
+
timestamp: Date.now(),
|
|
137
|
+
players: Array.from(this.state.players.values()),
|
|
138
|
+
projectiles: this.state.projectiles
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Client Prediction
|
|
145
|
+
```typescript
|
|
146
|
+
// client.ts
|
|
147
|
+
class NetworkClient {
|
|
148
|
+
private socket: Socket;
|
|
149
|
+
private localPlayer: LocalPlayer;
|
|
150
|
+
private pendingInputs: PlayerInput[] = [];
|
|
151
|
+
private serverTick = 0;
|
|
152
|
+
private interpolationBuffer: GameSnapshot[] = [];
|
|
153
|
+
|
|
154
|
+
constructor(serverUrl: string) {
|
|
155
|
+
this.socket = io(serverUrl);
|
|
156
|
+
this.setupHandlers();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private setupHandlers(): void {
|
|
160
|
+
this.socket.on('state', (snapshot: GameSnapshot) => {
|
|
161
|
+
this.handleServerState(snapshot);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
public sendInput(input: PlayerInput): void {
|
|
166
|
+
// Apply immediately (prediction)
|
|
167
|
+
this.localPlayer.applyInput(input);
|
|
168
|
+
|
|
169
|
+
// Save for reconciliation
|
|
170
|
+
this.pendingInputs.push(input);
|
|
171
|
+
|
|
172
|
+
// Send to server
|
|
173
|
+
this.socket.emit('input', input);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private handleServerState(snapshot: GameSnapshot): void {
|
|
177
|
+
this.serverTick = snapshot.tick;
|
|
178
|
+
|
|
179
|
+
// Find our player in the snapshot
|
|
180
|
+
const serverPlayer = snapshot.players.find(
|
|
181
|
+
p => p.id === this.localPlayer.id
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
if (serverPlayer) {
|
|
185
|
+
this.reconcile(serverPlayer);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Add to interpolation buffer for other players
|
|
189
|
+
this.interpolationBuffer.push(snapshot);
|
|
190
|
+
if (this.interpolationBuffer.length > 3) {
|
|
191
|
+
this.interpolationBuffer.shift();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private reconcile(serverState: PlayerState): void {
|
|
196
|
+
// Remove acknowledged inputs
|
|
197
|
+
this.pendingInputs = this.pendingInputs.filter(
|
|
198
|
+
input => input.tick > serverState.lastProcessedInput
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Check if prediction was correct
|
|
202
|
+
const positionError = distance(
|
|
203
|
+
this.localPlayer.position,
|
|
204
|
+
serverState.position
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
if (positionError > RECONCILIATION_THRESHOLD) {
|
|
208
|
+
// Snap to server position
|
|
209
|
+
this.localPlayer.position = { ...serverState.position };
|
|
210
|
+
|
|
211
|
+
// Re-apply pending inputs
|
|
212
|
+
for (const input of this.pendingInputs) {
|
|
213
|
+
this.localPlayer.applyInput(input);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
public update(deltaTime: number): void {
|
|
219
|
+
// Interpolate other players
|
|
220
|
+
this.interpolateEntities(deltaTime);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private interpolateEntities(deltaTime: number): void {
|
|
224
|
+
if (this.interpolationBuffer.length < 2) return;
|
|
225
|
+
|
|
226
|
+
const renderTime = Date.now() - INTERPOLATION_DELAY;
|
|
227
|
+
const [older, newer] = this.interpolationBuffer;
|
|
228
|
+
|
|
229
|
+
const t = (renderTime - older.timestamp) /
|
|
230
|
+
(newer.timestamp - older.timestamp);
|
|
231
|
+
|
|
232
|
+
for (const newState of newer.players) {
|
|
233
|
+
if (newState.id === this.localPlayer.id) continue;
|
|
234
|
+
|
|
235
|
+
const oldState = older.players.find(p => p.id === newState.id);
|
|
236
|
+
if (!oldState) continue;
|
|
237
|
+
|
|
238
|
+
const entity = this.getEntity(newState.id);
|
|
239
|
+
if (entity) {
|
|
240
|
+
entity.position = lerp(oldState.position, newState.position, t);
|
|
241
|
+
entity.rotation = lerpAngle(oldState.rotation, newState.rotation, t);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Lag Compensation
|
|
249
|
+
|
|
250
|
+
### Server-Side Rewind
|
|
251
|
+
```typescript
|
|
252
|
+
class LagCompensation {
|
|
253
|
+
private historyBuffer: GameSnapshot[] = [];
|
|
254
|
+
private maxHistoryMs = 1000;
|
|
255
|
+
|
|
256
|
+
saveState(snapshot: GameSnapshot): void {
|
|
257
|
+
this.historyBuffer.push(snapshot);
|
|
258
|
+
|
|
259
|
+
// Remove old states
|
|
260
|
+
const cutoff = Date.now() - this.maxHistoryMs;
|
|
261
|
+
this.historyBuffer = this.historyBuffer.filter(
|
|
262
|
+
s => s.timestamp > cutoff
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
rewindAndCheck(
|
|
267
|
+
shooterId: string,
|
|
268
|
+
targetId: string,
|
|
269
|
+
clientTime: number,
|
|
270
|
+
shotOrigin: Vector3,
|
|
271
|
+
shotDirection: Vector3
|
|
272
|
+
): HitResult {
|
|
273
|
+
// Find the state closest to client's time
|
|
274
|
+
const historicalState = this.findStateAtTime(clientTime);
|
|
275
|
+
if (!historicalState) return { hit: false };
|
|
276
|
+
|
|
277
|
+
// Get historical positions
|
|
278
|
+
const shooter = historicalState.players.find(p => p.id === shooterId);
|
|
279
|
+
const target = historicalState.players.find(p => p.id === targetId);
|
|
280
|
+
|
|
281
|
+
if (!shooter || !target) return { hit: false };
|
|
282
|
+
|
|
283
|
+
// Perform hit detection with historical positions
|
|
284
|
+
const hitbox = this.getHitbox(target);
|
|
285
|
+
const hit = this.raycast(shotOrigin, shotDirection, hitbox);
|
|
286
|
+
|
|
287
|
+
return hit;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private findStateAtTime(targetTime: number): GameSnapshot | null {
|
|
291
|
+
for (let i = this.historyBuffer.length - 1; i >= 0; i--) {
|
|
292
|
+
if (this.historyBuffer[i].timestamp <= targetTime) {
|
|
293
|
+
return this.historyBuffer[i];
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return this.historyBuffer[0] || null;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Matchmaking
|
|
302
|
+
|
|
303
|
+
### Simple Matchmaking Service
|
|
304
|
+
```typescript
|
|
305
|
+
interface MatchRequest {
|
|
306
|
+
playerId: string;
|
|
307
|
+
rating: number;
|
|
308
|
+
queuedAt: number;
|
|
309
|
+
region: string;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
interface Match {
|
|
313
|
+
id: string;
|
|
314
|
+
players: string[];
|
|
315
|
+
serverAddress: string;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
class MatchmakingService {
|
|
319
|
+
private queue: MatchRequest[] = [];
|
|
320
|
+
private ratingRange = 100;
|
|
321
|
+
private maxWaitTime = 60000;
|
|
322
|
+
|
|
323
|
+
async queuePlayer(request: MatchRequest): Promise<void> {
|
|
324
|
+
this.queue.push(request);
|
|
325
|
+
await this.tryMatch();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async removePlayer(playerId: string): Promise<void> {
|
|
329
|
+
this.queue = this.queue.filter(r => r.playerId !== playerId);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
private async tryMatch(): Promise<void> {
|
|
333
|
+
const now = Date.now();
|
|
334
|
+
|
|
335
|
+
// Sort by queue time
|
|
336
|
+
this.queue.sort((a, b) => a.queuedAt - b.queuedAt);
|
|
337
|
+
|
|
338
|
+
for (const request of this.queue) {
|
|
339
|
+
const waitTime = now - request.queuedAt;
|
|
340
|
+
const expandedRange = this.ratingRange + (waitTime / 1000) * 10;
|
|
341
|
+
|
|
342
|
+
const candidates = this.queue.filter(
|
|
343
|
+
r => r.playerId !== request.playerId &&
|
|
344
|
+
r.region === request.region &&
|
|
345
|
+
Math.abs(r.rating - request.rating) <= expandedRange
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
if (candidates.length >= 1) { // For 1v1
|
|
349
|
+
const match = await this.createMatch([request, candidates[0]]);
|
|
350
|
+
this.removeFromQueue([request.playerId, candidates[0].playerId]);
|
|
351
|
+
await this.notifyPlayers(match);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private async createMatch(requests: MatchRequest[]): Promise<Match> {
|
|
358
|
+
const server = await this.allocateServer(requests[0].region);
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
id: generateId(),
|
|
362
|
+
players: requests.map(r => r.playerId),
|
|
363
|
+
serverAddress: server.address
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private removeFromQueue(playerIds: string[]): void {
|
|
368
|
+
this.queue = this.queue.filter(
|
|
369
|
+
r => !playerIds.includes(r.playerId)
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Unity Netcode
|
|
376
|
+
|
|
377
|
+
### Network Object
|
|
378
|
+
```csharp
|
|
379
|
+
using Unity.Netcode;
|
|
380
|
+
using UnityEngine;
|
|
381
|
+
|
|
382
|
+
public class NetworkPlayer : NetworkBehaviour
|
|
383
|
+
{
|
|
384
|
+
private NetworkVariable<Vector3> _position = new(
|
|
385
|
+
writePerm: NetworkVariableWritePermission.Server
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
private NetworkVariable<float> _health = new(
|
|
389
|
+
100f,
|
|
390
|
+
NetworkVariableReadPermission.Everyone,
|
|
391
|
+
NetworkVariableWritePermission.Server
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
public override void OnNetworkSpawn()
|
|
395
|
+
{
|
|
396
|
+
if (IsOwner)
|
|
397
|
+
{
|
|
398
|
+
// Setup for local player
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
_health.OnValueChanged += OnHealthChanged;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
private void Update()
|
|
405
|
+
{
|
|
406
|
+
if (IsOwner)
|
|
407
|
+
{
|
|
408
|
+
HandleInput();
|
|
409
|
+
}
|
|
410
|
+
else
|
|
411
|
+
{
|
|
412
|
+
// Interpolate position for remote players
|
|
413
|
+
transform.position = Vector3.Lerp(
|
|
414
|
+
transform.position,
|
|
415
|
+
_position.Value,
|
|
416
|
+
Time.deltaTime * 10f
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private void HandleInput()
|
|
422
|
+
{
|
|
423
|
+
var input = new Vector3(
|
|
424
|
+
Input.GetAxis("Horizontal"),
|
|
425
|
+
0,
|
|
426
|
+
Input.GetAxis("Vertical")
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
MoveServerRpc(input);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
[ServerRpc]
|
|
433
|
+
private void MoveServerRpc(Vector3 input)
|
|
434
|
+
{
|
|
435
|
+
_position.Value += input * 5f * Time.deltaTime;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
[ServerRpc]
|
|
439
|
+
public void TakeDamageServerRpc(float damage)
|
|
440
|
+
{
|
|
441
|
+
_health.Value -= damage;
|
|
442
|
+
|
|
443
|
+
if (_health.Value <= 0)
|
|
444
|
+
{
|
|
445
|
+
DieClientRpc();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
[ClientRpc]
|
|
450
|
+
private void DieClientRpc()
|
|
451
|
+
{
|
|
452
|
+
// Play death animation on all clients
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
private void OnHealthChanged(float oldValue, float newValue)
|
|
456
|
+
{
|
|
457
|
+
// Update UI
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
## Best Practices
|
|
463
|
+
|
|
464
|
+
1. **Server Authority**: Never trust client data
|
|
465
|
+
2. **Input Prediction**: Smooth local experience
|
|
466
|
+
3. **Interpolation**: Smooth remote movement
|
|
467
|
+
4. **Delta Compression**: Minimize bandwidth
|
|
468
|
+
5. **Region Selection**: Match by latency
|
|
469
|
+
|
|
470
|
+
## Anti-Patterns
|
|
471
|
+
|
|
472
|
+
- Trusting client physics
|
|
473
|
+
- Sending full state every frame
|
|
474
|
+
- No lag compensation
|
|
475
|
+
- Ignoring packet loss
|
|
476
|
+
- Single global server
|
|
477
|
+
|
|
478
|
+
## When to Use
|
|
479
|
+
|
|
480
|
+
- Real-time multiplayer games
|
|
481
|
+
- Competitive games
|
|
482
|
+
- Co-op experiences
|
|
483
|
+
- Persistent online worlds
|
|
484
|
+
|
|
485
|
+
## When NOT to Use
|
|
486
|
+
|
|
487
|
+
- Turn-based games (simpler needed)
|
|
488
|
+
- Single-player only
|
|
489
|
+
- Local multiplayer only
|
|
490
|
+
- Asynchronous multiplayer
|