@esengine/network 2.1.0 → 2.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/dist/NetworkPlugin.d.ts +150 -8
- package/dist/NetworkPlugin.d.ts.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1773 -503
- package/dist/index.js.map +1 -1
- package/dist/protocol.d.ts +125 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/sync/StateDelta.d.ts +177 -0
- package/dist/sync/StateDelta.d.ts.map +1 -0
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/systems/NetworkAOISystem.d.ts +172 -0
- package/dist/systems/NetworkAOISystem.d.ts.map +1 -0
- package/dist/systems/NetworkInputSystem.d.ts +69 -2
- package/dist/systems/NetworkInputSystem.d.ts.map +1 -1
- package/dist/systems/NetworkPredictionSystem.d.ts +113 -0
- package/dist/systems/NetworkPredictionSystem.d.ts.map +1 -0
- package/dist/systems/NetworkSyncSystem.d.ts +88 -9
- package/dist/systems/NetworkSyncSystem.d.ts.map +1 -1
- package/dist/tokens.d.ts +12 -0
- package/dist/tokens.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -19,7 +19,12 @@ var gameProtocol = rpc.define({
|
|
|
19
19
|
* @zh 离开房间
|
|
20
20
|
* @en Leave room
|
|
21
21
|
*/
|
|
22
|
-
leave: rpc.api()
|
|
22
|
+
leave: rpc.api(),
|
|
23
|
+
/**
|
|
24
|
+
* @zh 重连
|
|
25
|
+
* @en Reconnect
|
|
26
|
+
*/
|
|
27
|
+
reconnect: rpc.api()
|
|
23
28
|
},
|
|
24
29
|
msg: {
|
|
25
30
|
/**
|
|
@@ -41,7 +46,12 @@ var gameProtocol = rpc.define({
|
|
|
41
46
|
* @zh 实体销毁
|
|
42
47
|
* @en Entity despawn
|
|
43
48
|
*/
|
|
44
|
-
despawn: rpc.msg()
|
|
49
|
+
despawn: rpc.msg(),
|
|
50
|
+
/**
|
|
51
|
+
* @zh 完整状态快照
|
|
52
|
+
* @en Full state snapshot
|
|
53
|
+
*/
|
|
54
|
+
fullState: rpc.msg()
|
|
45
55
|
}
|
|
46
56
|
});
|
|
47
57
|
|
|
@@ -51,6 +61,8 @@ var NetworkServiceToken = createServiceToken("networkService");
|
|
|
51
61
|
var NetworkSyncSystemToken = createServiceToken("networkSyncSystem");
|
|
52
62
|
var NetworkSpawnSystemToken = createServiceToken("networkSpawnSystem");
|
|
53
63
|
var NetworkInputSystemToken = createServiceToken("networkInputSystem");
|
|
64
|
+
var NetworkPredictionSystemToken = createServiceToken("networkPredictionSystem");
|
|
65
|
+
var NetworkAOISystemToken = createServiceToken("networkAOISystem");
|
|
54
66
|
|
|
55
67
|
// src/NetworkPlugin.ts
|
|
56
68
|
import { Core } from "@esengine/ecs-framework";
|
|
@@ -454,222 +466,1602 @@ NetworkTransform = _ts_decorate2([
|
|
|
454
466
|
})
|
|
455
467
|
], NetworkTransform);
|
|
456
468
|
|
|
457
|
-
// src/
|
|
458
|
-
var
|
|
459
|
-
constructor() {
|
|
460
|
-
|
|
461
|
-
__publicField(this, "
|
|
469
|
+
// src/sync/SnapshotBuffer.ts
|
|
470
|
+
var _SnapshotBuffer = class _SnapshotBuffer {
|
|
471
|
+
constructor(config) {
|
|
472
|
+
__publicField(this, "_buffer", []);
|
|
473
|
+
__publicField(this, "_maxSize");
|
|
474
|
+
__publicField(this, "_interpolationDelay");
|
|
475
|
+
this._maxSize = config.maxSize;
|
|
476
|
+
this._interpolationDelay = config.interpolationDelay;
|
|
477
|
+
}
|
|
478
|
+
get size() {
|
|
479
|
+
return this._buffer.length;
|
|
462
480
|
}
|
|
463
481
|
/**
|
|
464
|
-
* @zh
|
|
465
|
-
* @en
|
|
482
|
+
* @zh 获取插值延迟
|
|
483
|
+
* @en Get interpolation delay
|
|
466
484
|
*/
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
485
|
+
get interpolationDelay() {
|
|
486
|
+
return this._interpolationDelay;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* @zh 添加快照
|
|
490
|
+
* @en Add snapshot
|
|
491
|
+
*/
|
|
492
|
+
push(snapshot) {
|
|
493
|
+
let insertIndex = this._buffer.length;
|
|
494
|
+
for (let i = this._buffer.length - 1; i >= 0; i--) {
|
|
495
|
+
if (this._buffer[i].timestamp <= snapshot.timestamp) {
|
|
496
|
+
insertIndex = i + 1;
|
|
497
|
+
break;
|
|
476
498
|
}
|
|
499
|
+
if (i === 0) {
|
|
500
|
+
insertIndex = 0;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
this._buffer.splice(insertIndex, 0, snapshot);
|
|
504
|
+
while (this._buffer.length > this._maxSize) {
|
|
505
|
+
this._buffer.shift();
|
|
477
506
|
}
|
|
478
507
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
508
|
+
/**
|
|
509
|
+
* @zh 获取用于插值的两个快照
|
|
510
|
+
* @en Get two snapshots for interpolation
|
|
511
|
+
*/
|
|
512
|
+
getInterpolationSnapshots(renderTime) {
|
|
513
|
+
if (this._buffer.length < 2) {
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
const targetTime = renderTime - this._interpolationDelay;
|
|
517
|
+
for (let i = 0; i < this._buffer.length - 1; i++) {
|
|
518
|
+
const prev = this._buffer[i];
|
|
519
|
+
const next = this._buffer[i + 1];
|
|
520
|
+
if (prev.timestamp <= targetTime && next.timestamp >= targetTime) {
|
|
521
|
+
const duration = next.timestamp - prev.timestamp;
|
|
522
|
+
const t = duration > 0 ? (targetTime - prev.timestamp) / duration : 0;
|
|
523
|
+
return [
|
|
524
|
+
prev,
|
|
525
|
+
next,
|
|
526
|
+
Math.max(0, Math.min(1, t))
|
|
527
|
+
];
|
|
486
528
|
}
|
|
487
529
|
}
|
|
530
|
+
if (targetTime > this._buffer[this._buffer.length - 1].timestamp) {
|
|
531
|
+
const prev = this._buffer[this._buffer.length - 2];
|
|
532
|
+
const next = this._buffer[this._buffer.length - 1];
|
|
533
|
+
const duration = next.timestamp - prev.timestamp;
|
|
534
|
+
const t = duration > 0 ? (targetTime - prev.timestamp) / duration : 1;
|
|
535
|
+
return [
|
|
536
|
+
prev,
|
|
537
|
+
next,
|
|
538
|
+
Math.min(t, 2)
|
|
539
|
+
];
|
|
540
|
+
}
|
|
541
|
+
return null;
|
|
488
542
|
}
|
|
489
543
|
/**
|
|
490
|
-
* @zh
|
|
491
|
-
* @en
|
|
544
|
+
* @zh 获取最新快照
|
|
545
|
+
* @en Get latest snapshot
|
|
492
546
|
*/
|
|
493
|
-
|
|
494
|
-
this.
|
|
547
|
+
getLatest() {
|
|
548
|
+
return this._buffer.length > 0 ? this._buffer[this._buffer.length - 1] : null;
|
|
495
549
|
}
|
|
496
550
|
/**
|
|
497
|
-
* @zh
|
|
498
|
-
* @en
|
|
551
|
+
* @zh 获取特定时间之后的所有快照
|
|
552
|
+
* @en Get all snapshots after a specific time
|
|
499
553
|
*/
|
|
500
|
-
|
|
501
|
-
this.
|
|
554
|
+
getSnapshotsAfter(timestamp) {
|
|
555
|
+
return this._buffer.filter((s) => s.timestamp > timestamp);
|
|
502
556
|
}
|
|
503
557
|
/**
|
|
504
|
-
* @zh
|
|
505
|
-
* @en
|
|
558
|
+
* @zh 清空缓冲区
|
|
559
|
+
* @en Clear buffer
|
|
506
560
|
*/
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
}
|
|
510
|
-
_interpolate(transform, deltaTime) {
|
|
511
|
-
const t = Math.min(1, transform.lerpSpeed * deltaTime);
|
|
512
|
-
transform.currentX += (transform.targetX - transform.currentX) * t;
|
|
513
|
-
transform.currentY += (transform.targetY - transform.currentY) * t;
|
|
514
|
-
let angleDiff = transform.targetRotation - transform.currentRotation;
|
|
515
|
-
while (angleDiff > Math.PI) angleDiff -= Math.PI * 2;
|
|
516
|
-
while (angleDiff < -Math.PI) angleDiff += Math.PI * 2;
|
|
517
|
-
transform.currentRotation += angleDiff * t;
|
|
518
|
-
}
|
|
519
|
-
onDestroy() {
|
|
520
|
-
this._netIdToEntity.clear();
|
|
561
|
+
clear() {
|
|
562
|
+
this._buffer.length = 0;
|
|
521
563
|
}
|
|
522
564
|
};
|
|
523
|
-
__name(
|
|
524
|
-
var
|
|
565
|
+
__name(_SnapshotBuffer, "SnapshotBuffer");
|
|
566
|
+
var SnapshotBuffer = _SnapshotBuffer;
|
|
567
|
+
function createSnapshotBuffer(maxSize = 30, interpolationDelay = 100) {
|
|
568
|
+
return new SnapshotBuffer({
|
|
569
|
+
maxSize,
|
|
570
|
+
interpolationDelay
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
__name(createSnapshotBuffer, "createSnapshotBuffer");
|
|
525
574
|
|
|
526
|
-
// src/
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
return null;
|
|
553
|
-
}
|
|
554
|
-
const entity = factory(this.scene, msg);
|
|
555
|
-
const identity = entity.addComponent(new NetworkIdentity());
|
|
556
|
-
identity.netId = msg.netId;
|
|
557
|
-
identity.ownerId = msg.ownerId;
|
|
558
|
-
identity.prefabType = msg.prefab;
|
|
559
|
-
identity.bHasAuthority = msg.ownerId === this._localPlayerId;
|
|
560
|
-
identity.bIsLocalPlayer = identity.bHasAuthority;
|
|
561
|
-
const transform = entity.addComponent(new NetworkTransform());
|
|
562
|
-
transform.setTarget(msg.pos.x, msg.pos.y, msg.rot ?? 0);
|
|
563
|
-
transform.snap();
|
|
564
|
-
this._syncSystem.registerEntity(msg.netId, entity.id);
|
|
565
|
-
return entity;
|
|
575
|
+
// src/sync/IInterpolator.ts
|
|
576
|
+
function lerp(a, b, t) {
|
|
577
|
+
return a + (b - a) * t;
|
|
578
|
+
}
|
|
579
|
+
__name(lerp, "lerp");
|
|
580
|
+
function lerpAngle(a, b, t) {
|
|
581
|
+
let diff = b - a;
|
|
582
|
+
while (diff > Math.PI) diff -= Math.PI * 2;
|
|
583
|
+
while (diff < -Math.PI) diff += Math.PI * 2;
|
|
584
|
+
return a + diff * t;
|
|
585
|
+
}
|
|
586
|
+
__name(lerpAngle, "lerpAngle");
|
|
587
|
+
function smoothDamp(current, target, velocity, smoothTime, deltaTime, maxSpeed = Infinity) {
|
|
588
|
+
smoothTime = Math.max(1e-4, smoothTime);
|
|
589
|
+
const omega = 2 / smoothTime;
|
|
590
|
+
const x = omega * deltaTime;
|
|
591
|
+
const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);
|
|
592
|
+
let change = current - target;
|
|
593
|
+
const maxChange = maxSpeed * smoothTime;
|
|
594
|
+
change = Math.max(-maxChange, Math.min(maxChange, change));
|
|
595
|
+
const temp = (velocity + omega * change) * deltaTime;
|
|
596
|
+
let newVelocity = (velocity - omega * temp) * exp;
|
|
597
|
+
let newValue = target + (change + temp) * exp;
|
|
598
|
+
if (target - current > 0 === newValue > target) {
|
|
599
|
+
newValue = target;
|
|
600
|
+
newVelocity = (newValue - target) / deltaTime;
|
|
566
601
|
}
|
|
602
|
+
return [
|
|
603
|
+
newValue,
|
|
604
|
+
newVelocity
|
|
605
|
+
];
|
|
606
|
+
}
|
|
607
|
+
__name(smoothDamp, "smoothDamp");
|
|
608
|
+
|
|
609
|
+
// src/sync/TransformInterpolator.ts
|
|
610
|
+
var _TransformInterpolator = class _TransformInterpolator {
|
|
567
611
|
/**
|
|
568
|
-
* @zh
|
|
569
|
-
* @en
|
|
612
|
+
* @zh 在两个变换状态之间插值
|
|
613
|
+
* @en Interpolate between two transform states
|
|
570
614
|
*/
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
}
|
|
578
|
-
this._syncSystem.unregisterEntity(msg.netId);
|
|
615
|
+
interpolate(from, to, t) {
|
|
616
|
+
return {
|
|
617
|
+
x: lerp(from.x, to.x, t),
|
|
618
|
+
y: lerp(from.y, to.y, t),
|
|
619
|
+
rotation: lerpAngle(from.rotation, to.rotation, t)
|
|
620
|
+
};
|
|
579
621
|
}
|
|
580
622
|
/**
|
|
581
|
-
* @zh
|
|
582
|
-
* @en
|
|
623
|
+
* @zh 基于速度外推变换状态
|
|
624
|
+
* @en Extrapolate transform state based on velocity
|
|
583
625
|
*/
|
|
584
|
-
|
|
585
|
-
|
|
626
|
+
extrapolate(state, deltaTime) {
|
|
627
|
+
return {
|
|
628
|
+
x: state.x + state.velocityX * deltaTime,
|
|
629
|
+
y: state.y + state.velocityY * deltaTime,
|
|
630
|
+
rotation: state.rotation + state.angularVelocity * deltaTime,
|
|
631
|
+
velocityX: state.velocityX,
|
|
632
|
+
velocityY: state.velocityY,
|
|
633
|
+
angularVelocity: state.angularVelocity
|
|
634
|
+
};
|
|
586
635
|
}
|
|
636
|
+
};
|
|
637
|
+
__name(_TransformInterpolator, "TransformInterpolator");
|
|
638
|
+
var TransformInterpolator = _TransformInterpolator;
|
|
639
|
+
var _HermiteTransformInterpolator = class _HermiteTransformInterpolator {
|
|
587
640
|
/**
|
|
588
|
-
* @zh
|
|
589
|
-
* @en
|
|
641
|
+
* @zh 使用赫尔米特插值
|
|
642
|
+
* @en Use Hermite interpolation
|
|
590
643
|
*/
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
644
|
+
interpolate(from, to, t) {
|
|
645
|
+
const t2 = t * t;
|
|
646
|
+
const t3 = t2 * t;
|
|
647
|
+
const h00 = 2 * t3 - 3 * t2 + 1;
|
|
648
|
+
const h10 = t3 - 2 * t2 + t;
|
|
649
|
+
const h01 = -2 * t3 + 3 * t2;
|
|
650
|
+
const h11 = t3 - t2;
|
|
651
|
+
const dt = 0.1;
|
|
652
|
+
const x = h00 * from.x + h10 * from.velocityX * dt + h01 * to.x + h11 * to.velocityX * dt;
|
|
653
|
+
const y = h00 * from.y + h10 * from.velocityY * dt + h01 * to.y + h11 * to.velocityY * dt;
|
|
654
|
+
const dh00 = 6 * t2 - 6 * t;
|
|
655
|
+
const dh10 = 3 * t2 - 4 * t + 1;
|
|
656
|
+
const dh01 = -6 * t2 + 6 * t;
|
|
657
|
+
const dh11 = 3 * t2 - 2 * t;
|
|
658
|
+
const velocityX = (dh00 * from.x + dh10 * from.velocityX * dt + dh01 * to.x + dh11 * to.velocityX * dt) / dt;
|
|
659
|
+
const velocityY = (dh00 * from.y + dh10 * from.velocityY * dt + dh01 * to.y + dh11 * to.velocityY * dt) / dt;
|
|
660
|
+
return {
|
|
661
|
+
x,
|
|
662
|
+
y,
|
|
663
|
+
rotation: lerpAngle(from.rotation, to.rotation, t),
|
|
664
|
+
velocityX,
|
|
665
|
+
velocityY,
|
|
666
|
+
angularVelocity: lerp(from.angularVelocity, to.angularVelocity, t)
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
__name(_HermiteTransformInterpolator, "HermiteTransformInterpolator");
|
|
671
|
+
var HermiteTransformInterpolator = _HermiteTransformInterpolator;
|
|
672
|
+
function createTransformInterpolator() {
|
|
673
|
+
return new TransformInterpolator();
|
|
674
|
+
}
|
|
675
|
+
__name(createTransformInterpolator, "createTransformInterpolator");
|
|
676
|
+
function createHermiteTransformInterpolator() {
|
|
677
|
+
return new HermiteTransformInterpolator();
|
|
678
|
+
}
|
|
679
|
+
__name(createHermiteTransformInterpolator, "createHermiteTransformInterpolator");
|
|
680
|
+
|
|
681
|
+
// src/sync/ClientPrediction.ts
|
|
682
|
+
var _ClientPrediction = class _ClientPrediction {
|
|
683
|
+
constructor(predictor, config) {
|
|
684
|
+
__publicField(this, "_predictor");
|
|
685
|
+
__publicField(this, "_config");
|
|
686
|
+
__publicField(this, "_pendingInputs", []);
|
|
687
|
+
__publicField(this, "_lastAcknowledgedSequence", 0);
|
|
688
|
+
__publicField(this, "_currentSequence", 0);
|
|
689
|
+
__publicField(this, "_lastServerState", null);
|
|
690
|
+
__publicField(this, "_predictedState", null);
|
|
691
|
+
__publicField(this, "_correctionOffset", {
|
|
692
|
+
x: 0,
|
|
693
|
+
y: 0
|
|
694
|
+
});
|
|
695
|
+
this._predictor = predictor;
|
|
696
|
+
this._config = {
|
|
697
|
+
maxUnacknowledgedInputs: 60,
|
|
698
|
+
reconciliationThreshold: 0.1,
|
|
699
|
+
reconciliationSpeed: 10,
|
|
700
|
+
...config
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* @zh 获取当前预测状态
|
|
705
|
+
* @en Get current predicted state
|
|
706
|
+
*/
|
|
707
|
+
get predictedState() {
|
|
708
|
+
return this._predictedState;
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* @zh 获取校正偏移
|
|
712
|
+
* @en Get correction offset
|
|
713
|
+
*/
|
|
714
|
+
get correctionOffset() {
|
|
715
|
+
return this._correctionOffset;
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* @zh 获取待确认输入数量
|
|
719
|
+
* @en Get pending input count
|
|
720
|
+
*/
|
|
721
|
+
get pendingInputCount() {
|
|
722
|
+
return this._pendingInputs.length;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* @zh 记录并预测输入
|
|
726
|
+
* @en Record and predict input
|
|
727
|
+
*
|
|
728
|
+
* @param input - @zh 输入数据 @en Input data
|
|
729
|
+
* @param currentState - @zh 当前状态 @en Current state
|
|
730
|
+
* @param deltaTime - @zh 时间间隔 @en Delta time
|
|
731
|
+
* @returns @zh 预测的状态 @en Predicted state
|
|
732
|
+
*/
|
|
733
|
+
recordInput(input, currentState, deltaTime) {
|
|
734
|
+
this._currentSequence++;
|
|
735
|
+
const inputSnapshot = {
|
|
736
|
+
sequence: this._currentSequence,
|
|
737
|
+
input,
|
|
738
|
+
timestamp: Date.now()
|
|
739
|
+
};
|
|
740
|
+
this._pendingInputs.push(inputSnapshot);
|
|
741
|
+
while (this._pendingInputs.length > this._config.maxUnacknowledgedInputs) {
|
|
742
|
+
this._pendingInputs.shift();
|
|
743
|
+
}
|
|
744
|
+
this._predictedState = this._predictor.predict(currentState, input, deltaTime);
|
|
745
|
+
return this._predictedState;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* @zh 获取下一个要发送的输入
|
|
749
|
+
* @en Get next input to send
|
|
750
|
+
*/
|
|
751
|
+
getInputToSend() {
|
|
752
|
+
return this._pendingInputs.length > 0 ? this._pendingInputs[this._pendingInputs.length - 1] : null;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* @zh 获取当前序列号
|
|
756
|
+
* @en Get current sequence number
|
|
757
|
+
*/
|
|
758
|
+
get currentSequence() {
|
|
759
|
+
return this._currentSequence;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* @zh 处理服务器状态并进行校正
|
|
763
|
+
* @en Process server state and reconcile
|
|
764
|
+
*
|
|
765
|
+
* @param serverState - @zh 服务器状态 @en Server state
|
|
766
|
+
* @param acknowledgedSequence - @zh 已确认的输入序列号 @en Acknowledged input sequence
|
|
767
|
+
* @param stateGetter - @zh 获取状态位置的函数 @en Function to get state position
|
|
768
|
+
* @param deltaTime - @zh 帧时间 @en Frame delta time
|
|
769
|
+
*/
|
|
770
|
+
reconcile(serverState, acknowledgedSequence, stateGetter, deltaTime) {
|
|
771
|
+
this._lastServerState = serverState;
|
|
772
|
+
this._lastAcknowledgedSequence = acknowledgedSequence;
|
|
773
|
+
while (this._pendingInputs.length > 0 && this._pendingInputs[0].sequence <= acknowledgedSequence) {
|
|
774
|
+
this._pendingInputs.shift();
|
|
775
|
+
}
|
|
776
|
+
let state = serverState;
|
|
777
|
+
for (const inputSnapshot of this._pendingInputs) {
|
|
778
|
+
state = this._predictor.predict(state, inputSnapshot.input, deltaTime);
|
|
779
|
+
}
|
|
780
|
+
const serverPos = stateGetter(serverState);
|
|
781
|
+
const predictedPos = stateGetter(state);
|
|
782
|
+
const errorX = serverPos.x - predictedPos.x;
|
|
783
|
+
const errorY = serverPos.y - predictedPos.y;
|
|
784
|
+
const errorMagnitude = Math.sqrt(errorX * errorX + errorY * errorY);
|
|
785
|
+
if (errorMagnitude > this._config.reconciliationThreshold) {
|
|
786
|
+
const t = Math.min(1, this._config.reconciliationSpeed * deltaTime);
|
|
787
|
+
this._correctionOffset.x += errorX * t;
|
|
788
|
+
this._correctionOffset.y += errorY * t;
|
|
789
|
+
}
|
|
790
|
+
const decayRate = 0.9;
|
|
791
|
+
this._correctionOffset.x *= decayRate;
|
|
792
|
+
this._correctionOffset.y *= decayRate;
|
|
793
|
+
this._predictedState = state;
|
|
794
|
+
return state;
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* @zh 清空预测状态
|
|
798
|
+
* @en Clear prediction state
|
|
799
|
+
*/
|
|
800
|
+
clear() {
|
|
801
|
+
this._pendingInputs.length = 0;
|
|
802
|
+
this._lastAcknowledgedSequence = 0;
|
|
803
|
+
this._currentSequence = 0;
|
|
804
|
+
this._lastServerState = null;
|
|
805
|
+
this._predictedState = null;
|
|
806
|
+
this._correctionOffset = {
|
|
807
|
+
x: 0,
|
|
808
|
+
y: 0
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
};
|
|
812
|
+
__name(_ClientPrediction, "ClientPrediction");
|
|
813
|
+
var ClientPrediction = _ClientPrediction;
|
|
814
|
+
function createClientPrediction(predictor, config) {
|
|
815
|
+
return new ClientPrediction(predictor, config);
|
|
816
|
+
}
|
|
817
|
+
__name(createClientPrediction, "createClientPrediction");
|
|
818
|
+
|
|
819
|
+
// src/sync/StateDelta.ts
|
|
820
|
+
var DeltaFlags = {
|
|
821
|
+
NONE: 0,
|
|
822
|
+
POSITION: 1 << 0,
|
|
823
|
+
ROTATION: 1 << 1,
|
|
824
|
+
VELOCITY: 1 << 2,
|
|
825
|
+
ANGULAR_VELOCITY: 1 << 3,
|
|
826
|
+
CUSTOM: 1 << 4
|
|
827
|
+
};
|
|
828
|
+
var DEFAULT_CONFIG = {
|
|
829
|
+
positionThreshold: 0.01,
|
|
830
|
+
rotationThreshold: 1e-3,
|
|
831
|
+
velocityThreshold: 0.1,
|
|
832
|
+
fullSnapshotInterval: 60
|
|
833
|
+
};
|
|
834
|
+
var _StateDeltaCompressor = class _StateDeltaCompressor {
|
|
835
|
+
constructor(config) {
|
|
836
|
+
__publicField(this, "_config");
|
|
837
|
+
__publicField(this, "_lastStates", /* @__PURE__ */ new Map());
|
|
838
|
+
__publicField(this, "_frameCounter", 0);
|
|
839
|
+
this._config = {
|
|
840
|
+
...DEFAULT_CONFIG,
|
|
841
|
+
...config
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* @zh 获取配置
|
|
846
|
+
* @en Get configuration
|
|
847
|
+
*/
|
|
848
|
+
get config() {
|
|
849
|
+
return this._config;
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* @zh 压缩同步数据为增量格式
|
|
853
|
+
* @en Compress sync data to delta format
|
|
854
|
+
*/
|
|
855
|
+
compress(data) {
|
|
856
|
+
this._frameCounter++;
|
|
857
|
+
const isFullSnapshot = this._frameCounter % this._config.fullSnapshotInterval === 0;
|
|
858
|
+
const deltaEntities = [];
|
|
859
|
+
for (const entity of data.entities) {
|
|
860
|
+
const lastState = this._lastStates.get(entity.netId);
|
|
861
|
+
if (isFullSnapshot || !lastState) {
|
|
862
|
+
deltaEntities.push(this._createFullDelta(entity));
|
|
863
|
+
} else {
|
|
864
|
+
const delta = this._calculateDelta(lastState, entity);
|
|
865
|
+
if (delta) {
|
|
866
|
+
deltaEntities.push(delta);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
this._lastStates.set(entity.netId, {
|
|
870
|
+
...entity
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
return {
|
|
874
|
+
frame: data.frame,
|
|
875
|
+
timestamp: data.timestamp,
|
|
876
|
+
ackSeq: data.ackSeq,
|
|
877
|
+
entities: deltaEntities,
|
|
878
|
+
isFullSnapshot
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* @zh 解压增量数据为完整同步数据
|
|
883
|
+
* @en Decompress delta data to full sync data
|
|
884
|
+
*/
|
|
885
|
+
decompress(data) {
|
|
886
|
+
const entities = [];
|
|
887
|
+
for (const delta of data.entities) {
|
|
888
|
+
const lastState = this._lastStates.get(delta.netId);
|
|
889
|
+
const fullState = this._applyDelta(lastState, delta);
|
|
890
|
+
entities.push(fullState);
|
|
891
|
+
this._lastStates.set(delta.netId, fullState);
|
|
892
|
+
}
|
|
893
|
+
return {
|
|
894
|
+
frame: data.frame,
|
|
895
|
+
timestamp: data.timestamp,
|
|
896
|
+
ackSeq: data.ackSeq,
|
|
897
|
+
entities
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* @zh 移除实体状态
|
|
902
|
+
* @en Remove entity state
|
|
903
|
+
*/
|
|
904
|
+
removeEntity(netId) {
|
|
905
|
+
this._lastStates.delete(netId);
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* @zh 清除所有状态
|
|
909
|
+
* @en Clear all states
|
|
910
|
+
*/
|
|
911
|
+
clear() {
|
|
912
|
+
this._lastStates.clear();
|
|
913
|
+
this._frameCounter = 0;
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* @zh 强制下一次发送完整快照
|
|
917
|
+
* @en Force next send to be a full snapshot
|
|
918
|
+
*/
|
|
919
|
+
forceFullSnapshot() {
|
|
920
|
+
this._frameCounter = this._config.fullSnapshotInterval - 1;
|
|
921
|
+
}
|
|
922
|
+
// =========================================================================
|
|
923
|
+
// 私有方法 | Private Methods
|
|
924
|
+
// =========================================================================
|
|
925
|
+
_createFullDelta(entity) {
|
|
926
|
+
let flags = 0;
|
|
927
|
+
if (entity.pos) flags |= DeltaFlags.POSITION;
|
|
928
|
+
if (entity.rot !== void 0) flags |= DeltaFlags.ROTATION;
|
|
929
|
+
if (entity.vel) flags |= DeltaFlags.VELOCITY;
|
|
930
|
+
if (entity.angVel !== void 0) flags |= DeltaFlags.ANGULAR_VELOCITY;
|
|
931
|
+
if (entity.custom) flags |= DeltaFlags.CUSTOM;
|
|
932
|
+
return {
|
|
933
|
+
netId: entity.netId,
|
|
934
|
+
flags,
|
|
935
|
+
pos: entity.pos,
|
|
936
|
+
rot: entity.rot,
|
|
937
|
+
vel: entity.vel,
|
|
938
|
+
angVel: entity.angVel,
|
|
939
|
+
custom: entity.custom
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
_calculateDelta(lastState, currentState) {
|
|
943
|
+
let flags = 0;
|
|
944
|
+
const delta = {
|
|
945
|
+
netId: currentState.netId,
|
|
946
|
+
flags: 0
|
|
947
|
+
};
|
|
948
|
+
if (currentState.pos) {
|
|
949
|
+
const posChanged = !lastState.pos || Math.abs(currentState.pos.x - lastState.pos.x) > this._config.positionThreshold || Math.abs(currentState.pos.y - lastState.pos.y) > this._config.positionThreshold;
|
|
950
|
+
if (posChanged) {
|
|
951
|
+
flags |= DeltaFlags.POSITION;
|
|
952
|
+
delta.pos = currentState.pos;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
if (currentState.rot !== void 0) {
|
|
956
|
+
const rotChanged = lastState.rot === void 0 || Math.abs(currentState.rot - lastState.rot) > this._config.rotationThreshold;
|
|
957
|
+
if (rotChanged) {
|
|
958
|
+
flags |= DeltaFlags.ROTATION;
|
|
959
|
+
delta.rot = currentState.rot;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (currentState.vel) {
|
|
963
|
+
const velChanged = !lastState.vel || Math.abs(currentState.vel.x - lastState.vel.x) > this._config.velocityThreshold || Math.abs(currentState.vel.y - lastState.vel.y) > this._config.velocityThreshold;
|
|
964
|
+
if (velChanged) {
|
|
965
|
+
flags |= DeltaFlags.VELOCITY;
|
|
966
|
+
delta.vel = currentState.vel;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
if (currentState.angVel !== void 0) {
|
|
970
|
+
const angVelChanged = lastState.angVel === void 0 || Math.abs(currentState.angVel - lastState.angVel) > this._config.velocityThreshold;
|
|
971
|
+
if (angVelChanged) {
|
|
972
|
+
flags |= DeltaFlags.ANGULAR_VELOCITY;
|
|
973
|
+
delta.angVel = currentState.angVel;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
if (currentState.custom) {
|
|
977
|
+
const customChanged = !this._customDataEqual(lastState.custom, currentState.custom);
|
|
978
|
+
if (customChanged) {
|
|
979
|
+
flags |= DeltaFlags.CUSTOM;
|
|
980
|
+
delta.custom = currentState.custom;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
if (flags === 0) {
|
|
984
|
+
return null;
|
|
985
|
+
}
|
|
986
|
+
delta.flags = flags;
|
|
987
|
+
return delta;
|
|
988
|
+
}
|
|
989
|
+
_applyDelta(lastState, delta) {
|
|
990
|
+
const state = {
|
|
991
|
+
netId: delta.netId
|
|
992
|
+
};
|
|
993
|
+
if (delta.flags & DeltaFlags.POSITION) {
|
|
994
|
+
state.pos = delta.pos;
|
|
995
|
+
} else if (lastState?.pos) {
|
|
996
|
+
state.pos = lastState.pos;
|
|
997
|
+
}
|
|
998
|
+
if (delta.flags & DeltaFlags.ROTATION) {
|
|
999
|
+
state.rot = delta.rot;
|
|
1000
|
+
} else if (lastState?.rot !== void 0) {
|
|
1001
|
+
state.rot = lastState.rot;
|
|
1002
|
+
}
|
|
1003
|
+
if (delta.flags & DeltaFlags.VELOCITY) {
|
|
1004
|
+
state.vel = delta.vel;
|
|
1005
|
+
} else if (lastState?.vel) {
|
|
1006
|
+
state.vel = lastState.vel;
|
|
1007
|
+
}
|
|
1008
|
+
if (delta.flags & DeltaFlags.ANGULAR_VELOCITY) {
|
|
1009
|
+
state.angVel = delta.angVel;
|
|
1010
|
+
} else if (lastState?.angVel !== void 0) {
|
|
1011
|
+
state.angVel = lastState.angVel;
|
|
1012
|
+
}
|
|
1013
|
+
if (delta.flags & DeltaFlags.CUSTOM) {
|
|
1014
|
+
state.custom = delta.custom;
|
|
1015
|
+
} else if (lastState?.custom) {
|
|
1016
|
+
state.custom = lastState.custom;
|
|
1017
|
+
}
|
|
1018
|
+
return state;
|
|
1019
|
+
}
|
|
1020
|
+
_customDataEqual(a, b) {
|
|
1021
|
+
if (a === b) return true;
|
|
1022
|
+
if (!a || !b) return false;
|
|
1023
|
+
const keysA = Object.keys(a);
|
|
1024
|
+
const keysB = Object.keys(b);
|
|
1025
|
+
if (keysA.length !== keysB.length) return false;
|
|
1026
|
+
for (const key of keysA) {
|
|
1027
|
+
if (a[key] !== b[key]) return false;
|
|
1028
|
+
}
|
|
1029
|
+
return true;
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
__name(_StateDeltaCompressor, "StateDeltaCompressor");
|
|
1033
|
+
var StateDeltaCompressor = _StateDeltaCompressor;
|
|
1034
|
+
function createStateDeltaCompressor(config) {
|
|
1035
|
+
return new StateDeltaCompressor(config);
|
|
1036
|
+
}
|
|
1037
|
+
__name(createStateDeltaCompressor, "createStateDeltaCompressor");
|
|
1038
|
+
|
|
1039
|
+
// src/systems/NetworkSyncSystem.ts
|
|
1040
|
+
var DEFAULT_CONFIG2 = {
|
|
1041
|
+
bufferSize: 30,
|
|
1042
|
+
interpolationDelay: 100,
|
|
1043
|
+
enableExtrapolation: true,
|
|
1044
|
+
maxExtrapolationTime: 200,
|
|
1045
|
+
useHermiteInterpolation: false
|
|
1046
|
+
};
|
|
1047
|
+
var _NetworkSyncSystem = class _NetworkSyncSystem extends EntitySystem {
|
|
1048
|
+
constructor(config) {
|
|
1049
|
+
super(Matcher.all(NetworkIdentity, NetworkTransform));
|
|
1050
|
+
__publicField(this, "_netIdToEntity", /* @__PURE__ */ new Map());
|
|
1051
|
+
__publicField(this, "_entitySnapshots", /* @__PURE__ */ new Map());
|
|
1052
|
+
__publicField(this, "_interpolator");
|
|
1053
|
+
__publicField(this, "_config");
|
|
1054
|
+
__publicField(this, "_serverTimeOffset", 0);
|
|
1055
|
+
__publicField(this, "_lastSyncTime", 0);
|
|
1056
|
+
__publicField(this, "_renderTime", 0);
|
|
1057
|
+
this._config = {
|
|
1058
|
+
...DEFAULT_CONFIG2,
|
|
1059
|
+
...config
|
|
1060
|
+
};
|
|
1061
|
+
this._interpolator = createTransformInterpolator();
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* @zh 获取配置
|
|
1065
|
+
* @en Get configuration
|
|
1066
|
+
*/
|
|
1067
|
+
get config() {
|
|
1068
|
+
return this._config;
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* @zh 获取服务器时间偏移
|
|
1072
|
+
* @en Get server time offset
|
|
1073
|
+
*/
|
|
1074
|
+
get serverTimeOffset() {
|
|
1075
|
+
return this._serverTimeOffset;
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* @zh 获取当前渲染时间
|
|
1079
|
+
* @en Get current render time
|
|
1080
|
+
*/
|
|
1081
|
+
get renderTime() {
|
|
1082
|
+
return this._renderTime;
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* @zh 处理同步消息(新版,带时间戳)
|
|
1086
|
+
* @en Handle sync message (new version with timestamp)
|
|
1087
|
+
*/
|
|
1088
|
+
handleSyncData(data) {
|
|
1089
|
+
const serverTime = data.timestamp;
|
|
1090
|
+
const clientTime = Date.now();
|
|
1091
|
+
this._serverTimeOffset = serverTime - clientTime;
|
|
1092
|
+
this._lastSyncTime = clientTime;
|
|
1093
|
+
for (const state of data.entities) {
|
|
1094
|
+
this._processEntityState(state, serverTime);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* @zh 处理同步消息(兼容旧版)
|
|
1099
|
+
* @en Handle sync message (backwards compatible)
|
|
1100
|
+
*/
|
|
1101
|
+
handleSync(msg) {
|
|
1102
|
+
const now = Date.now();
|
|
1103
|
+
for (const state of msg.entities) {
|
|
1104
|
+
const entityId = this._netIdToEntity.get(state.netId);
|
|
1105
|
+
if (entityId === void 0) continue;
|
|
1106
|
+
const entity = this.scene?.findEntityById(entityId);
|
|
1107
|
+
if (!entity) continue;
|
|
1108
|
+
const transform = entity.getComponent(NetworkTransform);
|
|
1109
|
+
if (transform && state.pos) {
|
|
1110
|
+
transform.setTarget(state.pos.x, state.pos.y, state.rot ?? 0);
|
|
1111
|
+
}
|
|
1112
|
+
this._processEntityState({
|
|
1113
|
+
netId: state.netId,
|
|
1114
|
+
pos: state.pos,
|
|
1115
|
+
rot: state.rot
|
|
1116
|
+
}, now);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
_processEntityState(state, serverTime) {
|
|
1120
|
+
const entityId = this._netIdToEntity.get(state.netId);
|
|
1121
|
+
if (entityId === void 0) return;
|
|
1122
|
+
let snapshotData = this._entitySnapshots.get(state.netId);
|
|
1123
|
+
if (!snapshotData) {
|
|
1124
|
+
snapshotData = {
|
|
1125
|
+
buffer: createSnapshotBuffer(this._config.bufferSize, this._config.interpolationDelay),
|
|
1126
|
+
lastServerTime: 0
|
|
1127
|
+
};
|
|
1128
|
+
this._entitySnapshots.set(state.netId, snapshotData);
|
|
1129
|
+
}
|
|
1130
|
+
const transformState = {
|
|
1131
|
+
x: state.pos?.x ?? 0,
|
|
1132
|
+
y: state.pos?.y ?? 0,
|
|
1133
|
+
rotation: state.rot ?? 0,
|
|
1134
|
+
velocityX: state.vel?.x ?? 0,
|
|
1135
|
+
velocityY: state.vel?.y ?? 0,
|
|
1136
|
+
angularVelocity: state.angVel ?? 0
|
|
1137
|
+
};
|
|
1138
|
+
const snapshot = {
|
|
1139
|
+
timestamp: serverTime,
|
|
1140
|
+
state: transformState
|
|
1141
|
+
};
|
|
1142
|
+
snapshotData.buffer.push(snapshot);
|
|
1143
|
+
snapshotData.lastServerTime = serverTime;
|
|
1144
|
+
}
|
|
1145
|
+
process(entities) {
|
|
1146
|
+
const deltaTime = Time.deltaTime;
|
|
1147
|
+
const clientTime = Date.now();
|
|
1148
|
+
this._renderTime = clientTime + this._serverTimeOffset;
|
|
1149
|
+
for (const entity of entities) {
|
|
1150
|
+
const transform = this.requireComponent(entity, NetworkTransform);
|
|
1151
|
+
const identity = this.requireComponent(entity, NetworkIdentity);
|
|
1152
|
+
if (identity.bHasAuthority) continue;
|
|
1153
|
+
if (transform.bInterpolate) {
|
|
1154
|
+
this._interpolateEntity(identity.netId, transform, deltaTime);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
_interpolateEntity(netId, transform, deltaTime) {
|
|
1159
|
+
const snapshotData = this._entitySnapshots.get(netId);
|
|
1160
|
+
if (snapshotData && snapshotData.buffer.size >= 2) {
|
|
1161
|
+
const result = snapshotData.buffer.getInterpolationSnapshots(this._renderTime);
|
|
1162
|
+
if (result) {
|
|
1163
|
+
const [prev, next, t] = result;
|
|
1164
|
+
const interpolated = this._interpolator.interpolate(prev.state, next.state, t);
|
|
1165
|
+
transform.currentX = interpolated.x;
|
|
1166
|
+
transform.currentY = interpolated.y;
|
|
1167
|
+
transform.currentRotation = interpolated.rotation;
|
|
1168
|
+
transform.targetX = next.state.x;
|
|
1169
|
+
transform.targetY = next.state.y;
|
|
1170
|
+
transform.targetRotation = next.state.rotation;
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
if (this._config.enableExtrapolation) {
|
|
1174
|
+
const latest = snapshotData.buffer.getLatest();
|
|
1175
|
+
if (latest) {
|
|
1176
|
+
const timeSinceLastSnapshot = this._renderTime - latest.timestamp;
|
|
1177
|
+
if (timeSinceLastSnapshot > 0 && timeSinceLastSnapshot < this._config.maxExtrapolationTime) {
|
|
1178
|
+
const extrapolated = this._interpolator.extrapolate(latest.state, timeSinceLastSnapshot / 1e3);
|
|
1179
|
+
transform.currentX = extrapolated.x;
|
|
1180
|
+
transform.currentY = extrapolated.y;
|
|
1181
|
+
transform.currentRotation = extrapolated.rotation;
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
this._simpleLerp(transform, deltaTime);
|
|
1188
|
+
}
|
|
1189
|
+
_simpleLerp(transform, deltaTime) {
|
|
1190
|
+
const t = Math.min(1, transform.lerpSpeed * deltaTime);
|
|
1191
|
+
transform.currentX += (transform.targetX - transform.currentX) * t;
|
|
1192
|
+
transform.currentY += (transform.targetY - transform.currentY) * t;
|
|
1193
|
+
let angleDiff = transform.targetRotation - transform.currentRotation;
|
|
1194
|
+
while (angleDiff > Math.PI) angleDiff -= Math.PI * 2;
|
|
1195
|
+
while (angleDiff < -Math.PI) angleDiff += Math.PI * 2;
|
|
1196
|
+
transform.currentRotation += angleDiff * t;
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* @zh 注册网络实体
|
|
1200
|
+
* @en Register network entity
|
|
1201
|
+
*/
|
|
1202
|
+
registerEntity(netId, entityId) {
|
|
1203
|
+
this._netIdToEntity.set(netId, entityId);
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* @zh 注销网络实体
|
|
1207
|
+
* @en Unregister network entity
|
|
1208
|
+
*/
|
|
1209
|
+
unregisterEntity(netId) {
|
|
1210
|
+
this._netIdToEntity.delete(netId);
|
|
1211
|
+
this._entitySnapshots.delete(netId);
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* @zh 根据网络 ID 获取实体 ID
|
|
1215
|
+
* @en Get entity ID by network ID
|
|
1216
|
+
*/
|
|
1217
|
+
getEntityId(netId) {
|
|
1218
|
+
return this._netIdToEntity.get(netId);
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* @zh 获取实体的快照缓冲区
|
|
1222
|
+
* @en Get entity's snapshot buffer
|
|
1223
|
+
*/
|
|
1224
|
+
getSnapshotBuffer(netId) {
|
|
1225
|
+
return this._entitySnapshots.get(netId)?.buffer;
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* @zh 清空所有快照缓冲
|
|
1229
|
+
* @en Clear all snapshot buffers
|
|
1230
|
+
*/
|
|
1231
|
+
clearSnapshots() {
|
|
1232
|
+
for (const data of this._entitySnapshots.values()) {
|
|
1233
|
+
data.buffer.clear();
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
onDestroy() {
|
|
1237
|
+
this._netIdToEntity.clear();
|
|
1238
|
+
this._entitySnapshots.clear();
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
__name(_NetworkSyncSystem, "NetworkSyncSystem");
|
|
1242
|
+
var NetworkSyncSystem = _NetworkSyncSystem;
|
|
1243
|
+
|
|
1244
|
+
// src/systems/NetworkSpawnSystem.ts
|
|
1245
|
+
import { EntitySystem as EntitySystem2, Matcher as Matcher2 } from "@esengine/ecs-framework";
|
|
1246
|
+
var _NetworkSpawnSystem = class _NetworkSpawnSystem extends EntitySystem2 {
|
|
1247
|
+
constructor(syncSystem) {
|
|
1248
|
+
super(Matcher2.nothing());
|
|
1249
|
+
__publicField(this, "_syncSystem");
|
|
1250
|
+
__publicField(this, "_prefabFactories", /* @__PURE__ */ new Map());
|
|
1251
|
+
__publicField(this, "_localPlayerId", 0);
|
|
1252
|
+
this._syncSystem = syncSystem;
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* @zh 设置本地玩家 ID
|
|
1256
|
+
* @en Set local player ID
|
|
1257
|
+
*/
|
|
1258
|
+
setLocalPlayerId(id) {
|
|
1259
|
+
this._localPlayerId = id;
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* @zh 处理生成消息
|
|
1263
|
+
* @en Handle spawn message
|
|
1264
|
+
*/
|
|
1265
|
+
handleSpawn(msg) {
|
|
1266
|
+
if (!this.scene) return null;
|
|
1267
|
+
const factory = this._prefabFactories.get(msg.prefab);
|
|
1268
|
+
if (!factory) {
|
|
1269
|
+
this.logger.warn(`Unknown prefab: ${msg.prefab}`);
|
|
1270
|
+
return null;
|
|
1271
|
+
}
|
|
1272
|
+
const entity = factory(this.scene, msg);
|
|
1273
|
+
const identity = entity.addComponent(new NetworkIdentity());
|
|
1274
|
+
identity.netId = msg.netId;
|
|
1275
|
+
identity.ownerId = msg.ownerId;
|
|
1276
|
+
identity.prefabType = msg.prefab;
|
|
1277
|
+
identity.bHasAuthority = msg.ownerId === this._localPlayerId;
|
|
1278
|
+
identity.bIsLocalPlayer = identity.bHasAuthority;
|
|
1279
|
+
const transform = entity.addComponent(new NetworkTransform());
|
|
1280
|
+
transform.setTarget(msg.pos.x, msg.pos.y, msg.rot ?? 0);
|
|
1281
|
+
transform.snap();
|
|
1282
|
+
this._syncSystem.registerEntity(msg.netId, entity.id);
|
|
1283
|
+
return entity;
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* @zh 处理销毁消息
|
|
1287
|
+
* @en Handle despawn message
|
|
1288
|
+
*/
|
|
1289
|
+
handleDespawn(msg) {
|
|
1290
|
+
const entityId = this._syncSystem.getEntityId(msg.netId);
|
|
1291
|
+
if (entityId === void 0) return;
|
|
1292
|
+
const entity = this.scene?.findEntityById(entityId);
|
|
1293
|
+
if (entity) {
|
|
1294
|
+
entity.destroy();
|
|
1295
|
+
}
|
|
1296
|
+
this._syncSystem.unregisterEntity(msg.netId);
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* @zh 注册预制体工厂
|
|
1300
|
+
* @en Register prefab factory
|
|
1301
|
+
*/
|
|
1302
|
+
registerPrefab(prefabType, factory) {
|
|
1303
|
+
this._prefabFactories.set(prefabType, factory);
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* @zh 注销预制体工厂
|
|
1307
|
+
* @en Unregister prefab factory
|
|
1308
|
+
*/
|
|
1309
|
+
unregisterPrefab(prefabType) {
|
|
1310
|
+
this._prefabFactories.delete(prefabType);
|
|
1311
|
+
}
|
|
1312
|
+
onDestroy() {
|
|
1313
|
+
this._prefabFactories.clear();
|
|
1314
|
+
}
|
|
1315
|
+
};
|
|
1316
|
+
__name(_NetworkSpawnSystem, "NetworkSpawnSystem");
|
|
1317
|
+
var NetworkSpawnSystem = _NetworkSpawnSystem;
|
|
1318
|
+
|
|
1319
|
+
// src/systems/NetworkInputSystem.ts
|
|
1320
|
+
import { EntitySystem as EntitySystem3, Matcher as Matcher3 } from "@esengine/ecs-framework";
|
|
1321
|
+
var DEFAULT_CONFIG3 = {
|
|
1322
|
+
sendInterval: 16,
|
|
1323
|
+
mergeIdenticalInputs: true,
|
|
1324
|
+
maxQueueLength: 10
|
|
1325
|
+
};
|
|
1326
|
+
var _NetworkInputSystem = class _NetworkInputSystem extends EntitySystem3 {
|
|
1327
|
+
constructor(networkService, config) {
|
|
1328
|
+
super(Matcher3.nothing());
|
|
1329
|
+
__publicField(this, "_networkService");
|
|
1330
|
+
__publicField(this, "_config");
|
|
1331
|
+
__publicField(this, "_predictionSystem", null);
|
|
1332
|
+
__publicField(this, "_frame", 0);
|
|
1333
|
+
__publicField(this, "_inputSequence", 0);
|
|
1334
|
+
__publicField(this, "_inputQueue", []);
|
|
1335
|
+
__publicField(this, "_lastSendTime", 0);
|
|
1336
|
+
__publicField(this, "_lastMoveDir", {
|
|
1337
|
+
x: 0,
|
|
1338
|
+
y: 0
|
|
1339
|
+
});
|
|
1340
|
+
this._networkService = networkService;
|
|
1341
|
+
this._config = {
|
|
1342
|
+
...DEFAULT_CONFIG3,
|
|
1343
|
+
...config
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* @zh 获取配置
|
|
1348
|
+
* @en Get configuration
|
|
1349
|
+
*/
|
|
1350
|
+
get config() {
|
|
1351
|
+
return this._config;
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* @zh 获取当前帧号
|
|
1355
|
+
* @en Get current frame number
|
|
1356
|
+
*/
|
|
1357
|
+
get frame() {
|
|
1358
|
+
return this._frame;
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* @zh 获取当前输入序列号
|
|
1362
|
+
* @en Get current input sequence
|
|
1363
|
+
*/
|
|
1364
|
+
get inputSequence() {
|
|
1365
|
+
return this._inputSequence;
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* @zh 设置预测系统引用
|
|
1369
|
+
* @en Set prediction system reference
|
|
1370
|
+
*/
|
|
1371
|
+
setPredictionSystem(system) {
|
|
1372
|
+
this._predictionSystem = system;
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* @zh 处理输入队列
|
|
1376
|
+
* @en Process input queue
|
|
1377
|
+
*/
|
|
1378
|
+
process() {
|
|
1379
|
+
if (!this._networkService.isConnected) return;
|
|
1380
|
+
this._frame++;
|
|
1381
|
+
const now = Date.now();
|
|
1382
|
+
if (now - this._lastSendTime < this._config.sendInterval) return;
|
|
1383
|
+
if (this._predictionSystem) {
|
|
1384
|
+
const predictedInput = this._predictionSystem.getInputToSend();
|
|
1385
|
+
if (predictedInput) {
|
|
1386
|
+
this._networkService.sendInput(predictedInput);
|
|
1387
|
+
this._lastSendTime = now;
|
|
1388
|
+
}
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
if (this._inputQueue.length === 0) return;
|
|
1392
|
+
let mergedInput;
|
|
1393
|
+
if (this._config.mergeIdenticalInputs && this._inputQueue.length > 1) {
|
|
1394
|
+
mergedInput = this._mergeInputs(this._inputQueue);
|
|
1395
|
+
this._inputQueue.length = 0;
|
|
1396
|
+
} else {
|
|
1397
|
+
mergedInput = this._inputQueue.shift();
|
|
1398
|
+
}
|
|
1399
|
+
this._inputSequence++;
|
|
1400
|
+
const input = {
|
|
1401
|
+
seq: this._inputSequence,
|
|
1402
|
+
frame: this._frame,
|
|
1403
|
+
timestamp: mergedInput.timestamp,
|
|
1404
|
+
moveDir: mergedInput.moveDir,
|
|
1405
|
+
actions: mergedInput.actions
|
|
1406
|
+
};
|
|
1407
|
+
this._networkService.sendInput(input);
|
|
1408
|
+
this._lastSendTime = now;
|
|
1409
|
+
}
|
|
1410
|
+
_mergeInputs(inputs) {
|
|
1411
|
+
const allActions = [];
|
|
1412
|
+
let lastMoveDir;
|
|
1413
|
+
for (const input of inputs) {
|
|
1414
|
+
if (input.moveDir) {
|
|
1415
|
+
lastMoveDir = input.moveDir;
|
|
1416
|
+
}
|
|
1417
|
+
if (input.actions) {
|
|
1418
|
+
allActions.push(...input.actions);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
return {
|
|
1422
|
+
moveDir: lastMoveDir,
|
|
1423
|
+
actions: allActions.length > 0 ? allActions : void 0,
|
|
1424
|
+
timestamp: inputs[inputs.length - 1].timestamp
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
/**
|
|
1428
|
+
* @zh 添加移动输入
|
|
1429
|
+
* @en Add move input
|
|
1430
|
+
*/
|
|
1431
|
+
addMoveInput(x, y) {
|
|
1432
|
+
if (this._config.mergeIdenticalInputs && this._lastMoveDir.x === x && this._lastMoveDir.y === y && this._inputQueue.length > 0) {
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
this._lastMoveDir = {
|
|
1436
|
+
x,
|
|
1437
|
+
y
|
|
1438
|
+
};
|
|
1439
|
+
if (this._predictionSystem) {
|
|
1440
|
+
this._predictionSystem.setInput(x, y);
|
|
1441
|
+
}
|
|
1442
|
+
this._addToQueue({
|
|
1443
|
+
moveDir: {
|
|
1444
|
+
x,
|
|
1445
|
+
y
|
|
1446
|
+
},
|
|
1447
|
+
timestamp: Date.now()
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* @zh 添加动作输入
|
|
1452
|
+
* @en Add action input
|
|
1453
|
+
*/
|
|
1454
|
+
addActionInput(action) {
|
|
1455
|
+
const lastInput = this._inputQueue[this._inputQueue.length - 1];
|
|
1456
|
+
if (lastInput) {
|
|
1457
|
+
lastInput.actions = lastInput.actions || [];
|
|
1458
|
+
lastInput.actions.push(action);
|
|
1459
|
+
} else {
|
|
1460
|
+
this._addToQueue({
|
|
1461
|
+
actions: [
|
|
1462
|
+
action
|
|
1463
|
+
],
|
|
1464
|
+
timestamp: Date.now()
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
if (this._predictionSystem) {
|
|
1468
|
+
this._predictionSystem.setInput(this._lastMoveDir.x, this._lastMoveDir.y, [
|
|
1469
|
+
action
|
|
1470
|
+
]);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
_addToQueue(input) {
|
|
1474
|
+
this._inputQueue.push(input);
|
|
1475
|
+
while (this._inputQueue.length > this._config.maxQueueLength) {
|
|
1476
|
+
this._inputQueue.shift();
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* @zh 清空输入队列
|
|
1481
|
+
* @en Clear input queue
|
|
1482
|
+
*/
|
|
1483
|
+
clearQueue() {
|
|
1484
|
+
this._inputQueue.length = 0;
|
|
1485
|
+
this._lastMoveDir = {
|
|
1486
|
+
x: 0,
|
|
1487
|
+
y: 0
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
/**
|
|
1491
|
+
* @zh 重置状态
|
|
1492
|
+
* @en Reset state
|
|
1493
|
+
*/
|
|
1494
|
+
reset() {
|
|
1495
|
+
this._frame = 0;
|
|
1496
|
+
this._inputSequence = 0;
|
|
1497
|
+
this.clearQueue();
|
|
1498
|
+
}
|
|
1499
|
+
onDestroy() {
|
|
1500
|
+
this._inputQueue.length = 0;
|
|
1501
|
+
this._predictionSystem = null;
|
|
1502
|
+
}
|
|
1503
|
+
};
|
|
1504
|
+
__name(_NetworkInputSystem, "NetworkInputSystem");
|
|
1505
|
+
var NetworkInputSystem = _NetworkInputSystem;
|
|
1506
|
+
function createNetworkInputSystem(networkService, config) {
|
|
1507
|
+
return new NetworkInputSystem(networkService, config);
|
|
1508
|
+
}
|
|
1509
|
+
__name(createNetworkInputSystem, "createNetworkInputSystem");
|
|
1510
|
+
|
|
1511
|
+
// src/systems/NetworkPredictionSystem.ts
|
|
1512
|
+
import { EntitySystem as EntitySystem4, Matcher as Matcher4, Time as Time2 } from "@esengine/ecs-framework";
|
|
1513
|
+
var DEFAULT_CONFIG4 = {
|
|
1514
|
+
moveSpeed: 200,
|
|
1515
|
+
enabled: true,
|
|
1516
|
+
maxUnacknowledgedInputs: 60,
|
|
1517
|
+
reconciliationThreshold: 0.5,
|
|
1518
|
+
reconciliationSpeed: 10
|
|
1519
|
+
};
|
|
1520
|
+
var _a;
|
|
1521
|
+
var SimpleMovementPredictor = (_a = class {
|
|
1522
|
+
constructor(_moveSpeed) {
|
|
1523
|
+
__publicField(this, "_moveSpeed");
|
|
1524
|
+
this._moveSpeed = _moveSpeed;
|
|
1525
|
+
}
|
|
1526
|
+
predict(state, input, deltaTime) {
|
|
1527
|
+
const velocityX = input.x * this._moveSpeed;
|
|
1528
|
+
const velocityY = input.y * this._moveSpeed;
|
|
1529
|
+
return {
|
|
1530
|
+
x: state.x + velocityX * deltaTime,
|
|
1531
|
+
y: state.y + velocityY * deltaTime,
|
|
1532
|
+
rotation: state.rotation,
|
|
1533
|
+
velocityX,
|
|
1534
|
+
velocityY
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
}, __name(_a, "SimpleMovementPredictor"), _a);
|
|
1538
|
+
var _NetworkPredictionSystem = class _NetworkPredictionSystem extends EntitySystem4 {
|
|
1539
|
+
constructor(config) {
|
|
1540
|
+
super(Matcher4.all(NetworkIdentity, NetworkTransform));
|
|
1541
|
+
__publicField(this, "_config");
|
|
1542
|
+
__publicField(this, "_predictor");
|
|
1543
|
+
__publicField(this, "_prediction", null);
|
|
1544
|
+
__publicField(this, "_localPlayerNetId", -1);
|
|
1545
|
+
__publicField(this, "_currentInput", {
|
|
1546
|
+
x: 0,
|
|
1547
|
+
y: 0
|
|
1548
|
+
});
|
|
1549
|
+
__publicField(this, "_inputSequence", 0);
|
|
1550
|
+
this._config = {
|
|
1551
|
+
...DEFAULT_CONFIG4,
|
|
1552
|
+
...config
|
|
1553
|
+
};
|
|
1554
|
+
this._predictor = new SimpleMovementPredictor(this._config.moveSpeed);
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* @zh 获取配置
|
|
1558
|
+
* @en Get configuration
|
|
1559
|
+
*/
|
|
1560
|
+
get config() {
|
|
1561
|
+
return this._config;
|
|
1562
|
+
}
|
|
1563
|
+
/**
|
|
1564
|
+
* @zh 获取当前输入序列号
|
|
1565
|
+
* @en Get current input sequence number
|
|
1566
|
+
*/
|
|
1567
|
+
get inputSequence() {
|
|
1568
|
+
return this._inputSequence;
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* @zh 获取待确认输入数量
|
|
1572
|
+
* @en Get pending input count
|
|
1573
|
+
*/
|
|
1574
|
+
get pendingInputCount() {
|
|
1575
|
+
return this._prediction?.pendingInputCount ?? 0;
|
|
1576
|
+
}
|
|
1577
|
+
/**
|
|
1578
|
+
* @zh 是否启用预测
|
|
1579
|
+
* @en Whether prediction is enabled
|
|
1580
|
+
*/
|
|
1581
|
+
get enabled() {
|
|
1582
|
+
return this._config.enabled;
|
|
1583
|
+
}
|
|
1584
|
+
set enabled(value) {
|
|
1585
|
+
this._config.enabled = value;
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1588
|
+
* @zh 设置本地玩家网络 ID
|
|
1589
|
+
* @en Set local player network ID
|
|
1590
|
+
*/
|
|
1591
|
+
setLocalPlayerNetId(netId) {
|
|
1592
|
+
this._localPlayerNetId = netId;
|
|
1593
|
+
this._prediction = createClientPrediction(this._predictor, {
|
|
1594
|
+
maxUnacknowledgedInputs: this._config.maxUnacknowledgedInputs,
|
|
1595
|
+
reconciliationThreshold: this._config.reconciliationThreshold,
|
|
1596
|
+
reconciliationSpeed: this._config.reconciliationSpeed
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* @zh 设置移动输入
|
|
1601
|
+
* @en Set movement input
|
|
1602
|
+
*/
|
|
1603
|
+
setInput(x, y, actions) {
|
|
1604
|
+
this._currentInput = {
|
|
1605
|
+
x,
|
|
1606
|
+
y,
|
|
1607
|
+
actions
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* @zh 获取下一个要发送的输入(带序列号)
|
|
1612
|
+
* @en Get next input to send (with sequence number)
|
|
1613
|
+
*/
|
|
1614
|
+
getInputToSend() {
|
|
1615
|
+
if (!this._prediction) return null;
|
|
1616
|
+
const input = this._prediction.getInputToSend();
|
|
1617
|
+
if (!input) return null;
|
|
1618
|
+
return {
|
|
1619
|
+
seq: input.sequence,
|
|
1620
|
+
frame: 0,
|
|
1621
|
+
timestamp: input.timestamp,
|
|
1622
|
+
moveDir: {
|
|
1623
|
+
x: input.input.x,
|
|
1624
|
+
y: input.input.y
|
|
1625
|
+
},
|
|
1626
|
+
actions: input.input.actions
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
/**
|
|
1630
|
+
* @zh 处理服务器同步数据进行校正
|
|
1631
|
+
* @en Process server sync data for reconciliation
|
|
1632
|
+
*/
|
|
1633
|
+
reconcileWithServer(data) {
|
|
1634
|
+
if (!this._prediction || this._localPlayerNetId < 0) return;
|
|
1635
|
+
const localState = data.entities.find((e) => e.netId === this._localPlayerNetId);
|
|
1636
|
+
if (!localState || !localState.pos) return;
|
|
1637
|
+
const serverState = {
|
|
1638
|
+
x: localState.pos.x,
|
|
1639
|
+
y: localState.pos.y,
|
|
1640
|
+
rotation: localState.rot ?? 0,
|
|
1641
|
+
velocityX: localState.vel?.x ?? 0,
|
|
1642
|
+
velocityY: localState.vel?.y ?? 0
|
|
1643
|
+
};
|
|
1644
|
+
if (data.ackSeq !== void 0) {
|
|
1645
|
+
this._prediction.reconcile(serverState, data.ackSeq, (state) => ({
|
|
1646
|
+
x: state.x,
|
|
1647
|
+
y: state.y
|
|
1648
|
+
}), Time2.deltaTime);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
process(entities) {
|
|
1652
|
+
if (!this._config.enabled || !this._prediction) return;
|
|
1653
|
+
const deltaTime = Time2.deltaTime;
|
|
1654
|
+
for (const entity of entities) {
|
|
1655
|
+
const identity = this.requireComponent(entity, NetworkIdentity);
|
|
1656
|
+
if (!identity.bHasAuthority || identity.netId !== this._localPlayerNetId) continue;
|
|
1657
|
+
const transform = this.requireComponent(entity, NetworkTransform);
|
|
1658
|
+
const currentState = {
|
|
1659
|
+
x: transform.currentX,
|
|
1660
|
+
y: transform.currentY,
|
|
1661
|
+
rotation: transform.currentRotation,
|
|
1662
|
+
velocityX: 0,
|
|
1663
|
+
velocityY: 0
|
|
1664
|
+
};
|
|
1665
|
+
if (this._currentInput.x !== 0 || this._currentInput.y !== 0) {
|
|
1666
|
+
const predicted = this._prediction.recordInput(this._currentInput, currentState, deltaTime);
|
|
1667
|
+
transform.currentX = predicted.x;
|
|
1668
|
+
transform.currentY = predicted.y;
|
|
1669
|
+
transform.currentRotation = predicted.rotation;
|
|
1670
|
+
transform.targetX = predicted.x;
|
|
1671
|
+
transform.targetY = predicted.y;
|
|
1672
|
+
transform.targetRotation = predicted.rotation;
|
|
1673
|
+
this._inputSequence = this._prediction.currentSequence;
|
|
1674
|
+
}
|
|
1675
|
+
const offset = this._prediction.correctionOffset;
|
|
1676
|
+
if (Math.abs(offset.x) > 0.01 || Math.abs(offset.y) > 0.01) {
|
|
1677
|
+
transform.currentX += offset.x * deltaTime * 5;
|
|
1678
|
+
transform.currentY += offset.y * deltaTime * 5;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* @zh 重置预测状态
|
|
1684
|
+
* @en Reset prediction state
|
|
1685
|
+
*/
|
|
1686
|
+
reset() {
|
|
1687
|
+
this._prediction?.clear();
|
|
1688
|
+
this._inputSequence = 0;
|
|
1689
|
+
this._currentInput = {
|
|
1690
|
+
x: 0,
|
|
1691
|
+
y: 0
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
onDestroy() {
|
|
1695
|
+
this._prediction?.clear();
|
|
1696
|
+
this._prediction = null;
|
|
1697
|
+
}
|
|
597
1698
|
};
|
|
598
|
-
__name(
|
|
599
|
-
var
|
|
1699
|
+
__name(_NetworkPredictionSystem, "NetworkPredictionSystem");
|
|
1700
|
+
var NetworkPredictionSystem = _NetworkPredictionSystem;
|
|
1701
|
+
function createNetworkPredictionSystem(config) {
|
|
1702
|
+
return new NetworkPredictionSystem(config);
|
|
1703
|
+
}
|
|
1704
|
+
__name(createNetworkPredictionSystem, "createNetworkPredictionSystem");
|
|
600
1705
|
|
|
601
|
-
// src/systems/
|
|
602
|
-
import { EntitySystem as
|
|
603
|
-
var
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
1706
|
+
// src/systems/NetworkAOISystem.ts
|
|
1707
|
+
import { EntitySystem as EntitySystem5, Matcher as Matcher5 } from "@esengine/ecs-framework";
|
|
1708
|
+
var DEFAULT_CONFIG5 = {
|
|
1709
|
+
cellSize: 100,
|
|
1710
|
+
defaultViewRange: 500,
|
|
1711
|
+
enabled: true
|
|
1712
|
+
};
|
|
1713
|
+
var _NetworkAOISystem = class _NetworkAOISystem extends EntitySystem5 {
|
|
1714
|
+
constructor(config) {
|
|
1715
|
+
super(Matcher5.all(NetworkIdentity, NetworkTransform));
|
|
1716
|
+
__publicField(this, "_config");
|
|
1717
|
+
__publicField(this, "_observers", /* @__PURE__ */ new Map());
|
|
1718
|
+
__publicField(this, "_cells", /* @__PURE__ */ new Map());
|
|
1719
|
+
__publicField(this, "_listeners", /* @__PURE__ */ new Set());
|
|
1720
|
+
__publicField(this, "_entityNetIdMap", /* @__PURE__ */ new Map());
|
|
1721
|
+
__publicField(this, "_netIdEntityMap", /* @__PURE__ */ new Map());
|
|
1722
|
+
this._config = {
|
|
1723
|
+
...DEFAULT_CONFIG5,
|
|
1724
|
+
...config
|
|
1725
|
+
};
|
|
610
1726
|
}
|
|
611
1727
|
/**
|
|
612
|
-
* @zh
|
|
613
|
-
* @en
|
|
1728
|
+
* @zh 获取配置
|
|
1729
|
+
* @en Get configuration
|
|
614
1730
|
*/
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
this._frame++;
|
|
618
|
-
while (this._inputQueue.length > 0) {
|
|
619
|
-
const input = this._inputQueue.shift();
|
|
620
|
-
input.frame = this._frame;
|
|
621
|
-
this._networkService.sendInput(input);
|
|
622
|
-
}
|
|
1731
|
+
get config() {
|
|
1732
|
+
return this._config;
|
|
623
1733
|
}
|
|
624
1734
|
/**
|
|
625
|
-
* @zh
|
|
626
|
-
* @en
|
|
1735
|
+
* @zh 是否启用
|
|
1736
|
+
* @en Is enabled
|
|
627
1737
|
*/
|
|
628
|
-
|
|
629
|
-
this.
|
|
630
|
-
|
|
631
|
-
|
|
1738
|
+
get enabled() {
|
|
1739
|
+
return this._config.enabled;
|
|
1740
|
+
}
|
|
1741
|
+
set enabled(value) {
|
|
1742
|
+
this._config.enabled = value;
|
|
1743
|
+
}
|
|
1744
|
+
/**
|
|
1745
|
+
* @zh 观察者数量
|
|
1746
|
+
* @en Observer count
|
|
1747
|
+
*/
|
|
1748
|
+
get observerCount() {
|
|
1749
|
+
return this._observers.size;
|
|
1750
|
+
}
|
|
1751
|
+
// =========================================================================
|
|
1752
|
+
// 观察者管理 | Observer Management
|
|
1753
|
+
// =========================================================================
|
|
1754
|
+
/**
|
|
1755
|
+
* @zh 添加观察者(通常是玩家实体)
|
|
1756
|
+
* @en Add observer (usually player entity)
|
|
1757
|
+
*/
|
|
1758
|
+
addObserver(netId, x, y, viewRange) {
|
|
1759
|
+
if (this._observers.has(netId)) {
|
|
1760
|
+
this.updateObserverPosition(netId, x, y);
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
const range = viewRange ?? this._config.defaultViewRange;
|
|
1764
|
+
const cellKey = this._getCellKey(x, y);
|
|
1765
|
+
const data = {
|
|
1766
|
+
netId,
|
|
1767
|
+
position: {
|
|
632
1768
|
x,
|
|
633
1769
|
y
|
|
1770
|
+
},
|
|
1771
|
+
viewRange: range,
|
|
1772
|
+
viewRangeSq: range * range,
|
|
1773
|
+
cellKey,
|
|
1774
|
+
visibleEntities: /* @__PURE__ */ new Set()
|
|
1775
|
+
};
|
|
1776
|
+
this._observers.set(netId, data);
|
|
1777
|
+
this._addToCell(cellKey, netId);
|
|
1778
|
+
this._updateVisibility(data);
|
|
1779
|
+
}
|
|
1780
|
+
/**
|
|
1781
|
+
* @zh 移除观察者
|
|
1782
|
+
* @en Remove observer
|
|
1783
|
+
*/
|
|
1784
|
+
removeObserver(netId) {
|
|
1785
|
+
const data = this._observers.get(netId);
|
|
1786
|
+
if (!data) return false;
|
|
1787
|
+
for (const visibleNetId of data.visibleEntities) {
|
|
1788
|
+
this._emitEvent({
|
|
1789
|
+
type: "exit",
|
|
1790
|
+
observerNetId: netId,
|
|
1791
|
+
targetNetId: visibleNetId
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
this._removeFromCell(data.cellKey, netId);
|
|
1795
|
+
this._observers.delete(netId);
|
|
1796
|
+
return true;
|
|
1797
|
+
}
|
|
1798
|
+
/**
|
|
1799
|
+
* @zh 更新观察者位置
|
|
1800
|
+
* @en Update observer position
|
|
1801
|
+
*/
|
|
1802
|
+
updateObserverPosition(netId, x, y) {
|
|
1803
|
+
const data = this._observers.get(netId);
|
|
1804
|
+
if (!data) return;
|
|
1805
|
+
const newCellKey = this._getCellKey(x, y);
|
|
1806
|
+
if (newCellKey !== data.cellKey) {
|
|
1807
|
+
this._removeFromCell(data.cellKey, netId);
|
|
1808
|
+
data.cellKey = newCellKey;
|
|
1809
|
+
this._addToCell(newCellKey, netId);
|
|
1810
|
+
}
|
|
1811
|
+
data.position.x = x;
|
|
1812
|
+
data.position.y = y;
|
|
1813
|
+
this._updateVisibility(data);
|
|
1814
|
+
}
|
|
1815
|
+
/**
|
|
1816
|
+
* @zh 更新观察者视野范围
|
|
1817
|
+
* @en Update observer view range
|
|
1818
|
+
*/
|
|
1819
|
+
updateObserverViewRange(netId, viewRange) {
|
|
1820
|
+
const data = this._observers.get(netId);
|
|
1821
|
+
if (!data) return;
|
|
1822
|
+
data.viewRange = viewRange;
|
|
1823
|
+
data.viewRangeSq = viewRange * viewRange;
|
|
1824
|
+
this._updateVisibility(data);
|
|
1825
|
+
}
|
|
1826
|
+
// =========================================================================
|
|
1827
|
+
// 实体管理 | Entity Management
|
|
1828
|
+
// =========================================================================
|
|
1829
|
+
/**
|
|
1830
|
+
* @zh 注册网络实体
|
|
1831
|
+
* @en Register network entity
|
|
1832
|
+
*/
|
|
1833
|
+
registerEntity(entity, netId) {
|
|
1834
|
+
this._entityNetIdMap.set(entity, netId);
|
|
1835
|
+
this._netIdEntityMap.set(netId, entity);
|
|
1836
|
+
}
|
|
1837
|
+
/**
|
|
1838
|
+
* @zh 注销网络实体
|
|
1839
|
+
* @en Unregister network entity
|
|
1840
|
+
*/
|
|
1841
|
+
unregisterEntity(entity) {
|
|
1842
|
+
const netId = this._entityNetIdMap.get(entity);
|
|
1843
|
+
if (netId !== void 0) {
|
|
1844
|
+
for (const [, data] of this._observers) {
|
|
1845
|
+
if (data.visibleEntities.has(netId)) {
|
|
1846
|
+
data.visibleEntities.delete(netId);
|
|
1847
|
+
this._emitEvent({
|
|
1848
|
+
type: "exit",
|
|
1849
|
+
observerNetId: data.netId,
|
|
1850
|
+
targetNetId: netId
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
this._netIdEntityMap.delete(netId);
|
|
1855
|
+
}
|
|
1856
|
+
this._entityNetIdMap.delete(entity);
|
|
1857
|
+
}
|
|
1858
|
+
// =========================================================================
|
|
1859
|
+
// 查询接口 | Query Interface
|
|
1860
|
+
// =========================================================================
|
|
1861
|
+
/**
|
|
1862
|
+
* @zh 获取观察者能看到的实体网络 ID 列表
|
|
1863
|
+
* @en Get list of entity network IDs visible to observer
|
|
1864
|
+
*/
|
|
1865
|
+
getVisibleEntities(observerNetId) {
|
|
1866
|
+
const data = this._observers.get(observerNetId);
|
|
1867
|
+
return data ? Array.from(data.visibleEntities) : [];
|
|
1868
|
+
}
|
|
1869
|
+
/**
|
|
1870
|
+
* @zh 获取能看到指定实体的观察者网络 ID 列表
|
|
1871
|
+
* @en Get list of observer network IDs that can see the entity
|
|
1872
|
+
*/
|
|
1873
|
+
getObserversOf(entityNetId) {
|
|
1874
|
+
const observers = [];
|
|
1875
|
+
for (const [, data] of this._observers) {
|
|
1876
|
+
if (data.visibleEntities.has(entityNetId)) {
|
|
1877
|
+
observers.push(data.netId);
|
|
634
1878
|
}
|
|
1879
|
+
}
|
|
1880
|
+
return observers;
|
|
1881
|
+
}
|
|
1882
|
+
/**
|
|
1883
|
+
* @zh 检查观察者是否能看到目标
|
|
1884
|
+
* @en Check if observer can see target
|
|
1885
|
+
*/
|
|
1886
|
+
canSee(observerNetId, targetNetId) {
|
|
1887
|
+
const data = this._observers.get(observerNetId);
|
|
1888
|
+
return data?.visibleEntities.has(targetNetId) ?? false;
|
|
1889
|
+
}
|
|
1890
|
+
/**
|
|
1891
|
+
* @zh 过滤同步数据,只保留观察者能看到的实体
|
|
1892
|
+
* @en Filter sync data to only include entities visible to observer
|
|
1893
|
+
*/
|
|
1894
|
+
filterSyncData(observerNetId, entities) {
|
|
1895
|
+
if (!this._config.enabled) {
|
|
1896
|
+
return entities;
|
|
1897
|
+
}
|
|
1898
|
+
const data = this._observers.get(observerNetId);
|
|
1899
|
+
if (!data) {
|
|
1900
|
+
return entities;
|
|
1901
|
+
}
|
|
1902
|
+
return entities.filter((entity) => {
|
|
1903
|
+
if (entity.netId === observerNetId) return true;
|
|
1904
|
+
return data.visibleEntities.has(entity.netId);
|
|
635
1905
|
});
|
|
636
1906
|
}
|
|
1907
|
+
// =========================================================================
|
|
1908
|
+
// 事件系统 | Event System
|
|
1909
|
+
// =========================================================================
|
|
637
1910
|
/**
|
|
638
|
-
* @zh
|
|
639
|
-
* @en Add
|
|
1911
|
+
* @zh 添加事件监听器
|
|
1912
|
+
* @en Add event listener
|
|
640
1913
|
*/
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
1914
|
+
addListener(listener) {
|
|
1915
|
+
this._listeners.add(listener);
|
|
1916
|
+
}
|
|
1917
|
+
/**
|
|
1918
|
+
* @zh 移除事件监听器
|
|
1919
|
+
* @en Remove event listener
|
|
1920
|
+
*/
|
|
1921
|
+
removeListener(listener) {
|
|
1922
|
+
this._listeners.delete(listener);
|
|
1923
|
+
}
|
|
1924
|
+
// =========================================================================
|
|
1925
|
+
// 系统生命周期 | System Lifecycle
|
|
1926
|
+
// =========================================================================
|
|
1927
|
+
process(entities) {
|
|
1928
|
+
if (!this._config.enabled) return;
|
|
1929
|
+
for (const entity of entities) {
|
|
1930
|
+
const identity = this.requireComponent(entity, NetworkIdentity);
|
|
1931
|
+
const transform = this.requireComponent(entity, NetworkTransform);
|
|
1932
|
+
if (!this._entityNetIdMap.has(entity)) {
|
|
1933
|
+
this.registerEntity(entity, identity.netId);
|
|
1934
|
+
}
|
|
1935
|
+
if (identity.bHasAuthority && this._observers.has(identity.netId)) {
|
|
1936
|
+
this.updateObserverPosition(identity.netId, transform.currentX, transform.currentY);
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
this._updateAllObserversVisibility(entities);
|
|
1940
|
+
}
|
|
1941
|
+
_updateAllObserversVisibility(entities) {
|
|
1942
|
+
for (const [, data] of this._observers) {
|
|
1943
|
+
const newVisible = /* @__PURE__ */ new Set();
|
|
1944
|
+
for (const entity of entities) {
|
|
1945
|
+
const identity = this.requireComponent(entity, NetworkIdentity);
|
|
1946
|
+
const transform = this.requireComponent(entity, NetworkTransform);
|
|
1947
|
+
if (identity.netId === data.netId) continue;
|
|
1948
|
+
const dx = transform.currentX - data.position.x;
|
|
1949
|
+
const dy = transform.currentY - data.position.y;
|
|
1950
|
+
const distSq = dx * dx + dy * dy;
|
|
1951
|
+
if (distSq <= data.viewRangeSq) {
|
|
1952
|
+
newVisible.add(identity.netId);
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
for (const netId of newVisible) {
|
|
1956
|
+
if (!data.visibleEntities.has(netId)) {
|
|
1957
|
+
this._emitEvent({
|
|
1958
|
+
type: "enter",
|
|
1959
|
+
observerNetId: data.netId,
|
|
1960
|
+
targetNetId: netId
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
for (const netId of data.visibleEntities) {
|
|
1965
|
+
if (!newVisible.has(netId)) {
|
|
1966
|
+
this._emitEvent({
|
|
1967
|
+
type: "exit",
|
|
1968
|
+
observerNetId: data.netId,
|
|
1969
|
+
targetNetId: netId
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
data.visibleEntities = newVisible;
|
|
653
1974
|
}
|
|
654
1975
|
}
|
|
1976
|
+
/**
|
|
1977
|
+
* @zh 清除所有数据
|
|
1978
|
+
* @en Clear all data
|
|
1979
|
+
*/
|
|
1980
|
+
clear() {
|
|
1981
|
+
this._observers.clear();
|
|
1982
|
+
this._cells.clear();
|
|
1983
|
+
this._entityNetIdMap.clear();
|
|
1984
|
+
this._netIdEntityMap.clear();
|
|
1985
|
+
}
|
|
655
1986
|
onDestroy() {
|
|
656
|
-
this.
|
|
1987
|
+
this.clear();
|
|
1988
|
+
this._listeners.clear();
|
|
1989
|
+
}
|
|
1990
|
+
// =========================================================================
|
|
1991
|
+
// 私有方法 | Private Methods
|
|
1992
|
+
// =========================================================================
|
|
1993
|
+
_getCellKey(x, y) {
|
|
1994
|
+
const cellX = Math.floor(x / this._config.cellSize);
|
|
1995
|
+
const cellY = Math.floor(y / this._config.cellSize);
|
|
1996
|
+
return `${cellX},${cellY}`;
|
|
1997
|
+
}
|
|
1998
|
+
_addToCell(cellKey, netId) {
|
|
1999
|
+
let cell = this._cells.get(cellKey);
|
|
2000
|
+
if (!cell) {
|
|
2001
|
+
cell = /* @__PURE__ */ new Set();
|
|
2002
|
+
this._cells.set(cellKey, cell);
|
|
2003
|
+
}
|
|
2004
|
+
cell.add(netId);
|
|
2005
|
+
}
|
|
2006
|
+
_removeFromCell(cellKey, netId) {
|
|
2007
|
+
const cell = this._cells.get(cellKey);
|
|
2008
|
+
if (cell) {
|
|
2009
|
+
cell.delete(netId);
|
|
2010
|
+
if (cell.size === 0) {
|
|
2011
|
+
this._cells.delete(cellKey);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
_updateVisibility(data) {
|
|
2016
|
+
}
|
|
2017
|
+
_emitEvent(event) {
|
|
2018
|
+
for (const listener of this._listeners) {
|
|
2019
|
+
try {
|
|
2020
|
+
listener(event);
|
|
2021
|
+
} catch (e) {
|
|
2022
|
+
console.error("[NetworkAOISystem] Listener error:", e);
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
657
2025
|
}
|
|
658
2026
|
};
|
|
659
|
-
__name(
|
|
660
|
-
var
|
|
2027
|
+
__name(_NetworkAOISystem, "NetworkAOISystem");
|
|
2028
|
+
var NetworkAOISystem = _NetworkAOISystem;
|
|
2029
|
+
function createNetworkAOISystem(config) {
|
|
2030
|
+
return new NetworkAOISystem(config);
|
|
2031
|
+
}
|
|
2032
|
+
__name(createNetworkAOISystem, "createNetworkAOISystem");
|
|
661
2033
|
|
|
662
2034
|
// src/NetworkPlugin.ts
|
|
2035
|
+
var DEFAULT_CONFIG6 = {
|
|
2036
|
+
enablePrediction: true,
|
|
2037
|
+
enableAutoReconnect: true,
|
|
2038
|
+
maxReconnectAttempts: 5,
|
|
2039
|
+
reconnectInterval: 2e3,
|
|
2040
|
+
enableAOI: false
|
|
2041
|
+
};
|
|
663
2042
|
var _NetworkPlugin = class _NetworkPlugin {
|
|
664
|
-
constructor() {
|
|
2043
|
+
constructor(config) {
|
|
665
2044
|
__publicField(this, "name", "@esengine/network");
|
|
666
|
-
__publicField(this, "version", "2.
|
|
2045
|
+
__publicField(this, "version", "2.1.0");
|
|
2046
|
+
__publicField(this, "_config");
|
|
667
2047
|
__publicField(this, "_networkService");
|
|
668
2048
|
__publicField(this, "_syncSystem");
|
|
669
2049
|
__publicField(this, "_spawnSystem");
|
|
670
2050
|
__publicField(this, "_inputSystem");
|
|
2051
|
+
__publicField(this, "_predictionSystem", null);
|
|
2052
|
+
__publicField(this, "_aoiSystem", null);
|
|
671
2053
|
__publicField(this, "_localPlayerId", 0);
|
|
2054
|
+
__publicField(this, "_reconnectState", null);
|
|
2055
|
+
__publicField(this, "_reconnectTimer", null);
|
|
2056
|
+
__publicField(this, "_lastConnectOptions", null);
|
|
2057
|
+
this._config = {
|
|
2058
|
+
...DEFAULT_CONFIG6,
|
|
2059
|
+
...config
|
|
2060
|
+
};
|
|
672
2061
|
}
|
|
2062
|
+
// =========================================================================
|
|
2063
|
+
// Getters | 属性访问器
|
|
2064
|
+
// =========================================================================
|
|
673
2065
|
/**
|
|
674
2066
|
* @zh 网络服务
|
|
675
2067
|
* @en Network service
|
|
@@ -698,6 +2090,20 @@ var _NetworkPlugin = class _NetworkPlugin {
|
|
|
698
2090
|
get inputSystem() {
|
|
699
2091
|
return this._inputSystem;
|
|
700
2092
|
}
|
|
2093
|
+
/**
|
|
2094
|
+
* @zh 预测系统
|
|
2095
|
+
* @en Prediction system
|
|
2096
|
+
*/
|
|
2097
|
+
get predictionSystem() {
|
|
2098
|
+
return this._predictionSystem;
|
|
2099
|
+
}
|
|
2100
|
+
/**
|
|
2101
|
+
* @zh AOI 系统
|
|
2102
|
+
* @en AOI system
|
|
2103
|
+
*/
|
|
2104
|
+
get aoiSystem() {
|
|
2105
|
+
return this._aoiSystem;
|
|
2106
|
+
}
|
|
701
2107
|
/**
|
|
702
2108
|
* @zh 本地玩家 ID
|
|
703
2109
|
* @en Local player ID
|
|
@@ -712,6 +2118,30 @@ var _NetworkPlugin = class _NetworkPlugin {
|
|
|
712
2118
|
get isConnected() {
|
|
713
2119
|
return this._networkService?.isConnected ?? false;
|
|
714
2120
|
}
|
|
2121
|
+
/**
|
|
2122
|
+
* @zh 是否正在重连
|
|
2123
|
+
* @en Is reconnecting
|
|
2124
|
+
*/
|
|
2125
|
+
get isReconnecting() {
|
|
2126
|
+
return this._reconnectState?.isReconnecting ?? false;
|
|
2127
|
+
}
|
|
2128
|
+
/**
|
|
2129
|
+
* @zh 是否启用预测
|
|
2130
|
+
* @en Is prediction enabled
|
|
2131
|
+
*/
|
|
2132
|
+
get isPredictionEnabled() {
|
|
2133
|
+
return this._config.enablePrediction && this._predictionSystem !== null;
|
|
2134
|
+
}
|
|
2135
|
+
/**
|
|
2136
|
+
* @zh 是否启用 AOI
|
|
2137
|
+
* @en Is AOI enabled
|
|
2138
|
+
*/
|
|
2139
|
+
get isAOIEnabled() {
|
|
2140
|
+
return this._config.enableAOI && this._aoiSystem !== null;
|
|
2141
|
+
}
|
|
2142
|
+
// =========================================================================
|
|
2143
|
+
// Plugin Lifecycle | 插件生命周期
|
|
2144
|
+
// =========================================================================
|
|
715
2145
|
/**
|
|
716
2146
|
* @zh 安装插件
|
|
717
2147
|
* @en Install plugin
|
|
@@ -728,12 +2158,22 @@ var _NetworkPlugin = class _NetworkPlugin {
|
|
|
728
2158
|
* @en Uninstall plugin
|
|
729
2159
|
*/
|
|
730
2160
|
uninstall() {
|
|
2161
|
+
this._clearReconnectTimer();
|
|
731
2162
|
this._networkService?.disconnect();
|
|
732
2163
|
}
|
|
733
2164
|
_setupSystems(scene) {
|
|
734
|
-
this._syncSystem = new NetworkSyncSystem();
|
|
2165
|
+
this._syncSystem = new NetworkSyncSystem(this._config.syncConfig);
|
|
735
2166
|
this._spawnSystem = new NetworkSpawnSystem(this._syncSystem);
|
|
736
|
-
this._inputSystem = new NetworkInputSystem(this._networkService);
|
|
2167
|
+
this._inputSystem = new NetworkInputSystem(this._networkService, this._config.inputConfig);
|
|
2168
|
+
if (this._config.enablePrediction) {
|
|
2169
|
+
this._predictionSystem = new NetworkPredictionSystem(this._config.predictionConfig);
|
|
2170
|
+
this._inputSystem.setPredictionSystem(this._predictionSystem);
|
|
2171
|
+
scene.addSystem(this._predictionSystem);
|
|
2172
|
+
}
|
|
2173
|
+
if (this._config.enableAOI) {
|
|
2174
|
+
this._aoiSystem = new NetworkAOISystem(this._config.aoiConfig);
|
|
2175
|
+
scene.addSystem(this._aoiSystem);
|
|
2176
|
+
}
|
|
737
2177
|
scene.addSystem(this._syncSystem);
|
|
738
2178
|
scene.addSystem(this._spawnSystem);
|
|
739
2179
|
scene.addSystem(this._inputSystem);
|
|
@@ -741,21 +2181,34 @@ var _NetworkPlugin = class _NetworkPlugin {
|
|
|
741
2181
|
}
|
|
742
2182
|
_setupMessageHandlers() {
|
|
743
2183
|
this._networkService.onSync((data) => {
|
|
744
|
-
this._syncSystem.
|
|
745
|
-
|
|
746
|
-
|
|
2184
|
+
this._syncSystem.handleSyncData(data);
|
|
2185
|
+
if (this._predictionSystem) {
|
|
2186
|
+
this._predictionSystem.reconcileWithServer(data);
|
|
2187
|
+
}
|
|
747
2188
|
}).onSpawn((data) => {
|
|
748
2189
|
this._spawnSystem.handleSpawn(data);
|
|
749
2190
|
}).onDespawn((data) => {
|
|
750
2191
|
this._spawnSystem.handleDespawn(data);
|
|
751
2192
|
});
|
|
2193
|
+
this._networkService.on("fullState", (data) => {
|
|
2194
|
+
this._handleFullState(data);
|
|
2195
|
+
});
|
|
752
2196
|
}
|
|
2197
|
+
// =========================================================================
|
|
2198
|
+
// Connection | 连接管理
|
|
2199
|
+
// =========================================================================
|
|
753
2200
|
/**
|
|
754
2201
|
* @zh 连接到服务器
|
|
755
2202
|
* @en Connect to server
|
|
756
2203
|
*/
|
|
757
2204
|
async connect(options) {
|
|
2205
|
+
this._lastConnectOptions = options;
|
|
758
2206
|
try {
|
|
2207
|
+
const originalOnDisconnect = options.onDisconnect;
|
|
2208
|
+
options.onDisconnect = (reason) => {
|
|
2209
|
+
originalOnDisconnect?.(reason);
|
|
2210
|
+
this._handleDisconnect(reason);
|
|
2211
|
+
};
|
|
759
2212
|
await this._networkService.connect(options);
|
|
760
2213
|
const result = await this._networkService.call("join", {
|
|
761
2214
|
playerName: options.playerName,
|
|
@@ -763,8 +2216,20 @@ var _NetworkPlugin = class _NetworkPlugin {
|
|
|
763
2216
|
});
|
|
764
2217
|
this._localPlayerId = result.playerId;
|
|
765
2218
|
this._spawnSystem.setLocalPlayerId(this._localPlayerId);
|
|
2219
|
+
if (this._predictionSystem) {
|
|
2220
|
+
}
|
|
2221
|
+
if (this._config.enableAutoReconnect) {
|
|
2222
|
+
this._reconnectState = {
|
|
2223
|
+
token: this._generateReconnectToken(),
|
|
2224
|
+
playerId: result.playerId,
|
|
2225
|
+
roomId: result.roomId,
|
|
2226
|
+
attempts: 0,
|
|
2227
|
+
isReconnecting: false
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
766
2230
|
return true;
|
|
767
2231
|
} catch (err) {
|
|
2232
|
+
console.error("[NetworkPlugin] Connection failed:", err);
|
|
768
2233
|
return false;
|
|
769
2234
|
}
|
|
770
2235
|
}
|
|
@@ -773,386 +2238,181 @@ var _NetworkPlugin = class _NetworkPlugin {
|
|
|
773
2238
|
* @en Disconnect
|
|
774
2239
|
*/
|
|
775
2240
|
async disconnect() {
|
|
2241
|
+
this._clearReconnectTimer();
|
|
2242
|
+
this._reconnectState = null;
|
|
776
2243
|
try {
|
|
777
2244
|
await this._networkService.call("leave", void 0);
|
|
778
2245
|
} catch {
|
|
779
2246
|
}
|
|
780
2247
|
this._networkService.disconnect();
|
|
2248
|
+
this._cleanup();
|
|
781
2249
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
registerPrefab(prefabType, factory) {
|
|
787
|
-
this._spawnSystem?.registerPrefab(prefabType, factory);
|
|
788
|
-
}
|
|
789
|
-
/**
|
|
790
|
-
* @zh 发送移动输入
|
|
791
|
-
* @en Send move input
|
|
792
|
-
*/
|
|
793
|
-
sendMoveInput(x, y) {
|
|
794
|
-
this._inputSystem?.addMoveInput(x, y);
|
|
795
|
-
}
|
|
796
|
-
/**
|
|
797
|
-
* @zh 发送动作输入
|
|
798
|
-
* @en Send action input
|
|
799
|
-
*/
|
|
800
|
-
sendActionInput(action) {
|
|
801
|
-
this._inputSystem?.addActionInput(action);
|
|
802
|
-
}
|
|
803
|
-
};
|
|
804
|
-
__name(_NetworkPlugin, "NetworkPlugin");
|
|
805
|
-
var NetworkPlugin = _NetworkPlugin;
|
|
806
|
-
|
|
807
|
-
// src/sync/SnapshotBuffer.ts
|
|
808
|
-
var _SnapshotBuffer = class _SnapshotBuffer {
|
|
809
|
-
constructor(config) {
|
|
810
|
-
__publicField(this, "_buffer", []);
|
|
811
|
-
__publicField(this, "_maxSize");
|
|
812
|
-
__publicField(this, "_interpolationDelay");
|
|
813
|
-
this._maxSize = config.maxSize;
|
|
814
|
-
this._interpolationDelay = config.interpolationDelay;
|
|
815
|
-
}
|
|
816
|
-
get size() {
|
|
817
|
-
return this._buffer.length;
|
|
818
|
-
}
|
|
819
|
-
/**
|
|
820
|
-
* @zh 获取插值延迟
|
|
821
|
-
* @en Get interpolation delay
|
|
822
|
-
*/
|
|
823
|
-
get interpolationDelay() {
|
|
824
|
-
return this._interpolationDelay;
|
|
825
|
-
}
|
|
826
|
-
/**
|
|
827
|
-
* @zh 添加快照
|
|
828
|
-
* @en Add snapshot
|
|
829
|
-
*/
|
|
830
|
-
push(snapshot) {
|
|
831
|
-
let insertIndex = this._buffer.length;
|
|
832
|
-
for (let i = this._buffer.length - 1; i >= 0; i--) {
|
|
833
|
-
if (this._buffer[i].timestamp <= snapshot.timestamp) {
|
|
834
|
-
insertIndex = i + 1;
|
|
835
|
-
break;
|
|
836
|
-
}
|
|
837
|
-
if (i === 0) {
|
|
838
|
-
insertIndex = 0;
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
this._buffer.splice(insertIndex, 0, snapshot);
|
|
842
|
-
while (this._buffer.length > this._maxSize) {
|
|
843
|
-
this._buffer.shift();
|
|
2250
|
+
_handleDisconnect(reason) {
|
|
2251
|
+
console.log("[NetworkPlugin] Disconnected:", reason);
|
|
2252
|
+
if (this._config.enableAutoReconnect && this._reconnectState && !this._reconnectState.isReconnecting) {
|
|
2253
|
+
this._attemptReconnect();
|
|
844
2254
|
}
|
|
845
2255
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
return null;
|
|
2256
|
+
_attemptReconnect() {
|
|
2257
|
+
if (!this._reconnectState || !this._lastConnectOptions) return;
|
|
2258
|
+
if (this._reconnectState.attempts >= this._config.maxReconnectAttempts) {
|
|
2259
|
+
console.error("[NetworkPlugin] Max reconnection attempts reached");
|
|
2260
|
+
this._reconnectState = null;
|
|
2261
|
+
return;
|
|
853
2262
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
const
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
2263
|
+
this._reconnectState.isReconnecting = true;
|
|
2264
|
+
this._reconnectState.attempts++;
|
|
2265
|
+
console.log(`[NetworkPlugin] Attempting reconnection (${this._reconnectState.attempts}/${this._config.maxReconnectAttempts})`);
|
|
2266
|
+
this._reconnectTimer = setTimeout(async () => {
|
|
2267
|
+
try {
|
|
2268
|
+
await this._networkService.connect(this._lastConnectOptions);
|
|
2269
|
+
const result = await this._networkService.call("reconnect", {
|
|
2270
|
+
playerId: this._reconnectState.playerId,
|
|
2271
|
+
roomId: this._reconnectState.roomId,
|
|
2272
|
+
token: this._reconnectState.token
|
|
2273
|
+
});
|
|
2274
|
+
if (result.success) {
|
|
2275
|
+
console.log("[NetworkPlugin] Reconnection successful");
|
|
2276
|
+
this._reconnectState.isReconnecting = false;
|
|
2277
|
+
this._reconnectState.attempts = 0;
|
|
2278
|
+
if (result.state) {
|
|
2279
|
+
this._handleFullState(result.state);
|
|
2280
|
+
}
|
|
2281
|
+
} else {
|
|
2282
|
+
console.error("[NetworkPlugin] Reconnection rejected:", result.error);
|
|
2283
|
+
this._attemptReconnect();
|
|
2284
|
+
}
|
|
2285
|
+
} catch (err) {
|
|
2286
|
+
console.error("[NetworkPlugin] Reconnection failed:", err);
|
|
2287
|
+
if (this._reconnectState) {
|
|
2288
|
+
this._reconnectState.isReconnecting = false;
|
|
2289
|
+
}
|
|
2290
|
+
this._attemptReconnect();
|
|
2291
|
+
}
|
|
2292
|
+
}, this._config.reconnectInterval);
|
|
2293
|
+
}
|
|
2294
|
+
_handleFullState(data) {
|
|
2295
|
+
this._syncSystem.clearSnapshots();
|
|
2296
|
+
for (const entityData of data.entities) {
|
|
2297
|
+
this._spawnSystem.handleSpawn(entityData);
|
|
2298
|
+
if (entityData.state) {
|
|
2299
|
+
this._syncSystem.handleSyncData({
|
|
2300
|
+
frame: data.frame,
|
|
2301
|
+
timestamp: data.timestamp,
|
|
2302
|
+
entities: [
|
|
2303
|
+
entityData.state
|
|
2304
|
+
]
|
|
2305
|
+
});
|
|
866
2306
|
}
|
|
867
2307
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
return [
|
|
874
|
-
prev,
|
|
875
|
-
next,
|
|
876
|
-
Math.min(t, 2)
|
|
877
|
-
];
|
|
2308
|
+
}
|
|
2309
|
+
_clearReconnectTimer() {
|
|
2310
|
+
if (this._reconnectTimer) {
|
|
2311
|
+
clearTimeout(this._reconnectTimer);
|
|
2312
|
+
this._reconnectTimer = null;
|
|
878
2313
|
}
|
|
879
|
-
return null;
|
|
880
2314
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
* @en Get latest snapshot
|
|
884
|
-
*/
|
|
885
|
-
getLatest() {
|
|
886
|
-
return this._buffer.length > 0 ? this._buffer[this._buffer.length - 1] : null;
|
|
2315
|
+
_generateReconnectToken() {
|
|
2316
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
|
887
2317
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
return this._buffer.filter((s) => s.timestamp > timestamp);
|
|
2318
|
+
_cleanup() {
|
|
2319
|
+
this._localPlayerId = 0;
|
|
2320
|
+
this._syncSystem?.clearSnapshots();
|
|
2321
|
+
this._predictionSystem?.reset();
|
|
2322
|
+
this._inputSystem?.reset();
|
|
894
2323
|
}
|
|
2324
|
+
// =========================================================================
|
|
2325
|
+
// Game API | 游戏接口
|
|
2326
|
+
// =========================================================================
|
|
895
2327
|
/**
|
|
896
|
-
* @zh
|
|
897
|
-
* @en
|
|
2328
|
+
* @zh 注册预制体工厂
|
|
2329
|
+
* @en Register prefab factory
|
|
898
2330
|
*/
|
|
899
|
-
|
|
900
|
-
this.
|
|
901
|
-
}
|
|
902
|
-
};
|
|
903
|
-
__name(_SnapshotBuffer, "SnapshotBuffer");
|
|
904
|
-
var SnapshotBuffer = _SnapshotBuffer;
|
|
905
|
-
function createSnapshotBuffer(maxSize = 30, interpolationDelay = 100) {
|
|
906
|
-
return new SnapshotBuffer({
|
|
907
|
-
maxSize,
|
|
908
|
-
interpolationDelay
|
|
909
|
-
});
|
|
910
|
-
}
|
|
911
|
-
__name(createSnapshotBuffer, "createSnapshotBuffer");
|
|
912
|
-
|
|
913
|
-
// src/sync/IInterpolator.ts
|
|
914
|
-
function lerp(a, b, t) {
|
|
915
|
-
return a + (b - a) * t;
|
|
916
|
-
}
|
|
917
|
-
__name(lerp, "lerp");
|
|
918
|
-
function lerpAngle(a, b, t) {
|
|
919
|
-
let diff = b - a;
|
|
920
|
-
while (diff > Math.PI) diff -= Math.PI * 2;
|
|
921
|
-
while (diff < -Math.PI) diff += Math.PI * 2;
|
|
922
|
-
return a + diff * t;
|
|
923
|
-
}
|
|
924
|
-
__name(lerpAngle, "lerpAngle");
|
|
925
|
-
function smoothDamp(current, target, velocity, smoothTime, deltaTime, maxSpeed = Infinity) {
|
|
926
|
-
smoothTime = Math.max(1e-4, smoothTime);
|
|
927
|
-
const omega = 2 / smoothTime;
|
|
928
|
-
const x = omega * deltaTime;
|
|
929
|
-
const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);
|
|
930
|
-
let change = current - target;
|
|
931
|
-
const maxChange = maxSpeed * smoothTime;
|
|
932
|
-
change = Math.max(-maxChange, Math.min(maxChange, change));
|
|
933
|
-
const temp = (velocity + omega * change) * deltaTime;
|
|
934
|
-
let newVelocity = (velocity - omega * temp) * exp;
|
|
935
|
-
let newValue = target + (change + temp) * exp;
|
|
936
|
-
if (target - current > 0 === newValue > target) {
|
|
937
|
-
newValue = target;
|
|
938
|
-
newVelocity = (newValue - target) / deltaTime;
|
|
2331
|
+
registerPrefab(prefabType, factory) {
|
|
2332
|
+
this._spawnSystem?.registerPrefab(prefabType, factory);
|
|
939
2333
|
}
|
|
940
|
-
return [
|
|
941
|
-
newValue,
|
|
942
|
-
newVelocity
|
|
943
|
-
];
|
|
944
|
-
}
|
|
945
|
-
__name(smoothDamp, "smoothDamp");
|
|
946
|
-
|
|
947
|
-
// src/sync/TransformInterpolator.ts
|
|
948
|
-
var _TransformInterpolator = class _TransformInterpolator {
|
|
949
2334
|
/**
|
|
950
|
-
* @zh
|
|
951
|
-
* @en
|
|
2335
|
+
* @zh 发送移动输入
|
|
2336
|
+
* @en Send move input
|
|
952
2337
|
*/
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
x: lerp(from.x, to.x, t),
|
|
956
|
-
y: lerp(from.y, to.y, t),
|
|
957
|
-
rotation: lerpAngle(from.rotation, to.rotation, t)
|
|
958
|
-
};
|
|
2338
|
+
sendMoveInput(x, y) {
|
|
2339
|
+
this._inputSystem?.addMoveInput(x, y);
|
|
959
2340
|
}
|
|
960
2341
|
/**
|
|
961
|
-
* @zh
|
|
962
|
-
* @en
|
|
2342
|
+
* @zh 发送动作输入
|
|
2343
|
+
* @en Send action input
|
|
963
2344
|
*/
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
x: state.x + state.velocityX * deltaTime,
|
|
967
|
-
y: state.y + state.velocityY * deltaTime,
|
|
968
|
-
rotation: state.rotation + state.angularVelocity * deltaTime,
|
|
969
|
-
velocityX: state.velocityX,
|
|
970
|
-
velocityY: state.velocityY,
|
|
971
|
-
angularVelocity: state.angularVelocity
|
|
972
|
-
};
|
|
2345
|
+
sendActionInput(action) {
|
|
2346
|
+
this._inputSystem?.addActionInput(action);
|
|
973
2347
|
}
|
|
974
|
-
};
|
|
975
|
-
__name(_TransformInterpolator, "TransformInterpolator");
|
|
976
|
-
var TransformInterpolator = _TransformInterpolator;
|
|
977
|
-
var _HermiteTransformInterpolator = class _HermiteTransformInterpolator {
|
|
978
2348
|
/**
|
|
979
|
-
* @zh
|
|
980
|
-
* @en
|
|
2349
|
+
* @zh 设置本地玩家网络 ID(用于预测)
|
|
2350
|
+
* @en Set local player network ID (for prediction)
|
|
981
2351
|
*/
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
const h10 = t3 - 2 * t2 + t;
|
|
987
|
-
const h01 = -2 * t3 + 3 * t2;
|
|
988
|
-
const h11 = t3 - t2;
|
|
989
|
-
const dt = 0.1;
|
|
990
|
-
const x = h00 * from.x + h10 * from.velocityX * dt + h01 * to.x + h11 * to.velocityX * dt;
|
|
991
|
-
const y = h00 * from.y + h10 * from.velocityY * dt + h01 * to.y + h11 * to.velocityY * dt;
|
|
992
|
-
const dh00 = 6 * t2 - 6 * t;
|
|
993
|
-
const dh10 = 3 * t2 - 4 * t + 1;
|
|
994
|
-
const dh01 = -6 * t2 + 6 * t;
|
|
995
|
-
const dh11 = 3 * t2 - 2 * t;
|
|
996
|
-
const velocityX = (dh00 * from.x + dh10 * from.velocityX * dt + dh01 * to.x + dh11 * to.velocityX * dt) / dt;
|
|
997
|
-
const velocityY = (dh00 * from.y + dh10 * from.velocityY * dt + dh01 * to.y + dh11 * to.velocityY * dt) / dt;
|
|
998
|
-
return {
|
|
999
|
-
x,
|
|
1000
|
-
y,
|
|
1001
|
-
rotation: lerpAngle(from.rotation, to.rotation, t),
|
|
1002
|
-
velocityX,
|
|
1003
|
-
velocityY,
|
|
1004
|
-
angularVelocity: lerp(from.angularVelocity, to.angularVelocity, t)
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
};
|
|
1008
|
-
__name(_HermiteTransformInterpolator, "HermiteTransformInterpolator");
|
|
1009
|
-
var HermiteTransformInterpolator = _HermiteTransformInterpolator;
|
|
1010
|
-
function createTransformInterpolator() {
|
|
1011
|
-
return new TransformInterpolator();
|
|
1012
|
-
}
|
|
1013
|
-
__name(createTransformInterpolator, "createTransformInterpolator");
|
|
1014
|
-
function createHermiteTransformInterpolator() {
|
|
1015
|
-
return new HermiteTransformInterpolator();
|
|
1016
|
-
}
|
|
1017
|
-
__name(createHermiteTransformInterpolator, "createHermiteTransformInterpolator");
|
|
1018
|
-
|
|
1019
|
-
// src/sync/ClientPrediction.ts
|
|
1020
|
-
var _ClientPrediction = class _ClientPrediction {
|
|
1021
|
-
constructor(predictor, config) {
|
|
1022
|
-
__publicField(this, "_predictor");
|
|
1023
|
-
__publicField(this, "_config");
|
|
1024
|
-
__publicField(this, "_pendingInputs", []);
|
|
1025
|
-
__publicField(this, "_lastAcknowledgedSequence", 0);
|
|
1026
|
-
__publicField(this, "_currentSequence", 0);
|
|
1027
|
-
__publicField(this, "_lastServerState", null);
|
|
1028
|
-
__publicField(this, "_predictedState", null);
|
|
1029
|
-
__publicField(this, "_correctionOffset", {
|
|
1030
|
-
x: 0,
|
|
1031
|
-
y: 0
|
|
1032
|
-
});
|
|
1033
|
-
this._predictor = predictor;
|
|
1034
|
-
this._config = {
|
|
1035
|
-
maxUnacknowledgedInputs: 60,
|
|
1036
|
-
reconciliationThreshold: 0.1,
|
|
1037
|
-
reconciliationSpeed: 10,
|
|
1038
|
-
...config
|
|
1039
|
-
};
|
|
2352
|
+
setLocalPlayerNetId(netId) {
|
|
2353
|
+
if (this._predictionSystem) {
|
|
2354
|
+
this._predictionSystem.setLocalPlayerNetId(netId);
|
|
2355
|
+
}
|
|
1040
2356
|
}
|
|
1041
2357
|
/**
|
|
1042
|
-
* @zh
|
|
1043
|
-
* @en
|
|
2358
|
+
* @zh 启用/禁用预测
|
|
2359
|
+
* @en Enable/disable prediction
|
|
1044
2360
|
*/
|
|
1045
|
-
|
|
1046
|
-
|
|
2361
|
+
setPredictionEnabled(enabled) {
|
|
2362
|
+
if (this._predictionSystem) {
|
|
2363
|
+
this._predictionSystem.enabled = enabled;
|
|
2364
|
+
}
|
|
1047
2365
|
}
|
|
2366
|
+
// =========================================================================
|
|
2367
|
+
// AOI API | AOI 接口
|
|
2368
|
+
// =========================================================================
|
|
1048
2369
|
/**
|
|
1049
|
-
* @zh
|
|
1050
|
-
* @en
|
|
2370
|
+
* @zh 添加 AOI 观察者(玩家)
|
|
2371
|
+
* @en Add AOI observer (player)
|
|
1051
2372
|
*/
|
|
1052
|
-
|
|
1053
|
-
|
|
2373
|
+
addAOIObserver(netId, x, y, viewRange) {
|
|
2374
|
+
this._aoiSystem?.addObserver(netId, x, y, viewRange);
|
|
1054
2375
|
}
|
|
1055
2376
|
/**
|
|
1056
|
-
* @zh
|
|
1057
|
-
* @en
|
|
2377
|
+
* @zh 移除 AOI 观察者
|
|
2378
|
+
* @en Remove AOI observer
|
|
1058
2379
|
*/
|
|
1059
|
-
|
|
1060
|
-
|
|
2380
|
+
removeAOIObserver(netId) {
|
|
2381
|
+
this._aoiSystem?.removeObserver(netId);
|
|
1061
2382
|
}
|
|
1062
2383
|
/**
|
|
1063
|
-
* @zh
|
|
1064
|
-
* @en
|
|
1065
|
-
*
|
|
1066
|
-
* @param input - @zh 输入数据 @en Input data
|
|
1067
|
-
* @param currentState - @zh 当前状态 @en Current state
|
|
1068
|
-
* @param deltaTime - @zh 时间间隔 @en Delta time
|
|
1069
|
-
* @returns @zh 预测的状态 @en Predicted state
|
|
2384
|
+
* @zh 更新 AOI 观察者位置
|
|
2385
|
+
* @en Update AOI observer position
|
|
1070
2386
|
*/
|
|
1071
|
-
|
|
1072
|
-
this.
|
|
1073
|
-
const inputSnapshot = {
|
|
1074
|
-
sequence: this._currentSequence,
|
|
1075
|
-
input,
|
|
1076
|
-
timestamp: Date.now()
|
|
1077
|
-
};
|
|
1078
|
-
this._pendingInputs.push(inputSnapshot);
|
|
1079
|
-
while (this._pendingInputs.length > this._config.maxUnacknowledgedInputs) {
|
|
1080
|
-
this._pendingInputs.shift();
|
|
1081
|
-
}
|
|
1082
|
-
this._predictedState = this._predictor.predict(currentState, input, deltaTime);
|
|
1083
|
-
return this._predictedState;
|
|
2387
|
+
updateAOIObserverPosition(netId, x, y) {
|
|
2388
|
+
this._aoiSystem?.updateObserverPosition(netId, x, y);
|
|
1084
2389
|
}
|
|
1085
2390
|
/**
|
|
1086
|
-
* @zh
|
|
1087
|
-
* @en Get
|
|
2391
|
+
* @zh 获取观察者可见的实体
|
|
2392
|
+
* @en Get entities visible to observer
|
|
1088
2393
|
*/
|
|
1089
|
-
|
|
1090
|
-
return this.
|
|
2394
|
+
getVisibleEntities(observerNetId) {
|
|
2395
|
+
return this._aoiSystem?.getVisibleEntities(observerNetId) ?? [];
|
|
1091
2396
|
}
|
|
1092
2397
|
/**
|
|
1093
|
-
* @zh
|
|
1094
|
-
* @en
|
|
2398
|
+
* @zh 检查是否可见
|
|
2399
|
+
* @en Check if visible
|
|
1095
2400
|
*/
|
|
1096
|
-
|
|
1097
|
-
return this.
|
|
2401
|
+
canSee(observerNetId, targetNetId) {
|
|
2402
|
+
return this._aoiSystem?.canSee(observerNetId, targetNetId) ?? true;
|
|
1098
2403
|
}
|
|
1099
2404
|
/**
|
|
1100
|
-
* @zh
|
|
1101
|
-
* @en
|
|
1102
|
-
*
|
|
1103
|
-
* @param serverState - @zh 服务器状态 @en Server state
|
|
1104
|
-
* @param acknowledgedSequence - @zh 已确认的输入序列号 @en Acknowledged input sequence
|
|
1105
|
-
* @param stateGetter - @zh 获取状态位置的函数 @en Function to get state position
|
|
1106
|
-
* @param deltaTime - @zh 帧时间 @en Frame delta time
|
|
2405
|
+
* @zh 启用/禁用 AOI
|
|
2406
|
+
* @en Enable/disable AOI
|
|
1107
2407
|
*/
|
|
1108
|
-
|
|
1109
|
-
this.
|
|
1110
|
-
|
|
1111
|
-
while (this._pendingInputs.length > 0 && this._pendingInputs[0].sequence <= acknowledgedSequence) {
|
|
1112
|
-
this._pendingInputs.shift();
|
|
1113
|
-
}
|
|
1114
|
-
let state = serverState;
|
|
1115
|
-
for (const inputSnapshot of this._pendingInputs) {
|
|
1116
|
-
state = this._predictor.predict(state, inputSnapshot.input, deltaTime);
|
|
1117
|
-
}
|
|
1118
|
-
const serverPos = stateGetter(serverState);
|
|
1119
|
-
const predictedPos = stateGetter(state);
|
|
1120
|
-
const errorX = serverPos.x - predictedPos.x;
|
|
1121
|
-
const errorY = serverPos.y - predictedPos.y;
|
|
1122
|
-
const errorMagnitude = Math.sqrt(errorX * errorX + errorY * errorY);
|
|
1123
|
-
if (errorMagnitude > this._config.reconciliationThreshold) {
|
|
1124
|
-
const t = Math.min(1, this._config.reconciliationSpeed * deltaTime);
|
|
1125
|
-
this._correctionOffset.x += errorX * t;
|
|
1126
|
-
this._correctionOffset.y += errorY * t;
|
|
2408
|
+
setAOIEnabled(enabled) {
|
|
2409
|
+
if (this._aoiSystem) {
|
|
2410
|
+
this._aoiSystem.enabled = enabled;
|
|
1127
2411
|
}
|
|
1128
|
-
const decayRate = 0.9;
|
|
1129
|
-
this._correctionOffset.x *= decayRate;
|
|
1130
|
-
this._correctionOffset.y *= decayRate;
|
|
1131
|
-
this._predictedState = state;
|
|
1132
|
-
return state;
|
|
1133
|
-
}
|
|
1134
|
-
/**
|
|
1135
|
-
* @zh 清空预测状态
|
|
1136
|
-
* @en Clear prediction state
|
|
1137
|
-
*/
|
|
1138
|
-
clear() {
|
|
1139
|
-
this._pendingInputs.length = 0;
|
|
1140
|
-
this._lastAcknowledgedSequence = 0;
|
|
1141
|
-
this._currentSequence = 0;
|
|
1142
|
-
this._lastServerState = null;
|
|
1143
|
-
this._predictedState = null;
|
|
1144
|
-
this._correctionOffset = {
|
|
1145
|
-
x: 0,
|
|
1146
|
-
y: 0
|
|
1147
|
-
};
|
|
1148
2412
|
}
|
|
1149
2413
|
};
|
|
1150
|
-
__name(
|
|
1151
|
-
var
|
|
1152
|
-
function createClientPrediction(predictor, config) {
|
|
1153
|
-
return new ClientPrediction(predictor, config);
|
|
1154
|
-
}
|
|
1155
|
-
__name(createClientPrediction, "createClientPrediction");
|
|
2414
|
+
__name(_NetworkPlugin, "NetworkPlugin");
|
|
2415
|
+
var NetworkPlugin = _NetworkPlugin;
|
|
1156
2416
|
|
|
1157
2417
|
// src/nodes/NetworkNodes.ts
|
|
1158
2418
|
var IsLocalPlayerTemplate = {
|
|
@@ -1184,15 +2444,15 @@ var IsLocalPlayerTemplate = {
|
|
|
1184
2444
|
};
|
|
1185
2445
|
var _IsLocalPlayerExecutor = class _IsLocalPlayerExecutor {
|
|
1186
2446
|
execute(node, context) {
|
|
1187
|
-
var
|
|
2447
|
+
var _a2;
|
|
1188
2448
|
const ctx = context;
|
|
1189
2449
|
let isLocal = false;
|
|
1190
2450
|
if (ctx.entity) {
|
|
1191
|
-
const identity = ctx.entity.getComponent((
|
|
2451
|
+
const identity = ctx.entity.getComponent((_a2 = class {
|
|
1192
2452
|
constructor() {
|
|
1193
2453
|
__publicField(this, "bIsLocalPlayer", false);
|
|
1194
2454
|
}
|
|
1195
|
-
}, __name(
|
|
2455
|
+
}, __name(_a2, "NetworkIdentity"), _a2));
|
|
1196
2456
|
if (identity) {
|
|
1197
2457
|
isLocal = identity.bIsLocalPlayer;
|
|
1198
2458
|
}
|
|
@@ -1272,15 +2532,15 @@ var HasAuthorityTemplate = {
|
|
|
1272
2532
|
};
|
|
1273
2533
|
var _HasAuthorityExecutor = class _HasAuthorityExecutor {
|
|
1274
2534
|
execute(node, context) {
|
|
1275
|
-
var
|
|
2535
|
+
var _a2;
|
|
1276
2536
|
const ctx = context;
|
|
1277
2537
|
let hasAuthority = false;
|
|
1278
2538
|
if (ctx.entity) {
|
|
1279
|
-
const identity = ctx.entity.getComponent((
|
|
2539
|
+
const identity = ctx.entity.getComponent((_a2 = class {
|
|
1280
2540
|
constructor() {
|
|
1281
2541
|
__publicField(this, "bHasAuthority", false);
|
|
1282
2542
|
}
|
|
1283
|
-
}, __name(
|
|
2543
|
+
}, __name(_a2, "NetworkIdentity"), _a2));
|
|
1284
2544
|
if (identity) {
|
|
1285
2545
|
hasAuthority = identity.bHasAuthority;
|
|
1286
2546
|
}
|
|
@@ -1327,17 +2587,17 @@ var GetNetworkIdTemplate = {
|
|
|
1327
2587
|
};
|
|
1328
2588
|
var _GetNetworkIdExecutor = class _GetNetworkIdExecutor {
|
|
1329
2589
|
execute(node, context) {
|
|
1330
|
-
var
|
|
2590
|
+
var _a2;
|
|
1331
2591
|
const ctx = context;
|
|
1332
2592
|
let netId = 0;
|
|
1333
2593
|
let ownerId = 0;
|
|
1334
2594
|
if (ctx.entity) {
|
|
1335
|
-
const identity = ctx.entity.getComponent((
|
|
2595
|
+
const identity = ctx.entity.getComponent((_a2 = class {
|
|
1336
2596
|
constructor() {
|
|
1337
2597
|
__publicField(this, "netId", 0);
|
|
1338
2598
|
__publicField(this, "ownerId", 0);
|
|
1339
2599
|
}
|
|
1340
|
-
}, __name(
|
|
2600
|
+
}, __name(_a2, "NetworkIdentity"), _a2));
|
|
1341
2601
|
if (identity) {
|
|
1342
2602
|
netId = identity.netId;
|
|
1343
2603
|
ownerId = identity.ownerId;
|
|
@@ -1424,6 +2684,7 @@ var NetworkNodeDefinitions = {
|
|
|
1424
2684
|
};
|
|
1425
2685
|
export {
|
|
1426
2686
|
ClientPrediction,
|
|
2687
|
+
DeltaFlags,
|
|
1427
2688
|
GameNetworkService,
|
|
1428
2689
|
GetLocalPlayerIdExecutor,
|
|
1429
2690
|
GetLocalPlayerIdTemplate,
|
|
@@ -1436,11 +2697,15 @@ export {
|
|
|
1436
2697
|
IsLocalPlayerTemplate,
|
|
1437
2698
|
IsServerExecutor,
|
|
1438
2699
|
IsServerTemplate,
|
|
2700
|
+
NetworkAOISystem,
|
|
2701
|
+
NetworkAOISystemToken,
|
|
1439
2702
|
NetworkIdentity,
|
|
1440
2703
|
NetworkInputSystem,
|
|
1441
2704
|
NetworkInputSystemToken,
|
|
1442
2705
|
NetworkNodeDefinitions,
|
|
1443
2706
|
NetworkPlugin,
|
|
2707
|
+
NetworkPredictionSystem,
|
|
2708
|
+
NetworkPredictionSystemToken,
|
|
1444
2709
|
GameNetworkService as NetworkService,
|
|
1445
2710
|
NetworkServiceToken,
|
|
1446
2711
|
NetworkSpawnSystem,
|
|
@@ -1451,11 +2716,16 @@ export {
|
|
|
1451
2716
|
NetworkTransform,
|
|
1452
2717
|
RpcService,
|
|
1453
2718
|
SnapshotBuffer,
|
|
2719
|
+
StateDeltaCompressor,
|
|
1454
2720
|
TransformInterpolator,
|
|
1455
2721
|
createClientPrediction,
|
|
1456
2722
|
createHermiteTransformInterpolator,
|
|
2723
|
+
createNetworkAOISystem,
|
|
2724
|
+
createNetworkInputSystem,
|
|
2725
|
+
createNetworkPredictionSystem,
|
|
1457
2726
|
createNetworkService,
|
|
1458
2727
|
createSnapshotBuffer,
|
|
2728
|
+
createStateDeltaCompressor,
|
|
1459
2729
|
createTransformInterpolator,
|
|
1460
2730
|
gameProtocol,
|
|
1461
2731
|
lerp,
|