process TODO.md step2
This commit is contained in:
@@ -4,6 +4,54 @@ using Network.NetworkApplication;
|
||||
using UnityEngine;
|
||||
using Vector3 = UnityEngine.Vector3;
|
||||
|
||||
public static class ClientGameplayInputFlow
|
||||
{
|
||||
public static bool HasPlanarInput(Vector3 input)
|
||||
{
|
||||
return new Vector2(input.x, input.z).sqrMagnitude > 0f;
|
||||
}
|
||||
|
||||
public static bool TryCreateMoveInput(string playerId, long tick, Vector3 input, bool stopMessagePending, out MoveInput message)
|
||||
{
|
||||
if (!HasPlanarInput(input) && !stopMessagePending)
|
||||
{
|
||||
message = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
message = new MoveInput
|
||||
{
|
||||
PlayerId = playerId,
|
||||
Tick = tick,
|
||||
MoveX = input.x,
|
||||
MoveY = input.z
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
public static ShootInput CreateShootInput(string playerId, long tick, Vector3 aimDirection, string targetId = "")
|
||||
{
|
||||
var planarDirection = new Vector3(aimDirection.x, 0f, aimDirection.z);
|
||||
if (planarDirection.sqrMagnitude <= 0f)
|
||||
{
|
||||
planarDirection = Vector3.forward;
|
||||
}
|
||||
else
|
||||
{
|
||||
planarDirection.Normalize();
|
||||
}
|
||||
|
||||
return new ShootInput
|
||||
{
|
||||
PlayerId = playerId,
|
||||
Tick = tick,
|
||||
DirX = planarDirection.x,
|
||||
DirY = planarDirection.z,
|
||||
TargetId = targetId ?? string.Empty
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class MovementComponent : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private float _sendInterval = 0.05f;
|
||||
@@ -26,7 +74,10 @@ public class MovementComponent : MonoBehaviour
|
||||
private Vector3 _currentPos;
|
||||
private float _lerpTime;
|
||||
[SerializeField] private float _lerpRate = 0.1f;
|
||||
private MoveInput _cachedInput;
|
||||
private Vector3 _cachedMoveInput;
|
||||
private Vector3 _lastAimDirection = Vector3.forward;
|
||||
private bool _wasMovingLastFrame;
|
||||
private bool _stopMessagePending;
|
||||
|
||||
public void Init(bool isControlled, Player master, int speed = 0, long serverTick = 0)
|
||||
{
|
||||
@@ -44,13 +95,33 @@ public class MovementComponent : MonoBehaviour
|
||||
{
|
||||
if (_isControlled)
|
||||
{
|
||||
_cachedInput = CaptureInput();
|
||||
_cachedMoveInput = CaptureMovement();
|
||||
var hasMovement = ClientGameplayInputFlow.HasPlanarInput(_cachedMoveInput);
|
||||
if (hasMovement)
|
||||
{
|
||||
_lastAimDirection = _cachedMoveInput;
|
||||
_stopMessagePending = false;
|
||||
}
|
||||
else if (_wasMovingLastFrame)
|
||||
{
|
||||
_stopMessagePending = true;
|
||||
}
|
||||
|
||||
_wasMovingLastFrame = hasMovement;
|
||||
|
||||
var shootInput = CaptureShootInput();
|
||||
if (shootInput != null)
|
||||
{
|
||||
NetworkManager.Instance.SendShootInput(shootInput);
|
||||
}
|
||||
|
||||
if (Time.time - _lastSendTime > _sendInterval)
|
||||
{
|
||||
if (_cachedInput != null)
|
||||
if (ClientGameplayInputFlow.TryCreateMoveInput(_master.PlayerId, Tick, _cachedMoveInput, _stopMessagePending, out var moveInput))
|
||||
{
|
||||
NetworkManager.Instance.SendMoveInput(_cachedInput);
|
||||
NetworkManager.Instance.SendMoveInput(moveInput);
|
||||
_predictionBuffer.Record(moveInput);
|
||||
_stopMessagePending = false;
|
||||
}
|
||||
|
||||
_lastSendTime = Time.time;
|
||||
@@ -72,11 +143,7 @@ public class MovementComponent : MonoBehaviour
|
||||
_hasServerState = false;
|
||||
}
|
||||
|
||||
Simulate(_cachedInput);
|
||||
if (_cachedInput != null)
|
||||
{
|
||||
_predictionBuffer.Record(_cachedInput);
|
||||
}
|
||||
Simulate(_cachedMoveInput);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -98,27 +165,36 @@ public class MovementComponent : MonoBehaviour
|
||||
ReplayPendingInputs(replayInputs);
|
||||
}
|
||||
|
||||
private MoveInput CaptureInput()
|
||||
private Vector3 CaptureMovement()
|
||||
{
|
||||
var input = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical"));
|
||||
if (input == Vector3.zero)
|
||||
return new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
|
||||
}
|
||||
|
||||
private ShootInput CaptureShootInput()
|
||||
{
|
||||
if (!Input.GetMouseButtonDown(0))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new MoveInput
|
||||
{
|
||||
PlayerId = _master.PlayerId,
|
||||
Tick = Tick,
|
||||
MoveX = input.x,
|
||||
MoveY = input.z
|
||||
};
|
||||
return ClientGameplayInputFlow.CreateShootInput(_master.PlayerId, Tick, ResolveAimDirection());
|
||||
}
|
||||
|
||||
private void Simulate(MoveInput input)
|
||||
private Vector3 ResolveAimDirection()
|
||||
{
|
||||
var dir = input == null ? Vector3.zero : new Vector3(input.MoveX, 0f, input.MoveY);
|
||||
_rigid.velocity = _speed * dir;
|
||||
if (ClientGameplayInputFlow.HasPlanarInput(_lastAimDirection))
|
||||
{
|
||||
return _lastAimDirection;
|
||||
}
|
||||
|
||||
var forward = _master != null ? _master.transform.forward : transform.forward;
|
||||
var planarForward = new Vector3(forward.x, 0f, forward.z);
|
||||
return ClientGameplayInputFlow.HasPlanarInput(planarForward) ? planarForward : Vector3.forward;
|
||||
}
|
||||
|
||||
private void Simulate(Vector3 input)
|
||||
{
|
||||
_rigid.velocity = _speed * input;
|
||||
if (_isControlled)
|
||||
{
|
||||
MainUI.Instance.OnClientPosChanged(_rigid.position);
|
||||
@@ -182,4 +258,4 @@ public class MovementComponent : MonoBehaviour
|
||||
MainUI.Instance.OnClientPosChanged(_rigid.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "NetworkFramework",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:d972d56d6b084684b5b0666f4856da75"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 25ab7ef51eeef1e4ea6e24413cbabdff
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -13,16 +13,17 @@ public class NetworkManager : MonoBehaviour
|
||||
private const int DefaultReliablePort = 8080;
|
||||
private const int DefaultSyncPort = 8081;
|
||||
|
||||
public static NetworkManager Instance;
|
||||
private SharedNetworkRuntime _networkRuntime;
|
||||
private IPEndPoint _serverPoint;
|
||||
private uint _sequence = 0;
|
||||
private Task _networkDrainTask = Task.CompletedTask;
|
||||
[SerializeField] private GameObject _wrongWindow;
|
||||
[SerializeField] private bool _enableNetworkDiagnosticsOverlay = true;
|
||||
[SerializeField] private string _serverIp = DefaultServerIp;
|
||||
[SerializeField] private int _reliablePort = DefaultReliablePort;
|
||||
[SerializeField] private int _syncPort = DefaultSyncPort;
|
||||
|
||||
public static NetworkManager Instance;
|
||||
private SharedNetworkRuntime _networkRuntime;
|
||||
private IPEndPoint _serverPoint;
|
||||
private uint _sequence = 0;
|
||||
private Task _networkDrainTask = Task.CompletedTask;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@@ -197,24 +198,23 @@ public class NetworkManager : MonoBehaviour
|
||||
Debug.Log($"[NetworkManager] Session {lifecycleEvent.PreviousState} -> {lifecycleEvent.CurrentState} ({lifecycleEvent.Kind}) {lifecycleEvent.Reason}");
|
||||
}
|
||||
|
||||
public void SendMoveInput(string playerId, Vector3 input)
|
||||
{
|
||||
var message = new MoveInput
|
||||
{
|
||||
PlayerId = playerId,
|
||||
MoveX = input.x,
|
||||
MoveY = input.z
|
||||
};
|
||||
_networkRuntime.MessageManager.SendMessage(message, MessageType.MoveInput);
|
||||
Debug.Log($"PlayerMoveSeq: {_sequence++}");
|
||||
}
|
||||
|
||||
public void SendMoveInput(MoveInput message)
|
||||
{
|
||||
_networkRuntime.MessageManager.SendMessage(message, MessageType.MoveInput);
|
||||
Debug.Log($"PlayerMoveSeq: {_sequence++}");
|
||||
}
|
||||
|
||||
public void SendShootInput(string playerId, Vector3 direction, long tick = 0, string targetId = "")
|
||||
{
|
||||
SendShootInput(ClientGameplayInputFlow.CreateShootInput(playerId, tick, direction, targetId));
|
||||
}
|
||||
|
||||
public void SendShootInput(ShootInput message)
|
||||
{
|
||||
_networkRuntime.MessageManager.SendMessage(message, MessageType.ShootInput);
|
||||
Debug.Log($"PlayerShootSeq: {_sequence++}");
|
||||
}
|
||||
|
||||
public void SendLoginRequest(string playerId, int speed)
|
||||
{
|
||||
var request = new LoginRequest()
|
||||
|
||||
@@ -86,6 +86,38 @@ namespace Tests.EditMode.Network
|
||||
Assert.That(parsed.MoveY, Is.EqualTo(-1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SendMessage_ZeroVectorMoveInput_UsesSyncLanePolicy()
|
||||
{
|
||||
var reliableTransport = new FakeTransport();
|
||||
var syncTransport = new FakeTransport();
|
||||
var manager = new MessageManager(
|
||||
reliableTransport,
|
||||
new MainThreadNetworkDispatcher(),
|
||||
new DefaultMessageDeliveryPolicyResolver(),
|
||||
syncTransport);
|
||||
var message = new MoveInput
|
||||
{
|
||||
PlayerId = "player-1",
|
||||
Tick = 13,
|
||||
MoveX = 0f,
|
||||
MoveY = 0f
|
||||
};
|
||||
|
||||
manager.SendMessage(message, MessageType.MoveInput);
|
||||
|
||||
Assert.That(reliableTransport.SendCallCount, Is.EqualTo(0));
|
||||
Assert.That(syncTransport.SendCallCount, Is.EqualTo(1));
|
||||
|
||||
var envelope = Envelope.Parser.ParseFrom(syncTransport.LastSentData);
|
||||
var parsed = MoveInput.Parser.ParseFrom(envelope.Payload);
|
||||
Assert.That(envelope.Type, Is.EqualTo((int)MessageType.MoveInput));
|
||||
Assert.That(parsed.PlayerId, Is.EqualTo("player-1"));
|
||||
Assert.That(parsed.Tick, Is.EqualTo(13));
|
||||
Assert.That(parsed.MoveX, Is.EqualTo(0f));
|
||||
Assert.That(parsed.MoveY, Is.EqualTo(0f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SendMessage_ShootInput_UsesReliableLanePolicy()
|
||||
{
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"references": [
|
||||
"Network.Runtime",
|
||||
"UnityEngine.TestRunner",
|
||||
"UnityEditor.TestRunner"
|
||||
"UnityEditor.TestRunner",
|
||||
"NetworkFramework"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
|
||||
@@ -6,11 +6,54 @@ using Network.NetworkApplication;
|
||||
using Network.NetworkHost;
|
||||
using Network.NetworkTransport;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using Vector3 = UnityEngine.Vector3;
|
||||
|
||||
namespace Tests.EditMode.Network
|
||||
{
|
||||
public class SyncStrategyTests
|
||||
{
|
||||
[Test]
|
||||
public void ClientGameplayInputFlow_StopTransition_EmitsSingleZeroVectorMoveInput()
|
||||
{
|
||||
var released = ClientGameplayInputFlow.TryCreateMoveInput(
|
||||
"player-1",
|
||||
8,
|
||||
Vector3.zero,
|
||||
true,
|
||||
out var stopInput);
|
||||
var continuedIdle = ClientGameplayInputFlow.TryCreateMoveInput(
|
||||
"player-1",
|
||||
9,
|
||||
Vector3.zero,
|
||||
false,
|
||||
out var idleInput);
|
||||
|
||||
Assert.That(released, Is.True);
|
||||
Assert.That(stopInput, Is.Not.Null);
|
||||
Assert.That(stopInput.PlayerId, Is.EqualTo("player-1"));
|
||||
Assert.That(stopInput.Tick, Is.EqualTo(8));
|
||||
Assert.That(stopInput.MoveX, Is.EqualTo(0f));
|
||||
Assert.That(stopInput.MoveY, Is.EqualTo(0f));
|
||||
Assert.That(continuedIdle, Is.False);
|
||||
Assert.That(idleInput, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ClientGameplayInputFlow_CreateShootInput_UsesSplitShootMessageFields()
|
||||
{
|
||||
var shootInput = ClientGameplayInputFlow.CreateShootInput(
|
||||
"player-1",
|
||||
21,
|
||||
new Vector3(2f, 0f, 0f));
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ClientPredictionBuffer_AuthoritativeState_PrunesAcknowledgedMoveInputs()
|
||||
{
|
||||
@@ -149,4 +192,4 @@ namespace Tests.EditMode.Network
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user