netcode: replicate other players for connecting client
This commit is contained in:
@@ -23,10 +23,9 @@ public class Game : GameModule
|
||||
//options.CompileEnv.PreprocessorDefinitions.Add("COMPILE_WITH_CSG_BUILDER");
|
||||
//options.PublicDefinitions.Add("COMPILE_WITH_CSG_BUILDER");
|
||||
|
||||
options.PublicDependencies.Add("FidelityFXFSR");
|
||||
|
||||
base.Setup(options);
|
||||
|
||||
options.PublicDependencies.Add("FidelityFXFSR");
|
||||
// Here you can modify the build options for your game module
|
||||
// To reference another module use: options.PublicDependencies.Add("Audio");
|
||||
// To add C++ define use: options.PublicDefinitions.Add("COMPILE_WITH_FLAX");
|
||||
|
||||
@@ -10,34 +10,184 @@ namespace Game
|
||||
{
|
||||
public enum GameModeMessageType : byte
|
||||
{
|
||||
WelcomePlayer,
|
||||
SpawnPlayer,
|
||||
PlayerInput,
|
||||
|
||||
PlayerPosition, // debug
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static class GameModeManager
|
||||
{
|
||||
private class WorldState
|
||||
{
|
||||
public ulong frame;
|
||||
public List<PlayerActor> actors;
|
||||
|
||||
public Dictionary<uint, PlayerFrame[]> playerFrameHistory;
|
||||
|
||||
public WorldState()
|
||||
{
|
||||
actors = new List<PlayerActor>();
|
||||
playerFrameHistory = new Dictionary<uint, PlayerFrame[]>();
|
||||
}
|
||||
}
|
||||
|
||||
private class PlayerFrame
|
||||
{
|
||||
public ulong frame;
|
||||
public Vector3 position;
|
||||
}
|
||||
|
||||
private static Dictionary<uint, PlayerActor> players;
|
||||
private static Dictionary<uint, NetworkConnection> playerConnections;
|
||||
private static bool spawned = false;
|
||||
|
||||
private static WorldState worldState;
|
||||
private static PlayerFrame[] localPlayerFrameHistory;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
players = new Dictionary<uint, PlayerActor>();
|
||||
playerConnections = new Dictionary<uint, NetworkConnection>();
|
||||
localPlayerFrameHistory = new PlayerFrame[120];
|
||||
worldState = new WorldState();
|
||||
|
||||
NetworkManager.OnMessage += OnMessage;
|
||||
Level.SceneLoaded += OnLevelLoaded;
|
||||
Scripting.LateUpdate += OnLateUpdatePre;
|
||||
}
|
||||
|
||||
public static void Cleanup()
|
||||
{
|
||||
NetworkManager.OnMessage -= OnMessage;
|
||||
Level.SceneLoaded -= OnLevelLoaded;
|
||||
Scripting.LateUpdate -= OnLateUpdatePre;
|
||||
}
|
||||
|
||||
private static PlayerFrame GetPlayerFrame(uint playerIndex, ulong playerFrameIndex)
|
||||
{
|
||||
PlayerFrame[] playerFrames = worldState.playerFrameHistory[playerIndex];
|
||||
PlayerFrame playerFrame = playerFrames[playerFrameIndex % 120];
|
||||
|
||||
if (playerFrame.frame != playerFrameIndex)
|
||||
return null;
|
||||
|
||||
return playerFrame;
|
||||
}
|
||||
|
||||
public static void OnLevelLoaded(Scene scene, Guid assetGuid)
|
||||
{
|
||||
worldState.frame = 0;
|
||||
Console.Print("level loaded");
|
||||
}
|
||||
|
||||
public static void OnLateUpdatePre()
|
||||
{
|
||||
try
|
||||
{
|
||||
NetworkManager.IsServer = NetworkManager.server != null;
|
||||
NetworkManager.IsClient = NetworkManager.client != null && NetworkManager.server == null;
|
||||
OnLateUpdate();
|
||||
}
|
||||
finally
|
||||
{
|
||||
NetworkManager.IsServer = false;
|
||||
NetworkManager.IsClient = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void OnLateUpdate()
|
||||
{
|
||||
if (NetworkManager.IsServer)
|
||||
{
|
||||
foreach (KeyValuePair<uint,PlayerActor> kv in players)
|
||||
{
|
||||
var playerId = kv.Key;
|
||||
var playerActor = kv.Value;
|
||||
|
||||
var playerFrames = worldState.playerFrameHistory[playerId];
|
||||
var playerFrame = playerFrames[worldState.frame % 120];
|
||||
|
||||
playerFrame.frame = worldState.frame;
|
||||
playerFrame.position = playerActor.Position;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<uint, PlayerActor> kv in players)
|
||||
{
|
||||
var playerId = kv.Key;
|
||||
foreach (KeyValuePair<uint, PlayerActor> kv2 in players)
|
||||
{
|
||||
if (kv2.Key == playerId)
|
||||
continue;
|
||||
|
||||
var otherPlayerActor = kv2.Value;
|
||||
|
||||
// TODO: relevancy checks here etc.
|
||||
|
||||
{
|
||||
NetworkMessage message = NetworkManager.ServerBeginSendMessage();
|
||||
message.WriteByte((byte)GameModeMessageType.PlayerPosition);
|
||||
message.WriteUInt64(worldState.frame);
|
||||
message.WriteUInt32(kv2.Key);
|
||||
message.WriteSingle(otherPlayerActor.Position.X);
|
||||
message.WriteSingle(otherPlayerActor.Position.Y);
|
||||
message.WriteSingle(otherPlayerActor.Position.Z);
|
||||
NetworkManager.ServerEndSendMessage(ref message, playerConnections[playerId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (NetworkManager.IsClient)
|
||||
{
|
||||
if (!spawned)
|
||||
return;
|
||||
|
||||
PlayerActor playerActor = Level.GetActors<PlayerActor>().First(x =>
|
||||
x.GetScript<PlayerMovement>().PlayerId == NetworkManager.LocalPlayerClientId);
|
||||
|
||||
var playerFrame = localPlayerFrameHistory[worldState.frame % 120];
|
||||
|
||||
playerFrame.frame = worldState.frame;
|
||||
playerFrame.position = playerActor.Position;
|
||||
|
||||
{
|
||||
NetworkMessage message = NetworkManager.ClientBeginSendMessage();
|
||||
message.WriteByte((byte)GameModeMessageType.PlayerPosition);
|
||||
message.WriteUInt64(worldState.frame);
|
||||
message.WriteUInt32(NetworkManager.LocalPlayerClientId);
|
||||
message.WriteSingle(playerActor.Position.X);
|
||||
message.WriteSingle(playerActor.Position.Y);
|
||||
message.WriteSingle(playerActor.Position.Z);
|
||||
NetworkManager.ClientEndSendMessage(ref message);
|
||||
}
|
||||
}
|
||||
|
||||
worldState.frame++;
|
||||
|
||||
/*foreach (KeyValuePair<uint,PlayerActor> kv in players)
|
||||
{
|
||||
var playerId = kv.Key;
|
||||
var playerActor = kv.Value;
|
||||
var playerConnection = playerConnections[playerId];
|
||||
|
||||
if (NetworkManager.IsLocalClient && playerId == NetworkManager.LocalPlayerClientId)
|
||||
continue;
|
||||
|
||||
{
|
||||
NetworkMessage message = NetworkManager.ServerBeginSendMessage();
|
||||
message.WriteByte((byte)GameModeMessageType.PlayerPosition);
|
||||
message.WriteUInt64(0);
|
||||
message.WriteSingle(playerActor.Position.X);
|
||||
message.WriteSingle(playerActor.Position.Y);
|
||||
message.WriteSingle(playerActor.Position.Z);
|
||||
NetworkManager.ServerEndSendMessage(ref message, playerConnections[playerId]);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
public static bool OnMessage(ref NetworkEvent networkEvent)
|
||||
{
|
||||
byte messageTypeByte = networkEvent.Message.ReadByte();
|
||||
@@ -52,9 +202,38 @@ namespace Game
|
||||
//Console.Print($"GameModeManager: {messageType}");
|
||||
switch (messageType)
|
||||
{
|
||||
case GameModeMessageType.WelcomePlayer:
|
||||
{
|
||||
if (NetworkManager.IsClient)
|
||||
{
|
||||
worldState.frame = networkEvent.Message.ReadUInt64();
|
||||
int numActors = (int)networkEvent.Message.ReadUInt32();
|
||||
for (int i=0; i<numActors; i++)
|
||||
{
|
||||
uint playerId = networkEvent.Message.ReadUInt32();
|
||||
Vector3 playerPosition;
|
||||
playerPosition.X = networkEvent.Message.ReadSingle();
|
||||
playerPosition.Y = networkEvent.Message.ReadSingle();
|
||||
playerPosition.Z = networkEvent.Message.ReadSingle();
|
||||
|
||||
SpawnPlayer(playerId, playerPosition, new Vector3(0));
|
||||
}
|
||||
|
||||
Console.Print("received welcome: frame " + worldState.frame);
|
||||
|
||||
players.Add(NetworkManager.LocalPlayerClientId, null);
|
||||
//playerConnections.Add(NetworkManager.LocalPlayerClientId, connection);
|
||||
localPlayerFrameHistory = new PlayerFrame[120];
|
||||
for (int i = 0; i < localPlayerFrameHistory.Length; i++)
|
||||
localPlayerFrameHistory[i] = new PlayerFrame();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GameModeMessageType.SpawnPlayer:
|
||||
{
|
||||
SpawnPlayer(networkEvent.Message.ReadUInt32(),
|
||||
uint playerId = networkEvent.Message.ReadUInt32();
|
||||
ulong playerFrameIndex = networkEvent.Message.ReadUInt64();
|
||||
SpawnPlayer(playerId,
|
||||
new Vector3(networkEvent.Message.ReadSingle(), networkEvent.Message.ReadSingle(), networkEvent.Message.ReadSingle()),
|
||||
new Vector3(networkEvent.Message.ReadSingle(), networkEvent.Message.ReadSingle(), networkEvent.Message.ReadSingle()));
|
||||
break;
|
||||
@@ -74,6 +253,64 @@ namespace Game
|
||||
UpdatePlayerInput(playerId, inputState);
|
||||
break;
|
||||
}
|
||||
case GameModeMessageType.PlayerPosition:
|
||||
{
|
||||
uint playerId = networkEvent.Sender.ConnectionId;
|
||||
Vector3 reportedPosition;
|
||||
|
||||
ulong reportedFrame = networkEvent.Message.ReadUInt64();
|
||||
uint reportedPlayerId = networkEvent.Message.ReadUInt32();
|
||||
reportedPosition.X = networkEvent.Message.ReadSingle();
|
||||
reportedPosition.Y = networkEvent.Message.ReadSingle();
|
||||
reportedPosition.Z = networkEvent.Message.ReadSingle();
|
||||
|
||||
if (NetworkManager.IsLocalClient && !NetworkManager.IsServer)
|
||||
{
|
||||
|
||||
}
|
||||
else if (NetworkManager.IsServer)
|
||||
{
|
||||
PlayerFrame playerFrame = GetPlayerFrame(playerId, reportedFrame);
|
||||
PlayerActor playerActor = players[playerId];
|
||||
if (playerFrame == null)
|
||||
Console.Print("frame is in the past, unable to verify frame");
|
||||
else
|
||||
{
|
||||
Vector3 playerFramePosition = playerFrame.position;
|
||||
if ((playerFramePosition - reportedPosition).Length > 0.0001)
|
||||
{
|
||||
Console.Print("player drifted, len: " + (playerFramePosition - reportedPosition).Length);
|
||||
|
||||
{
|
||||
NetworkMessage message = NetworkManager.ServerBeginSendMessage();
|
||||
message.WriteByte((byte)GameModeMessageType.PlayerPosition);
|
||||
/*message.WriteUInt64(reportedFrame);
|
||||
message.WriteUInt32(playerId);
|
||||
message.WriteSingle(playerFramePosition.X);
|
||||
message.WriteSingle(playerFramePosition.Y);
|
||||
message.WriteSingle(playerFramePosition.Z);*/
|
||||
message.WriteUInt64(worldState.frame);
|
||||
message.WriteUInt32(playerId);
|
||||
message.WriteSingle(playerActor.Position.X);
|
||||
message.WriteSingle(playerActor.Position.Y);
|
||||
message.WriteSingle(playerActor.Position.Z);
|
||||
NetworkManager.ServerEndSendMessage(ref message, playerConnections[playerId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (NetworkManager.IsClient)
|
||||
{
|
||||
Console.Print($"we drifted, corrected. client frame: {worldState.frame}, server frame: {reportedFrame}");
|
||||
PlayerActor playerActor = Level.GetActors<PlayerActor>().FirstOrDefault(x =>
|
||||
x.GetScript<PlayerMovement>().PlayerId == reportedPlayerId);
|
||||
|
||||
if (playerActor != null)
|
||||
playerActor.SetPosition(reportedPosition);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Console.PrintError($"GameModeManager: Unhandled message type: {messageType}");
|
||||
return false;
|
||||
@@ -84,6 +321,22 @@ namespace Game
|
||||
|
||||
public static bool OnClientConnecting(NetworkConnection connection)
|
||||
{
|
||||
if (connection.ConnectionId != NetworkManager.LocalPlayerClientId)
|
||||
{
|
||||
Console.Print("sending welcome: frame " + worldState.frame);
|
||||
NetworkMessage message = NetworkManager.ServerBeginSendMessage();
|
||||
message.WriteByte((byte)GameModeMessageType.WelcomePlayer);
|
||||
message.WriteUInt64(worldState.frame);
|
||||
message.WriteUInt32((uint)worldState.actors.Count);
|
||||
foreach (PlayerActor playerActor in worldState.actors)
|
||||
{
|
||||
message.WriteUInt32(playerActor.GetScript<PlayerMovement>().PlayerId);
|
||||
message.WriteSingle(playerActor.Position.X);
|
||||
message.WriteSingle(playerActor.Position.Y);
|
||||
message.WriteSingle(playerActor.Position.Z);
|
||||
}
|
||||
NetworkManager.ServerEndSendMessage(ref message, connection);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -99,12 +352,19 @@ namespace Game
|
||||
Vector3 eulerAngles = randomSpawn.Orientation.EulerAngles;
|
||||
|
||||
players.Add(playerId, null);
|
||||
playerConnections.Add(playerId, connection);
|
||||
var playerFrames = new PlayerFrame[120];
|
||||
for (int i = 0; i < playerFrames.Length; i++)
|
||||
playerFrames[i] = new PlayerFrame();
|
||||
worldState.playerFrameHistory.Add(playerId, playerFrames);
|
||||
|
||||
|
||||
SpawnPlayer(playerId, position, eulerAngles);
|
||||
{
|
||||
NetworkMessage message = NetworkManager.ServerBeginSendMessage();
|
||||
message.WriteByte((byte)GameModeMessageType.SpawnPlayer);
|
||||
message.WriteUInt32(playerId);
|
||||
message.WriteUInt64(worldState.frame);
|
||||
message.WriteSingle(position.X);
|
||||
message.WriteSingle(position.Y);
|
||||
message.WriteSingle(position.Z);
|
||||
@@ -122,6 +382,8 @@ namespace Game
|
||||
if (NetworkManager.IsLocalClient)
|
||||
return; // Handled by listenserver
|
||||
|
||||
spawned = true;
|
||||
|
||||
string workDir = Directory.GetCurrentDirectory();
|
||||
string prefabPath = Path.Combine(workDir, "Content", "Common");
|
||||
var playerPrefab = Content.Load<Prefab>(Path.Combine(prefabPath, "PlayerPrefab.prefab"));
|
||||
@@ -132,7 +394,12 @@ namespace Game
|
||||
playerActor.Initialize(playerId);
|
||||
playerActor.Teleport(position, eulerAngles);
|
||||
|
||||
if (!players.ContainsKey(playerId))
|
||||
players.Add(playerId, null);
|
||||
players[playerId] = playerActor;
|
||||
|
||||
if (NetworkManager.IsServer)
|
||||
worldState.actors.Add(playerActor);
|
||||
}
|
||||
|
||||
private static void UpdatePlayerInput(uint playerId, PlayerInputState inputState)
|
||||
|
||||
@@ -43,7 +43,8 @@ namespace Game
|
||||
private static readonly ushort MaximumClients = 32;
|
||||
public static OnMessageDecl OnMessage;
|
||||
|
||||
public static bool IsServer => server != null;
|
||||
public static bool IsServer = false;
|
||||
public static bool IsClient = false;
|
||||
public static bool IsLocalClient = false; // Context dependant, true when message is handled by local client
|
||||
|
||||
public static void Init()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.Networking;
|
||||
using Console = Cabrito.Console;
|
||||
@@ -62,12 +63,34 @@ namespace Game
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLocalClient = IsServer;
|
||||
IsLocalClient = server != null;
|
||||
IsClient = true;
|
||||
OnNetworkMessage(ref networkEvent);
|
||||
|
||||
if (networkEvent.Message.Position > 0 &&
|
||||
networkEvent.Message.Position < networkEvent.Message.Length)
|
||||
{
|
||||
string err =
|
||||
$"Network message was not fully read: {networkEvent.Message.Position} / {networkEvent.Message.Length}.";
|
||||
|
||||
networkEvent.Message.Position = 0;
|
||||
byte[] messageBytes = new byte[networkEvent.Message.Length];
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* messageBytePtr = &messageBytes[0])
|
||||
networkEvent.Message.ReadBytes(messageBytePtr, (int)networkEvent.Message.Length);
|
||||
}
|
||||
|
||||
string messageBytesStr = string.Join(", ",
|
||||
messageBytes.Select(x => "0x" + ((int)x).ToString("X2")));
|
||||
|
||||
Console.PrintError(err + $"Message dump: {messageBytesStr}");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLocalClient = false;
|
||||
IsClient = false;
|
||||
client.RecycleMessage(networkEvent.Message);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -103,15 +103,24 @@ namespace Game
|
||||
{
|
||||
Console.Print($"Client({networkEvent.Sender.ConnectionId}) is trying to connect");
|
||||
|
||||
if (GameModeManager.OnClientConnecting(networkEvent.Sender))
|
||||
try
|
||||
{
|
||||
ConnectedClients.Add(networkEvent.Sender);
|
||||
Console.Print($"Client({networkEvent.Sender.ConnectionId}) connected. Total clients: {ConnectedClients.Count}");
|
||||
IsServer = true;
|
||||
if (GameModeManager.OnClientConnecting(networkEvent.Sender))
|
||||
{
|
||||
ConnectedClients.Add(networkEvent.Sender);
|
||||
Console.Print(
|
||||
$"Client({networkEvent.Sender.ConnectionId}) connected. Total clients: {ConnectedClients.Count}");
|
||||
|
||||
GameModeManager.OnClientConnected(networkEvent.Sender);
|
||||
GameModeManager.OnClientConnected(networkEvent.Sender);
|
||||
}
|
||||
else
|
||||
Console.Print($"Client({networkEvent.Sender.ConnectionId}) connection refused");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsServer = false;
|
||||
}
|
||||
else
|
||||
Console.Print($"Client({networkEvent.Sender.ConnectionId}) connection refused");
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -128,10 +137,32 @@ namespace Game
|
||||
{
|
||||
try
|
||||
{
|
||||
IsServer = true;
|
||||
OnNetworkMessage(ref networkEvent);
|
||||
|
||||
if (networkEvent.Message.Position > 0 &&
|
||||
networkEvent.Message.Position < networkEvent.Message.Length)
|
||||
{
|
||||
string err =
|
||||
$"Network message was not fully read: {networkEvent.Message.Position} / {networkEvent.Message.Length}.";
|
||||
|
||||
networkEvent.Message.Position = 0;
|
||||
byte[] messageBytes = new byte[networkEvent.Message.Length];
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* messageBytePtr = &messageBytes[0])
|
||||
networkEvent.Message.ReadBytes(messageBytePtr, (int)networkEvent.Message.Length);
|
||||
}
|
||||
|
||||
string messageBytesStr = string.Join(", ",
|
||||
messageBytes.Select(x => "0x" + ((int)x).ToString("X2")));
|
||||
|
||||
Console.PrintError(err + $"Message dump: {messageBytesStr}");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsServer = false;
|
||||
server.RecycleMessage(networkEvent.Message);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace Game
|
||||
private readonly bool predicting = false;
|
||||
|
||||
private readonly List<PhysicsColliderActor> touchingActors = new List<PhysicsColliderActor>();
|
||||
private int currentInputFrame;
|
||||
public int currentInputFrame;
|
||||
private int currentInputFrame2;
|
||||
|
||||
private Vector3 currentVelocity;
|
||||
|
||||
Reference in New Issue
Block a user