收口 Client 端,添加服务端约束
This commit is contained in:
@@ -109,6 +109,133 @@ namespace Tests.EditMode.Network
|
||||
Assert.That(remoteCombatPresentation.IsDead, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FakeTransportRoundTrip_IdleLoggedInPlayer_ReceivesPlayerState_AndCanShootWithoutMoveInput()
|
||||
{
|
||||
var clientReliableTransport = new GameplayFlowFakeTransport();
|
||||
var clientSyncTransport = new GameplayFlowFakeTransport();
|
||||
var clientRuntime = new SharedNetworkRuntime(
|
||||
clientReliableTransport,
|
||||
new MainThreadNetworkDispatcher(),
|
||||
syncTransport: clientSyncTransport);
|
||||
var clientHarness = new ClientGameplayTestHarness("player-a");
|
||||
clientHarness.Register(clientRuntime.MessageManager);
|
||||
|
||||
var serverTransports = new Dictionary<int, GameplayFlowFakeTransport>();
|
||||
var configuration = new ServerRuntimeConfiguration(9000)
|
||||
{
|
||||
SyncPort = 9001,
|
||||
Dispatcher = new MainThreadNetworkDispatcher(),
|
||||
TransportFactory = port => CreateTransport(serverTransports, port),
|
||||
AuthoritativeMovement = new ServerAuthoritativeMovementConfiguration
|
||||
{
|
||||
MoveSpeed = 10f,
|
||||
BroadcastInterval = TimeSpan.FromMilliseconds(50),
|
||||
DefaultHp = 100
|
||||
},
|
||||
AuthoritativeCombat = new ServerAuthoritativeCombatConfiguration
|
||||
{
|
||||
DamagePerShot = 30
|
||||
}
|
||||
};
|
||||
|
||||
clientRuntime.StartAsync().GetAwaiter().GetResult();
|
||||
using var serverRuntime = ServerRuntimeEntryPoint.StartAsync(configuration).GetAwaiter().GetResult();
|
||||
|
||||
serverTransports[9000].EmitReceive(
|
||||
GameplayFlowTestSupport.BuildEnvelope(
|
||||
MessageType.LoginRequest,
|
||||
new LoginRequest
|
||||
{
|
||||
PlayerId = "player-a",
|
||||
Speed = 5
|
||||
}),
|
||||
ClientPeer);
|
||||
serverTransports[9000].EmitReceive(
|
||||
GameplayFlowTestSupport.BuildEnvelope(
|
||||
MessageType.LoginRequest,
|
||||
new LoginRequest
|
||||
{
|
||||
PlayerId = "player-b",
|
||||
Speed = 5
|
||||
}),
|
||||
RemotePeer);
|
||||
|
||||
serverRuntime.Host.NotifyLoginStarted(ClientPeer);
|
||||
serverRuntime.Host.NotifyLoginSucceeded(ClientPeer);
|
||||
serverRuntime.Host.NotifyLoginStarted(RemotePeer);
|
||||
serverRuntime.Host.NotifyLoginSucceeded(RemotePeer);
|
||||
serverRuntime.UpdateAuthoritativeMovement(TimeSpan.FromMilliseconds(50));
|
||||
|
||||
TransferBroadcastMessages(serverTransports[9001], clientSyncTransport, ServerSender);
|
||||
clientRuntime.DrainPendingMessagesAsync().GetAwaiter().GetResult();
|
||||
|
||||
Assert.That(clientHarness.TryGetState("player-a", out var idleLocalState), Is.True);
|
||||
Assert.That(idleLocalState.Tick, Is.EqualTo(1));
|
||||
Assert.That(idleLocalState.Position.x, Is.EqualTo(0f).Within(0.0001f));
|
||||
Assert.That(idleLocalState.Position.z, Is.EqualTo(0f).Within(0.0001f));
|
||||
Assert.That(idleLocalState.Hp, Is.EqualTo(100));
|
||||
|
||||
serverTransports[9001].ClearOutgoing();
|
||||
serverTransports[9000].ClearOutgoing();
|
||||
|
||||
serverTransports[9001].EmitReceive(
|
||||
GameplayFlowTestSupport.BuildEnvelope(
|
||||
MessageType.MoveInput,
|
||||
new MoveInput
|
||||
{
|
||||
PlayerId = "player-b",
|
||||
Tick = 1,
|
||||
MoveX = 1f,
|
||||
MoveY = 0f
|
||||
}),
|
||||
RemotePeer);
|
||||
serverRuntime.DrainPendingMessagesAsync().GetAwaiter().GetResult();
|
||||
serverRuntime.UpdateAuthoritativeMovement(TimeSpan.FromMilliseconds(100));
|
||||
serverTransports[9001].ClearOutgoing();
|
||||
serverTransports[9000].ClearOutgoing();
|
||||
|
||||
ClientGameplayInputFlow.SendShootInput(
|
||||
clientRuntime.MessageManager,
|
||||
"player-a",
|
||||
3,
|
||||
Vector3.right);
|
||||
|
||||
Assert.That(clientReliableTransport.SentMessages.Count, Is.EqualTo(1));
|
||||
var outboundEnvelope = Envelope.Parser.ParseFrom(clientReliableTransport.SentMessages[0]);
|
||||
var outboundShootInput = ShootInput.Parser.ParseFrom(outboundEnvelope.Payload);
|
||||
Assert.That((MessageType)outboundEnvelope.Type, Is.EqualTo(MessageType.ShootInput));
|
||||
Assert.That(outboundShootInput.TargetId, Is.EqualTo(string.Empty));
|
||||
|
||||
TransferSentMessages(clientReliableTransport, serverTransports[9000], ClientPeer);
|
||||
|
||||
serverRuntime.DrainPendingMessagesAsync().GetAwaiter().GetResult();
|
||||
serverRuntime.UpdateAuthoritativeMovement(TimeSpan.FromMilliseconds(50));
|
||||
|
||||
TransferBroadcastMessages(serverTransports[9000], clientReliableTransport, ServerSender);
|
||||
clientRuntime.DrainPendingMessagesAsync().GetAwaiter().GetResult();
|
||||
TransferBroadcastMessages(serverTransports[9001], clientSyncTransport, ServerSender);
|
||||
clientRuntime.DrainPendingMessagesAsync().GetAwaiter().GetResult();
|
||||
|
||||
Assert.That(serverRuntime.TryGetAuthoritativeMovementState(ClientPeer, out var localMovementState), Is.True);
|
||||
Assert.That(localMovementState.PlayerId, Is.EqualTo("player-a"));
|
||||
Assert.That(localMovementState.LastAcceptedMoveTick, Is.EqualTo(0));
|
||||
Assert.That(localMovementState.PositionX, Is.EqualTo(0f).Within(0.0001f));
|
||||
Assert.That(localMovementState.PositionZ, Is.EqualTo(0f).Within(0.0001f));
|
||||
Assert.That(serverRuntime.TryGetAuthoritativeCombatState(ClientPeer, out var localCombatState), Is.True);
|
||||
Assert.That(localCombatState.LastAcceptedShootTick, Is.EqualTo(3));
|
||||
Assert.That(serverRuntime.TryGetAuthoritativeCombatState(RemotePeer, out var remoteCombatState), Is.True);
|
||||
Assert.That(remoteCombatState.PlayerId, Is.EqualTo("player-b"));
|
||||
Assert.That(remoteCombatState.Hp, Is.EqualTo(70));
|
||||
|
||||
Assert.That(clientHarness.TryGetState("player-b", out var remoteClientState), Is.True);
|
||||
Assert.That(remoteClientState.Hp, Is.EqualTo(70));
|
||||
Assert.That(clientHarness.TryGetCombatPresentation("player-b", out var remoteCombatPresentation), Is.True);
|
||||
Assert.That(remoteCombatPresentation.LastEventType, Is.EqualTo(CombatEventType.DamageApplied));
|
||||
Assert.That(remoteCombatPresentation.LastDamage, Is.EqualTo(30));
|
||||
Assert.That(remoteCombatPresentation.IsDead, Is.False);
|
||||
}
|
||||
|
||||
private static GameplayFlowFakeTransport CreateTransport(IDictionary<int, GameplayFlowFakeTransport> serverTransports, int port)
|
||||
{
|
||||
var transport = new GameplayFlowFakeTransport();
|
||||
|
||||
@@ -138,6 +138,53 @@ namespace Tests.EditMode.Network
|
||||
Assert.That(secondBroadcast.Velocity.Z, Is.EqualTo(0f).Within(0.0001f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NotifyLoginSucceeded_CreatesIdleAuthoritativeState_AndBroadcastsPlayerStateWithoutMoveInput()
|
||||
{
|
||||
var createdTransports = new Dictionary<int, FakeTransport>();
|
||||
var configuration = new ServerRuntimeConfiguration(9000)
|
||||
{
|
||||
SyncPort = 9001,
|
||||
Dispatcher = new MainThreadNetworkDispatcher(),
|
||||
TransportFactory = port => CreateTransport(createdTransports, port),
|
||||
AuthoritativeMovement = new ServerAuthoritativeMovementConfiguration
|
||||
{
|
||||
MoveSpeed = 10f,
|
||||
BroadcastInterval = TimeSpan.FromMilliseconds(50),
|
||||
DefaultHp = 100
|
||||
}
|
||||
};
|
||||
|
||||
using var runtime = ServerRuntimeEntryPoint.StartAsync(configuration).GetAwaiter().GetResult();
|
||||
|
||||
createdTransports[9000].EmitReceive(BuildEnvelope(MessageType.LoginRequest, new LoginRequest
|
||||
{
|
||||
PlayerId = "player-a",
|
||||
Speed = 5
|
||||
}), PeerA);
|
||||
|
||||
runtime.Host.NotifyLoginStarted(PeerA);
|
||||
runtime.Host.NotifyLoginSucceeded(PeerA);
|
||||
runtime.UpdateAuthoritativeMovement(TimeSpan.FromMilliseconds(50));
|
||||
|
||||
Assert.That(runtime.TryGetAuthoritativeMovementState(PeerA, out var state), Is.True);
|
||||
Assert.That(state.PlayerId, Is.EqualTo("player-a"));
|
||||
Assert.That(state.LastAcceptedMoveTick, Is.EqualTo(0));
|
||||
Assert.That(state.PositionX, Is.EqualTo(0f).Within(0.0001f));
|
||||
Assert.That(state.PositionZ, Is.EqualTo(0f).Within(0.0001f));
|
||||
Assert.That(state.Hp, Is.EqualTo(100));
|
||||
Assert.That(createdTransports[9001].BroadcastMessages.Count, Is.EqualTo(1));
|
||||
|
||||
var broadcast = ParsePlayerState(createdTransports[9001].BroadcastMessages[0]);
|
||||
Assert.That(broadcast.PlayerId, Is.EqualTo("player-a"));
|
||||
Assert.That(broadcast.Tick, Is.EqualTo(1));
|
||||
Assert.That(broadcast.Position.X, Is.EqualTo(0f).Within(0.0001f));
|
||||
Assert.That(broadcast.Position.Z, Is.EqualTo(0f).Within(0.0001f));
|
||||
Assert.That(broadcast.Velocity.X, Is.EqualTo(0f).Within(0.0001f));
|
||||
Assert.That(broadcast.Velocity.Z, Is.EqualTo(0f).Within(0.0001f));
|
||||
Assert.That(broadcast.Hp, Is.EqualTo(100));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UpdateAuthoritativeMovement_UsesReliableLaneWhenSyncTransportIsUnavailable()
|
||||
{
|
||||
|
||||
@@ -54,6 +54,33 @@ namespace Tests.EditMode.Network
|
||||
Assert.That(shootInput.TargetId, Is.EqualTo(string.Empty));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ClientGameplayInputFlow_TryCreateShootInput_LocalFirePathKeepsTargetOptional()
|
||||
{
|
||||
var created = ClientGameplayInputFlow.TryCreateShootInput(
|
||||
"player-1",
|
||||
21,
|
||||
true,
|
||||
new Vector3(2f, 0f, 0f),
|
||||
out var shootInput);
|
||||
var ignored = ClientGameplayInputFlow.TryCreateShootInput(
|
||||
"player-1",
|
||||
22,
|
||||
false,
|
||||
Vector3.forward,
|
||||
out var ignoredShootInput);
|
||||
|
||||
Assert.That(created, Is.True);
|
||||
Assert.That(shootInput, Is.Not.Null);
|
||||
Assert.That(shootInput.PlayerId, Is.EqualTo("player-1"));
|
||||
Assert.That(shootInput.Tick, Is.EqualTo(21));
|
||||
Assert.That(shootInput.DirX, Is.EqualTo(1f));
|
||||
Assert.That(shootInput.DirY, Is.EqualTo(0f));
|
||||
Assert.That(shootInput.TargetId, Is.EqualTo(string.Empty));
|
||||
Assert.That(ignored, Is.False);
|
||||
Assert.That(ignoredShootInput, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ClientPredictionBuffer_AuthoritativeState_PrunesAcknowledgedMoveInputs()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user