clearup
This commit is contained in:
@@ -98,6 +98,7 @@ public sealed class ClientAuthoritativePlayerStateSnapshot
|
||||
SourceState = state.Clone();
|
||||
PlayerId = SourceState.PlayerId ?? string.Empty;
|
||||
Tick = SourceState.Tick;
|
||||
AcknowledgedMoveTick = SourceState.AcknowledgedMoveTick;
|
||||
Position = SourceState.Position != null ? SourceState.Position.ToVector3() : Vector3.zero;
|
||||
Velocity = SourceState.Velocity != null ? SourceState.Velocity.ToVector3() : Vector3.zero;
|
||||
Rotation = SourceState.Rotation;
|
||||
@@ -110,6 +111,8 @@ public sealed class ClientAuthoritativePlayerStateSnapshot
|
||||
|
||||
public long Tick { get; }
|
||||
|
||||
public long AcknowledgedMoveTick { get; }
|
||||
|
||||
public Vector3 Position { get; }
|
||||
|
||||
public Vector3 Velocity { get; }
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
using UnityEngine;
|
||||
using Vector3 = UnityEngine.Vector3;
|
||||
|
||||
public readonly struct ControlledPlayerCorrectionSettings
|
||||
{
|
||||
public ControlledPlayerCorrectionSettings(
|
||||
float authoritativeCadenceSeconds,
|
||||
float moveSpeed,
|
||||
float turnSpeedDegreesPerSecond,
|
||||
float snapDistanceMultiplier = 3f,
|
||||
float snapAngleMultiplier = 15f)
|
||||
{
|
||||
AuthoritativeCadenceSeconds = Mathf.Max(0f, authoritativeCadenceSeconds);
|
||||
MoveSpeed = Mathf.Max(0f, moveSpeed);
|
||||
TurnSpeedDegreesPerSecond = Mathf.Max(0f, turnSpeedDegreesPerSecond);
|
||||
SnapDistanceMultiplier = Mathf.Max(1f, snapDistanceMultiplier);
|
||||
SnapAngleMultiplier = Mathf.Max(1f, snapAngleMultiplier);
|
||||
}
|
||||
|
||||
public float AuthoritativeCadenceSeconds { get; }
|
||||
|
||||
public float MoveSpeed { get; }
|
||||
|
||||
public float TurnSpeedDegreesPerSecond { get; }
|
||||
|
||||
public float SnapDistanceMultiplier { get; }
|
||||
|
||||
public float SnapAngleMultiplier { get; }
|
||||
|
||||
public float MaxBoundedPositionCorrection => MoveSpeed * AuthoritativeCadenceSeconds;
|
||||
|
||||
public float MaxBoundedRotationCorrectionDegrees => TurnSpeedDegreesPerSecond * AuthoritativeCadenceSeconds;
|
||||
|
||||
public float SnapPositionThreshold => MaxBoundedPositionCorrection * SnapDistanceMultiplier;
|
||||
|
||||
public float SnapRotationThresholdDegrees => MaxBoundedRotationCorrectionDegrees * SnapAngleMultiplier;
|
||||
|
||||
public int MaxCorrectionSteps => Mathf.Max(1, Mathf.CeilToInt(SnapDistanceMultiplier));
|
||||
}
|
||||
|
||||
public readonly struct ControlledPlayerVisualCorrectionState
|
||||
{
|
||||
public static ControlledPlayerVisualCorrectionState None => default;
|
||||
|
||||
public ControlledPlayerVisualCorrectionState(Vector3 targetPosition, Quaternion targetRotation, int remainingStepBudget)
|
||||
{
|
||||
TargetPosition = targetPosition;
|
||||
TargetRotation = targetRotation;
|
||||
RemainingStepBudget = Mathf.Max(0, remainingStepBudget);
|
||||
}
|
||||
|
||||
public Vector3 TargetPosition { get; }
|
||||
|
||||
public Quaternion TargetRotation { get; }
|
||||
|
||||
public int RemainingStepBudget { get; }
|
||||
|
||||
public bool IsActive => RemainingStepBudget > 0;
|
||||
}
|
||||
|
||||
public readonly struct ControlledPlayerCorrectionResult
|
||||
{
|
||||
public ControlledPlayerCorrectionResult(
|
||||
Vector3 position,
|
||||
Quaternion rotation,
|
||||
bool usedHardSnap,
|
||||
ControlledPlayerVisualCorrectionState nextState)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
UsedHardSnap = usedHardSnap;
|
||||
NextState = nextState;
|
||||
}
|
||||
|
||||
public Vector3 Position { get; }
|
||||
|
||||
public Quaternion Rotation { get; }
|
||||
|
||||
public bool UsedHardSnap { get; }
|
||||
|
||||
public ControlledPlayerVisualCorrectionState NextState { get; }
|
||||
}
|
||||
|
||||
public static class ControlledPlayerCorrection
|
||||
{
|
||||
public static ControlledPlayerCorrectionResult Resolve(
|
||||
Vector3 currentPosition,
|
||||
Quaternion currentRotation,
|
||||
Vector3 targetPosition,
|
||||
Quaternion targetRotation,
|
||||
ControlledPlayerCorrectionSettings settings)
|
||||
{
|
||||
return Resolve(
|
||||
currentPosition,
|
||||
currentRotation,
|
||||
targetPosition,
|
||||
targetRotation,
|
||||
settings,
|
||||
ControlledPlayerVisualCorrectionState.None);
|
||||
}
|
||||
|
||||
public static ControlledPlayerCorrectionResult Resolve(
|
||||
Vector3 currentPosition,
|
||||
Quaternion currentRotation,
|
||||
Vector3 targetPosition,
|
||||
Quaternion targetRotation,
|
||||
ControlledPlayerCorrectionSettings settings,
|
||||
ControlledPlayerVisualCorrectionState activeCorrection)
|
||||
{
|
||||
var positionError = Vector3.Distance(currentPosition, targetPosition);
|
||||
var rotationError = Quaternion.Angle(currentRotation, targetRotation);
|
||||
var boundedPositionCorrection = settings.MaxBoundedPositionCorrection;
|
||||
var boundedRotationCorrection = settings.MaxBoundedRotationCorrectionDegrees;
|
||||
|
||||
if (positionError <= Mathf.Epsilon && rotationError <= Mathf.Epsilon)
|
||||
{
|
||||
return new ControlledPlayerCorrectionResult(targetPosition, targetRotation, false, ControlledPlayerVisualCorrectionState.None);
|
||||
}
|
||||
|
||||
if (boundedPositionCorrection <= Mathf.Epsilon && boundedRotationCorrection <= Mathf.Epsilon)
|
||||
{
|
||||
return new ControlledPlayerCorrectionResult(targetPosition, targetRotation, true, ControlledPlayerVisualCorrectionState.None);
|
||||
}
|
||||
|
||||
if (positionError > settings.SnapPositionThreshold || rotationError > settings.SnapRotationThresholdDegrees)
|
||||
{
|
||||
return new ControlledPlayerCorrectionResult(targetPosition, targetRotation, true, ControlledPlayerVisualCorrectionState.None);
|
||||
}
|
||||
|
||||
var remainingStepBudget = activeCorrection.IsActive
|
||||
? activeCorrection.RemainingStepBudget
|
||||
: settings.MaxCorrectionSteps;
|
||||
if (remainingStepBudget <= 0)
|
||||
{
|
||||
return new ControlledPlayerCorrectionResult(targetPosition, targetRotation, true, ControlledPlayerVisualCorrectionState.None);
|
||||
}
|
||||
|
||||
var correctedPosition = Vector3.MoveTowards(currentPosition, targetPosition, boundedPositionCorrection);
|
||||
var correctedRotation = Quaternion.RotateTowards(currentRotation, targetRotation, boundedRotationCorrection);
|
||||
var nextPositionError = Vector3.Distance(correctedPosition, targetPosition);
|
||||
var nextRotationError = Quaternion.Angle(correctedRotation, targetRotation);
|
||||
if (nextPositionError <= Mathf.Epsilon && nextRotationError <= Mathf.Epsilon)
|
||||
{
|
||||
return new ControlledPlayerCorrectionResult(targetPosition, targetRotation, false, ControlledPlayerVisualCorrectionState.None);
|
||||
}
|
||||
|
||||
remainingStepBudget--;
|
||||
if (remainingStepBudget <= 0)
|
||||
{
|
||||
return new ControlledPlayerCorrectionResult(targetPosition, targetRotation, true, ControlledPlayerVisualCorrectionState.None);
|
||||
}
|
||||
|
||||
return new ControlledPlayerCorrectionResult(
|
||||
correctedPosition,
|
||||
correctedRotation,
|
||||
false,
|
||||
new ControlledPlayerVisualCorrectionState(targetPosition, targetRotation, remainingStepBudget));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f70a048da9736c498daa681ba153fb0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Network.Defines;
|
||||
using Network.NetworkApplication;
|
||||
using UnityEngine;
|
||||
using Vector3 = UnityEngine.Vector3;
|
||||
|
||||
@@ -20,6 +21,7 @@ public class MasterManager : MonoBehaviour
|
||||
|
||||
public void InitPlayersState(LoginResponse response)
|
||||
{
|
||||
var localBootstrap = ClientMovementBootstrap.FromLoginResponse(response);
|
||||
for (int i = 0; i < response.Positions.Count; i++)
|
||||
{
|
||||
string id = response.PlayerId[i];
|
||||
@@ -31,17 +33,17 @@ public class MasterManager : MonoBehaviour
|
||||
}
|
||||
else
|
||||
{
|
||||
RegisterLocalPlayer(response.Speed, response.ServerTick);
|
||||
RegisterLocalPlayer(localBootstrap);
|
||||
var ui = GameObject.Find("RegisterCanvas");
|
||||
ui.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterLocalPlayer(int speed, long serverTick)
|
||||
private void RegisterLocalPlayer(ClientMovementBootstrap bootstrap)
|
||||
{
|
||||
Player player = GameObject.Instantiate(_playerPrefab, _playerParent).GetComponent<Player>();
|
||||
player.LocalInit(LocalPlayerId, speed, serverTick);
|
||||
player.LocalInit(LocalPlayerId, bootstrap);
|
||||
_players.Add(LocalPlayerId, player);
|
||||
}
|
||||
|
||||
|
||||
@@ -106,6 +106,11 @@ public class MovementComponent : MonoBehaviour
|
||||
private Player _master;
|
||||
private const float TurnSpeedDegreesPerSecond = 180f;
|
||||
private const float UnityYawOffsetDegrees = 90f;
|
||||
|
||||
// Server authoritative movement cadence used for replay substepping.
|
||||
// This matches ServerAuthoritativeMovementConfiguration.SimulationInterval (50ms).
|
||||
private const float kServerSimulationStepSeconds = 0.05f;
|
||||
|
||||
private int _speed = 2;
|
||||
[SerializeField] private Rigidbody _rigid;
|
||||
private float _lastSendTime = 0;
|
||||
@@ -114,6 +119,7 @@ public class MovementComponent : MonoBehaviour
|
||||
private Vector3 _serverPosition;
|
||||
private bool _hasServerState = false;
|
||||
private ClientAuthoritativePlayerStateSnapshot _lastAuthoritativeState;
|
||||
private ControlledPlayerVisualCorrectionState _activeVisualCorrection;
|
||||
|
||||
public long Tick { get; private set; } = 0;
|
||||
private long _startTickOffset = 0;
|
||||
@@ -127,6 +133,11 @@ public class MovementComponent : MonoBehaviour
|
||||
private bool _wasMovingLastFrame;
|
||||
private bool _stopMessagePending;
|
||||
|
||||
public void Init(bool isControlled, Player master, ClientMovementBootstrap bootstrap)
|
||||
{
|
||||
Init(isControlled, master, bootstrap.AuthoritativeMoveSpeed, bootstrap.ServerTick);
|
||||
}
|
||||
|
||||
public void Init(bool isControlled, Player master, int speed = 0, long serverTick = 0)
|
||||
{
|
||||
_master = master;
|
||||
@@ -212,15 +223,24 @@ public class MovementComponent : MonoBehaviour
|
||||
|
||||
private void Reconcile(ClientAuthoritativePlayerStateSnapshot snapshot)
|
||||
{
|
||||
_serverPosition = snapshot.Position;
|
||||
if (!_predictionBuffer.TryApplyAuthoritativeState(snapshot.SourceState, out var replayInputs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_serverPosition = snapshot.Position;
|
||||
_rigid.position = _serverPosition;
|
||||
_rigid.rotation = snapshot.RotationQuaternion;
|
||||
_rigid.velocity = snapshot.Velocity;
|
||||
var correction = ControlledPlayerCorrection.Resolve(
|
||||
_rigid.position,
|
||||
_rigid.rotation,
|
||||
snapshot.Position,
|
||||
snapshot.RotationQuaternion,
|
||||
new ControlledPlayerCorrectionSettings(kServerSimulationStepSeconds, _speed, TurnSpeedDegreesPerSecond),
|
||||
_activeVisualCorrection);
|
||||
|
||||
_activeVisualCorrection = correction.NextState;
|
||||
_rigid.position = correction.Position;
|
||||
_rigid.rotation = correction.Rotation;
|
||||
_rigid.velocity = correction.UsedHardSnap ? snapshot.Velocity : Vector3.zero;
|
||||
_rigid.angularVelocity = Vector3.zero;
|
||||
ReplayPendingInputs(replayInputs);
|
||||
}
|
||||
@@ -305,7 +325,18 @@ public class MovementComponent : MonoBehaviour
|
||||
{
|
||||
foreach (var replayInput in replayInputs)
|
||||
{
|
||||
ApplyTankMovement(replayInput.Input.TurnInput, replayInput.Input.ThrottleInput, replayInput.SimulatedDurationSeconds);
|
||||
var remaining = replayInput.SimulatedDurationSeconds;
|
||||
while (remaining > 0f)
|
||||
{
|
||||
// Use the server's fixed cadence (50ms) as the substep size to ensure
|
||||
// replay trajectory matches live FixedUpdate prediction exactly.
|
||||
var step = Mathf.Min(remaining, kServerSimulationStepSeconds);
|
||||
ApplyTankMovement(
|
||||
replayInput.Input.TurnInput,
|
||||
replayInput.Input.ThrottleInput,
|
||||
step);
|
||||
remaining -= step;
|
||||
}
|
||||
}
|
||||
|
||||
if (_isControlled)
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Network.Defines {
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::Network.Defines.MoveInput), global::Network.Defines.MoveInput.Parser, new[]{ "PlayerId", "Tick", "TurnInput", "ThrottleInput" }, null, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::Network.Defines.ShootInput), global::Network.Defines.ShootInput.Parser, new[]{ "PlayerId", "Tick", "DirX", "DirY", "TargetId" }, null, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::Network.Defines.CombatEvent), global::Network.Defines.CombatEvent.Parser, new[]{ "Tick", "EventType", "AttackerId", "TargetId", "Damage", "HitPosition" }, null, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::Network.Defines.PlayerState), global::Network.Defines.PlayerState.Parser, new[]{ "PlayerId", "Position", "Velocity", "Rotation", "Tick", "Hp" }, null, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::Network.Defines.PlayerState), global::Network.Defines.PlayerState.Parser, new[]{ "PlayerId", "Position", "Velocity", "Rotation", "Tick", "Hp", "AcknowledgedMoveTick" }, null, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::Network.Defines.Heartbeat), global::Network.Defines.Heartbeat.Parser, null, null, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::Network.Defines.HeartbeatResponse), global::Network.Defines.HeartbeatResponse.Parser, new[]{ "ServerTick" }, null, null, null, null)
|
||||
}));
|
||||
@@ -2679,6 +2679,7 @@ namespace Network.Defines {
|
||||
rotation_ = other.rotation_;
|
||||
tick_ = other.tick_;
|
||||
hp_ = other.hp_;
|
||||
acknowledgedMoveTick_ = other.acknowledgedMoveTick_;
|
||||
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
|
||||
}
|
||||
|
||||
@@ -2760,6 +2761,18 @@ namespace Network.Defines {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "acknowledged_move_tick" field.</summary>
|
||||
public const int AcknowledgedMoveTickFieldNumber = 7;
|
||||
private long acknowledgedMoveTick_;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public long AcknowledgedMoveTick {
|
||||
get { return acknowledgedMoveTick_; }
|
||||
set {
|
||||
acknowledgedMoveTick_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override bool Equals(object other) {
|
||||
@@ -2781,6 +2794,7 @@ namespace Network.Defines {
|
||||
if (!pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.Equals(Rotation, other.Rotation)) return false;
|
||||
if (Tick != other.Tick) return false;
|
||||
if (Hp != other.Hp) return false;
|
||||
if (AcknowledgedMoveTick != other.AcknowledgedMoveTick) return false;
|
||||
return Equals(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
@@ -2794,6 +2808,7 @@ namespace Network.Defines {
|
||||
if (Rotation != 0F) hash ^= pbc::ProtobufEqualityComparers.BitwiseSingleEqualityComparer.GetHashCode(Rotation);
|
||||
if (Tick != 0L) hash ^= Tick.GetHashCode();
|
||||
if (Hp != 0) hash ^= Hp.GetHashCode();
|
||||
if (AcknowledgedMoveTick != 0L) hash ^= AcknowledgedMoveTick.GetHashCode();
|
||||
if (_unknownFields != null) {
|
||||
hash ^= _unknownFields.GetHashCode();
|
||||
}
|
||||
@@ -2836,6 +2851,10 @@ namespace Network.Defines {
|
||||
output.WriteRawTag(48);
|
||||
output.WriteInt32(Hp);
|
||||
}
|
||||
if (AcknowledgedMoveTick != 0L) {
|
||||
output.WriteRawTag(56);
|
||||
output.WriteInt64(AcknowledgedMoveTick);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(output);
|
||||
}
|
||||
@@ -2870,6 +2889,10 @@ namespace Network.Defines {
|
||||
output.WriteRawTag(48);
|
||||
output.WriteInt32(Hp);
|
||||
}
|
||||
if (AcknowledgedMoveTick != 0L) {
|
||||
output.WriteRawTag(56);
|
||||
output.WriteInt64(AcknowledgedMoveTick);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(ref output);
|
||||
}
|
||||
@@ -2898,6 +2921,9 @@ namespace Network.Defines {
|
||||
if (Hp != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeInt32Size(Hp);
|
||||
}
|
||||
if (AcknowledgedMoveTick != 0L) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeInt64Size(AcknowledgedMoveTick);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
size += _unknownFields.CalculateSize();
|
||||
}
|
||||
@@ -2934,6 +2960,9 @@ namespace Network.Defines {
|
||||
if (other.Hp != 0) {
|
||||
Hp = other.Hp;
|
||||
}
|
||||
if (other.AcknowledgedMoveTick != 0L) {
|
||||
AcknowledgedMoveTick = other.AcknowledgedMoveTick;
|
||||
}
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
@@ -2983,6 +3012,10 @@ namespace Network.Defines {
|
||||
Hp = input.ReadInt32();
|
||||
break;
|
||||
}
|
||||
case 56: {
|
||||
AcknowledgedMoveTick = input.ReadInt64();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -3032,6 +3065,10 @@ namespace Network.Defines {
|
||||
Hp = input.ReadInt32();
|
||||
break;
|
||||
}
|
||||
case 56: {
|
||||
AcknowledgedMoveTick = input.ReadInt64();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ message PlayerState {
|
||||
float rotation = 4;
|
||||
int64 tick = 5;
|
||||
int32 hp = 6;
|
||||
int64 acknowledged_move_tick = 7;
|
||||
}
|
||||
|
||||
message Heartbeat {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using Network.Defines;
|
||||
|
||||
namespace Network.NetworkApplication
|
||||
{
|
||||
public readonly struct ClientMovementBootstrap
|
||||
{
|
||||
public ClientMovementBootstrap(int authoritativeMoveSpeed, long serverTick)
|
||||
{
|
||||
AuthoritativeMoveSpeed = authoritativeMoveSpeed;
|
||||
ServerTick = serverTick;
|
||||
}
|
||||
|
||||
public int AuthoritativeMoveSpeed { get; }
|
||||
|
||||
public long ServerTick { get; }
|
||||
|
||||
public static ClientMovementBootstrap FromLoginResponse(LoginResponse response)
|
||||
{
|
||||
if (response == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(response));
|
||||
}
|
||||
|
||||
return new ClientMovementBootstrap(response.Speed, response.ServerTick);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f82d57bd033abb439004c7ad1b201b0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -24,6 +24,8 @@ namespace Network.NetworkApplication
|
||||
|
||||
public long? LastAuthoritativeTick { get; private set; }
|
||||
|
||||
public long? LastAcknowledgedMoveTick { get; private set; }
|
||||
|
||||
public IReadOnlyList<PredictedMoveStep> PendingInputs => pendingInputs;
|
||||
|
||||
public void Record(MoveInput input)
|
||||
@@ -66,9 +68,10 @@ namespace Network.NetworkApplication
|
||||
}
|
||||
|
||||
LastAuthoritativeTick = state.Tick;
|
||||
pendingInputs.RemoveAll(input => input.Input.Tick <= state.Tick);
|
||||
LastAcknowledgedMoveTick = state.AcknowledgedMoveTick;
|
||||
pendingInputs.RemoveAll(input => input.Input.Tick <= state.AcknowledgedMoveTick);
|
||||
replayInputs = pendingInputs.ToArray();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace Network.NetworkHost
|
||||
|
||||
public float TurnSpeedDegreesPerSecond { get; set; } = 180f;
|
||||
|
||||
public TimeSpan SimulationInterval { get; set; } = TimeSpan.FromMilliseconds(50);
|
||||
|
||||
public TimeSpan BroadcastInterval { get; set; } = TimeSpan.FromMilliseconds(50);
|
||||
|
||||
public int DefaultHp { get; set; } = 100;
|
||||
@@ -24,6 +26,11 @@ namespace Network.NetworkHost
|
||||
throw new ArgumentOutOfRangeException(nameof(TurnSpeedDegreesPerSecond), "Turn speed must be finite and non-negative.");
|
||||
}
|
||||
|
||||
if (SimulationInterval <= TimeSpan.Zero)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(SimulationInterval), "Simulation interval must be positive.");
|
||||
}
|
||||
|
||||
if (BroadcastInterval <= TimeSpan.Zero)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(BroadcastInterval), "Broadcast interval must be positive.");
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Network.NetworkHost
|
||||
private readonly IAuthoritativeMovementWorldValidator worldValidator;
|
||||
private readonly Dictionary<string, ServerAuthoritativeMovementState> statesByPeer = new();
|
||||
private long nextBroadcastTick = 1;
|
||||
private TimeSpan accumulatedSimulationTime;
|
||||
private TimeSpan accumulatedBroadcastTime;
|
||||
|
||||
public ServerAuthoritativeMovementCoordinator(
|
||||
@@ -31,6 +32,8 @@ namespace Network.NetworkHost
|
||||
this.worldValidator = worldValidator ?? throw new ArgumentNullException(nameof(worldValidator));
|
||||
}
|
||||
|
||||
public TimeSpan SimulationInterval => configuration.SimulationInterval;
|
||||
|
||||
public IReadOnlyList<ServerAuthoritativeMovementState> States
|
||||
{
|
||||
get
|
||||
@@ -153,25 +156,30 @@ namespace Network.NetworkHost
|
||||
|
||||
lock (gate)
|
||||
{
|
||||
foreach (var state in statesByPeer.Values)
|
||||
accumulatedSimulationTime += elapsed;
|
||||
while (accumulatedSimulationTime >= configuration.SimulationInterval)
|
||||
{
|
||||
IntegrateState(state, elapsed);
|
||||
}
|
||||
|
||||
accumulatedBroadcastTime += elapsed;
|
||||
while (accumulatedBroadcastTime >= configuration.BroadcastInterval)
|
||||
{
|
||||
accumulatedBroadcastTime -= configuration.BroadcastInterval;
|
||||
pendingBroadcasts ??= new List<PendingBroadcast>();
|
||||
accumulatedSimulationTime -= configuration.SimulationInterval;
|
||||
foreach (var state in statesByPeer.Values)
|
||||
{
|
||||
state.LastBroadcastTick = nextBroadcastTick;
|
||||
pendingBroadcasts.Add(new PendingBroadcast(
|
||||
state.RemoteEndPoint,
|
||||
BuildPlayerState(state, nextBroadcastTick)));
|
||||
IntegrateState(state, configuration.SimulationInterval);
|
||||
}
|
||||
|
||||
nextBroadcastTick++;
|
||||
accumulatedBroadcastTime += configuration.SimulationInterval;
|
||||
while (accumulatedBroadcastTime >= configuration.BroadcastInterval)
|
||||
{
|
||||
accumulatedBroadcastTime -= configuration.BroadcastInterval;
|
||||
pendingBroadcasts ??= new List<PendingBroadcast>();
|
||||
foreach (var state in statesByPeer.Values)
|
||||
{
|
||||
state.LastBroadcastTick = nextBroadcastTick;
|
||||
pendingBroadcasts.Add(new PendingBroadcast(
|
||||
state.RemoteEndPoint,
|
||||
BuildPlayerState(state, nextBroadcastTick)));
|
||||
}
|
||||
|
||||
nextBroadcastTick++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,6 +307,7 @@ namespace Network.NetworkHost
|
||||
lock (gate)
|
||||
{
|
||||
statesByPeer.Clear();
|
||||
accumulatedSimulationTime = TimeSpan.Zero;
|
||||
accumulatedBroadcastTime = TimeSpan.Zero;
|
||||
}
|
||||
}
|
||||
@@ -460,7 +469,8 @@ namespace Network.NetworkHost
|
||||
Z = state.VelocityZ
|
||||
},
|
||||
Rotation = state.Rotation,
|
||||
Hp = state.Hp
|
||||
Hp = state.Hp,
|
||||
AcknowledgedMoveTick = state.LastAcceptedMoveTick
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,8 @@ namespace Network.NetworkHost
|
||||
|
||||
public MultiSessionManager SessionCoordinator { get; }
|
||||
|
||||
public TimeSpan AuthoritativeMovementCadence => authoritativeMovementCoordinator.SimulationInterval;
|
||||
|
||||
public IReadOnlyList<ManagedNetworkSession> ManagedSessions => SessionCoordinator.Sessions;
|
||||
|
||||
public IReadOnlyList<ServerAuthoritativeMovementState> AuthoritativeMovementStates => authoritativeMovementCoordinator.States;
|
||||
|
||||
@@ -21,6 +21,8 @@ namespace Network.NetworkHost
|
||||
|
||||
public bool IsRunning { get; private set; }
|
||||
|
||||
public TimeSpan AuthoritativeMovementCadence => host.AuthoritativeMovementCadence;
|
||||
|
||||
public IReadOnlyList<ManagedNetworkSession> ManagedSessions => host.ManagedSessions;
|
||||
|
||||
public IReadOnlyList<ServerAuthoritativeMovementState> AuthoritativeMovementStates => host.AuthoritativeMovementStates;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Network.Defines;
|
||||
using Network.NetworkApplication;
|
||||
using UnityEngine;
|
||||
|
||||
public class Player : MonoBehaviour
|
||||
@@ -15,7 +16,7 @@ public class Player : MonoBehaviour
|
||||
[SerializeField] private bool _isControlled;
|
||||
private readonly ClientAuthoritativePlayerState _authoritativeState = new();
|
||||
|
||||
public void LocalInit(string playerId, int speed, long serverTick)
|
||||
public void LocalInit(string playerId, ClientMovementBootstrap bootstrap)
|
||||
{
|
||||
this.PlayerId = playerId;
|
||||
this._isControlled = true;
|
||||
@@ -24,7 +25,7 @@ public class Player : MonoBehaviour
|
||||
_meshRenderer.material = _materials[idx];
|
||||
|
||||
_playerUI.Init(this);
|
||||
_movement.Init(true, this, speed, serverTick);
|
||||
_movement.Init(true, this, bootstrap);
|
||||
}
|
||||
|
||||
public void RemoteInit(string playerId, UnityEngine.Vector3 pos)
|
||||
|
||||
Reference in New Issue
Block a user