单帧测试进行中
This commit is contained in:
@@ -40,6 +40,15 @@ namespace Network.NetworkApplication
|
||||
|
||||
public IReadOnlyList<PredictedMoveStep> PendingInputs => pendingInputs;
|
||||
|
||||
/// <summary>
|
||||
/// 清空所有 pending inputs。
|
||||
/// 用于 Reconcile 后清理已重放的输入,避免它们的时间被继续累积。
|
||||
/// </summary>
|
||||
public void ClearPendingInputs()
|
||||
{
|
||||
pendingInputs.Clear();
|
||||
}
|
||||
|
||||
public void Record(MoveInput input)
|
||||
{
|
||||
if (input == null)
|
||||
@@ -63,7 +72,8 @@ namespace Network.NetworkApplication
|
||||
}
|
||||
|
||||
var latest = pendingInputs[^1];
|
||||
pendingInputs[^1] = new PredictedMoveStep(latest.Input, latest.SimulatedDurationSeconds + simulatedDurationSeconds);
|
||||
pendingInputs[^1] =
|
||||
new PredictedMoveStep(latest.Input, latest.SimulatedDurationSeconds + simulatedDurationSeconds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -79,10 +89,12 @@ namespace Network.NetworkApplication
|
||||
}
|
||||
|
||||
var latest = pendingInputs[^1];
|
||||
pendingInputs[^1] = new PredictedMoveStep(latest.Input, latest.SimulatedDurationSeconds + elapsedSinceLastState);
|
||||
pendingInputs[^1] =
|
||||
new PredictedMoveStep(latest.Input, latest.SimulatedDurationSeconds + elapsedSinceLastState);
|
||||
}
|
||||
|
||||
public bool TryApplyAuthoritativeState(PlayerState state, float currentTime, out IReadOnlyList<PredictedMoveStep> replayInputs)
|
||||
public bool TryApplyAuthoritativeState(PlayerState state, float currentTime,
|
||||
out IReadOnlyList<PredictedMoveStep> replayInputs)
|
||||
{
|
||||
if (state == null)
|
||||
{
|
||||
@@ -105,5 +117,20 @@ namespace Network.NetworkApplication
|
||||
_lastAuthoritativeStateTime = currentTime;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 只清除已确认的旧输入,不触发 replay,不更新 LastAuthoritativeTick。
|
||||
/// 用于在校正被禁用时,保持 predictionBuffer 的输入与服务端同步。
|
||||
/// </summary>
|
||||
public void PruneAcknowledgedInputs(long acknowledgedMoveTick)
|
||||
{
|
||||
if (acknowledgedMoveTick <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pendingInputs.RemoveAll(input => input.Input.Tick <= acknowledgedMoveTick);
|
||||
LastAcknowledgedMoveTick = acknowledgedMoveTick;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ namespace Network.NetworkHost
|
||||
}
|
||||
|
||||
if (!movementCoordinator.TryGetState(acceptedPeer, out attackerState) &&
|
||||
!movementCoordinator.EnsureState(acceptedPeer, input.PlayerId, out attackerState))
|
||||
!movementCoordinator.EnsureState(acceptedPeer, input.PlayerId, null, out attackerState))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace Network.NetworkHost
|
||||
|
||||
public TimeSpan SimulationInterval => configuration.SimulationInterval;
|
||||
|
||||
public float MoveSpeed => configuration.MoveSpeed;
|
||||
|
||||
public IReadOnlyList<ServerAuthoritativeMovementState> States
|
||||
{
|
||||
get
|
||||
@@ -47,7 +49,7 @@ namespace Network.NetworkHost
|
||||
}
|
||||
}
|
||||
|
||||
public bool EnsureState(IPEndPoint remoteEndPoint, string playerId, out ServerAuthoritativeMovementState state)
|
||||
public bool EnsureState(IPEndPoint remoteEndPoint, string playerId, float? speed, out ServerAuthoritativeMovementState state)
|
||||
{
|
||||
if (remoteEndPoint == null)
|
||||
{
|
||||
@@ -73,14 +75,21 @@ namespace Network.NetworkHost
|
||||
return false;
|
||||
}
|
||||
|
||||
if (speed.HasValue)
|
||||
{
|
||||
existingState.Speed = speed.Value;
|
||||
}
|
||||
|
||||
state = CloneState(existingState);
|
||||
return true;
|
||||
}
|
||||
|
||||
var resolvedSpeed = speed ?? configuration.MoveSpeed;
|
||||
var createdState = new ServerAuthoritativeMovementState(
|
||||
normalizedSender,
|
||||
playerId,
|
||||
configuration.DefaultHp);
|
||||
configuration.DefaultHp,
|
||||
resolvedSpeed);
|
||||
statesByPeer.Add(key, createdState);
|
||||
state = CloneState(createdState);
|
||||
return true;
|
||||
@@ -343,7 +352,8 @@ namespace Network.NetworkHost
|
||||
Rotation = state.Rotation,
|
||||
IsDead = state.IsDead,
|
||||
InputX = state.InputX,
|
||||
InputY = state.InputY
|
||||
InputY = state.InputY,
|
||||
Speed = state.Speed
|
||||
};
|
||||
}
|
||||
|
||||
@@ -396,11 +406,11 @@ namespace Network.NetworkHost
|
||||
}
|
||||
|
||||
var rotationRadians = state.Rotation * (MathF.PI / 180f);
|
||||
var forwardX = MathF.Cos(rotationRadians);
|
||||
var forwardZ = MathF.Sin(rotationRadians);
|
||||
state.VelocityX = forwardX * (throttleInput * configuration.MoveSpeed);
|
||||
var forwardX = MathF.Sin(rotationRadians);
|
||||
var forwardZ = MathF.Cos(rotationRadians);
|
||||
state.VelocityX = forwardX * (throttleInput * state.Speed);
|
||||
state.VelocityY = 0f;
|
||||
state.VelocityZ = forwardZ * (throttleInput * configuration.MoveSpeed);
|
||||
state.VelocityZ = forwardZ * (throttleInput * state.Speed);
|
||||
|
||||
var candidatePositionX = state.PositionX + (state.VelocityX * deltaSeconds);
|
||||
var candidatePositionY = state.PositionY + (state.VelocityY * deltaSeconds);
|
||||
|
||||
@@ -5,12 +5,13 @@ namespace Network.NetworkHost
|
||||
{
|
||||
public sealed class ServerAuthoritativeMovementState
|
||||
{
|
||||
public ServerAuthoritativeMovementState(IPEndPoint remoteEndPoint, string playerId, int hp)
|
||||
public ServerAuthoritativeMovementState(IPEndPoint remoteEndPoint, string playerId, int hp, float speed = 5f)
|
||||
{
|
||||
RemoteEndPoint = remoteEndPoint ?? throw new ArgumentNullException(nameof(remoteEndPoint));
|
||||
PlayerId = playerId ?? throw new ArgumentNullException(nameof(playerId));
|
||||
Hp = hp;
|
||||
IsDead = hp <= 0;
|
||||
Speed = speed;
|
||||
}
|
||||
|
||||
public IPEndPoint RemoteEndPoint { get; }
|
||||
@@ -44,5 +45,7 @@ namespace Network.NetworkHost
|
||||
public float InputX { get; internal set; }
|
||||
|
||||
public float InputY { get; internal set; }
|
||||
|
||||
public float Speed { get; internal set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ namespace Network.NetworkHost
|
||||
|
||||
public MultiSessionManager SessionCoordinator { get; }
|
||||
|
||||
public float AuthoritativeMoveSpeed => authoritativeMovementCoordinator.MoveSpeed;
|
||||
|
||||
public TimeSpan AuthoritativeMovementCadence => authoritativeMovementCoordinator.SimulationInterval;
|
||||
|
||||
public IReadOnlyList<ManagedNetworkSession> ManagedSessions => SessionCoordinator.Sessions;
|
||||
@@ -268,14 +270,21 @@ namespace Network.NetworkHost
|
||||
public void NotifyLoginSucceeded(IPEndPoint remoteEndPoint)
|
||||
{
|
||||
SessionCoordinator.NotifyLoginSucceeded(remoteEndPoint);
|
||||
BootstrapAuthoritativeMovementState(remoteEndPoint);
|
||||
BootstrapAuthoritativeMovementState(remoteEndPoint, null);
|
||||
PublishMetricsSessionSnapshot(remoteEndPoint);
|
||||
}
|
||||
|
||||
public void NotifyLoginSucceeded(IPEndPoint remoteEndPoint, string playerId)
|
||||
{
|
||||
NotifyLoginSucceeded(remoteEndPoint, playerId, null);
|
||||
}
|
||||
|
||||
public void NotifyLoginSucceeded(IPEndPoint remoteEndPoint, string playerId, float? speed)
|
||||
{
|
||||
RememberPlayerId(remoteEndPoint, playerId);
|
||||
NotifyLoginSucceeded(remoteEndPoint);
|
||||
SessionCoordinator.NotifyLoginSucceeded(remoteEndPoint);
|
||||
BootstrapAuthoritativeMovementState(remoteEndPoint, speed);
|
||||
PublishMetricsSessionSnapshot(remoteEndPoint);
|
||||
}
|
||||
|
||||
public void NotifyLoginFailed(IPEndPoint remoteEndPoint, string reason = null)
|
||||
@@ -363,14 +372,14 @@ namespace Network.NetworkHost
|
||||
}
|
||||
}
|
||||
|
||||
private void BootstrapAuthoritativeMovementState(IPEndPoint remoteEndPoint)
|
||||
private void BootstrapAuthoritativeMovementState(IPEndPoint remoteEndPoint, float? speed)
|
||||
{
|
||||
if (!TryGetKnownPlayerId(remoteEndPoint, out var playerId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
authoritativeMovementCoordinator.EnsureState(remoteEndPoint, playerId, out _);
|
||||
authoritativeMovementCoordinator.EnsureState(remoteEndPoint, playerId, speed, out _);
|
||||
}
|
||||
|
||||
private void RememberPlayerId(IPEndPoint remoteEndPoint, string playerId)
|
||||
|
||||
Reference in New Issue
Block a user