Implement RPC Node

This commit is contained in:
Aslan 2026-02-02 12:17:04 -05:00
parent f7ee533d5a
commit 605e43273e
13 changed files with 524 additions and 206 deletions

View file

@ -2,7 +2,7 @@
[ext_resource type="Script" uid="uid://cbt6p1bhh4o8q" path="res://scripts/GameObjects/Nodes/StarNode.cs" id="1_o2f8k"] [ext_resource type="Script" uid="uid://cbt6p1bhh4o8q" path="res://scripts/GameObjects/Nodes/StarNode.cs" id="1_o2f8k"]
[sub_resource type="SphereMesh" id="SphereMesh_o2f8k"] [sub_resource type="SphereMesh" id="SphereMesh_2pyv3"]
radius = 5.0 radius = 5.0
height = 10.0 height = 10.0
@ -13,7 +13,7 @@ radius = 5.0
script = ExtResource("1_o2f8k") script = ExtResource("1_o2f8k")
[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=1423841842] [node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=1423841842]
mesh = SubResource("SphereMesh_o2f8k") mesh = SubResource("SphereMesh_2pyv3")
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1898956937] [node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1898956937]
shape = SubResource("SphereShape3D_o2f8k") shape = SubResource("SphereShape3D_o2f8k")

View file

@ -55,9 +55,8 @@ GameMenu = NodePath("../GameMenu")
[node name="QueueManager" type="Node" parent="." unique_id=355148200] [node name="QueueManager" type="Node" parent="." unique_id=355148200]
script = ExtResource("4_iywne") script = ExtResource("4_iywne")
[node name="NetworkManager" type="Node" parent="." unique_id=1765485895 node_paths=PackedStringArray("RPCNode")] [node name="NetworkManager" type="Node" parent="." unique_id=1765485895]
script = ExtResource("5_p57ef") script = ExtResource("5_p57ef")
RPCNode = NodePath("../RPC")
[node name="RPC" type="Node" parent="." unique_id=498537245] [node name="RPC" type="Node" parent="." unique_id=498537245]
script = ExtResource("6_u5sy4") script = ExtResource("6_u5sy4")

View file

@ -16,25 +16,29 @@ public partial class GameManager : Node3D
[Export] public int maxFarThreads = 16; [Export] public int maxFarThreads = 16;
public static bool Loading { get; private set; } = true; public static bool Loading { get; private set; } = true;
public static GameManager Singleton { get; private set; } public static GameManager Instance { get; private set; }
public static IGenerator Generator { get; private set; } public static IGenerator Generator { get; private set; }
public static Universe GameUniverse { get; private set; } public static Universe GameUniverse { get; private set; }
public Player MainPlayer { get; private set; } public Player MainPlayer { get; private set; }
public List<Player> Players { get; private set; } = []; public Dictionary<long, Player> Players { get; private set; } = [];
private double closeTickTimer = 0; private double closeTickTimer = 0;
private double farTickTimer = 0; private double farTickTimer = 0;
public bool simulating = false; public bool simulatingClose = false;
public bool simulatingFar = false;
public bool playerReady = false; public bool playerReady = false;
private readonly Dictionary<GameObject, Node3D> spawnedObjects = []; private readonly Dictionary<GameObject, Node3D> spawnedObjects = [];
private readonly ConcurrentQueue<GameObject> spawnQueue = []; private readonly ConcurrentQueue<GameObject> spawnQueue = [];
public override void _EnterTree()
{
Instance = this;
}
public override void _Ready() public override void _Ready()
{ {
Singleton = this;
Generator = new TestGenerator(); Generator = new TestGenerator();
if (Global.IsGameHost) if (Global.IsGameHost)
@ -53,7 +57,7 @@ public partial class GameManager : Node3D
public override void _ExitTree() public override void _ExitTree()
{ {
Loading = true; Loading = true;
Singleton = null; Instance = null;
} }
public override void _Process(double delta) public override void _Process(double delta)
@ -64,20 +68,14 @@ public partial class GameManager : Node3D
} }
closeTickTimer += delta; closeTickTimer += delta;
farTickTimer += delta; if (closeTickTimer >= closeTickInterval && !simulatingClose)
if (closeTickTimer >= closeTickInterval)
{ {
SimulateClose(closeTickTimer); SimulateClose(closeTickTimer);
closeTickTimer = 0; closeTickTimer = 0;
} }
if (simulating) farTickTimer += delta;
{ if (farTickTimer >= farTickInterval && !simulatingFar)
farTickTimer = 0;
}
if (farTickTimer >= farTickInterval)
{ {
double taskFarTickTimer = farTickTimer; double taskFarTickTimer = farTickTimer;
Task.Run(() => Task.Run(() =>
@ -94,22 +92,48 @@ public partial class GameManager : Node3D
return MainPlayer.PlayerData.CurrentSector; return MainPlayer.PlayerData.CurrentSector;
} }
public Player GetPlayer(long id)
{
Players.TryGetValue(id, out Player player);
return player;
}
private void SimulateClose(double delta) private void SimulateClose(double delta)
{ {
List<Sector> neighbours = GetCurrentSector().GetNeighbouringSectors(); simulatingClose = true;
neighbours.ForEach(sector => List<Sector> sectorsClose = GetCurrentSector().GetNeighbouringSectors();
List<Task> tasks = [];
sectorsClose.ForEach(sector =>
{ {
Task.Run(() => Task simulateTask = Task.Run(() =>
{ {
sector.Simulate(delta); sector.Simulate(delta);
}); });
tasks.Add(simulateTask);
}); });
Task.WaitAll([.. tasks]);
foreach (KeyValuePair<long, Player> player in Players)
{
List<Sector> playerSectors = player.Value.PlayerData.CurrentSector.GetNeighbouringSectors();
playerSectors.ForEach(sector =>
{
if (player.Key != 1)
{
sector.NetworkSync(player.Key);
}
});
}
simulatingClose = false;
} }
private void SimulateFar(double delta) private void SimulateFar(double delta)
{ {
simulating = true; simulatingFar = true;
List<Task> tasks = []; List<Task> tasks = [];
@ -134,7 +158,7 @@ public partial class GameManager : Node3D
Task.WaitAll([.. tasks]); Task.WaitAll([.. tasks]);
simulating = false; simulatingFar = false;
} }
private void SimulateFarClustered(double delta, int startX, int countX) private void SimulateFarClustered(double delta, int startX, int countX)
@ -195,7 +219,7 @@ public partial class GameManager : Node3D
neighbours.ForEach(sector => sector.SpawnObjects()); neighbours.ForEach(sector => sector.SpawnObjects());
} }
public Player SpawnPlayer(Character character, bool isMainPlayer = false) public Player SpawnPlayer(Character character, long networkId, bool isMainPlayer = false)
{ {
Player player = character.InstantiatePlayer(); Player player = character.InstantiatePlayer();
player.GameMenu = GameMenu; player.GameMenu = GameMenu;
@ -206,7 +230,7 @@ public partial class GameManager : Node3D
OnPlayerReady(); OnPlayerReady();
} }
Players.Add(player); Players.Add(networkId, player);
character.UpdateSector(); character.UpdateSector();
@ -262,10 +286,10 @@ public partial class GameManager : Node3D
{ {
Sector current = GetCurrentSector(); Sector current = GetCurrentSector();
foreach (Player player in Players) foreach (KeyValuePair<long, Player> player in Players)
{ {
player.PlayerData.UpdateSectorOffset(current); player.Value.PlayerData.UpdateSectorOffset(current);
player.PlayerData.UpdateNodePosition(); player.Value.PlayerData.UpdateNodePosition();
} }
List<Sector> nearby = current.GetNeighbouringSectors(); List<Sector> nearby = current.GetNeighbouringSectors();
@ -281,20 +305,18 @@ public partial class GameManager : Node3D
else else
{ {
gameObject.UpdateSectorOffset(current); gameObject.UpdateSectorOffset(current);
node.GlobalPosition = gameObject.LocalCoordinates + gameObject.SectorOffset; node._Process(0);
} }
} }
nearby.ForEach(sector => sector.SpawnObjects()); nearby.ForEach(sector => sector.SpawnObjects());
} }
// These should be in RPCNode and should be queued
public void SendUniverseToClient(long id) public void SendUniverseToClient(long id)
{ {
RpcId(id, nameof(RpcDownloadUniverse), Generator.GetUniverseSize(), Generator.GetSectorSize()); RpcId(id, nameof(RpcDownloadUniverse), Generator.GetUniverseSize(), Generator.GetSectorSize());
} }
// These should be in RPCNode and should be queued
[Rpc(MultiplayerApi.RpcMode.Authority)] [Rpc(MultiplayerApi.RpcMode.Authority)]
public void RpcDownloadUniverse(Vector3I universeSize, Vector3 sectorSize) public void RpcDownloadUniverse(Vector3I universeSize, Vector3 sectorSize)
{ {
@ -302,96 +324,4 @@ public partial class GameManager : Node3D
Loading = false; Loading = false;
} }
// These should be in RPCNode and should be queued
[Rpc(MultiplayerApi.RpcMode.AnyPeer)]
public void RpcSendNearbySectors(Vector3I coordinates, long id = -1)
{
Sector sector = GameUniverse.GetSector(coordinates);
if (sector == null)
{
return;
}
List<Sector> sectors = sector.GetNeighbouringSectors();
sectors.ForEach(sector => SendSectorToClients(sector, id == -1 ? null : id));
}
// These should be in RPCNode and should be queued
public void SendSectorToClients(Sector sector, long? id = null)
{
foreach (GameObject gameObject in sector.GameObjects)
{
if (gameObject is Character)
{
continue;
}
Godot.Collections.Dictionary gameObjectData = new() {
{ "type", gameObject.GetType().Name },
{ "sectorCoordinates", sector.Coordinates },
{ "coordinates", gameObject.LocalCoordinates },
{ "uuid", gameObject.UUID.ToString() },
};
if (id.HasValue)
{
RpcId(id.Value, nameof(RpcDownloadGameObject), gameObjectData);
}
else
{
Rpc(nameof(RpcDownloadGameObject), gameObjectData);
}
}
}
// These should be in RPCNode and should be queued
[Rpc(MultiplayerApi.RpcMode.Authority)]
public void RpcDownloadGameObject(Godot.Collections.Dictionary gameObjectData)
{
if (!gameObjectData.TryGetValue("type", out var typeData))
{
return;
}
if (!gameObjectData.TryGetValue("sectorCoordinates", out var sectorCoordinatesData))
{
return;
}
if (!gameObjectData.TryGetValue("coordinates", out var coordinatesData))
{
return;
}
if (!gameObjectData.TryGetValue("uuid", out var uuidData))
{
return;
}
string type = (string)typeData;
Vector3I sectorCoordinates = (Vector3I)sectorCoordinatesData;
Vector3 coordinates = (Vector3)coordinatesData;
Guid uuid = Guid.Parse((string)uuidData);
Sector sector = GameUniverse.GetSector(sectorCoordinates);
if (sector == null)
{
return;
}
GameObject gameObject;
switch (type)
{
case "Star":
gameObject = new Star(sector, coordinates);
break;
default:
return;
}
gameObject.UUID = uuid;
Spawn(gameObject);
sector.AssignObject(gameObject);
}
} }

View file

@ -4,13 +4,107 @@ using Godot;
public abstract class GameObject public abstract class GameObject
{ {
public Guid UUID { get; set; } [Flags]
public enum DirtyFlags : ulong
{
None = 0,
UUID = 1UL << 0,
Sector = 1UL << 1,
LocalCoordinates = 1UL << 2,
Rotation = 1UL << 3,
Velocity = 1UL << 4,
AngularVelocity = 1UL << 5
}
public DirtyFlags DirtyBits { get; protected set; } = DirtyFlags.None;
protected Guid _uuid;
public Guid UUID
{
get => _uuid;
set
{
if (_uuid != value)
{
_uuid = value;
DirtyBits |= DirtyFlags.UUID;
}
}
}
protected Vector3I _currentSectorCoordinates;
protected Sector _currentSector;
public Sector CurrentSector
{
get => _currentSector;
protected set
{
if (_currentSector != value)
{
_currentSector = value;
_currentSectorCoordinates = value.Coordinates;
DirtyBits |= DirtyFlags.Sector;
}
}
}
protected Vector3 _localCoordinates;
public Vector3 LocalCoordinates
{
get => _localCoordinates;
protected set
{
if (_localCoordinates != value)
{
_localCoordinates = value;
DirtyBits |= DirtyFlags.LocalCoordinates;
}
}
}
protected Vector3 _rotation;
public Vector3 Rotation
{
get => _rotation;
protected set
{
if (_rotation != value)
{
_rotation = value;
DirtyBits |= DirtyFlags.Rotation;
}
}
}
protected Vector3 _velocity;
public Vector3 Velocity
{
get => _velocity;
protected set
{
if (_velocity != value)
{
_velocity = value;
DirtyBits |= DirtyFlags.Velocity;
}
}
}
protected Vector3 _angularVelocity;
public Vector3 AngularVelocity
{
get => _angularVelocity;
protected set
{
if (_angularVelocity != value)
{
_angularVelocity = value;
DirtyBits |= DirtyFlags.AngularVelocity;
}
}
}
public Sector CurrentSector { get; protected set; }
public Vector3 LocalCoordinates { get; protected set; }
public Vector3Dec GlobalCoordinates { get; protected set; } public Vector3Dec GlobalCoordinates { get; protected set; }
public Vector3 SectorOffset { get; protected set; }
public Vector3 SectorOffset { get; set; }
protected bool reassigning = false; protected bool reassigning = false;
@ -21,6 +115,10 @@ public abstract class GameObject
CurrentSector = sector; CurrentSector = sector;
LocalCoordinates = localCoordinates; LocalCoordinates = localCoordinates;
Velocity = new(0, 0, 1);
AngularVelocity = Vector3.Zero;
Rotation = Vector3.Zero;
GlobalCoordinates = CalculateGlobalCoordinates(sector.GlobalCenterCoordinates, localCoordinates); GlobalCoordinates = CalculateGlobalCoordinates(sector.GlobalCenterCoordinates, localCoordinates);
} }
@ -46,6 +144,12 @@ public abstract class GameObject
); );
} }
public void ApplyVelocity(double delta)
{
SetCoordinatesFromLocal(LocalCoordinates + Velocity * delta);
Rotation += AngularVelocity * delta;
}
public bool IsInCurrentSector() public bool IsInCurrentSector()
{ {
return Helpers.IsInsideArea(CurrentSector.Size / 2, LocalCoordinates); return Helpers.IsInsideArea(CurrentSector.Size / 2, LocalCoordinates);
@ -87,15 +191,15 @@ public abstract class GameObject
UpdateSectorOffsetToMainPlayer(); UpdateSectorOffsetToMainPlayer();
List<Sector> neighbours = GameManager.Singleton.GetCurrentSector().GetNeighbouringSectors(); List<Sector> neighbours = GameManager.Instance.GetCurrentSector().GetNeighbouringSectors();
if (neighbours.Contains(sector)) if (neighbours.Contains(sector))
{ {
GameManager.Singleton.Spawn(this); GameManager.Instance.Spawn(this);
} }
else else
{ {
GameManager.Singleton.Despawn(this); GameManager.Instance.Despawn(this);
} }
reassigning = false; reassigning = false;
@ -114,7 +218,7 @@ public abstract class GameObject
public void UpdateSectorOffsetToMainPlayer() public void UpdateSectorOffsetToMainPlayer()
{ {
UpdateSectorOffset(GameManager.Singleton.GetCurrentSector()); UpdateSectorOffset(GameManager.Instance.GetCurrentSector());
} }
public void SetCoordinatesFromGlobal(Vector3Dec globalCoordinates) public void SetCoordinatesFromGlobal(Vector3Dec globalCoordinates)
@ -142,6 +246,8 @@ public abstract class GameObject
public virtual void Simulate(double delta) public virtual void Simulate(double delta)
{ {
ApplyVelocity(delta);
if (!reassigning && !IsInCurrentSector()) if (!reassigning && !IsInCurrentSector())
{ {
UpdateSector(); UpdateSector();
@ -157,4 +263,70 @@ public abstract class GameObject
return instance; return instance;
} }
public virtual Godot.Collections.Dictionary NetworkWrite(long id, bool full)
{
Godot.Collections.Dictionary gameObjectData = [];
if (full)
gameObjectData.Add("type", GetType().ToString());
if (DirtyBits.HasFlag(DirtyFlags.Sector) || full)
gameObjectData.Add("sectorCoordinates", _currentSectorCoordinates);
if (DirtyBits.HasFlag(DirtyFlags.LocalCoordinates) || full)
gameObjectData.Add("localCoordinates", _localCoordinates);
if (DirtyBits.HasFlag(DirtyFlags.Rotation) || full)
gameObjectData.Add("rotation", _rotation);
if (DirtyBits.HasFlag(DirtyFlags.Velocity) || full)
gameObjectData.Add("velocity", _velocity);
if (DirtyBits.HasFlag(DirtyFlags.AngularVelocity) || full)
gameObjectData.Add("angularVelocity", _angularVelocity);
if (gameObjectData.Count > 0)
{
gameObjectData.Add("uuid", UUID.ToString());
return gameObjectData;
}
return null;
}
public virtual void NetworkRead(Godot.Collections.Dictionary gameObjectData)
{
if (gameObjectData.TryGetValue("sectorCoordinates", out var sectorCoordinatesData))
{
Sector newSector = GameManager.GameUniverse.GetSector((Vector3I)sectorCoordinatesData);
CurrentSector.GameObjects.Remove(this);
newSector.GameObjects.Add(this);
AssignSector(newSector);
}
if (gameObjectData.TryGetValue("localCoordinates", out var localCoordinatesData))
LocalCoordinates = (Vector3)localCoordinatesData;
if (gameObjectData.TryGetValue("rotation", out var rotationData))
Rotation = (Vector3)rotationData;
if (gameObjectData.TryGetValue("velocity", out var velocityData))
Velocity = (Vector3)velocityData;
if (gameObjectData.TryGetValue("angularVelocity", out var angularVelocityData))
AngularVelocity = (Vector3)angularVelocityData;
}
public override bool Equals(object obj)
{
GameObject gameObj = (GameObject)obj;
return gameObj.UUID == UUID;
}
public override int GetHashCode()
{
return UUID.GetHashCode();
}
} }

View file

@ -16,17 +16,7 @@ public class Character(Sector sector, Vector3 localCoordinates) : GameObject(sec
if (IsMainPlayer()) if (IsMainPlayer())
{ {
GameManager.Singleton.ApplyOrigin(); GameManager.Instance.ApplyOrigin();
if (!Global.IsGameHost)
{
GameManager.Singleton.RpcId(
1,
nameof(GameManager.Singleton.RpcSendNearbySectors),
CurrentSector.Coordinates,
NetworkManager.Singleton.LocalNetId
);
}
} }
reassigning = false; reassigning = false;
@ -34,7 +24,7 @@ public class Character(Sector sector, Vector3 localCoordinates) : GameObject(sec
public bool IsMainPlayer() public bool IsMainPlayer()
{ {
return player == GameManager.Singleton.MainPlayer; return player == GameManager.Instance.MainPlayer;
} }
public Player InstantiatePlayer() public Player InstantiatePlayer()
@ -48,6 +38,14 @@ public class Character(Sector sector, Vector3 localCoordinates) : GameObject(sec
return instance; return instance;
} }
public override void Simulate(double delta)
{
if (!reassigning && !IsInCurrentSector())
{
UpdateSector();
}
}
public override void UpdateNodePosition() public override void UpdateNodePosition()
{ {
if (IsMainPlayer()) if (IsMainPlayer())

View file

@ -4,13 +4,41 @@ public partial class StarNode : StaticBody3D
{ {
public Star StarData { get; set; } public Star StarData { get; set; }
private Vector3 lastFrameCoordinates = Vector3.Zero;
private Vector3 lastFrameRotation = Vector3.Zero;
private Vector3 velocityOffset = Vector3.Zero;
private Vector3 angularVelocityOffset = Vector3.Zero;
public override void _Ready() public override void _Ready()
{ {
GlobalPosition = StarData.LocalCoordinates + StarData.SectorOffset; GlobalPosition = StarData.LocalCoordinates + StarData.SectorOffset;
GlobalRotation = StarData.Rotation;
} }
public override void _Process(double delta) public override void _Process(double delta)
{ {
GlobalPosition = StarData.LocalCoordinates + StarData.SectorOffset; if (StarData.LocalCoordinates == lastFrameCoordinates)
{
velocityOffset += StarData.Velocity * delta;
}
else
{
lastFrameCoordinates = StarData.LocalCoordinates;
velocityOffset = Vector3.Zero;
}
if (StarData.Rotation == lastFrameRotation)
{
angularVelocityOffset += StarData.AngularVelocity * delta;
}
else
{
lastFrameRotation = StarData.Rotation;
angularVelocityOffset = Vector3.Zero;
}
GlobalPosition = StarData.LocalCoordinates + StarData.SectorOffset + velocityOffset;
GlobalRotation = StarData.Rotation + angularVelocityOffset;
} }
} }

View file

@ -5,8 +5,6 @@ public class Star(Sector sector, Vector3 localCoordinates) : GameObject(sector,
public override void Simulate(double delta) public override void Simulate(double delta)
{ {
base.Simulate(delta); base.Simulate(delta);
//SetCoordinatesFromGlobal(new(GlobalCoordinates.X, GlobalCoordinates.Y, GlobalCoordinates.Z + 1 * (decimal)delta));
} }
public override Node3D Instantiate(Sector sector) public override Node3D Instantiate(Sector sector)
@ -20,4 +18,22 @@ public class Star(Sector sector, Vector3 localCoordinates) : GameObject(sector,
return instance; return instance;
} }
public override Godot.Collections.Dictionary NetworkWrite(long id, bool full)
{
Godot.Collections.Dictionary gameObjectData = base.NetworkWrite(id, full);
if (gameObjectData != null)
{
QueueManager.NetworkSyncQueue.Enqueue((id, gameObjectData));
}
DirtyBits = DirtyFlags.None;
return gameObjectData;
}
public override void NetworkRead(Godot.Collections.Dictionary gameObjectData)
{
base.NetworkRead(gameObjectData);
}
} }

View file

@ -30,9 +30,9 @@ public class TestGenerator : IGenerator
public Vector3I GetSectorOffset(Vector3I universeSize) public Vector3I GetSectorOffset(Vector3I universeSize)
{ {
return new( return new(
(int)Mathf.Floor(UNIVERSE_SIZE.X / 2), (int)Mathf.Floor(universeSize.X / 2),
(int)Mathf.Floor(UNIVERSE_SIZE.X / 2), (int)Mathf.Floor(universeSize.X / 2),
(int)Mathf.Floor(UNIVERSE_SIZE.X / 2) (int)Mathf.Floor(universeSize.X / 2)
); );
} }
@ -117,12 +117,15 @@ public class TestGenerator : IGenerator
starCount = Mathf.Clamp(starCount, 0, int.MaxValue); starCount = Mathf.Clamp(starCount, 0, int.MaxValue);
for (int i = 0; i < starCount; i++) for (int i = 0; i < starCount; i++)
{
if (coordinates.X == 5 && coordinates.Y == 5 && coordinates.Z == 5)
{ {
Vector3 localCoordinates = GenerateLocalCoordinates(); Vector3 localCoordinates = GenerateLocalCoordinates();
Star star = GenerateStar(sector, localCoordinates); Star star = GenerateStar(sector, localCoordinates);
sector.GameObjects.Add(star); sector.GameObjects.Add(star);
} }
}
int shipCount = rng.RandiRange( int shipCount = rng.RandiRange(
SHIPS_PER_SECTOR - SHIPS_PER_SECTOR_VARIANCE, SHIPS_PER_SECTOR - SHIPS_PER_SECTOR_VARIANCE,

View file

@ -3,15 +3,13 @@ using Godot;
public partial class NetworkManager : Node public partial class NetworkManager : Node
{ {
[Export] public RPCNode RPCNode { get; private set; } public static NetworkManager Instance { get; private set; }
public static NetworkManager Singleton { get; private set; }
public int LocalNetId { get; private set; } = -1; public int LocalNetId { get; private set; } = -1;
public override void _Ready() public override void _Ready()
{ {
Singleton = this; Instance = this;
if (Global.IsGameHost) if (Global.IsGameHost)
{ {
@ -66,7 +64,7 @@ public partial class NetworkManager : Node
{ {
if (Global.IsGameHost) if (Global.IsGameHost)
{ {
GameManager.Singleton.SendUniverseToClient(id); GameManager.Instance.SendUniverseToClient(id);
} }
_ = SpawnNetPlayer(id); _ = SpawnNetPlayer(id);
@ -75,7 +73,7 @@ public partial class NetworkManager : Node
public void OnPlayerDisconnected(long id) public void OnPlayerDisconnected(long id)
{ {
Player player = GetNode<Player>($"/root/Game/Space/Player-{id}"); Player player = GetNode<Player>($"/root/Game/Space/Player-{id}");
GameManager.Singleton.DespawnPlayer(player); GameManager.Instance.DespawnPlayer(player);
} }
private async Task SpawnNetPlayer(long id) private async Task SpawnNetPlayer(long id)
@ -88,14 +86,9 @@ public partial class NetworkManager : Node
bool isMainPlayer = LocalNetId == id; bool isMainPlayer = LocalNetId == id;
Character character = new(GameManager.GameUniverse.Sectors[5, 5, 5], new(0, 0, 0)); Character character = new(GameManager.GameUniverse.Sectors[5, 5, 5], new(0, 0, 0));
Player player = GameManager.Singleton.SpawnPlayer(character, isMainPlayer); Player player = GameManager.Instance.SpawnPlayer(character, id, isMainPlayer);
player.Name = $"Player-{id}"; player.Name = $"Player-{id}";
player.SetMultiplayerAuthority((int)id); player.SetMultiplayerAuthority((int)id);
if (isMainPlayer && !Global.IsGameHost)
{
GameManager.Singleton.RpcId(1, nameof(GameManager.Singleton.RpcSendNearbySectors), character.CurrentSector.Coordinates, id);
}
} }
} }

View file

@ -10,11 +10,11 @@ public partial class Player : CharacterBody3D
[Export] public double NetworkPhysicsSyncInterval = 0.05; [Export] public double NetworkPhysicsSyncInterval = 0.05;
public Control GameMenu { get; set; } public Control GameMenu { get; set; }
public Character PlayerData { get; set; } public Character PlayerData { get; set; }
private Vector3 gravityVelocity = Vector3.Zero; public Vector3 GravityVelocity { get; set; } = Vector3.Zero;
private Vector3 movementVelocity = Vector3.Zero; public Vector3 MovementVelocity { get; set; } = Vector3.Zero;
private GravityReceiver gravityReceiver; private GravityReceiver gravityReceiver;
private double cameraPitch = 0; private double cameraPitch = 0;
private Camera3D camera; private Camera3D camera;
@ -35,18 +35,20 @@ public partial class Player : CharacterBody3D
{ {
if (IsMultiplayerAuthority()) if (IsMultiplayerAuthority())
{ {
RPCNode rpc = RPCNode.Instance;
networkPhysicsSyncCounter += delta; networkPhysicsSyncCounter += delta;
if (networkPhysicsSyncCounter >= NetworkPhysicsSyncInterval) if (networkPhysicsSyncCounter >= NetworkPhysicsSyncInterval)
{ {
networkPhysicsSyncCounter = 0; networkPhysicsSyncCounter = 0;
Rpc(nameof(RpcSyncPhysics), movementVelocity, gravityVelocity, GlobalRotation); rpc.Rpc(nameof(rpc.RpcSyncPlayerPhysics), GetMultiplayerAuthority(), MovementVelocity, GravityVelocity, GlobalRotation);
} }
networkSyncCounter += delta; networkSyncCounter += delta;
if (networkSyncCounter >= NetworkSyncInterval) if (networkSyncCounter >= NetworkSyncInterval)
{ {
networkSyncCounter = 0; networkSyncCounter = 0;
Rpc(nameof(RpcSync), GlobalPosition, PlayerData.CurrentSector.Coordinates); rpc.Rpc(nameof(rpc.RpcSyncPlayer), GetMultiplayerAuthority(), GlobalPosition, PlayerData.CurrentSector.Coordinates);
} }
PlayerData.SetCoordinatesFromLocal(GlobalPosition); PlayerData.SetCoordinatesFromLocal(GlobalPosition);
@ -57,8 +59,8 @@ public partial class Player : CharacterBody3D
public override void _PhysicsProcess(double delta) public override void _PhysicsProcess(double delta)
{ {
Vector3 newGravityVelocity = gravityVelocity; Vector3 newGravityVelocity = GravityVelocity;
Vector3 newMovementVelocity = movementVelocity; Vector3 newMovementVelocity = MovementVelocity;
if (!GameMenu.Visible && IsMultiplayerAuthority()) if (!GameMenu.Visible && IsMultiplayerAuthority())
{ {
@ -76,8 +78,8 @@ public partial class Player : CharacterBody3D
} }
} }
gravityVelocity = newGravityVelocity; GravityVelocity = newGravityVelocity;
movementVelocity = newMovementVelocity; MovementVelocity = newMovementVelocity;
Velocity = newGravityVelocity + newMovementVelocity; Velocity = newGravityVelocity + newMovementVelocity;
MoveAndSlide(); MoveAndSlide();
@ -237,33 +239,4 @@ public partial class Player : CharacterBody3D
RotateObjectLocal(Vector3.Forward, rotateAmountZ); RotateObjectLocal(Vector3.Forward, rotateAmountZ);
} }
[Rpc(MultiplayerApi.RpcMode.AnyPeer)]
public void RpcSync(Vector3 position, Vector3I sectorCoordinates)
{
Sector sector = GameManager.GameUniverse.GetSector(sectorCoordinates);
if (sector == null)
{
return;
}
Vector3Dec newGlobal = PlayerData.CalculateGlobalCoordinates(sector.GlobalCenterCoordinates, position);
PlayerData.SetCoordinatesFromGlobal(newGlobal);
if (PlayerData.CurrentSector.Coordinates != sectorCoordinates)
{
sector.AssignObject(PlayerData);
}
GlobalPosition = position + PlayerData.SectorOffset;
}
[Rpc(MultiplayerApi.RpcMode.AnyPeer)]
public void RpcSyncPhysics(Vector3 _movementVelocity, Vector3 _gravityVelocity, Vector3 rotation)
{
movementVelocity = _movementVelocity;
gravityVelocity = _gravityVelocity;
GlobalRotation = rotation;
}
} }

View file

@ -8,11 +8,18 @@ public partial class QueueManager : Node
public static ConcurrentQueue<Action> ActionQueue = new(); public static ConcurrentQueue<Action> ActionQueue = new();
public static ConcurrentQueue<(Sector, GameObject)> SectorReassignQueue = new(); public static ConcurrentQueue<(Sector, GameObject)> SectorReassignQueue = new();
public static ConcurrentQueue<(long, Godot.Collections.Dictionary)> NetworkSyncQueue = new();
private readonly int sectorReassignQueueRateLimit = 500; private readonly int sectorReassignQueueRateLimit = 500;
private readonly int networkSyncQueueRateLimit = 10;
public override void _Process(double delta) public override void _Process(double delta)
{ {
if (!GameManager.Instance.playerReady)
{
return;
}
while (LogQueue.TryDequeue(out string text)) while (LogQueue.TryDequeue(out string text))
{ {
GD.Print(text); GD.Print(text);
@ -25,7 +32,7 @@ public partial class QueueManager : Node
int sectorReassignQueueProcessed = 0; int sectorReassignQueueProcessed = 0;
while ( while (
!GameManager.Singleton.simulating !GameManager.Instance.simulatingFar
&& sectorReassignQueueProcessed++ < sectorReassignQueueRateLimit && sectorReassignQueueProcessed++ < sectorReassignQueueRateLimit
&& SectorReassignQueue.TryDequeue(out var item) && SectorReassignQueue.TryDequeue(out var item)
) )
@ -37,5 +44,16 @@ public partial class QueueManager : Node
gameObject.AssignSector(sector); gameObject.AssignSector(sector);
} }
int networkSyncQueueProcessed = 0;
while (
networkSyncQueueProcessed++ < networkSyncQueueRateLimit
&& NetworkSyncQueue.TryDequeue(out var item)
)
{
var (clientId, gameObjectData) = item;
RPCNode.Instance.RpcId(clientId, nameof(RPCNode.RpcSyncGameObject), gameObjectData);
}
} }
} }

View file

@ -1,5 +1,171 @@
using System;
using System.Collections.Generic;
using Godot; using Godot;
public partial class RPCNode : Node public partial class RPCNode : Node
{ {
public static RPCNode Instance { get; private set; }
private readonly HashSet<Guid> requestedFullGameObjects = [];
public override void _EnterTree()
{
Instance = this;
}
public override void _ExitTree()
{
Instance = null;
}
[Rpc(MultiplayerApi.RpcMode.Authority)]
public void RpcSyncGameObject(Godot.Collections.Dictionary gameObjectData)
{
GD.Print("READING: " + gameObjectData);
if (!GameManager.Instance.playerReady)
{
return;
}
if (!gameObjectData.TryGetValue("uuid", out var uuidData))
return;
Guid uuid = Guid.Parse((string)uuidData);
List<Sector> sectors = GameManager.Instance.GetCurrentSector().GetNeighbouringSectors();
foreach (Sector sector in sectors)
{
GameObject gameObject = sector.GetObjectById(uuid);
if (gameObject != null)
{
gameObject.NetworkRead(gameObjectData);
return;
}
}
if (!TrySyncFullGameObject(gameObjectData))
{
if (requestedFullGameObjects.Contains(uuid))
{
return;
}
requestedFullGameObjects.Add(uuid);
RpcId(1, nameof(RequestFullGameObject), NetworkManager.Instance.LocalNetId, (string)uuidData);
}
}
public bool TrySyncFullGameObject(Godot.Collections.Dictionary gameObjectData)
{
if (!gameObjectData.TryGetValue("type", out var typeData))
return false;
if (!gameObjectData.TryGetValue("sectorCoordinates", out var sectorCoordinatesData))
return false;
if (!gameObjectData.TryGetValue("localCoordinates", out var localCoordinatesData))
return false;
if (!gameObjectData.TryGetValue("uuid", out var uuidData))
return false;
string type = (string)typeData;
Vector3I sectorCoordinates = (Vector3I)sectorCoordinatesData;
Vector3 localCoordinates = (Vector3)localCoordinatesData;
Guid uuid = Guid.Parse((string)uuidData);
Sector sector = GameManager.GameUniverse.GetSector(sectorCoordinates);
if (sector == null)
return false;
GameObject gameObject;
switch (type)
{
case "Star":
gameObject = new Star(sector, localCoordinates);
break;
default:
return false;
}
gameObject.UUID = uuid;
gameObject.NetworkRead(gameObjectData);
requestedFullGameObjects.Remove(uuid);
sector.AssignObject(gameObject);
return true;
}
[Rpc(MultiplayerApi.RpcMode.AnyPeer)]
public void RequestFullGameObject(long id, string uuidString)
{
if (!Global.IsGameHost)
{
return;
}
Guid uuid = Guid.Parse(uuidString);
List<Sector> sectors = GameManager.Instance.GetPlayer(id).PlayerData.CurrentSector.GetNeighbouringSectors();
foreach (Sector sector in sectors)
{
GameObject gameObject = sector.GetObjectById(uuid);
if (gameObject != null)
{
gameObject.NetworkWrite(id, true);
return;
}
}
}
[Rpc(MultiplayerApi.RpcMode.AnyPeer)]
public void RpcSyncPlayer(long id, Vector3 position, Vector3I sectorCoordinates)
{
if (!GameManager.Instance.playerReady)
{
return;
}
GameManager.Instance.Players.TryGetValue(id, out Player player);
if (player == null || !player.IsInsideTree())
{
return;
}
Character playerData = player.PlayerData;
Sector sector = GameManager.GameUniverse.GetSector(sectorCoordinates);
if (sector == null)
{
return;
}
Vector3Dec newGlobal = playerData.CalculateGlobalCoordinates(sector.GlobalCenterCoordinates, position);
playerData.SetCoordinatesFromGlobal(newGlobal);
if (playerData.CurrentSector.Coordinates != sectorCoordinates)
{
sector.AssignObject(playerData);
}
player.GlobalPosition = position + playerData.SectorOffset;
}
[Rpc(MultiplayerApi.RpcMode.AnyPeer)]
public void RpcSyncPlayerPhysics(long id, Vector3 _movementVelocity, Vector3 _gravityVelocity, Vector3 rotation)
{
if (!GameManager.Instance.playerReady)
{
return;
}
GameManager.Instance.Players.TryGetValue(id, out Player player);
if (player == null || !player.IsInsideTree())
{
return;
}
player.MovementVelocity = _movementVelocity;
player.GravityVelocity = _gravityVelocity;
player.GlobalRotation = rotation;
}
} }

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Godot; using Godot;
@ -46,6 +47,14 @@ public class Sector
}); });
} }
public void NetworkSync(long id)
{
GameObjects.ForEach(gameObject =>
{
gameObject.NetworkWrite(id, false);
});
}
public bool IsObjectInSector(GameObject gameObject) public bool IsObjectInSector(GameObject gameObject)
{ {
return Helpers.IsInsideGlobalArea(GlobalStartCoordinates, GlobalEndCoordinates, gameObject.GlobalCoordinates); return Helpers.IsInsideGlobalArea(GlobalStartCoordinates, GlobalEndCoordinates, gameObject.GlobalCoordinates);
@ -58,7 +67,20 @@ public class Sector
public void SpawnObjects() public void SpawnObjects()
{ {
GameObjects.ForEach(GameManager.Singleton.Spawn); GameObjects.ForEach(GameManager.Instance.Spawn);
}
public GameObject GetObjectById(Guid id)
{
foreach (GameObject gameObject in GameObjects)
{
if (gameObject.UUID.Equals(id))
{
return gameObject;
}
}
return null;
} }
public List<Sector> GetNeighbouringSectors() public List<Sector> GetNeighbouringSectors()