From 994823a9c3c698aa7e2918a42b9a87c1c6e55c67484fc13bb70f5566e08f3ff5 Mon Sep 17 00:00:00 2001 From: Aslan Date: Thu, 29 Jan 2026 09:43:15 -0500 Subject: [PATCH] Implement basic networking --- Imperfect Space.csproj.old | 11 -- Imperfect Space.csproj.old.1 | 9 - prefabs/gameObjects/node.tscn | 3 - prefabs/gameObjects/player.tscn | 6 +- scenes/game.tscn | 35 ++-- scenes/main_menu.tscn | 11 +- scripts/GameManager.cs | 201 +++++++++++++++++--- scripts/GameObject.cs | 251 +++++++++++++------------ scripts/GameObjects/Character.cs | 42 ++++- scripts/GameObjects/Star.cs | 5 +- scripts/Generator/IGenerator.cs | 2 +- scripts/Generator/TestGenerator.cs | 41 +++- scripts/Global.cs | 4 + scripts/Global.cs.uid | 1 + scripts/GravityReceiver.cs | 2 +- scripts/Helpers.cs | 5 + scripts/MainMenu/MainMenuController.cs | 11 +- scripts/NetworkManager.cs | 101 ++++++++++ scripts/NetworkManager.cs.uid | 1 + scripts/Player.cs | 79 ++++++-- scripts/QueueManager.cs | 2 - scripts/RPCNode.cs | 5 + scripts/RPCNode.cs.uid | 1 + scripts/Sector.cs | 2 +- scripts/Universe.cs | 29 +++ 25 files changed, 643 insertions(+), 217 deletions(-) delete mode 100644 Imperfect Space.csproj.old delete mode 100644 Imperfect Space.csproj.old.1 create mode 100644 scripts/Global.cs create mode 100644 scripts/Global.cs.uid create mode 100644 scripts/NetworkManager.cs create mode 100644 scripts/NetworkManager.cs.uid create mode 100644 scripts/RPCNode.cs create mode 100644 scripts/RPCNode.cs.uid diff --git a/Imperfect Space.csproj.old b/Imperfect Space.csproj.old deleted file mode 100644 index d7ef75c..0000000 --- a/Imperfect Space.csproj.old +++ /dev/null @@ -1,11 +0,0 @@ - - - net8.0 - net9.0 - true - ImperfectSpace - true - - diff --git a/Imperfect Space.csproj.old.1 b/Imperfect Space.csproj.old.1 deleted file mode 100644 index 787950a..0000000 --- a/Imperfect Space.csproj.old.1 +++ /dev/null @@ -1,9 +0,0 @@ - - - net8.0 - net9.0 - true - ImperfectSpace - true - - diff --git a/prefabs/gameObjects/node.tscn b/prefabs/gameObjects/node.tscn index cf463cc..9b9bf2d 100644 --- a/prefabs/gameObjects/node.tscn +++ b/prefabs/gameObjects/node.tscn @@ -1,6 +1,3 @@ [gd_scene format=3 uid="uid://b6c17xutiecq5"] -[ext_resource type="Script" uid="uid://dknsws58ej0bp" path="res://scripts/GameObject.cs" id="1_gt1sn"] - [node name="Node" type="StaticBody3D" unique_id=1306600716] -script = ExtResource("1_gt1sn") diff --git a/prefabs/gameObjects/player.tscn b/prefabs/gameObjects/player.tscn index eb5183f..01f9a65 100644 --- a/prefabs/gameObjects/player.tscn +++ b/prefabs/gameObjects/player.tscn @@ -8,8 +8,9 @@ [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_uwrxv"] [node name="Player" type="CharacterBody3D" unique_id=1391068938] +transform = Transform3D(-1, 0, 1.2246467991473532e-16, 0, 1, 0, -1.2246467991473532e-16, 0, -1, 0, 0, 0) script = ExtResource("1_74mkb") -SprintMultiplier = 500.0 +SprintMultiplier = 5.0 [node name="Camera" type="Camera3D" parent="." unique_id=1639995310] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0) @@ -28,4 +29,5 @@ script = ExtResource("2_y1ton") transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.06334634196567002, -0.29036251889898157) shadow_enabled = true spot_range = 25.0 -spot_angle = 90.0 +spot_angle = 85.0 +spot_angle_attenuation = 0.73204285 diff --git a/scenes/game.tscn b/scenes/game.tscn index c268d1a..75fb9b1 100644 --- a/scenes/game.tscn +++ b/scenes/game.tscn @@ -5,6 +5,8 @@ [ext_resource type="Script" uid="uid://n557xfrv0i6x" path="res://scripts/GameControlManager.cs" id="4_lbhrr"] [ext_resource type="Script" uid="uid://bwgjvm21oi3d6" path="res://scripts/GameManager.cs" id="4_p57ef"] [ext_resource type="Script" uid="uid://betypbypf6bf2" path="res://scripts/GameMenuController.cs" id="5_iywne"] +[ext_resource type="Script" uid="uid://bo57chobyuegg" path="res://scripts/NetworkManager.cs" id="5_p57ef"] +[ext_resource type="Script" uid="uid://by0357lop0qge" path="res://scripts/RPCNode.cs" id="6_u5sy4"] [sub_resource type="BoxMesh" id="BoxMesh_p57ef"] size = Vector3(2, 0.1, 2) @@ -15,31 +17,37 @@ size = Vector3(10, 0.5, 10) [sub_resource type="BoxShape3D" id="BoxShape3D_uwrxv"] size = Vector3(10, 5, 10) -[node name="Game" type="Node3D" unique_id=1201210338 node_paths=PackedStringArray("GameMenu", "QueueManager")] +[node name="Game" type="Node3D" unique_id=1201210338 node_paths=PackedStringArray("GameMenu", "QueueManager", "SpaceRoot")] script = ExtResource("4_p57ef") GameMenu = NodePath("GameMenu") QueueManager = NodePath("QueueManager") -closeTickInterval = 0.01 +SpaceRoot = NodePath("Space") +closeTickInterval = 0.1 -[node name="Plane" type="StaticBody3D" parent="." unique_id=1260154250] +[node name="Space" type="Node3D" parent="." unique_id=1006247987] + +[node name="Plane" type="StaticBody3D" parent="Space" unique_id=1260154250] transform = Transform3D(1, 0, 0, 0, 0.99999994, 0, 0, 0, 0.99999994, 0, -10, 0) -[node name="PlaneMesh" type="MeshInstance3D" parent="Plane" unique_id=107049489] +[node name="PlaneMesh" type="MeshInstance3D" parent="Space/Plane" unique_id=107049489] transform = Transform3D(5, 0, 0, 0, 1, 0, 0, 0, 5, 0, 0, 0) mesh = SubResource("BoxMesh_p57ef") -[node name="PlaneCollider" type="CollisionShape3D" parent="Plane" unique_id=970373853] +[node name="PlaneCollider" type="CollisionShape3D" parent="Space/Plane" unique_id=970373853] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.2, 0) shape = SubResource("BoxShape3D_8cj0n") -[node name="GravityZone" type="Area3D" parent="Plane" unique_id=1326543003] +[node name="GravityZone" type="Area3D" parent="Space/Plane" unique_id=1326543003] gravity_space_override = 2 script = ExtResource("1_yqjtg") -[node name="CollisionShape3D" type="CollisionShape3D" parent="Plane/GravityZone" unique_id=772916098] +[node name="CollisionShape3D" type="CollisionShape3D" parent="Space/Plane/GravityZone" unique_id=772916098] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, 0) shape = SubResource("BoxShape3D_uwrxv") +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="Space/Plane" unique_id=226559047] +transform = Transform3D(1, 0, 0, 0, 1.0000000600000036, 0, 0, 0, 1.0000000600000036, 0, 10.000000600000035, 0) + [node name="GameControlManager" type="Node" parent="." unique_id=1751385863 node_paths=PackedStringArray("GameMenu")] script = ExtResource("4_lbhrr") GameMenu = NodePath("../GameMenu") @@ -47,6 +55,13 @@ GameMenu = NodePath("../GameMenu") [node name="QueueManager" type="Node" parent="." unique_id=355148200] script = ExtResource("4_iywne") +[node name="NetworkManager" type="Node" parent="." unique_id=1765485895 node_paths=PackedStringArray("RPCNode")] +script = ExtResource("5_p57ef") +RPCNode = NodePath("../RPC") + +[node name="RPC" type="Node" parent="." unique_id=498537245] +script = ExtResource("6_u5sy4") + [node name="GameMenu" type="Control" parent="." unique_id=223510406] visible = false layout_mode = 3 @@ -77,8 +92,6 @@ offset_bottom = 63.0 layout_mode = 2 text = "Main Menu" -[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=226559047] - -[connection signal="body_entered" from="Plane/GravityZone" to="Plane/GravityZone" method="OnBodyEntered"] -[connection signal="body_exited" from="Plane/GravityZone" to="Plane/GravityZone" method="OnBodyExited"] +[connection signal="body_entered" from="Space/Plane/GravityZone" to="Space/Plane/GravityZone" method="OnBodyEntered"] +[connection signal="body_exited" from="Space/Plane/GravityZone" to="Space/Plane/GravityZone" method="OnBodyExited"] [connection signal="pressed" from="GameMenu/VBoxContainer/MainMenuButton" to="GameMenu" method="OnMainMenu"] diff --git a/scenes/main_menu.tscn b/scenes/main_menu.tscn index 31a8acf..848042c 100644 --- a/scenes/main_menu.tscn +++ b/scenes/main_menu.tscn @@ -23,13 +23,18 @@ grow_vertical = 2 layout_mode = 2 alignment = 1 -[node name="StartGameButton" type="Button" parent="CenterContainer/VBoxContainer" unique_id=364461644] +[node name="HostGameButton" type="Button" parent="CenterContainer/VBoxContainer" unique_id=364461644] layout_mode = 2 -text = "Start Game" +text = "Host Game" + +[node name="JoinGameButton" type="Button" parent="CenterContainer/VBoxContainer" unique_id=1719078976] +layout_mode = 2 +text = "Join Game" [node name="ExitGameButton" type="Button" parent="CenterContainer/VBoxContainer" unique_id=1050417063] layout_mode = 2 text = "Exit Game" -[connection signal="pressed" from="CenterContainer/VBoxContainer/StartGameButton" to="." method="OnStartGame"] +[connection signal="pressed" from="CenterContainer/VBoxContainer/HostGameButton" to="." method="OnHostGame"] +[connection signal="pressed" from="CenterContainer/VBoxContainer/JoinGameButton" to="." method="OnJoinGame"] [connection signal="pressed" from="CenterContainer/VBoxContainer/ExitGameButton" to="." method="OnExitGame"] diff --git a/scripts/GameManager.cs b/scripts/GameManager.cs index aeddbec..a83eaef 100644 --- a/scripts/GameManager.cs +++ b/scripts/GameManager.cs @@ -9,22 +9,24 @@ public partial class GameManager : Node3D { [Export] public Control GameMenu { get; private set; } [Export] public QueueManager QueueManager { get; private set; } + [Export] public Node3D SpaceRoot { get; private set; } [Export] public double closeTickInterval = 1; [Export] public double farTickInterval = 10; [Export] public int maxFarThreads = 16; - public Player MainPlayer { get; private set; } - + public static bool Loading { get; private set; } = true; public static GameManager Singleton { get; private set; } - public IGenerator Generator { get; private set; } - - public Universe GameUniverse { get; private set; } + public static IGenerator Generator { get; private set; } + public static Universe GameUniverse { get; private set; } + public Player MainPlayer { get; private set; } + public List Players { get; private set; } = []; private double closeTickTimer = 0; private double farTickTimer = 0; public bool simulating = false; + public bool playerReady = false; private readonly Dictionary spawnedObjects = []; private readonly ConcurrentQueue spawnQueue = []; @@ -34,19 +36,33 @@ public partial class GameManager : Node3D Singleton = this; Generator = new TestGenerator(); - GameUniverse = Generator.GenerateUniverse(); - Character character = new(GameUniverse.Sectors[50, 50, 50], new(0, 0, 0)); - MainPlayer = character.InstantiatePlayer(); - MainPlayer.GameMenu = GameMenu; - spawnedObjects.Add(character, MainPlayer); - CallDeferred("add_child", MainPlayer); + if (Global.IsGameHost) + { + GameUniverse = Generator.GenerateUniverse(); + Loading = false; + } + } + private void OnPlayerReady() + { + playerReady = true; SpawnClose(); } + public override void _ExitTree() + { + Loading = true; + Singleton = null; + } + public override void _Process(double delta) { + if (!playerReady || !Global.IsGameHost) + { + return; + } + closeTickTimer += delta; farTickTimer += delta; @@ -143,7 +159,6 @@ public partial class GameManager : Node3D for (int x = startX; x < endX; x++) { - QueueManager.LogQueue.Enqueue("Simulating: " + x); for (int y = 0; y < sizeY; y++) { Thread.Sleep(5); @@ -180,10 +195,34 @@ public partial class GameManager : Node3D neighbours.ForEach(sector => sector.SpawnObjects()); } + public Player SpawnPlayer(Character character, bool isMainPlayer = false) + { + Player player = character.InstantiatePlayer(); + player.GameMenu = GameMenu; + + if (isMainPlayer) + { + MainPlayer = player; + OnPlayerReady(); + } + + Players.Add(player); + + character.UpdateSector(); + + SpaceRoot.CallDeferred("add_child", player); + return player; + } + + public void DespawnPlayer(Player player) + { + SpaceRoot.CallDeferred("remove_child", player); + player.CallDeferred("queue_free"); + } + public void Spawn(GameObject gameObject) { spawnQueue.Enqueue(gameObject); - CallDeferred(nameof(ProcessSpawnQueue)); } @@ -202,7 +241,7 @@ public partial class GameManager : Node3D Node3D instance = gameObject.Instantiate(GetCurrentSector()); spawnedObjects.Add(gameObject, instance); - CallDeferred("add_child", instance); + SpaceRoot.CallDeferred("add_child", instance); } public void Despawn(GameObject gameObject) @@ -215,7 +254,7 @@ public partial class GameManager : Node3D Node3D nodeToDespawn = spawnedObjects.GetValueOrDefault(gameObject); spawnedObjects.Remove(gameObject); - CallDeferred("remove_child", nodeToDespawn); + SpaceRoot.CallDeferred("remove_child", nodeToDespawn); nodeToDespawn.CallDeferred("queue_free"); } @@ -223,24 +262,136 @@ public partial class GameManager : Node3D { Sector current = GetCurrentSector(); - List nearby = current.GetNeighbouringSectors(); - - foreach (Sector sector in nearby) + foreach (Player player in Players) { - foreach (GameObject gameObject in sector.GameObjects) - { - gameObject.UpdateSectorOffset(current); - } + player.PlayerData.UpdateSectorOffset(current); + player.PlayerData.UpdateNodePosition(); } - foreach (GameObject spawned in spawnedObjects.Keys) + List nearby = current.GetNeighbouringSectors(); + foreach (KeyValuePair spawned in spawnedObjects) { - if (!nearby.Contains(spawned.CurrentSector)) + GameObject gameObject = spawned.Key; + Node3D node = spawned.Value; + + if (!nearby.Contains(gameObject.CurrentSector)) { - Despawn(spawned); + Despawn(gameObject); + } + else + { + gameObject.UpdateSectorOffset(current); + node.GlobalPosition = gameObject.LocalCoordinates + gameObject.SectorOffset; } } nearby.ForEach(sector => sector.SpawnObjects()); } + + // These should be in RPCNode and should be queued + public void SendUniverseToClient(long id) + { + RpcId(id, nameof(RpcDownloadUniverse), Generator.GetUniverseSize(), Generator.GetSectorSize()); + } + + // These should be in RPCNode and should be queued + [Rpc(MultiplayerApi.RpcMode.Authority)] + public void RpcDownloadUniverse(Vector3I universeSize, Vector3 sectorSize) + { + GameUniverse = Generator.InitializeEmptyUniverse(universeSize, sectorSize); + + 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 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); + } } diff --git a/scripts/GameObject.cs b/scripts/GameObject.cs index fbc26aa..a689a96 100644 --- a/scripts/GameObject.cs +++ b/scripts/GameObject.cs @@ -1,157 +1,160 @@ +using System; using System.Collections.Generic; using Godot; public abstract class GameObject { - public Sector CurrentSector { get; protected set; } - public Vector3 LocalCoordinates { get; protected set; } - public Vector3Dec GlobalCoordinates { get; protected set; } + public Guid UUID { get; set; } - public Vector3 SectorOffset { get; set; } + public Sector CurrentSector { get; protected set; } + public Vector3 LocalCoordinates { get; protected set; } + public Vector3Dec GlobalCoordinates { get; protected set; } - private bool reassigning = false; + public Vector3 SectorOffset { get; set; } - public GameObject(Sector sector, Vector3 localCoordinates) - { - CurrentSector = sector; - LocalCoordinates = localCoordinates; + protected bool reassigning = false; - GlobalCoordinates = new - ( - sector.GlobalCenterCoordinates.X + (decimal)localCoordinates.X, - sector.GlobalCenterCoordinates.Y + (decimal)localCoordinates.Y, - sector.GlobalCenterCoordinates.Z + (decimal)localCoordinates.Z - ); - } + public GameObject(Sector sector, Vector3 localCoordinates) + { + UUID = Guid.NewGuid(); - public GameObject(Sector sector, double localX, double localY, double localZ) - { - CurrentSector = sector; - LocalCoordinates = new(localX, localY, localZ); + CurrentSector = sector; + LocalCoordinates = localCoordinates; - GlobalCoordinates = new - ( - sector.GlobalStartCoordinates.X + (decimal)localX, - sector.GlobalStartCoordinates.Y + (decimal)localY, - sector.GlobalStartCoordinates.Z + (decimal)localZ - ); - } + GlobalCoordinates = CalculateGlobalCoordinates(sector.GlobalCenterCoordinates, localCoordinates); + } - public GameObject(Vector3Dec coordinates) - { - GlobalCoordinates = coordinates; - UpdateSector(); - } + public GameObject(Vector3Dec coordinates) + { + GlobalCoordinates = coordinates; + UpdateSector(); + } - public GameObject(decimal x, decimal y, decimal z) - { - GlobalCoordinates = new(x, y, z); - UpdateSector(); - } + public GameObject(decimal x, decimal y, decimal z) + { + GlobalCoordinates = new(x, y, z); + UpdateSector(); + } - public bool IsInCurrentSector() - { - return Helpers.IsInsideArea(CurrentSector.Size / 2, LocalCoordinates); - } + public Vector3Dec CalculateGlobalCoordinates(Vector3Dec sectorCenter, Vector3 local) + { + return new + ( + sectorCenter.X + (decimal)local.X, + sectorCenter.Y + (decimal)local.Y, + sectorCenter.Z + (decimal)local.Z + ); + } - public void UpdateSector() - { - List neighbours = CurrentSector.GetNeighbouringSectors(); - foreach (Sector sector in neighbours) - { - if (sector.IsObjectInSector(this)) - { - sector.AssignObject(this); - reassigning = true; - return; - } - } + public bool IsInCurrentSector() + { + return Helpers.IsInsideArea(CurrentSector.Size / 2, LocalCoordinates); + } - foreach (Sector sector in GameManager.Singleton.GameUniverse.Sectors) - { - if (sector.IsObjectInSector(this)) - { - sector.AssignObject(this); - reassigning = true; - return; - } - } - } + public void UpdateSector() + { + List neighbours = CurrentSector.GetNeighbouringSectors(); + foreach (Sector sector in neighbours) + { + if (sector.IsObjectInSector(this)) + { + reassigning = true; + sector.AssignObject(this); + return; + } + } - public void AssignSector(Sector sector) - { - CurrentSector = sector; - ResetLocalCoordinates(); + if (!GameManager.GameUniverse.IsInside(CurrentSector.Coordinates, LocalCoordinates)) + { + return; + } - SectorOffset = GetSectorOffset(GameManager.Singleton.GetCurrentSector()); + foreach (Sector sector in GameManager.GameUniverse.Sectors) + { + if (sector.IsObjectInSector(this)) + { + reassigning = true; + sector.AssignObject(this); + return; + } + } + } - if (this is Character) - { - GameManager.Singleton.ApplyOrigin(); - } + public virtual void AssignSector(Sector sector) + { + CurrentSector = sector; + ResetLocalCoordinates(); - if (this is not Character) - { - List neighbours = GameManager.Singleton.GetCurrentSector().GetNeighbouringSectors(); + UpdateSectorOffsetToMainPlayer(); - if (neighbours.Contains(sector)) - { - GameManager.Singleton.Spawn(this); - } - else - { - GameManager.Singleton.Despawn(this); - } - } + List neighbours = GameManager.Singleton.GetCurrentSector().GetNeighbouringSectors(); - reassigning = false; - } + if (neighbours.Contains(sector)) + { + GameManager.Singleton.Spawn(this); + } + else + { + GameManager.Singleton.Despawn(this); + } - public Vector3 GetSectorOffset(Sector sector) - { - Vector3Dec relative = CurrentSector.GlobalCenterCoordinates - sector.GlobalCenterCoordinates; - return relative.ToVector3(); - } + reassigning = false; + } - public virtual void UpdateSectorOffset(Sector sector) - { - SectorOffset = GetSectorOffset(sector); - } + public Vector3 GetSectorOffset(Sector sector) + { + Vector3Dec relative = CurrentSector.GlobalCenterCoordinates - sector.GlobalCenterCoordinates; + return relative.ToVector3(); + } - public void SetCoordinatesFromGlobal(Vector3Dec globalCoordinates) - { - GlobalCoordinates = globalCoordinates; - LocalCoordinates = (globalCoordinates - CurrentSector.GlobalCenterCoordinates).ToVector3(); + public void UpdateSectorOffset(Sector sector) + { + SectorOffset = GetSectorOffset(sector); + } - UpdateNodePosition(); - } + public void UpdateSectorOffsetToMainPlayer() + { + UpdateSectorOffset(GameManager.Singleton.GetCurrentSector()); + } - public void SetCoordinatesFromLocal(Vector3 localCoordinates) - { - LocalCoordinates = localCoordinates; - GlobalCoordinates = Vector3Dec.FromVector3(localCoordinates) + CurrentSector.GlobalCenterCoordinates; + public void SetCoordinatesFromGlobal(Vector3Dec globalCoordinates) + { + GlobalCoordinates = globalCoordinates; + LocalCoordinates = (globalCoordinates - CurrentSector.GlobalCenterCoordinates).ToVector3(); - UpdateNodePosition(); - } + UpdateNodePosition(); + } - public void ResetLocalCoordinates() - { - SetCoordinatesFromGlobal(GlobalCoordinates); - } + public void SetCoordinatesFromLocal(Vector3 localCoordinates) + { + LocalCoordinates = localCoordinates; + GlobalCoordinates = Vector3Dec.FromVector3(localCoordinates) + CurrentSector.GlobalCenterCoordinates; - public virtual void UpdateNodePosition() { } + UpdateNodePosition(); + } - public virtual void Simulate(double delta) - { - if (!reassigning && !IsInCurrentSector()) - { - UpdateSector(); - } - } + public void ResetLocalCoordinates() + { + SetCoordinatesFromGlobal(GlobalCoordinates); + } - public virtual Node3D Instantiate(Sector sector) - { - PackedScene modulePrefab = ResourceLoader.Load("res://prefabs/gameObjects/node.tscn"); - return modulePrefab.Instantiate(); - } + public virtual void UpdateNodePosition() { } + + public virtual void Simulate(double delta) + { + if (!reassigning && !IsInCurrentSector()) + { + UpdateSector(); + } + } + + public virtual Node3D Instantiate(Sector sector) + { + PackedScene modulePrefab = ResourceLoader.Load("res://prefabs/gameObjects/node.tscn"); + Node3D instance = modulePrefab.Instantiate(); + + instance.Name = $"GameObject-{UUID}"; + + return instance; + } } diff --git a/scripts/GameObjects/Character.cs b/scripts/GameObjects/Character.cs index 7930bf6..2b7ceef 100644 --- a/scripts/GameObjects/Character.cs +++ b/scripts/GameObjects/Character.cs @@ -4,6 +4,39 @@ public class Character(Sector sector, Vector3 localCoordinates) : GameObject(sec { private Player player; + public bool SheduledUpdateNode = false; + + public override void AssignSector(Sector sector) + { + CurrentSector = sector; + + ResetLocalCoordinates(); + UpdateSectorOffsetToMainPlayer(); + UpdateNodePosition(); + + if (IsMainPlayer()) + { + GameManager.Singleton.ApplyOrigin(); + + if (!Global.IsGameHost) + { + GameManager.Singleton.RpcId( + 1, + nameof(GameManager.Singleton.RpcSendNearbySectors), + CurrentSector.Coordinates, + NetworkManager.Singleton.LocalNetId + ); + } + } + + reassigning = false; + } + + public bool IsMainPlayer() + { + return player == GameManager.Singleton.MainPlayer; + } + public Player InstantiatePlayer() { PackedScene prefab = ResourceLoader.Load("res://prefabs/gameObjects/player.tscn"); @@ -17,6 +50,13 @@ public class Character(Sector sector, Vector3 localCoordinates) : GameObject(sec public override void UpdateNodePosition() { - player.GlobalPosition = LocalCoordinates; + if (IsMainPlayer()) + { + player.GlobalPosition = LocalCoordinates; + } + else + { + player.GlobalPosition = LocalCoordinates + SectorOffset; + } } } diff --git a/scripts/GameObjects/Star.cs b/scripts/GameObjects/Star.cs index 7e2e143..16a7914 100644 --- a/scripts/GameObjects/Star.cs +++ b/scripts/GameObjects/Star.cs @@ -6,7 +6,7 @@ public class Star(Sector sector, Vector3 localCoordinates) : GameObject(sector, { base.Simulate(delta); - SetCoordinatesFromGlobal(new(GlobalCoordinates.X, GlobalCoordinates.Y, GlobalCoordinates.Z + 1 * (decimal)delta)); + //SetCoordinatesFromGlobal(new(GlobalCoordinates.X, GlobalCoordinates.Y, GlobalCoordinates.Z + 1 * (decimal)delta)); } public override Node3D Instantiate(Sector sector) @@ -14,8 +14,9 @@ public class Star(Sector sector, Vector3 localCoordinates) : GameObject(sector, PackedScene prefab = ResourceLoader.Load("res://prefabs/gameObjects/star.tscn"); StarNode instance = prefab.Instantiate(); + instance.Name = $"Star-{UUID}"; instance.StarData = this; - SectorOffset = GetSectorOffset(sector); + UpdateSectorOffsetToMainPlayer(); return instance; } diff --git a/scripts/Generator/IGenerator.cs b/scripts/Generator/IGenerator.cs index d95ce7f..796d4ea 100644 --- a/scripts/Generator/IGenerator.cs +++ b/scripts/Generator/IGenerator.cs @@ -1,4 +1,3 @@ -using System; using Godot; public interface IGenerator @@ -6,6 +5,7 @@ public interface IGenerator public Vector3I GetUniverseSize(); public Vector3 GetSectorSize(); + public Universe InitializeEmptyUniverse(Vector3I universeSize, Vector3 sectorSize); public Universe GenerateUniverse(); public Sector GenerateSector(Vector3I coordinates); diff --git a/scripts/Generator/TestGenerator.cs b/scripts/Generator/TestGenerator.cs index 285c33f..8a54986 100644 --- a/scripts/Generator/TestGenerator.cs +++ b/scripts/Generator/TestGenerator.cs @@ -4,10 +4,10 @@ using Godot; public class TestGenerator : IGenerator { - public static Vector3I UNIVERSE_SIZE = new(100, 100, 100); - public static Vector3 SECTOR_SIZE = new(5000, 5000, 5000); - public static int STARS_PER_SECTOR = 100; - public static int STARS_PER_SECTOR_VARIANCE = 5; + public static Vector3I UNIVERSE_SIZE = new(10, 10, 10); + public static Vector3 SECTOR_SIZE = new(50, 50, 50); + public static int STARS_PER_SECTOR = 1; + public static int STARS_PER_SECTOR_VARIANCE = 0; public static int SHIPS_PER_SECTOR = 0; public static int SHIPS_PER_SECTOR_VARIANCE = 0; public static int STATIONS_PER_SECTOR = 0; @@ -22,7 +22,12 @@ public class TestGenerator : IGenerator return UNIVERSE_SIZE; } - public Vector3I GetSectorOffset() + public Vector3 GetSectorSize() + { + return SECTOR_SIZE; + } + + public Vector3I GetSectorOffset(Vector3I universeSize) { return new( (int)Mathf.Floor(UNIVERSE_SIZE.X / 2), @@ -31,9 +36,31 @@ public class TestGenerator : IGenerator ); } - public Vector3 GetSectorSize() + public Vector3I GetSectorOffset() { - return SECTOR_SIZE; + return GetSectorOffset(UNIVERSE_SIZE); + } + + public Universe InitializeEmptyUniverse(Vector3I universeSize, Vector3 sectorSize) + { + Universe universe = new(universeSize); + + for (int x = 0; x < universeSize.X; x++) + { + for (int y = 0; y < universeSize.Y; y++) + { + for (int z = 0; z < universeSize.Z; z++) + { + universe.Sectors[x, y, z] = new Sector( + new(x, y, z), + GetSectorOffset(universeSize), + sectorSize + ); + } + } + } + + return universe; } public Universe GenerateUniverse() diff --git a/scripts/Global.cs b/scripts/Global.cs new file mode 100644 index 0000000..b13a22f --- /dev/null +++ b/scripts/Global.cs @@ -0,0 +1,4 @@ +public static class Global +{ + public static bool IsGameHost = false; +} diff --git a/scripts/Global.cs.uid b/scripts/Global.cs.uid new file mode 100644 index 0000000..1230bb2 --- /dev/null +++ b/scripts/Global.cs.uid @@ -0,0 +1 @@ +uid://dufeaph62nh7y diff --git a/scripts/GravityReceiver.cs b/scripts/GravityReceiver.cs index e7f3583..83f7706 100644 --- a/scripts/GravityReceiver.cs +++ b/scripts/GravityReceiver.cs @@ -16,7 +16,7 @@ public partial class GravityReceiver : Node3D public override void _Ready() { owner = GetParent(); - root = GetTree().Root.GetNode("Game"); + root = GetTree().Root.GetNode("Game/Space"); } public override void _PhysicsProcess(double delta) diff --git a/scripts/Helpers.cs b/scripts/Helpers.cs index 7858291..3b1add7 100644 --- a/scripts/Helpers.cs +++ b/scripts/Helpers.cs @@ -7,6 +7,11 @@ public static class Helpers return value >= min && value <= max; } + public static bool IsBetween(double value, double min, int max) + { + return value >= min && value < max; + } + public static bool IsInsideArea(Vector3 area, Vector3 coordinates) { if (coordinates.X >= area.X || coordinates.Y >= area.Y || coordinates.Z >= area.Z) diff --git a/scripts/MainMenu/MainMenuController.cs b/scripts/MainMenu/MainMenuController.cs index 970fe6a..5b2b14f 100644 --- a/scripts/MainMenu/MainMenuController.cs +++ b/scripts/MainMenu/MainMenuController.cs @@ -2,8 +2,17 @@ using Godot; public partial class MainMenuController : Node { - public void OnStartGame() + public void OnHostGame() { + Global.IsGameHost = true; + + GetTree().ChangeSceneToFile("res://scenes/game.tscn"); + } + + public void OnJoinGame() + { + Global.IsGameHost = false; + GetTree().ChangeSceneToFile("res://scenes/game.tscn"); } diff --git a/scripts/NetworkManager.cs b/scripts/NetworkManager.cs new file mode 100644 index 0000000..7ab4303 --- /dev/null +++ b/scripts/NetworkManager.cs @@ -0,0 +1,101 @@ +using System.Threading.Tasks; +using Godot; + +public partial class NetworkManager : Node +{ + [Export] public RPCNode RPCNode { get; private set; } + + public static NetworkManager Singleton { get; private set; } + + public int LocalNetId { get; private set; } = -1; + + public override void _Ready() + { + Singleton = this; + + if (Global.IsGameHost) + { + CreateServer(); + } + else + { + CreateClient(); + } + + GetTree().GetMultiplayer().PeerConnected += OnPlayerConnected; + GetTree().GetMultiplayer().PeerDisconnected += OnPlayerDisconnected; + GetTree().GetMultiplayer().ServerDisconnected += OnServerClosed; + } + + public override void _ExitTree() + { + GetTree().GetMultiplayer().MultiplayerPeer.Close(); + GetTree().GetMultiplayer().MultiplayerPeer = null; + } + + public void CreateServer() + { + ENetMultiplayerPeer peer = new(); + peer.CreateServer(2142, 128); + + GetTree().GetMultiplayer().MultiplayerPeer = peer; + + LocalNetId = GetTree().GetMultiplayer().GetUniqueId(); + + _ = SpawnNetPlayer(LocalNetId); + } + + public void CreateClient() + { + ENetMultiplayerPeer peer = new(); + peer.CreateClient("127.0.0.1", 2142); + + GetTree().GetMultiplayer().MultiplayerPeer = peer; + + LocalNetId = GetTree().GetMultiplayer().GetUniqueId(); + + _ = SpawnNetPlayer(LocalNetId); + } + + public void OnServerClosed() + { + GetTree().ChangeSceneToFile("res://scenes/main_menu.tscn"); + } + + public void OnPlayerConnected(long id) + { + if (Global.IsGameHost) + { + GameManager.Singleton.SendUniverseToClient(id); + } + + _ = SpawnNetPlayer(id); + } + + public void OnPlayerDisconnected(long id) + { + Player player = GetNode($"/root/Game/Space/Player-{id}"); + GameManager.Singleton.DespawnPlayer(player); + } + + private async Task SpawnNetPlayer(long id) + { + while (GameManager.Loading) + { + await Task.Delay(10); + } + + bool isMainPlayer = LocalNetId == id; + + Character character = new(GameManager.GameUniverse.Sectors[5, 5, 5], new(0, 0, 0)); + Player player = GameManager.Singleton.SpawnPlayer(character, isMainPlayer); + player.Name = $"Player-{id}"; + + player.SetMultiplayerAuthority((int)id); + + if (isMainPlayer && !Global.IsGameHost) + { + GameManager.Singleton.RpcId(1, nameof(GameManager.Singleton.RpcSendNearbySectors), character.CurrentSector.Coordinates, id); + } + } +} diff --git a/scripts/NetworkManager.cs.uid b/scripts/NetworkManager.cs.uid new file mode 100644 index 0000000..16dd97f --- /dev/null +++ b/scripts/NetworkManager.cs.uid @@ -0,0 +1 @@ +uid://bo57chobyuegg diff --git a/scripts/Player.cs b/scripts/Player.cs index e6a4136..121c6c6 100644 --- a/scripts/Player.cs +++ b/scripts/Player.cs @@ -2,11 +2,12 @@ using Godot; public partial class Player : CharacterBody3D { - [Export] public double Speed = 5; [Export] public double SprintMultiplier = 2; [Export] public double JumpForce = 5; [Export] public double MouseSensitivity = 0.2; + [Export] public double NetworkSyncInterval = 0.2; + [Export] public double NetworkPhysicsSyncInterval = 0.05; public Control GameMenu { get; set; } @@ -14,13 +15,17 @@ public partial class Player : CharacterBody3D private Vector3 gravityVelocity = Vector3.Zero; private Vector3 movementVelocity = Vector3.Zero; + private GravityReceiver gravityReceiver; private double cameraPitch = 0; private Camera3D camera; - private GravityReceiver gravityReceiver; + private double networkSyncCounter = 0; + private double networkPhysicsSyncCounter = 0; public override void _Ready() { camera = GetNode("Camera"); + camera.Current = IsMultiplayerAuthority(); + gravityReceiver = GetNode("GravityReceiver"); PlayerData.UpdateNodePosition(); @@ -28,7 +33,25 @@ public partial class Player : CharacterBody3D public override void _Process(double delta) { - PlayerData.SetCoordinatesFromLocal(GlobalPosition); + if (IsMultiplayerAuthority()) + { + networkPhysicsSyncCounter += delta; + if (networkPhysicsSyncCounter >= NetworkPhysicsSyncInterval) + { + networkPhysicsSyncCounter = 0; + Rpc(nameof(RpcSyncPhysics), movementVelocity, gravityVelocity, GlobalRotation); + } + + networkSyncCounter += delta; + if (networkSyncCounter >= NetworkSyncInterval) + { + networkSyncCounter = 0; + Rpc(nameof(RpcSync), GlobalPosition, PlayerData.CurrentSector.Coordinates); + } + + PlayerData.SetCoordinatesFromLocal(GlobalPosition); + } + PlayerData.Simulate(delta); } @@ -37,21 +60,18 @@ public partial class Player : CharacterBody3D Vector3 newGravityVelocity = gravityVelocity; Vector3 newMovementVelocity = movementVelocity; - if (IsInGravity()) + if (!GameMenu.Visible && IsMultiplayerAuthority()) { - ProcessGravity(delta, ref newGravityVelocity); - if (!GameMenu.Visible) + if (IsInGravity()) { + ProcessGravity(delta, ref newGravityVelocity); ProcessControlsGravity(ref newGravityVelocity, ref newMovementVelocity); } - } - else - { - newGravityVelocity = Vector3.Zero; - - ProcessCamera(delta); - if (!GameMenu.Visible) + else { + newGravityVelocity = Vector3.Zero; + + ProcessCamera(delta); ProcessControls(ref newMovementVelocity); } } @@ -69,6 +89,10 @@ public partial class Player : CharacterBody3D { return; } + if (!IsMultiplayerAuthority()) + { + return; + } if (@event is InputEventMouseMotion motion) { @@ -213,4 +237,33 @@ public partial class Player : CharacterBody3D 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; + } } diff --git a/scripts/QueueManager.cs b/scripts/QueueManager.cs index 7c6f07f..fdbb2da 100644 --- a/scripts/QueueManager.cs +++ b/scripts/QueueManager.cs @@ -23,8 +23,6 @@ public partial class QueueManager : Node action(); } - GD.Print(SectorReassignQueue.Count); - int sectorReassignQueueProcessed = 0; while ( !GameManager.Singleton.simulating diff --git a/scripts/RPCNode.cs b/scripts/RPCNode.cs new file mode 100644 index 0000000..5397ef2 --- /dev/null +++ b/scripts/RPCNode.cs @@ -0,0 +1,5 @@ +using Godot; + +public partial class RPCNode : Node +{ +} diff --git a/scripts/RPCNode.cs.uid b/scripts/RPCNode.cs.uid new file mode 100644 index 0000000..c9959c2 --- /dev/null +++ b/scripts/RPCNode.cs.uid @@ -0,0 +1 @@ +uid://by0357lop0qge diff --git a/scripts/Sector.cs b/scripts/Sector.cs index 224531d..077d196 100644 --- a/scripts/Sector.cs +++ b/scripts/Sector.cs @@ -65,7 +65,7 @@ public class Sector { List neighbours = []; - Sector[,,] allSectors = GameManager.Singleton.GameUniverse.Sectors; + Sector[,,] allSectors = GameManager.GameUniverse.Sectors; int sizeX = allSectors.GetLength(0); int sizeY = allSectors.GetLength(1); diff --git a/scripts/Universe.cs b/scripts/Universe.cs index aa137d6..f75ba77 100644 --- a/scripts/Universe.cs +++ b/scripts/Universe.cs @@ -4,15 +4,18 @@ using Godot; public class Universe { public Sector[,,] Sectors; + public Vector3I Size; public Universe(Vector3I size) { Sectors = new Sector[size.X, size.Y, size.Z]; + Size = size; } public Universe(int sizeX, int sizeY, int sizeZ) { Sectors = new Sector[sizeX, sizeY, sizeZ]; + Size = new(sizeX, sizeY, sizeZ); } public Sector GetSector(Vector3I coordinates) @@ -25,6 +28,32 @@ public class Universe return Sectors[x, y, z]; } + public bool IsInside(Vector3I sectorCoordinates, Vector3 localCoordinates) + { + Vector3 sectorSize = GameManager.Generator.GetSectorSize() / 2; + Vector3I universeSize = GameManager.GameUniverse.Size; + + if ( + sectorCoordinates.X == 0 && localCoordinates.X < -sectorSize.X || + sectorCoordinates.Y == 0 && localCoordinates.Y < -sectorSize.Y || + sectorCoordinates.Z == 0 && localCoordinates.Z < -sectorSize.Z + ) + { + return false; + } + + if ( + sectorCoordinates.X == universeSize.X - 1 && localCoordinates.X >= sectorSize.X || + sectorCoordinates.Y == universeSize.Y - 1 && localCoordinates.Y >= sectorSize.Y || + sectorCoordinates.Z == universeSize.Z - 1 && localCoordinates.Z >= sectorSize.Z + ) + { + return false; + } + + return true; + } + public void ForEachSector(Action action) { int sizeX = Sectors.GetLength(0);