@esengine/network 1.0.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/LICENSE +21 -0
- package/dist/NetworkPlugin.d.ts +100 -0
- package/dist/NetworkPlugin.d.ts.map +1 -0
- package/dist/components/NetworkIdentity.d.ts +51 -0
- package/dist/components/NetworkIdentity.d.ts.map +1 -0
- package/dist/components/NetworkTransform.d.ts +71 -0
- package/dist/components/NetworkTransform.d.ts.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1348 -0
- package/dist/index.js.map +1 -0
- package/dist/nodes/NetworkNodes.d.ts +77 -0
- package/dist/nodes/NetworkNodes.d.ts.map +1 -0
- package/dist/nodes/index.d.ts +9 -0
- package/dist/nodes/index.d.ts.map +1 -0
- package/dist/services/NetworkService.d.ts +62 -0
- package/dist/services/NetworkService.d.ts.map +1 -0
- package/dist/sync/ClientPrediction.d.ts +158 -0
- package/dist/sync/ClientPrediction.d.ts.map +1 -0
- package/dist/sync/IInterpolator.d.ts +62 -0
- package/dist/sync/IInterpolator.d.ts.map +1 -0
- package/dist/sync/IStateSnapshot.d.ts +116 -0
- package/dist/sync/IStateSnapshot.d.ts.map +1 -0
- package/dist/sync/SnapshotBuffer.d.ts +58 -0
- package/dist/sync/SnapshotBuffer.d.ts.map +1 -0
- package/dist/sync/TransformInterpolator.d.ts +47 -0
- package/dist/sync/TransformInterpolator.d.ts.map +1 -0
- package/dist/sync/index.d.ts +15 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/systems/NetworkInputSystem.d.ts +32 -0
- package/dist/systems/NetworkInputSystem.d.ts.map +1 -0
- package/dist/systems/NetworkSpawnSystem.d.ts +37 -0
- package/dist/systems/NetworkSpawnSystem.d.ts.map +1 -0
- package/dist/systems/NetworkSyncSystem.d.ts +39 -0
- package/dist/systems/NetworkSyncSystem.d.ts.map +1 -0
- package/dist/tokens.d.ts +29 -0
- package/dist/tokens.d.ts.map +1 -0
- package/package.json +51 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1348 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
4
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
5
|
+
|
|
6
|
+
// src/index.ts
|
|
7
|
+
import { serviceProto as serviceProto2 } from "@esengine/network-protocols";
|
|
8
|
+
|
|
9
|
+
// src/tokens.ts
|
|
10
|
+
import { createServiceToken } from "@esengine/ecs-framework";
|
|
11
|
+
var NetworkServiceToken = createServiceToken("networkService");
|
|
12
|
+
var NetworkSyncSystemToken = createServiceToken("networkSyncSystem");
|
|
13
|
+
var NetworkSpawnSystemToken = createServiceToken("networkSpawnSystem");
|
|
14
|
+
var NetworkInputSystemToken = createServiceToken("networkInputSystem");
|
|
15
|
+
|
|
16
|
+
// src/NetworkPlugin.ts
|
|
17
|
+
import { Core } from "@esengine/ecs-framework";
|
|
18
|
+
|
|
19
|
+
// src/services/NetworkService.ts
|
|
20
|
+
import { WsClient } from "tsrpc-browser";
|
|
21
|
+
import { serviceProto } from "@esengine/network-protocols";
|
|
22
|
+
var ENetworkState = /* @__PURE__ */ (function(ENetworkState2) {
|
|
23
|
+
ENetworkState2[ENetworkState2["Disconnected"] = 0] = "Disconnected";
|
|
24
|
+
ENetworkState2[ENetworkState2["Connecting"] = 1] = "Connecting";
|
|
25
|
+
ENetworkState2[ENetworkState2["Connected"] = 2] = "Connected";
|
|
26
|
+
return ENetworkState2;
|
|
27
|
+
})({});
|
|
28
|
+
function createClient(serverUrl) {
|
|
29
|
+
return new WsClient(serviceProto, {
|
|
30
|
+
server: serverUrl,
|
|
31
|
+
json: true,
|
|
32
|
+
logLevel: "warn"
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
__name(createClient, "createClient");
|
|
36
|
+
var _NetworkService = class _NetworkService {
|
|
37
|
+
constructor() {
|
|
38
|
+
__publicField(this, "_client", null);
|
|
39
|
+
__publicField(this, "_state", 0);
|
|
40
|
+
__publicField(this, "_clientId", 0);
|
|
41
|
+
__publicField(this, "_roomId", "");
|
|
42
|
+
__publicField(this, "_callbacks", {});
|
|
43
|
+
}
|
|
44
|
+
get state() {
|
|
45
|
+
return this._state;
|
|
46
|
+
}
|
|
47
|
+
get clientId() {
|
|
48
|
+
return this._clientId;
|
|
49
|
+
}
|
|
50
|
+
get roomId() {
|
|
51
|
+
return this._roomId;
|
|
52
|
+
}
|
|
53
|
+
get isConnected() {
|
|
54
|
+
return this._state === 2;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 设置回调
|
|
58
|
+
* Set callbacks
|
|
59
|
+
*/
|
|
60
|
+
setCallbacks(callbacks) {
|
|
61
|
+
this._callbacks = {
|
|
62
|
+
...this._callbacks,
|
|
63
|
+
...callbacks
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 连接到服务器
|
|
68
|
+
* Connect to server
|
|
69
|
+
*/
|
|
70
|
+
async connect(serverUrl, playerName, roomId) {
|
|
71
|
+
if (this._state !== 0) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
this._state = 1;
|
|
75
|
+
this._client = createClient(serverUrl);
|
|
76
|
+
this._setupListeners();
|
|
77
|
+
const connectResult = await this._client.connect();
|
|
78
|
+
if (!connectResult.isSucc) {
|
|
79
|
+
this._state = 0;
|
|
80
|
+
this._callbacks.onError?.(new Error(connectResult.errMsg));
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const joinResult = await this._client.callApi("Join", {
|
|
84
|
+
playerName,
|
|
85
|
+
roomId
|
|
86
|
+
});
|
|
87
|
+
if (!joinResult.isSucc) {
|
|
88
|
+
await this._client.disconnect();
|
|
89
|
+
this._state = 0;
|
|
90
|
+
this._callbacks.onError?.(new Error(joinResult.err.message));
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
this._clientId = joinResult.res.clientId;
|
|
94
|
+
this._roomId = joinResult.res.roomId;
|
|
95
|
+
this._state = 2;
|
|
96
|
+
this._callbacks.onConnected?.(this._clientId, this._roomId);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 断开连接
|
|
101
|
+
* Disconnect
|
|
102
|
+
*/
|
|
103
|
+
async disconnect() {
|
|
104
|
+
if (this._client) {
|
|
105
|
+
await this._client.disconnect();
|
|
106
|
+
}
|
|
107
|
+
this._state = 0;
|
|
108
|
+
this._clientId = 0;
|
|
109
|
+
this._roomId = "";
|
|
110
|
+
this._client = null;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 发送输入
|
|
114
|
+
* Send input
|
|
115
|
+
*/
|
|
116
|
+
sendInput(input) {
|
|
117
|
+
if (!this.isConnected || !this._client) return;
|
|
118
|
+
this._client.sendMsg("Input", {
|
|
119
|
+
input
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
_setupListeners() {
|
|
123
|
+
if (!this._client) return;
|
|
124
|
+
this._client.listenMsg("Sync", (msg) => {
|
|
125
|
+
this._callbacks.onSync?.(msg);
|
|
126
|
+
});
|
|
127
|
+
this._client.listenMsg("Spawn", (msg) => {
|
|
128
|
+
this._callbacks.onSpawn?.(msg);
|
|
129
|
+
});
|
|
130
|
+
this._client.listenMsg("Despawn", (msg) => {
|
|
131
|
+
this._callbacks.onDespawn?.(msg);
|
|
132
|
+
});
|
|
133
|
+
this._client.flows.postDisconnectFlow.push((v) => {
|
|
134
|
+
this._state = 0;
|
|
135
|
+
this._callbacks.onDisconnected?.();
|
|
136
|
+
return v;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
__name(_NetworkService, "NetworkService");
|
|
141
|
+
var NetworkService = _NetworkService;
|
|
142
|
+
|
|
143
|
+
// src/systems/NetworkSyncSystem.ts
|
|
144
|
+
import { EntitySystem, Matcher, Time } from "@esengine/ecs-framework";
|
|
145
|
+
|
|
146
|
+
// src/components/NetworkIdentity.ts
|
|
147
|
+
import { Component, ECSComponent, Serialize, Serializable, Property } from "@esengine/ecs-framework";
|
|
148
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
149
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
150
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
151
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
152
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
153
|
+
}
|
|
154
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
155
|
+
function _ts_metadata(k, v) {
|
|
156
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
157
|
+
}
|
|
158
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
159
|
+
var _NetworkIdentity = class _NetworkIdentity extends Component {
|
|
160
|
+
constructor() {
|
|
161
|
+
super(...arguments);
|
|
162
|
+
/**
|
|
163
|
+
* 网络实体 ID
|
|
164
|
+
* Network entity ID
|
|
165
|
+
*/
|
|
166
|
+
__publicField(this, "netId", 0);
|
|
167
|
+
/**
|
|
168
|
+
* 所有者客户端 ID
|
|
169
|
+
* Owner client ID
|
|
170
|
+
*/
|
|
171
|
+
__publicField(this, "ownerId", 0);
|
|
172
|
+
/**
|
|
173
|
+
* 是否为本地玩家拥有
|
|
174
|
+
* Is owned by local player
|
|
175
|
+
*/
|
|
176
|
+
__publicField(this, "bIsLocalPlayer", false);
|
|
177
|
+
/**
|
|
178
|
+
* 是否有权限控制
|
|
179
|
+
* Has authority
|
|
180
|
+
*/
|
|
181
|
+
__publicField(this, "bHasAuthority", false);
|
|
182
|
+
/**
|
|
183
|
+
* 预制体类型
|
|
184
|
+
* Prefab type
|
|
185
|
+
*/
|
|
186
|
+
__publicField(this, "prefabType", "");
|
|
187
|
+
/**
|
|
188
|
+
* 同步间隔 (ms)
|
|
189
|
+
* Sync interval in milliseconds
|
|
190
|
+
*/
|
|
191
|
+
__publicField(this, "syncInterval", 100);
|
|
192
|
+
/**
|
|
193
|
+
* 上次同步时间
|
|
194
|
+
* Last sync time
|
|
195
|
+
*/
|
|
196
|
+
__publicField(this, "lastSyncTime", 0);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* 检查是否需要同步
|
|
200
|
+
* Check if sync is needed
|
|
201
|
+
*/
|
|
202
|
+
needsSync(now) {
|
|
203
|
+
return now - this.lastSyncTime >= this.syncInterval;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
__name(_NetworkIdentity, "NetworkIdentity");
|
|
207
|
+
var NetworkIdentity = _NetworkIdentity;
|
|
208
|
+
_ts_decorate([
|
|
209
|
+
Serialize(),
|
|
210
|
+
Property({
|
|
211
|
+
type: "integer",
|
|
212
|
+
label: "Net ID",
|
|
213
|
+
readOnly: true
|
|
214
|
+
}),
|
|
215
|
+
_ts_metadata("design:type", Number)
|
|
216
|
+
], NetworkIdentity.prototype, "netId", void 0);
|
|
217
|
+
_ts_decorate([
|
|
218
|
+
Serialize(),
|
|
219
|
+
Property({
|
|
220
|
+
type: "integer",
|
|
221
|
+
label: "Owner ID",
|
|
222
|
+
readOnly: true
|
|
223
|
+
}),
|
|
224
|
+
_ts_metadata("design:type", Number)
|
|
225
|
+
], NetworkIdentity.prototype, "ownerId", void 0);
|
|
226
|
+
_ts_decorate([
|
|
227
|
+
Serialize(),
|
|
228
|
+
Property({
|
|
229
|
+
type: "string",
|
|
230
|
+
label: "Prefab Type"
|
|
231
|
+
}),
|
|
232
|
+
_ts_metadata("design:type", String)
|
|
233
|
+
], NetworkIdentity.prototype, "prefabType", void 0);
|
|
234
|
+
_ts_decorate([
|
|
235
|
+
Serialize(),
|
|
236
|
+
Property({
|
|
237
|
+
type: "number",
|
|
238
|
+
label: "Sync Interval",
|
|
239
|
+
min: 16
|
|
240
|
+
}),
|
|
241
|
+
_ts_metadata("design:type", Number)
|
|
242
|
+
], NetworkIdentity.prototype, "syncInterval", void 0);
|
|
243
|
+
NetworkIdentity = _ts_decorate([
|
|
244
|
+
ECSComponent("NetworkIdentity"),
|
|
245
|
+
Serializable({
|
|
246
|
+
version: 1,
|
|
247
|
+
typeId: "NetworkIdentity"
|
|
248
|
+
})
|
|
249
|
+
], NetworkIdentity);
|
|
250
|
+
|
|
251
|
+
// src/components/NetworkTransform.ts
|
|
252
|
+
import { Component as Component2, ECSComponent as ECSComponent2, Serialize as Serialize2, Serializable as Serializable2, Property as Property2 } from "@esengine/ecs-framework";
|
|
253
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
254
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
255
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
256
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
257
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
258
|
+
}
|
|
259
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
260
|
+
function _ts_metadata2(k, v) {
|
|
261
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
262
|
+
}
|
|
263
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
264
|
+
var _NetworkTransform = class _NetworkTransform extends Component2 {
|
|
265
|
+
constructor() {
|
|
266
|
+
super(...arguments);
|
|
267
|
+
/**
|
|
268
|
+
* 目标位置 X
|
|
269
|
+
* Target position X
|
|
270
|
+
*/
|
|
271
|
+
__publicField(this, "targetX", 0);
|
|
272
|
+
/**
|
|
273
|
+
* 目标位置 Y
|
|
274
|
+
* Target position Y
|
|
275
|
+
*/
|
|
276
|
+
__publicField(this, "targetY", 0);
|
|
277
|
+
/**
|
|
278
|
+
* 目标旋转
|
|
279
|
+
* Target rotation
|
|
280
|
+
*/
|
|
281
|
+
__publicField(this, "targetRotation", 0);
|
|
282
|
+
/**
|
|
283
|
+
* 当前位置 X
|
|
284
|
+
* Current position X
|
|
285
|
+
*/
|
|
286
|
+
__publicField(this, "currentX", 0);
|
|
287
|
+
/**
|
|
288
|
+
* 当前位置 Y
|
|
289
|
+
* Current position Y
|
|
290
|
+
*/
|
|
291
|
+
__publicField(this, "currentY", 0);
|
|
292
|
+
/**
|
|
293
|
+
* 当前旋转
|
|
294
|
+
* Current rotation
|
|
295
|
+
*/
|
|
296
|
+
__publicField(this, "currentRotation", 0);
|
|
297
|
+
/**
|
|
298
|
+
* 插值速度
|
|
299
|
+
* Interpolation speed
|
|
300
|
+
*/
|
|
301
|
+
__publicField(this, "lerpSpeed", 10);
|
|
302
|
+
/**
|
|
303
|
+
* 是否启用插值
|
|
304
|
+
* Enable interpolation
|
|
305
|
+
*/
|
|
306
|
+
__publicField(this, "bInterpolate", true);
|
|
307
|
+
/**
|
|
308
|
+
* 同步间隔 (ms)
|
|
309
|
+
* Sync interval in milliseconds
|
|
310
|
+
*/
|
|
311
|
+
__publicField(this, "syncInterval", 50);
|
|
312
|
+
/**
|
|
313
|
+
* 上次同步时间
|
|
314
|
+
* Last sync time
|
|
315
|
+
*/
|
|
316
|
+
__publicField(this, "lastSyncTime", 0);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* 设置目标位置
|
|
320
|
+
* Set target position
|
|
321
|
+
*/
|
|
322
|
+
setTarget(x, y, rotation) {
|
|
323
|
+
this.targetX = x;
|
|
324
|
+
this.targetY = y;
|
|
325
|
+
if (rotation !== void 0) {
|
|
326
|
+
this.targetRotation = rotation;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* 立即跳转到目标位置
|
|
331
|
+
* Snap to target position immediately
|
|
332
|
+
*/
|
|
333
|
+
snap() {
|
|
334
|
+
this.currentX = this.targetX;
|
|
335
|
+
this.currentY = this.targetY;
|
|
336
|
+
this.currentRotation = this.targetRotation;
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
__name(_NetworkTransform, "NetworkTransform");
|
|
340
|
+
var NetworkTransform = _NetworkTransform;
|
|
341
|
+
_ts_decorate2([
|
|
342
|
+
Serialize2(),
|
|
343
|
+
Property2({
|
|
344
|
+
type: "number",
|
|
345
|
+
label: "Lerp Speed",
|
|
346
|
+
min: 0.1,
|
|
347
|
+
max: 50
|
|
348
|
+
}),
|
|
349
|
+
_ts_metadata2("design:type", Number)
|
|
350
|
+
], NetworkTransform.prototype, "lerpSpeed", void 0);
|
|
351
|
+
_ts_decorate2([
|
|
352
|
+
Serialize2(),
|
|
353
|
+
Property2({
|
|
354
|
+
type: "boolean",
|
|
355
|
+
label: "Interpolate"
|
|
356
|
+
}),
|
|
357
|
+
_ts_metadata2("design:type", Boolean)
|
|
358
|
+
], NetworkTransform.prototype, "bInterpolate", void 0);
|
|
359
|
+
_ts_decorate2([
|
|
360
|
+
Serialize2(),
|
|
361
|
+
Property2({
|
|
362
|
+
type: "number",
|
|
363
|
+
label: "Sync Interval",
|
|
364
|
+
min: 16
|
|
365
|
+
}),
|
|
366
|
+
_ts_metadata2("design:type", Number)
|
|
367
|
+
], NetworkTransform.prototype, "syncInterval", void 0);
|
|
368
|
+
NetworkTransform = _ts_decorate2([
|
|
369
|
+
ECSComponent2("NetworkTransform", {
|
|
370
|
+
requires: [
|
|
371
|
+
"NetworkIdentity"
|
|
372
|
+
]
|
|
373
|
+
}),
|
|
374
|
+
Serializable2({
|
|
375
|
+
version: 1,
|
|
376
|
+
typeId: "NetworkTransform"
|
|
377
|
+
})
|
|
378
|
+
], NetworkTransform);
|
|
379
|
+
|
|
380
|
+
// src/systems/NetworkSyncSystem.ts
|
|
381
|
+
var _NetworkSyncSystem = class _NetworkSyncSystem extends EntitySystem {
|
|
382
|
+
constructor(networkService) {
|
|
383
|
+
super(Matcher.all(NetworkIdentity, NetworkTransform));
|
|
384
|
+
__publicField(this, "_networkService");
|
|
385
|
+
__publicField(this, "_netIdToEntity", /* @__PURE__ */ new Map());
|
|
386
|
+
this._networkService = networkService;
|
|
387
|
+
}
|
|
388
|
+
onInitialize() {
|
|
389
|
+
this._networkService.setCallbacks({
|
|
390
|
+
onSync: this._handleSync.bind(this)
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* 处理实体列表
|
|
395
|
+
* Process entities
|
|
396
|
+
*/
|
|
397
|
+
process(entities) {
|
|
398
|
+
const deltaTime = Time.deltaTime;
|
|
399
|
+
for (const entity of entities) {
|
|
400
|
+
const transform = this.requireComponent(entity, NetworkTransform);
|
|
401
|
+
const identity = this.requireComponent(entity, NetworkIdentity);
|
|
402
|
+
if (!identity.bHasAuthority && transform.bInterpolate) {
|
|
403
|
+
this._interpolate(transform, deltaTime);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* 注册网络实体
|
|
409
|
+
* Register network entity
|
|
410
|
+
*/
|
|
411
|
+
registerEntity(netId, entityId) {
|
|
412
|
+
this._netIdToEntity.set(netId, entityId);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* 注销网络实体
|
|
416
|
+
* Unregister network entity
|
|
417
|
+
*/
|
|
418
|
+
unregisterEntity(netId) {
|
|
419
|
+
this._netIdToEntity.delete(netId);
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* 根据网络 ID 获取实体 ID
|
|
423
|
+
* Get entity ID by network ID
|
|
424
|
+
*/
|
|
425
|
+
getEntityId(netId) {
|
|
426
|
+
return this._netIdToEntity.get(netId);
|
|
427
|
+
}
|
|
428
|
+
_handleSync(msg) {
|
|
429
|
+
for (const state of msg.entities) {
|
|
430
|
+
const entityId = this._netIdToEntity.get(state.netId);
|
|
431
|
+
if (entityId === void 0) continue;
|
|
432
|
+
const entity = this.scene?.findEntityById(entityId);
|
|
433
|
+
if (!entity) continue;
|
|
434
|
+
const transform = entity.getComponent(NetworkTransform);
|
|
435
|
+
if (transform && state.pos) {
|
|
436
|
+
transform.setTarget(state.pos.x, state.pos.y, state.rot);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
_interpolate(transform, deltaTime) {
|
|
441
|
+
const t = Math.min(1, transform.lerpSpeed * deltaTime);
|
|
442
|
+
transform.currentX += (transform.targetX - transform.currentX) * t;
|
|
443
|
+
transform.currentY += (transform.targetY - transform.currentY) * t;
|
|
444
|
+
let angleDiff = transform.targetRotation - transform.currentRotation;
|
|
445
|
+
while (angleDiff > Math.PI) angleDiff -= Math.PI * 2;
|
|
446
|
+
while (angleDiff < -Math.PI) angleDiff += Math.PI * 2;
|
|
447
|
+
transform.currentRotation += angleDiff * t;
|
|
448
|
+
}
|
|
449
|
+
onDestroy() {
|
|
450
|
+
this._netIdToEntity.clear();
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
__name(_NetworkSyncSystem, "NetworkSyncSystem");
|
|
454
|
+
var NetworkSyncSystem = _NetworkSyncSystem;
|
|
455
|
+
|
|
456
|
+
// src/systems/NetworkSpawnSystem.ts
|
|
457
|
+
import { EntitySystem as EntitySystem2, Matcher as Matcher2 } from "@esengine/ecs-framework";
|
|
458
|
+
var _NetworkSpawnSystem = class _NetworkSpawnSystem extends EntitySystem2 {
|
|
459
|
+
constructor(networkService, syncSystem) {
|
|
460
|
+
super(Matcher2.nothing());
|
|
461
|
+
__publicField(this, "_networkService");
|
|
462
|
+
__publicField(this, "_syncSystem");
|
|
463
|
+
__publicField(this, "_prefabFactories", /* @__PURE__ */ new Map());
|
|
464
|
+
this._networkService = networkService;
|
|
465
|
+
this._syncSystem = syncSystem;
|
|
466
|
+
}
|
|
467
|
+
onInitialize() {
|
|
468
|
+
this._networkService.setCallbacks({
|
|
469
|
+
onSpawn: this._handleSpawn.bind(this),
|
|
470
|
+
onDespawn: this._handleDespawn.bind(this)
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* 注册预制体工厂
|
|
475
|
+
* Register prefab factory
|
|
476
|
+
*/
|
|
477
|
+
registerPrefab(prefabType, factory) {
|
|
478
|
+
this._prefabFactories.set(prefabType, factory);
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* 注销预制体工厂
|
|
482
|
+
* Unregister prefab factory
|
|
483
|
+
*/
|
|
484
|
+
unregisterPrefab(prefabType) {
|
|
485
|
+
this._prefabFactories.delete(prefabType);
|
|
486
|
+
}
|
|
487
|
+
_handleSpawn(msg) {
|
|
488
|
+
if (!this.scene) return;
|
|
489
|
+
const factory = this._prefabFactories.get(msg.prefab);
|
|
490
|
+
if (!factory) {
|
|
491
|
+
this.logger.warn(`Unknown prefab: ${msg.prefab}`);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const entity = factory(this.scene, msg);
|
|
495
|
+
const identity = entity.addComponent(new NetworkIdentity());
|
|
496
|
+
identity.netId = msg.netId;
|
|
497
|
+
identity.ownerId = msg.ownerId;
|
|
498
|
+
identity.prefabType = msg.prefab;
|
|
499
|
+
identity.bHasAuthority = msg.ownerId === this._networkService.clientId;
|
|
500
|
+
identity.bIsLocalPlayer = identity.bHasAuthority;
|
|
501
|
+
const transform = entity.addComponent(new NetworkTransform());
|
|
502
|
+
transform.setTarget(msg.pos.x, msg.pos.y, msg.rot);
|
|
503
|
+
transform.snap();
|
|
504
|
+
this._syncSystem.registerEntity(msg.netId, entity.id);
|
|
505
|
+
}
|
|
506
|
+
_handleDespawn(msg) {
|
|
507
|
+
const entityId = this._syncSystem.getEntityId(msg.netId);
|
|
508
|
+
if (entityId === void 0) return;
|
|
509
|
+
const entity = this.scene?.findEntityById(entityId);
|
|
510
|
+
if (entity) {
|
|
511
|
+
entity.destroy();
|
|
512
|
+
}
|
|
513
|
+
this._syncSystem.unregisterEntity(msg.netId);
|
|
514
|
+
}
|
|
515
|
+
onDestroy() {
|
|
516
|
+
this._prefabFactories.clear();
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
__name(_NetworkSpawnSystem, "NetworkSpawnSystem");
|
|
520
|
+
var NetworkSpawnSystem = _NetworkSpawnSystem;
|
|
521
|
+
|
|
522
|
+
// src/systems/NetworkInputSystem.ts
|
|
523
|
+
import { EntitySystem as EntitySystem3, Matcher as Matcher3 } from "@esengine/ecs-framework";
|
|
524
|
+
var _NetworkInputSystem = class _NetworkInputSystem extends EntitySystem3 {
|
|
525
|
+
constructor(networkService) {
|
|
526
|
+
super(Matcher3.nothing());
|
|
527
|
+
__publicField(this, "_networkService");
|
|
528
|
+
__publicField(this, "_frame", 0);
|
|
529
|
+
__publicField(this, "_inputQueue", []);
|
|
530
|
+
this._networkService = networkService;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* 处理输入队列
|
|
534
|
+
* Process input queue
|
|
535
|
+
*/
|
|
536
|
+
process() {
|
|
537
|
+
if (!this._networkService.isConnected) return;
|
|
538
|
+
this._frame++;
|
|
539
|
+
while (this._inputQueue.length > 0) {
|
|
540
|
+
const input = this._inputQueue.shift();
|
|
541
|
+
input.frame = this._frame;
|
|
542
|
+
this._networkService.sendInput(input);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* 添加移动输入
|
|
547
|
+
* Add move input
|
|
548
|
+
*/
|
|
549
|
+
addMoveInput(x, y) {
|
|
550
|
+
this._inputQueue.push({
|
|
551
|
+
frame: 0,
|
|
552
|
+
moveDir: {
|
|
553
|
+
x,
|
|
554
|
+
y
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* 添加动作输入
|
|
560
|
+
* Add action input
|
|
561
|
+
*/
|
|
562
|
+
addActionInput(action) {
|
|
563
|
+
const lastInput = this._inputQueue[this._inputQueue.length - 1];
|
|
564
|
+
if (lastInput) {
|
|
565
|
+
lastInput.actions = lastInput.actions || [];
|
|
566
|
+
lastInput.actions.push(action);
|
|
567
|
+
} else {
|
|
568
|
+
this._inputQueue.push({
|
|
569
|
+
frame: 0,
|
|
570
|
+
actions: [
|
|
571
|
+
action
|
|
572
|
+
]
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
onDestroy() {
|
|
577
|
+
this._inputQueue.length = 0;
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
__name(_NetworkInputSystem, "NetworkInputSystem");
|
|
581
|
+
var NetworkInputSystem = _NetworkInputSystem;
|
|
582
|
+
|
|
583
|
+
// src/NetworkPlugin.ts
|
|
584
|
+
var _NetworkPlugin = class _NetworkPlugin {
|
|
585
|
+
constructor() {
|
|
586
|
+
__publicField(this, "name", "@esengine/network");
|
|
587
|
+
__publicField(this, "version", "1.0.0");
|
|
588
|
+
__publicField(this, "_networkService");
|
|
589
|
+
__publicField(this, "_syncSystem");
|
|
590
|
+
__publicField(this, "_spawnSystem");
|
|
591
|
+
__publicField(this, "_inputSystem");
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* 网络服务
|
|
595
|
+
* Network service
|
|
596
|
+
*/
|
|
597
|
+
get networkService() {
|
|
598
|
+
return this._networkService;
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* 同步系统
|
|
602
|
+
* Sync system
|
|
603
|
+
*/
|
|
604
|
+
get syncSystem() {
|
|
605
|
+
return this._syncSystem;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* 生成系统
|
|
609
|
+
* Spawn system
|
|
610
|
+
*/
|
|
611
|
+
get spawnSystem() {
|
|
612
|
+
return this._spawnSystem;
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* 输入系统
|
|
616
|
+
* Input system
|
|
617
|
+
*/
|
|
618
|
+
get inputSystem() {
|
|
619
|
+
return this._inputSystem;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* 是否已连接
|
|
623
|
+
* Is connected
|
|
624
|
+
*/
|
|
625
|
+
get isConnected() {
|
|
626
|
+
return this._networkService?.isConnected ?? false;
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* 安装插件
|
|
630
|
+
* Install plugin
|
|
631
|
+
*/
|
|
632
|
+
install(_core, _services) {
|
|
633
|
+
this._networkService = new NetworkService();
|
|
634
|
+
const scene = Core.scene;
|
|
635
|
+
if (scene) {
|
|
636
|
+
this._setupSystems(scene);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* 卸载插件
|
|
641
|
+
* Uninstall plugin
|
|
642
|
+
*/
|
|
643
|
+
uninstall() {
|
|
644
|
+
this._networkService?.disconnect();
|
|
645
|
+
}
|
|
646
|
+
_setupSystems(scene) {
|
|
647
|
+
this._syncSystem = new NetworkSyncSystem(this._networkService);
|
|
648
|
+
this._spawnSystem = new NetworkSpawnSystem(this._networkService, this._syncSystem);
|
|
649
|
+
this._inputSystem = new NetworkInputSystem(this._networkService);
|
|
650
|
+
scene.addSystem(this._syncSystem);
|
|
651
|
+
scene.addSystem(this._spawnSystem);
|
|
652
|
+
scene.addSystem(this._inputSystem);
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* 连接到服务器
|
|
656
|
+
* Connect to server
|
|
657
|
+
*/
|
|
658
|
+
async connect(serverUrl, playerName, roomId) {
|
|
659
|
+
return this._networkService.connect(serverUrl, playerName, roomId);
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* 断开连接
|
|
663
|
+
* Disconnect
|
|
664
|
+
*/
|
|
665
|
+
async disconnect() {
|
|
666
|
+
await this._networkService.disconnect();
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* 注册预制体工厂
|
|
670
|
+
* Register prefab factory
|
|
671
|
+
*/
|
|
672
|
+
registerPrefab(prefabType, factory) {
|
|
673
|
+
this._spawnSystem?.registerPrefab(prefabType, factory);
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* 发送移动输入
|
|
677
|
+
* Send move input
|
|
678
|
+
*/
|
|
679
|
+
sendMoveInput(x, y) {
|
|
680
|
+
this._inputSystem?.addMoveInput(x, y);
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* 发送动作输入
|
|
684
|
+
* Send action input
|
|
685
|
+
*/
|
|
686
|
+
sendActionInput(action) {
|
|
687
|
+
this._inputSystem?.addActionInput(action);
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
__name(_NetworkPlugin, "NetworkPlugin");
|
|
691
|
+
var NetworkPlugin = _NetworkPlugin;
|
|
692
|
+
|
|
693
|
+
// src/sync/SnapshotBuffer.ts
|
|
694
|
+
var _SnapshotBuffer = class _SnapshotBuffer {
|
|
695
|
+
constructor(config) {
|
|
696
|
+
__publicField(this, "_buffer", []);
|
|
697
|
+
__publicField(this, "_maxSize");
|
|
698
|
+
__publicField(this, "_interpolationDelay");
|
|
699
|
+
this._maxSize = config.maxSize;
|
|
700
|
+
this._interpolationDelay = config.interpolationDelay;
|
|
701
|
+
}
|
|
702
|
+
get size() {
|
|
703
|
+
return this._buffer.length;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* @zh 获取插值延迟
|
|
707
|
+
* @en Get interpolation delay
|
|
708
|
+
*/
|
|
709
|
+
get interpolationDelay() {
|
|
710
|
+
return this._interpolationDelay;
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* @zh 添加快照
|
|
714
|
+
* @en Add snapshot
|
|
715
|
+
*/
|
|
716
|
+
push(snapshot) {
|
|
717
|
+
let insertIndex = this._buffer.length;
|
|
718
|
+
for (let i = this._buffer.length - 1; i >= 0; i--) {
|
|
719
|
+
if (this._buffer[i].timestamp <= snapshot.timestamp) {
|
|
720
|
+
insertIndex = i + 1;
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
if (i === 0) {
|
|
724
|
+
insertIndex = 0;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
this._buffer.splice(insertIndex, 0, snapshot);
|
|
728
|
+
while (this._buffer.length > this._maxSize) {
|
|
729
|
+
this._buffer.shift();
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* @zh 获取用于插值的两个快照
|
|
734
|
+
* @en Get two snapshots for interpolation
|
|
735
|
+
*/
|
|
736
|
+
getInterpolationSnapshots(renderTime) {
|
|
737
|
+
if (this._buffer.length < 2) {
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
const targetTime = renderTime - this._interpolationDelay;
|
|
741
|
+
for (let i = 0; i < this._buffer.length - 1; i++) {
|
|
742
|
+
const prev = this._buffer[i];
|
|
743
|
+
const next = this._buffer[i + 1];
|
|
744
|
+
if (prev.timestamp <= targetTime && next.timestamp >= targetTime) {
|
|
745
|
+
const duration = next.timestamp - prev.timestamp;
|
|
746
|
+
const t = duration > 0 ? (targetTime - prev.timestamp) / duration : 0;
|
|
747
|
+
return [
|
|
748
|
+
prev,
|
|
749
|
+
next,
|
|
750
|
+
Math.max(0, Math.min(1, t))
|
|
751
|
+
];
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
if (targetTime > this._buffer[this._buffer.length - 1].timestamp) {
|
|
755
|
+
const prev = this._buffer[this._buffer.length - 2];
|
|
756
|
+
const next = this._buffer[this._buffer.length - 1];
|
|
757
|
+
const duration = next.timestamp - prev.timestamp;
|
|
758
|
+
const t = duration > 0 ? (targetTime - prev.timestamp) / duration : 1;
|
|
759
|
+
return [
|
|
760
|
+
prev,
|
|
761
|
+
next,
|
|
762
|
+
Math.min(t, 2)
|
|
763
|
+
];
|
|
764
|
+
}
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* @zh 获取最新快照
|
|
769
|
+
* @en Get latest snapshot
|
|
770
|
+
*/
|
|
771
|
+
getLatest() {
|
|
772
|
+
return this._buffer.length > 0 ? this._buffer[this._buffer.length - 1] : null;
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* @zh 获取特定时间之后的所有快照
|
|
776
|
+
* @en Get all snapshots after a specific time
|
|
777
|
+
*/
|
|
778
|
+
getSnapshotsAfter(timestamp) {
|
|
779
|
+
return this._buffer.filter((s) => s.timestamp > timestamp);
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* @zh 清空缓冲区
|
|
783
|
+
* @en Clear buffer
|
|
784
|
+
*/
|
|
785
|
+
clear() {
|
|
786
|
+
this._buffer.length = 0;
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
__name(_SnapshotBuffer, "SnapshotBuffer");
|
|
790
|
+
var SnapshotBuffer = _SnapshotBuffer;
|
|
791
|
+
function createSnapshotBuffer(maxSize = 30, interpolationDelay = 100) {
|
|
792
|
+
return new SnapshotBuffer({
|
|
793
|
+
maxSize,
|
|
794
|
+
interpolationDelay
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
__name(createSnapshotBuffer, "createSnapshotBuffer");
|
|
798
|
+
|
|
799
|
+
// src/sync/IInterpolator.ts
|
|
800
|
+
function lerp(a, b, t) {
|
|
801
|
+
return a + (b - a) * t;
|
|
802
|
+
}
|
|
803
|
+
__name(lerp, "lerp");
|
|
804
|
+
function lerpAngle(a, b, t) {
|
|
805
|
+
let diff = b - a;
|
|
806
|
+
while (diff > Math.PI) diff -= Math.PI * 2;
|
|
807
|
+
while (diff < -Math.PI) diff += Math.PI * 2;
|
|
808
|
+
return a + diff * t;
|
|
809
|
+
}
|
|
810
|
+
__name(lerpAngle, "lerpAngle");
|
|
811
|
+
function smoothDamp(current, target, velocity, smoothTime, deltaTime, maxSpeed = Infinity) {
|
|
812
|
+
smoothTime = Math.max(1e-4, smoothTime);
|
|
813
|
+
const omega = 2 / smoothTime;
|
|
814
|
+
const x = omega * deltaTime;
|
|
815
|
+
const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);
|
|
816
|
+
let change = current - target;
|
|
817
|
+
const maxChange = maxSpeed * smoothTime;
|
|
818
|
+
change = Math.max(-maxChange, Math.min(maxChange, change));
|
|
819
|
+
const temp = (velocity + omega * change) * deltaTime;
|
|
820
|
+
let newVelocity = (velocity - omega * temp) * exp;
|
|
821
|
+
let newValue = target + (change + temp) * exp;
|
|
822
|
+
if (target - current > 0 === newValue > target) {
|
|
823
|
+
newValue = target;
|
|
824
|
+
newVelocity = (newValue - target) / deltaTime;
|
|
825
|
+
}
|
|
826
|
+
return [
|
|
827
|
+
newValue,
|
|
828
|
+
newVelocity
|
|
829
|
+
];
|
|
830
|
+
}
|
|
831
|
+
__name(smoothDamp, "smoothDamp");
|
|
832
|
+
|
|
833
|
+
// src/sync/TransformInterpolator.ts
|
|
834
|
+
var _TransformInterpolator = class _TransformInterpolator {
|
|
835
|
+
/**
|
|
836
|
+
* @zh 在两个变换状态之间插值
|
|
837
|
+
* @en Interpolate between two transform states
|
|
838
|
+
*/
|
|
839
|
+
interpolate(from, to, t) {
|
|
840
|
+
return {
|
|
841
|
+
x: lerp(from.x, to.x, t),
|
|
842
|
+
y: lerp(from.y, to.y, t),
|
|
843
|
+
rotation: lerpAngle(from.rotation, to.rotation, t)
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* @zh 基于速度外推变换状态
|
|
848
|
+
* @en Extrapolate transform state based on velocity
|
|
849
|
+
*/
|
|
850
|
+
extrapolate(state, deltaTime) {
|
|
851
|
+
return {
|
|
852
|
+
x: state.x + state.velocityX * deltaTime,
|
|
853
|
+
y: state.y + state.velocityY * deltaTime,
|
|
854
|
+
rotation: state.rotation + state.angularVelocity * deltaTime,
|
|
855
|
+
velocityX: state.velocityX,
|
|
856
|
+
velocityY: state.velocityY,
|
|
857
|
+
angularVelocity: state.angularVelocity
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
__name(_TransformInterpolator, "TransformInterpolator");
|
|
862
|
+
var TransformInterpolator = _TransformInterpolator;
|
|
863
|
+
var _HermiteTransformInterpolator = class _HermiteTransformInterpolator {
|
|
864
|
+
/**
|
|
865
|
+
* @zh 使用赫尔米特插值
|
|
866
|
+
* @en Use Hermite interpolation
|
|
867
|
+
*/
|
|
868
|
+
interpolate(from, to, t) {
|
|
869
|
+
const t2 = t * t;
|
|
870
|
+
const t3 = t2 * t;
|
|
871
|
+
const h00 = 2 * t3 - 3 * t2 + 1;
|
|
872
|
+
const h10 = t3 - 2 * t2 + t;
|
|
873
|
+
const h01 = -2 * t3 + 3 * t2;
|
|
874
|
+
const h11 = t3 - t2;
|
|
875
|
+
const dt = 0.1;
|
|
876
|
+
const x = h00 * from.x + h10 * from.velocityX * dt + h01 * to.x + h11 * to.velocityX * dt;
|
|
877
|
+
const y = h00 * from.y + h10 * from.velocityY * dt + h01 * to.y + h11 * to.velocityY * dt;
|
|
878
|
+
const dh00 = 6 * t2 - 6 * t;
|
|
879
|
+
const dh10 = 3 * t2 - 4 * t + 1;
|
|
880
|
+
const dh01 = -6 * t2 + 6 * t;
|
|
881
|
+
const dh11 = 3 * t2 - 2 * t;
|
|
882
|
+
const velocityX = (dh00 * from.x + dh10 * from.velocityX * dt + dh01 * to.x + dh11 * to.velocityX * dt) / dt;
|
|
883
|
+
const velocityY = (dh00 * from.y + dh10 * from.velocityY * dt + dh01 * to.y + dh11 * to.velocityY * dt) / dt;
|
|
884
|
+
return {
|
|
885
|
+
x,
|
|
886
|
+
y,
|
|
887
|
+
rotation: lerpAngle(from.rotation, to.rotation, t),
|
|
888
|
+
velocityX,
|
|
889
|
+
velocityY,
|
|
890
|
+
angularVelocity: lerp(from.angularVelocity, to.angularVelocity, t)
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
__name(_HermiteTransformInterpolator, "HermiteTransformInterpolator");
|
|
895
|
+
var HermiteTransformInterpolator = _HermiteTransformInterpolator;
|
|
896
|
+
function createTransformInterpolator() {
|
|
897
|
+
return new TransformInterpolator();
|
|
898
|
+
}
|
|
899
|
+
__name(createTransformInterpolator, "createTransformInterpolator");
|
|
900
|
+
function createHermiteTransformInterpolator() {
|
|
901
|
+
return new HermiteTransformInterpolator();
|
|
902
|
+
}
|
|
903
|
+
__name(createHermiteTransformInterpolator, "createHermiteTransformInterpolator");
|
|
904
|
+
|
|
905
|
+
// src/sync/ClientPrediction.ts
|
|
906
|
+
var _ClientPrediction = class _ClientPrediction {
|
|
907
|
+
constructor(predictor, config) {
|
|
908
|
+
__publicField(this, "_predictor");
|
|
909
|
+
__publicField(this, "_config");
|
|
910
|
+
__publicField(this, "_pendingInputs", []);
|
|
911
|
+
__publicField(this, "_lastAcknowledgedSequence", 0);
|
|
912
|
+
__publicField(this, "_currentSequence", 0);
|
|
913
|
+
__publicField(this, "_lastServerState", null);
|
|
914
|
+
__publicField(this, "_predictedState", null);
|
|
915
|
+
__publicField(this, "_correctionOffset", {
|
|
916
|
+
x: 0,
|
|
917
|
+
y: 0
|
|
918
|
+
});
|
|
919
|
+
this._predictor = predictor;
|
|
920
|
+
this._config = {
|
|
921
|
+
maxUnacknowledgedInputs: 60,
|
|
922
|
+
reconciliationThreshold: 0.1,
|
|
923
|
+
reconciliationSpeed: 10,
|
|
924
|
+
...config
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* @zh 获取当前预测状态
|
|
929
|
+
* @en Get current predicted state
|
|
930
|
+
*/
|
|
931
|
+
get predictedState() {
|
|
932
|
+
return this._predictedState;
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* @zh 获取校正偏移
|
|
936
|
+
* @en Get correction offset
|
|
937
|
+
*/
|
|
938
|
+
get correctionOffset() {
|
|
939
|
+
return this._correctionOffset;
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* @zh 获取待确认输入数量
|
|
943
|
+
* @en Get pending input count
|
|
944
|
+
*/
|
|
945
|
+
get pendingInputCount() {
|
|
946
|
+
return this._pendingInputs.length;
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* @zh 记录并预测输入
|
|
950
|
+
* @en Record and predict input
|
|
951
|
+
*
|
|
952
|
+
* @param input - @zh 输入数据 @en Input data
|
|
953
|
+
* @param currentState - @zh 当前状态 @en Current state
|
|
954
|
+
* @param deltaTime - @zh 时间间隔 @en Delta time
|
|
955
|
+
* @returns @zh 预测的状态 @en Predicted state
|
|
956
|
+
*/
|
|
957
|
+
recordInput(input, currentState, deltaTime) {
|
|
958
|
+
this._currentSequence++;
|
|
959
|
+
const inputSnapshot = {
|
|
960
|
+
sequence: this._currentSequence,
|
|
961
|
+
input,
|
|
962
|
+
timestamp: Date.now()
|
|
963
|
+
};
|
|
964
|
+
this._pendingInputs.push(inputSnapshot);
|
|
965
|
+
while (this._pendingInputs.length > this._config.maxUnacknowledgedInputs) {
|
|
966
|
+
this._pendingInputs.shift();
|
|
967
|
+
}
|
|
968
|
+
this._predictedState = this._predictor.predict(currentState, input, deltaTime);
|
|
969
|
+
return this._predictedState;
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* @zh 获取下一个要发送的输入
|
|
973
|
+
* @en Get next input to send
|
|
974
|
+
*/
|
|
975
|
+
getInputToSend() {
|
|
976
|
+
return this._pendingInputs.length > 0 ? this._pendingInputs[this._pendingInputs.length - 1] : null;
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* @zh 获取当前序列号
|
|
980
|
+
* @en Get current sequence number
|
|
981
|
+
*/
|
|
982
|
+
get currentSequence() {
|
|
983
|
+
return this._currentSequence;
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* @zh 处理服务器状态并进行校正
|
|
987
|
+
* @en Process server state and reconcile
|
|
988
|
+
*
|
|
989
|
+
* @param serverState - @zh 服务器状态 @en Server state
|
|
990
|
+
* @param acknowledgedSequence - @zh 已确认的输入序列号 @en Acknowledged input sequence
|
|
991
|
+
* @param stateGetter - @zh 获取状态位置的函数 @en Function to get state position
|
|
992
|
+
* @param deltaTime - @zh 帧时间 @en Frame delta time
|
|
993
|
+
*/
|
|
994
|
+
reconcile(serverState, acknowledgedSequence, stateGetter, deltaTime) {
|
|
995
|
+
this._lastServerState = serverState;
|
|
996
|
+
this._lastAcknowledgedSequence = acknowledgedSequence;
|
|
997
|
+
while (this._pendingInputs.length > 0 && this._pendingInputs[0].sequence <= acknowledgedSequence) {
|
|
998
|
+
this._pendingInputs.shift();
|
|
999
|
+
}
|
|
1000
|
+
let state = serverState;
|
|
1001
|
+
for (const inputSnapshot of this._pendingInputs) {
|
|
1002
|
+
state = this._predictor.predict(state, inputSnapshot.input, deltaTime);
|
|
1003
|
+
}
|
|
1004
|
+
const serverPos = stateGetter(serverState);
|
|
1005
|
+
const predictedPos = stateGetter(state);
|
|
1006
|
+
const errorX = serverPos.x - predictedPos.x;
|
|
1007
|
+
const errorY = serverPos.y - predictedPos.y;
|
|
1008
|
+
const errorMagnitude = Math.sqrt(errorX * errorX + errorY * errorY);
|
|
1009
|
+
if (errorMagnitude > this._config.reconciliationThreshold) {
|
|
1010
|
+
const t = Math.min(1, this._config.reconciliationSpeed * deltaTime);
|
|
1011
|
+
this._correctionOffset.x += errorX * t;
|
|
1012
|
+
this._correctionOffset.y += errorY * t;
|
|
1013
|
+
}
|
|
1014
|
+
const decayRate = 0.9;
|
|
1015
|
+
this._correctionOffset.x *= decayRate;
|
|
1016
|
+
this._correctionOffset.y *= decayRate;
|
|
1017
|
+
this._predictedState = state;
|
|
1018
|
+
return state;
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* @zh 清空预测状态
|
|
1022
|
+
* @en Clear prediction state
|
|
1023
|
+
*/
|
|
1024
|
+
clear() {
|
|
1025
|
+
this._pendingInputs.length = 0;
|
|
1026
|
+
this._lastAcknowledgedSequence = 0;
|
|
1027
|
+
this._currentSequence = 0;
|
|
1028
|
+
this._lastServerState = null;
|
|
1029
|
+
this._predictedState = null;
|
|
1030
|
+
this._correctionOffset = {
|
|
1031
|
+
x: 0,
|
|
1032
|
+
y: 0
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
};
|
|
1036
|
+
__name(_ClientPrediction, "ClientPrediction");
|
|
1037
|
+
var ClientPrediction = _ClientPrediction;
|
|
1038
|
+
function createClientPrediction(predictor, config) {
|
|
1039
|
+
return new ClientPrediction(predictor, config);
|
|
1040
|
+
}
|
|
1041
|
+
__name(createClientPrediction, "createClientPrediction");
|
|
1042
|
+
|
|
1043
|
+
// src/nodes/NetworkNodes.ts
|
|
1044
|
+
var IsLocalPlayerTemplate = {
|
|
1045
|
+
type: "IsLocalPlayer",
|
|
1046
|
+
title: "Is Local Player",
|
|
1047
|
+
category: "entity",
|
|
1048
|
+
description: "Check if this entity is the local player / \u68C0\u67E5\u6B64\u5B9E\u4F53\u662F\u5426\u662F\u672C\u5730\u73A9\u5BB6",
|
|
1049
|
+
keywords: [
|
|
1050
|
+
"network",
|
|
1051
|
+
"local",
|
|
1052
|
+
"player",
|
|
1053
|
+
"authority",
|
|
1054
|
+
"owner"
|
|
1055
|
+
],
|
|
1056
|
+
menuPath: [
|
|
1057
|
+
"Network",
|
|
1058
|
+
"Is Local Player"
|
|
1059
|
+
],
|
|
1060
|
+
isPure: true,
|
|
1061
|
+
inputs: [],
|
|
1062
|
+
outputs: [
|
|
1063
|
+
{
|
|
1064
|
+
name: "isLocal",
|
|
1065
|
+
displayName: "Is Local",
|
|
1066
|
+
type: "bool"
|
|
1067
|
+
}
|
|
1068
|
+
],
|
|
1069
|
+
color: "#ff9800"
|
|
1070
|
+
};
|
|
1071
|
+
var _IsLocalPlayerExecutor = class _IsLocalPlayerExecutor {
|
|
1072
|
+
execute(node, context) {
|
|
1073
|
+
var _a;
|
|
1074
|
+
const ctx = context;
|
|
1075
|
+
let isLocal = false;
|
|
1076
|
+
if (ctx.entity) {
|
|
1077
|
+
const identity = ctx.entity.getComponent((_a = class {
|
|
1078
|
+
constructor() {
|
|
1079
|
+
__publicField(this, "bIsLocalPlayer", false);
|
|
1080
|
+
}
|
|
1081
|
+
}, __name(_a, "NetworkIdentity"), _a));
|
|
1082
|
+
if (identity) {
|
|
1083
|
+
isLocal = identity.bIsLocalPlayer;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
return {
|
|
1087
|
+
outputs: {
|
|
1088
|
+
isLocal
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
__name(_IsLocalPlayerExecutor, "IsLocalPlayerExecutor");
|
|
1094
|
+
var IsLocalPlayerExecutor = _IsLocalPlayerExecutor;
|
|
1095
|
+
var IsServerTemplate = {
|
|
1096
|
+
type: "IsServer",
|
|
1097
|
+
title: "Is Server",
|
|
1098
|
+
category: "entity",
|
|
1099
|
+
description: "Check if running on server / \u68C0\u67E5\u662F\u5426\u5728\u670D\u52A1\u5668\u4E0A\u8FD0\u884C",
|
|
1100
|
+
keywords: [
|
|
1101
|
+
"network",
|
|
1102
|
+
"server",
|
|
1103
|
+
"authority",
|
|
1104
|
+
"host"
|
|
1105
|
+
],
|
|
1106
|
+
menuPath: [
|
|
1107
|
+
"Network",
|
|
1108
|
+
"Is Server"
|
|
1109
|
+
],
|
|
1110
|
+
isPure: true,
|
|
1111
|
+
inputs: [],
|
|
1112
|
+
outputs: [
|
|
1113
|
+
{
|
|
1114
|
+
name: "isServer",
|
|
1115
|
+
displayName: "Is Server",
|
|
1116
|
+
type: "bool"
|
|
1117
|
+
}
|
|
1118
|
+
],
|
|
1119
|
+
color: "#ff9800"
|
|
1120
|
+
};
|
|
1121
|
+
var _IsServerExecutor = class _IsServerExecutor {
|
|
1122
|
+
execute(_node, context) {
|
|
1123
|
+
const ctx = context;
|
|
1124
|
+
return {
|
|
1125
|
+
outputs: {
|
|
1126
|
+
isServer: ctx.isServer ?? false
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
__name(_IsServerExecutor, "IsServerExecutor");
|
|
1132
|
+
var IsServerExecutor = _IsServerExecutor;
|
|
1133
|
+
var HasAuthorityTemplate = {
|
|
1134
|
+
type: "HasAuthority",
|
|
1135
|
+
title: "Has Authority",
|
|
1136
|
+
category: "entity",
|
|
1137
|
+
description: "Check if this entity has authority / \u68C0\u67E5\u6B64\u5B9E\u4F53\u662F\u5426\u6709\u6743\u9650\u63A7\u5236",
|
|
1138
|
+
keywords: [
|
|
1139
|
+
"network",
|
|
1140
|
+
"authority",
|
|
1141
|
+
"control",
|
|
1142
|
+
"owner"
|
|
1143
|
+
],
|
|
1144
|
+
menuPath: [
|
|
1145
|
+
"Network",
|
|
1146
|
+
"Has Authority"
|
|
1147
|
+
],
|
|
1148
|
+
isPure: true,
|
|
1149
|
+
inputs: [],
|
|
1150
|
+
outputs: [
|
|
1151
|
+
{
|
|
1152
|
+
name: "hasAuthority",
|
|
1153
|
+
displayName: "Has Authority",
|
|
1154
|
+
type: "bool"
|
|
1155
|
+
}
|
|
1156
|
+
],
|
|
1157
|
+
color: "#ff9800"
|
|
1158
|
+
};
|
|
1159
|
+
var _HasAuthorityExecutor = class _HasAuthorityExecutor {
|
|
1160
|
+
execute(node, context) {
|
|
1161
|
+
var _a;
|
|
1162
|
+
const ctx = context;
|
|
1163
|
+
let hasAuthority = false;
|
|
1164
|
+
if (ctx.entity) {
|
|
1165
|
+
const identity = ctx.entity.getComponent((_a = class {
|
|
1166
|
+
constructor() {
|
|
1167
|
+
__publicField(this, "bHasAuthority", false);
|
|
1168
|
+
}
|
|
1169
|
+
}, __name(_a, "NetworkIdentity"), _a));
|
|
1170
|
+
if (identity) {
|
|
1171
|
+
hasAuthority = identity.bHasAuthority;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
return {
|
|
1175
|
+
outputs: {
|
|
1176
|
+
hasAuthority
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
__name(_HasAuthorityExecutor, "HasAuthorityExecutor");
|
|
1182
|
+
var HasAuthorityExecutor = _HasAuthorityExecutor;
|
|
1183
|
+
var GetNetworkIdTemplate = {
|
|
1184
|
+
type: "GetNetworkId",
|
|
1185
|
+
title: "Get Network ID",
|
|
1186
|
+
category: "entity",
|
|
1187
|
+
description: "Get the network ID of this entity / \u83B7\u53D6\u6B64\u5B9E\u4F53\u7684\u7F51\u7EDC ID",
|
|
1188
|
+
keywords: [
|
|
1189
|
+
"network",
|
|
1190
|
+
"id",
|
|
1191
|
+
"netid",
|
|
1192
|
+
"identity"
|
|
1193
|
+
],
|
|
1194
|
+
menuPath: [
|
|
1195
|
+
"Network",
|
|
1196
|
+
"Get Network ID"
|
|
1197
|
+
],
|
|
1198
|
+
isPure: true,
|
|
1199
|
+
inputs: [],
|
|
1200
|
+
outputs: [
|
|
1201
|
+
{
|
|
1202
|
+
name: "netId",
|
|
1203
|
+
displayName: "Net ID",
|
|
1204
|
+
type: "int"
|
|
1205
|
+
},
|
|
1206
|
+
{
|
|
1207
|
+
name: "ownerId",
|
|
1208
|
+
displayName: "Owner ID",
|
|
1209
|
+
type: "int"
|
|
1210
|
+
}
|
|
1211
|
+
],
|
|
1212
|
+
color: "#ff9800"
|
|
1213
|
+
};
|
|
1214
|
+
var _GetNetworkIdExecutor = class _GetNetworkIdExecutor {
|
|
1215
|
+
execute(node, context) {
|
|
1216
|
+
var _a;
|
|
1217
|
+
const ctx = context;
|
|
1218
|
+
let netId = 0;
|
|
1219
|
+
let ownerId = 0;
|
|
1220
|
+
if (ctx.entity) {
|
|
1221
|
+
const identity = ctx.entity.getComponent((_a = class {
|
|
1222
|
+
constructor() {
|
|
1223
|
+
__publicField(this, "netId", 0);
|
|
1224
|
+
__publicField(this, "ownerId", 0);
|
|
1225
|
+
}
|
|
1226
|
+
}, __name(_a, "NetworkIdentity"), _a));
|
|
1227
|
+
if (identity) {
|
|
1228
|
+
netId = identity.netId;
|
|
1229
|
+
ownerId = identity.ownerId;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
return {
|
|
1233
|
+
outputs: {
|
|
1234
|
+
netId,
|
|
1235
|
+
ownerId
|
|
1236
|
+
}
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1240
|
+
__name(_GetNetworkIdExecutor, "GetNetworkIdExecutor");
|
|
1241
|
+
var GetNetworkIdExecutor = _GetNetworkIdExecutor;
|
|
1242
|
+
var GetLocalPlayerIdTemplate = {
|
|
1243
|
+
type: "GetLocalPlayerId",
|
|
1244
|
+
title: "Get Local Player ID",
|
|
1245
|
+
category: "entity",
|
|
1246
|
+
description: "Get the local player ID / \u83B7\u53D6\u672C\u5730\u73A9\u5BB6 ID",
|
|
1247
|
+
keywords: [
|
|
1248
|
+
"network",
|
|
1249
|
+
"local",
|
|
1250
|
+
"player",
|
|
1251
|
+
"id"
|
|
1252
|
+
],
|
|
1253
|
+
menuPath: [
|
|
1254
|
+
"Network",
|
|
1255
|
+
"Get Local Player ID"
|
|
1256
|
+
],
|
|
1257
|
+
isPure: true,
|
|
1258
|
+
inputs: [],
|
|
1259
|
+
outputs: [
|
|
1260
|
+
{
|
|
1261
|
+
name: "playerId",
|
|
1262
|
+
displayName: "Player ID",
|
|
1263
|
+
type: "int"
|
|
1264
|
+
}
|
|
1265
|
+
],
|
|
1266
|
+
color: "#ff9800"
|
|
1267
|
+
};
|
|
1268
|
+
var _GetLocalPlayerIdExecutor = class _GetLocalPlayerIdExecutor {
|
|
1269
|
+
execute(_node, context) {
|
|
1270
|
+
const ctx = context;
|
|
1271
|
+
return {
|
|
1272
|
+
outputs: {
|
|
1273
|
+
playerId: ctx.localPlayerId ?? 0
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
__name(_GetLocalPlayerIdExecutor, "GetLocalPlayerIdExecutor");
|
|
1279
|
+
var GetLocalPlayerIdExecutor = _GetLocalPlayerIdExecutor;
|
|
1280
|
+
var NetworkNodeDefinitions = {
|
|
1281
|
+
templates: [
|
|
1282
|
+
IsLocalPlayerTemplate,
|
|
1283
|
+
IsServerTemplate,
|
|
1284
|
+
HasAuthorityTemplate,
|
|
1285
|
+
GetNetworkIdTemplate,
|
|
1286
|
+
GetLocalPlayerIdTemplate
|
|
1287
|
+
],
|
|
1288
|
+
executors: /* @__PURE__ */ new Map([
|
|
1289
|
+
[
|
|
1290
|
+
"IsLocalPlayer",
|
|
1291
|
+
new IsLocalPlayerExecutor()
|
|
1292
|
+
],
|
|
1293
|
+
[
|
|
1294
|
+
"IsServer",
|
|
1295
|
+
new IsServerExecutor()
|
|
1296
|
+
],
|
|
1297
|
+
[
|
|
1298
|
+
"HasAuthority",
|
|
1299
|
+
new HasAuthorityExecutor()
|
|
1300
|
+
],
|
|
1301
|
+
[
|
|
1302
|
+
"GetNetworkId",
|
|
1303
|
+
new GetNetworkIdExecutor()
|
|
1304
|
+
],
|
|
1305
|
+
[
|
|
1306
|
+
"GetLocalPlayerId",
|
|
1307
|
+
new GetLocalPlayerIdExecutor()
|
|
1308
|
+
]
|
|
1309
|
+
])
|
|
1310
|
+
};
|
|
1311
|
+
export {
|
|
1312
|
+
ClientPrediction,
|
|
1313
|
+
ENetworkState,
|
|
1314
|
+
GetLocalPlayerIdExecutor,
|
|
1315
|
+
GetLocalPlayerIdTemplate,
|
|
1316
|
+
GetNetworkIdExecutor,
|
|
1317
|
+
GetNetworkIdTemplate,
|
|
1318
|
+
HasAuthorityExecutor,
|
|
1319
|
+
HasAuthorityTemplate,
|
|
1320
|
+
HermiteTransformInterpolator,
|
|
1321
|
+
IsLocalPlayerExecutor,
|
|
1322
|
+
IsLocalPlayerTemplate,
|
|
1323
|
+
IsServerExecutor,
|
|
1324
|
+
IsServerTemplate,
|
|
1325
|
+
NetworkIdentity,
|
|
1326
|
+
NetworkInputSystem,
|
|
1327
|
+
NetworkInputSystemToken,
|
|
1328
|
+
NetworkNodeDefinitions,
|
|
1329
|
+
NetworkPlugin,
|
|
1330
|
+
NetworkService,
|
|
1331
|
+
NetworkServiceToken,
|
|
1332
|
+
NetworkSpawnSystem,
|
|
1333
|
+
NetworkSpawnSystemToken,
|
|
1334
|
+
NetworkSyncSystem,
|
|
1335
|
+
NetworkSyncSystemToken,
|
|
1336
|
+
NetworkTransform,
|
|
1337
|
+
SnapshotBuffer,
|
|
1338
|
+
TransformInterpolator,
|
|
1339
|
+
createClientPrediction,
|
|
1340
|
+
createHermiteTransformInterpolator,
|
|
1341
|
+
createSnapshotBuffer,
|
|
1342
|
+
createTransformInterpolator,
|
|
1343
|
+
lerp,
|
|
1344
|
+
lerpAngle,
|
|
1345
|
+
serviceProto2 as serviceProto,
|
|
1346
|
+
smoothDamp
|
|
1347
|
+
};
|
|
1348
|
+
//# sourceMappingURL=index.js.map
|