Added dynamic spawning and despawning of objects

This commit is contained in:
Aslan 2026-01-27 07:08:03 -05:00
parent adbb208436
commit e0cf8d9755
23 changed files with 794 additions and 350 deletions

View file

@ -10,6 +10,8 @@ public class FastUniqueList<T>
public T this[int index] => list[index];
public IEnumerator<T> GetEnumerator() => list.GetEnumerator();
public bool Contains(T item)
{
return set.Contains(item);

View file

@ -1,148 +1,246 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Godot;
public partial class GameManager : Node
public partial class GameManager : Node3D
{
[Export] Player player;
[Export] public Control GameMenu { get; private set; }
[Export] public QueueManager QueueManager { get; private set; }
[Export] double closeTickInterval = 1;
[Export] double farTickInterval = 10;
[Export] int maxFarThreads = 16;
[Export] public double closeTickInterval = 1;
[Export] public double farTickInterval = 10;
[Export] public int maxFarThreads = 16;
public static GameManager Singleton { get; private set; }
public IGenerator Generator { get; private set; }
public Player MainPlayer { get; private set; }
public Universe GameUniverse { get; private set; }
public Sector CurrentSector { get; private set; }
public static GameManager Singleton { get; private set; }
public IGenerator Generator { get; private set; }
private double closeTickTimer = 0;
private double farTickTimer = 0;
public Universe GameUniverse { get; private set; }
public override void _Ready()
{
Singleton = this;
private double closeTickTimer = 0;
private double farTickTimer = 0;
Generator = new TestGenerator();
Generator.GenerateUniverse((universe) =>
{
GameUniverse = universe;
CurrentSector = universe.Sectors[1, 1, 1];
});
}
public bool simulating = false;
public override void _Process(double delta)
{
closeTickTimer += delta;
farTickTimer += delta;
private readonly Dictionary<GameObject, Node3D> spawnedObjects = [];
private readonly ConcurrentQueue<GameObject> spawnQueue = [];
if (closeTickTimer >= closeTickInterval)
{
SimulateClose(closeTickTimer);
closeTickTimer = 0;
}
public override void _Ready()
{
Singleton = this;
if (farTickTimer >= farTickInterval)
{
SimulateFar(farTickTimer);
farTickTimer = 0;
}
}
Generator = new TestGenerator();
GameUniverse = Generator.GenerateUniverse();
private void SimulateClose(double delta)
{
Vector3I currentCoordinates = CurrentSector.Coordinates;
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);
Sector[,,] sectors = GameUniverse.Sectors;
SpawnClose();
}
int sizeX = sectors.GetLength(0);
int sizeY = sectors.GetLength(1);
int sizeZ = sectors.GetLength(2);
public override void _Process(double delta)
{
closeTickTimer += delta;
farTickTimer += delta;
int startX = Mathf.Clamp(currentCoordinates.X - 1, 0, sizeX);
int startY = Mathf.Clamp(currentCoordinates.Y - 1, 0, sizeY);
int startZ = Mathf.Clamp(currentCoordinates.Z - 1, 0, sizeZ);
if (closeTickTimer >= closeTickInterval)
{
SimulateClose(closeTickTimer);
closeTickTimer = 0;
}
int endX = Mathf.Clamp(currentCoordinates.X + 1, 0, sizeX);
int endY = Mathf.Clamp(currentCoordinates.Y + 1, 0, sizeY);
int endZ = Mathf.Clamp(currentCoordinates.Z + 1, 0, sizeZ);
if (simulating)
{
farTickTimer = 0;
}
for (int x = startX; x <= endX; x++)
{
for (int y = startY; y <= endY; y++)
{
for (int z = startZ; z <= endZ; z++)
{
int taskX = x;
int taskY = y;
int taskZ = z;
if (farTickTimer >= farTickInterval)
{
double taskFarTickTimer = farTickTimer;
Task.Run(() =>
{
SimulateFar(taskFarTickTimer);
});
Task.Run(() =>
{
sectors[taskX, taskY, taskZ].Simulate(delta);
});
}
}
}
}
farTickTimer = 0;
}
}
private void SimulateFar(double delta)
{
Sector[,,] sectors = GameUniverse.Sectors;
public Sector GetCurrentSector()
{
return MainPlayer.PlayerData.CurrentSector;
}
int sizeX = sectors.GetLength(0);
int countX = Mathf.Clamp(sizeX / maxFarThreads, 1, int.MaxValue);
private void SimulateClose(double delta)
{
List<Sector> neighbours = GetCurrentSector().GetNeighbouringSectors();
for (int x = 0; x < sizeX; x += countX)
{
double taskDelta = delta;
int taskStartX = x;
int taskCountX = countX;
neighbours.ForEach(sector =>
{
Task.Run(() =>
{
sector.Simulate(delta);
});
});
}
_ = SimulateFarThreaded(taskDelta, taskStartX, taskCountX);
}
}
private void SimulateFar(double delta)
{
simulating = true;
private async Task SimulateFarThreaded(double delta, int startX, int countX)
{
Vector3I currentCoordinates = CurrentSector.Coordinates;
List<Task> tasks = [];
Sector[,,] sectors = GameUniverse.Sectors;
Sector[,,] sectors = GameUniverse.Sectors;
int sizeX = sectors.GetLength(0);
int sizeY = sectors.GetLength(1);
int sizeZ = sectors.GetLength(2);
int sizeX = sectors.GetLength(0);
int countX = Mathf.Clamp(sizeX / maxFarThreads, 1, int.MaxValue);
int startSkipX = Mathf.Clamp(currentCoordinates.X - 1, 0, sizeX);
int startSkipY = Mathf.Clamp(currentCoordinates.Y - 1, 0, sizeY);
int startSkipZ = Mathf.Clamp(currentCoordinates.Z - 1, 0, sizeZ);
for (int x = 0; x < sizeX; x += countX)
{
double taskDelta = delta;
int taskStartX = x;
int taskCountX = countX;
int endSkipX = Mathf.Clamp(currentCoordinates.X + 1, 0, sizeX);
int endSkipY = Mathf.Clamp(currentCoordinates.Y + 1, 0, sizeY);
int endSkipZ = Mathf.Clamp(currentCoordinates.Z + 1, 0, sizeZ);
Task clusteredTask = Task.Run(() =>
{
SimulateFarClustered(taskDelta, taskStartX, taskCountX);
});
int endX = Mathf.Clamp(startX + countX, 0, sizeX);
tasks.Add(clusteredTask);
}
for (int x = startX; x < endX; x++)
{
for (int y = 0; y < sizeY; y++)
{
for (int z = 0; z < sizeZ; z++)
{
if (Helpers.IsBetweenInclusive(x, startSkipX, endSkipX))
{
if (Helpers.IsBetweenInclusive(y, startSkipY, endSkipY))
{
if (Helpers.IsBetweenInclusive(z, startSkipZ, endSkipZ))
{
continue;
}
}
}
Task.WaitAll([.. tasks]);
sectors[x, y, z].Simulate(delta);
}
}
}
}
simulating = false;
}
private void SimulateFarClustered(double delta, int startX, int countX)
{
Vector3I currentCoordinates = GetCurrentSector().Coordinates;
Sector[,,] sectors = GameUniverse.Sectors;
int sizeX = sectors.GetLength(0);
int sizeY = sectors.GetLength(1);
int sizeZ = sectors.GetLength(2);
int startSkipX = Mathf.Clamp(currentCoordinates.X - 1, 0, sizeX);
int startSkipY = Mathf.Clamp(currentCoordinates.Y - 1, 0, sizeY);
int startSkipZ = Mathf.Clamp(currentCoordinates.Z - 1, 0, sizeZ);
int endSkipX = Mathf.Clamp(currentCoordinates.X + 1, 0, sizeX);
int endSkipY = Mathf.Clamp(currentCoordinates.Y + 1, 0, sizeY);
int endSkipZ = Mathf.Clamp(currentCoordinates.Z + 1, 0, sizeZ);
int endX = Mathf.Clamp(startX + countX, 0, sizeX);
for (int x = startX; x < endX; x++)
{
QueueManager.LogQueue.Enqueue("Simulating: " + x);
for (int y = 0; y < sizeY; y++)
{
Thread.Sleep(5);
for (int z = 0; z < sizeZ; z++)
{
if (Helpers.IsBetweenInclusive(x, startSkipX, endSkipX))
{
if (Helpers.IsBetweenInclusive(y, startSkipY, endSkipY))
{
if (Helpers.IsBetweenInclusive(z, startSkipZ, endSkipZ))
{
continue;
}
}
}
try
{
sectors[x, y, z].Simulate(delta);
}
catch (Exception e)
{
QueueManager.LogQueue.Enqueue("EXCEPTION: " + e.Message);
}
}
}
}
}
private void SpawnClose()
{
List<Sector> neighbours = GetCurrentSector().GetNeighbouringSectors();
neighbours.ForEach(sector => sector.SpawnObjects());
}
public void Spawn(GameObject gameObject)
{
spawnQueue.Enqueue(gameObject);
CallDeferred(nameof(ProcessSpawnQueue));
}
private void ProcessSpawnQueue()
{
spawnQueue.TryDequeue(out GameObject gameObject);
if (gameObject == null)
{
return;
}
if (spawnedObjects.ContainsKey(gameObject))
{
return;
}
Node3D instance = gameObject.Instantiate(GetCurrentSector());
spawnedObjects.Add(gameObject, instance);
CallDeferred("add_child", instance);
}
public void Despawn(GameObject gameObject)
{
if (!spawnedObjects.ContainsKey(gameObject))
{
return;
}
Node3D nodeToDespawn = spawnedObjects.GetValueOrDefault(gameObject);
spawnedObjects.Remove(gameObject);
CallDeferred("remove_child", nodeToDespawn);
nodeToDespawn.CallDeferred("queue_free");
}
public void ApplyOrigin()
{
Sector current = GetCurrentSector();
List<Sector> nearby = current.GetNeighbouringSectors();
foreach (Sector sector in nearby)
{
foreach (GameObject gameObject in sector.GameObjects)
{
gameObject.UpdateSectorOffset(current);
}
}
foreach (GameObject spawned in spawnedObjects.Keys)
{
if (!nearby.Contains(spawned.CurrentSector))
{
Despawn(spawned);
}
}
nearby.ForEach(sector => sector.SpawnObjects());
}
}

View file

@ -1,71 +1,157 @@
using System.Collections.Generic;
using Godot;
public abstract class GameObject
{
public Sector CurrentSector { get; private set; }
public Vector3 LocalCoordinates;
public Vector3Dec GlobalCoordinates;
public Sector CurrentSector { get; protected set; }
public Vector3 LocalCoordinates { get; protected set; }
public Vector3Dec GlobalCoordinates { get; protected set; }
public GameObject(Sector sector, Vector3 localCoordinates)
{
CurrentSector = sector;
LocalCoordinates = localCoordinates;
public Vector3 SectorOffset { get; set; }
GlobalCoordinates = new
(
sector.GlobalStartCoordinates.X + (decimal)localCoordinates.X,
sector.GlobalStartCoordinates.Y + (decimal)localCoordinates.Y,
sector.GlobalStartCoordinates.Z + (decimal)localCoordinates.Z
);
}
private bool reassigning = false;
public GameObject(Sector sector, double localX, double localY, double localZ)
{
CurrentSector = sector;
LocalCoordinates = new(localX, localY, localZ);
public GameObject(Sector sector, Vector3 localCoordinates)
{
CurrentSector = sector;
LocalCoordinates = localCoordinates;
GlobalCoordinates = new
(
sector.GlobalStartCoordinates.X + (decimal)localX,
sector.GlobalStartCoordinates.Y + (decimal)localY,
sector.GlobalStartCoordinates.Z + (decimal)localZ
);
}
GlobalCoordinates = new
(
sector.GlobalCenterCoordinates.X + (decimal)localCoordinates.X,
sector.GlobalCenterCoordinates.Y + (decimal)localCoordinates.Y,
sector.GlobalCenterCoordinates.Z + (decimal)localCoordinates.Z
);
}
public GameObject(Vector3Dec coordinates)
{
GlobalCoordinates = coordinates;
UpdateSector();
}
public GameObject(Sector sector, double localX, double localY, double localZ)
{
CurrentSector = sector;
LocalCoordinates = new(localX, localY, localZ);
public GameObject(decimal x, decimal y, decimal z)
{
GlobalCoordinates = new(x, y, z);
UpdateSector();
}
GlobalCoordinates = new
(
sector.GlobalStartCoordinates.X + (decimal)localX,
sector.GlobalStartCoordinates.Y + (decimal)localY,
sector.GlobalStartCoordinates.Z + (decimal)localZ
);
}
public Vector3 GetSectorCoordinates()
{
Vector3Dec relativeSectorCoordinates = GlobalCoordinates - CurrentSector.GlobalStartCoordinates;
public GameObject(Vector3Dec coordinates)
{
GlobalCoordinates = coordinates;
UpdateSector();
}
return relativeSectorCoordinates.ToVector3();
}
public GameObject(decimal x, decimal y, decimal z)
{
GlobalCoordinates = new(x, y, z);
UpdateSector();
}
public bool IsInSector()
{
return Helpers.IsInsideArea(CurrentSector.Size, GetSectorCoordinates());
}
public bool IsInCurrentSector()
{
return Helpers.IsInsideArea(CurrentSector.Size / 2, LocalCoordinates);
}
public void UpdateSector()
{
public void UpdateSector()
{
List<Sector> neighbours = CurrentSector.GetNeighbouringSectors();
foreach (Sector sector in neighbours)
{
if (sector.IsObjectInSector(this))
{
sector.AssignObject(this);
reassigning = true;
return;
}
}
}
foreach (Sector sector in GameManager.Singleton.GameUniverse.Sectors)
{
if (sector.IsObjectInSector(this))
{
sector.AssignObject(this);
reassigning = true;
return;
}
}
}
public virtual void Simulate(double delta)
{
if (!IsInSector())
{
UpdateSector();
}
}
public void AssignSector(Sector sector)
{
CurrentSector = sector;
ResetLocalCoordinates();
SectorOffset = GetSectorOffset(GameManager.Singleton.GetCurrentSector());
if (this is Character)
{
GameManager.Singleton.ApplyOrigin();
}
if (this is not Character)
{
List<Sector> neighbours = GameManager.Singleton.GetCurrentSector().GetNeighbouringSectors();
if (neighbours.Contains(sector))
{
GameManager.Singleton.Spawn(this);
}
else
{
GameManager.Singleton.Despawn(this);
}
}
reassigning = false;
}
public Vector3 GetSectorOffset(Sector sector)
{
Vector3Dec relative = CurrentSector.GlobalCenterCoordinates - sector.GlobalCenterCoordinates;
return relative.ToVector3();
}
public virtual void UpdateSectorOffset(Sector sector)
{
SectorOffset = GetSectorOffset(sector);
}
public void SetCoordinatesFromGlobal(Vector3Dec globalCoordinates)
{
GlobalCoordinates = globalCoordinates;
LocalCoordinates = (globalCoordinates - CurrentSector.GlobalCenterCoordinates).ToVector3();
UpdateNodePosition();
}
public void SetCoordinatesFromLocal(Vector3 localCoordinates)
{
LocalCoordinates = localCoordinates;
GlobalCoordinates = Vector3Dec.FromVector3(localCoordinates) + CurrentSector.GlobalCenterCoordinates;
UpdateNodePosition();
}
public void ResetLocalCoordinates()
{
SetCoordinatesFromGlobal(GlobalCoordinates);
}
public virtual void UpdateNodePosition() { }
public virtual void Simulate(double delta)
{
if (!reassigning && !IsInCurrentSector())
{
UpdateSector();
}
}
public virtual Node3D Instantiate(Sector sector)
{
PackedScene modulePrefab = ResourceLoader.Load<PackedScene>("res://prefabs/gameObjects/node.tscn");
return modulePrefab.Instantiate<Node3D>();
}
}

View file

@ -0,0 +1,22 @@
using Godot;
public class Character(Sector sector, Vector3 localCoordinates) : GameObject(sector, localCoordinates)
{
private Player player;
public Player InstantiatePlayer()
{
PackedScene prefab = ResourceLoader.Load<PackedScene>("res://prefabs/gameObjects/player.tscn");
Player instance = prefab.Instantiate<Player>();
instance.PlayerData = this;
player = instance;
return instance;
}
public override void UpdateNodePosition()
{
player.GlobalPosition = LocalCoordinates;
}
}

View file

@ -0,0 +1 @@
uid://dbl3bjk8yp3eb

View file

@ -1,5 +1,16 @@
using Godot;
public partial class StarNode : Node
public partial class StarNode : StaticBody3D
{
public Star StarData { get; set; }
public override void _Ready()
{
GlobalPosition = StarData.LocalCoordinates + StarData.SectorOffset;
}
public override void _Process(double delta)
{
GlobalPosition = StarData.LocalCoordinates + StarData.SectorOffset;
}
}

View file

@ -5,5 +5,18 @@ public class Star(Sector sector, Vector3 localCoordinates) : GameObject(sector,
public override void Simulate(double delta)
{
base.Simulate(delta);
SetCoordinatesFromGlobal(new(GlobalCoordinates.X, GlobalCoordinates.Y, GlobalCoordinates.Z + 1 * (decimal)delta));
}
public override Node3D Instantiate(Sector sector)
{
PackedScene prefab = ResourceLoader.Load<PackedScene>("res://prefabs/gameObjects/star.tscn");
StarNode instance = prefab.Instantiate<StarNode>();
instance.StarData = this;
SectorOffset = GetSectorOffset(sector);
return instance;
}
}

View file

@ -1,13 +1,12 @@
using System;
using System.Threading.Tasks;
using Godot;
public interface IGenerator
{
public Vector3I GetUniverseSize();
public Vector3 GetSectorSizeEachDirection();
public Vector3 GetSectorSize();
public Task GenerateUniverse(Action<Universe> callback);
public Universe GenerateUniverse();
public Sector GenerateSector(Vector3I coordinates);
public Star GenerateStar(Sector sector, Vector3 localCoordinates);

View file

@ -1,134 +1,169 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Godot;
public class TestGenerator : IGenerator
{
public static Vector3I UNIVERSE_SIZE = new(3, 3, 3);
public static Vector3 SECTOR_SIZE_EACH_DIRECTION = new(1000, 1000, 1000);
public static int STARS_PER_SECTOR = 5;
public static int STARS_PER_SECTOR_VARIANCE = 2;
public static int SHIPS_PER_SECTOR = 0;
public static int SHIPS_PER_SECTOR_VARIANCE = 0;
public static int STATIONS_PER_SECTOR = 0;
public static int STATIONS_PER_SECTOR_VARIANCE = 0;
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 int SHIPS_PER_SECTOR = 0;
public static int SHIPS_PER_SECTOR_VARIANCE = 0;
public static int STATIONS_PER_SECTOR = 0;
public static int STATIONS_PER_SECTOR_VARIANCE = 0;
private RandomNumberGenerator rng;
private readonly int threads = 16;
public Vector3I GetUniverseSize()
{
return UNIVERSE_SIZE;
}
private RandomNumberGenerator rng;
public Vector3 GetSectorSizeEachDirection()
{
return SECTOR_SIZE_EACH_DIRECTION;
}
public Vector3I GetUniverseSize()
{
return UNIVERSE_SIZE;
}
public async Task GenerateUniverse(Action<Universe> callback)
{
rng = new();
public Vector3I GetSectorOffset()
{
return new(
(int)Mathf.Floor(UNIVERSE_SIZE.X / 2),
(int)Mathf.Floor(UNIVERSE_SIZE.X / 2),
(int)Mathf.Floor(UNIVERSE_SIZE.X / 2)
);
}
Universe universe = new(UNIVERSE_SIZE);
for (int x = 0; x < UNIVERSE_SIZE.X; x++)
{
for (int y = 0; y < UNIVERSE_SIZE.Y; y++)
{
for (int z = 0; z < UNIVERSE_SIZE.Z; z++)
{
universe.Sectors[x, y, z] = GenerateSector(new(x, y, z));
}
}
}
public Vector3 GetSectorSize()
{
return SECTOR_SIZE;
}
callback(universe);
}
public Universe GenerateUniverse()
{
rng = new();
public Sector GenerateSector(Vector3I coordinates)
{
Sector sector = new(coordinates, SECTOR_SIZE_EACH_DIRECTION);
List<Task> tasks = [];
int starCount = rng.RandiRange(
STARS_PER_SECTOR - STARS_PER_SECTOR_VARIANCE,
STARS_PER_SECTOR + STARS_PER_SECTOR_VARIANCE
);
starCount = Mathf.Clamp(starCount, 0, int.MaxValue);
Universe universe = new(UNIVERSE_SIZE);
int countX = Mathf.Clamp(UNIVERSE_SIZE.X / threads, 1, int.MaxValue);
for (int i = 0; i < starCount; i++)
{
Vector3 localCoordinates = GenerateLocalCoordinates();
Star star = GenerateStar(sector, localCoordinates);
for (int x = 0; x < UNIVERSE_SIZE.X; x += countX)
{
int taskStartX = x;
int taskCountX = countX;
sector.GameObjects.Add(star);
}
Task clusteredTask = Task.Run(() =>
{
GenerateClustered(universe, taskStartX, taskCountX);
});
int shipCount = rng.RandiRange(
SHIPS_PER_SECTOR - SHIPS_PER_SECTOR_VARIANCE,
SHIPS_PER_SECTOR + SHIPS_PER_SECTOR_VARIANCE
);
shipCount = Mathf.Clamp(shipCount, 0, int.MaxValue);
tasks.Add(clusteredTask);
}
for (int i = 0; i < shipCount; i++)
{
Vector3 localCoordinates = GenerateLocalCoordinates();
Vessel ship = GenerateShip(sector, localCoordinates);
Task.WaitAll([.. tasks]);
sector.GameObjects.Add(ship);
}
return universe;
}
int stationCount = rng.RandiRange(
STATIONS_PER_SECTOR - STATIONS_PER_SECTOR_VARIANCE,
STATIONS_PER_SECTOR + STATIONS_PER_SECTOR_VARIANCE
);
stationCount = Mathf.Clamp(stationCount, 0, int.MaxValue);
private void GenerateClustered(Universe universe, int startX, int countX)
{
int endX = Mathf.Clamp(startX + countX, 0, UNIVERSE_SIZE.X);
for (int i = 0; i < stationCount; i++)
{
Vector3 localCoordinates = GenerateLocalCoordinates();
Vessel station = GenerateStation(sector, localCoordinates);
for (int x = startX; x < endX; x++)
{
for (int y = 0; y < UNIVERSE_SIZE.Y; y++)
{
for (int z = 0; z < UNIVERSE_SIZE.Z; z++)
{
universe.Sectors[x, y, z] = GenerateSector(new(x, y, z));
}
}
}
}
sector.GameObjects.Add(station);
}
public Sector GenerateSector(Vector3I coordinates)
{
Sector sector = new(coordinates, GetSectorOffset(), SECTOR_SIZE);
return sector;
}
int starCount = rng.RandiRange(
STARS_PER_SECTOR - STARS_PER_SECTOR_VARIANCE,
STARS_PER_SECTOR + STARS_PER_SECTOR_VARIANCE
);
starCount = Mathf.Clamp(starCount, 0, int.MaxValue);
public Star GenerateStar(Sector sector, Vector3 localCoordinates)
{
return new Star(sector, localCoordinates);
}
for (int i = 0; i < starCount; i++)
{
Vector3 localCoordinates = GenerateLocalCoordinates();
Star star = GenerateStar(sector, localCoordinates);
public Star GenerateStar(Sector sector)
{
return GenerateStar(sector, GenerateLocalCoordinates());
}
sector.GameObjects.Add(star);
}
public Vessel GenerateShip(Sector sector, Vector3 localCoordinates)
{
return new Vessel(sector, localCoordinates);
}
int shipCount = rng.RandiRange(
SHIPS_PER_SECTOR - SHIPS_PER_SECTOR_VARIANCE,
SHIPS_PER_SECTOR + SHIPS_PER_SECTOR_VARIANCE
);
shipCount = Mathf.Clamp(shipCount, 0, int.MaxValue);
public Vessel GenerateShip(Sector sector)
{
return GenerateShip(sector, GenerateLocalCoordinates());
}
for (int i = 0; i < shipCount; i++)
{
Vector3 localCoordinates = GenerateLocalCoordinates();
Vessel ship = GenerateShip(sector, localCoordinates);
public Vessel GenerateStation(Sector sector, Vector3 localCoordinates)
{
return new Vessel(sector, localCoordinates);
}
sector.GameObjects.Add(ship);
}
public Vessel GenerateStation(Sector sector)
{
return GenerateStation(sector, GenerateLocalCoordinates());
}
int stationCount = rng.RandiRange(
STATIONS_PER_SECTOR - STATIONS_PER_SECTOR_VARIANCE,
STATIONS_PER_SECTOR + STATIONS_PER_SECTOR_VARIANCE
);
stationCount = Mathf.Clamp(stationCount, 0, int.MaxValue);
public Vector3 GenerateLocalCoordinates()
{
double x = rng.Randf() * SECTOR_SIZE_EACH_DIRECTION.X;
double y = rng.Randf() * SECTOR_SIZE_EACH_DIRECTION.Y;
double z = rng.Randf() * SECTOR_SIZE_EACH_DIRECTION.Z;
for (int i = 0; i < stationCount; i++)
{
Vector3 localCoordinates = GenerateLocalCoordinates();
Vessel station = GenerateStation(sector, localCoordinates);
return new(x, y, z);
}
sector.GameObjects.Add(station);
}
return sector;
}
public Star GenerateStar(Sector sector, Vector3 localCoordinates)
{
return new Star(sector, localCoordinates);
}
public Star GenerateStar(Sector sector)
{
return GenerateStar(sector, GenerateLocalCoordinates());
}
public Vessel GenerateShip(Sector sector, Vector3 localCoordinates)
{
return new Vessel(sector, localCoordinates);
}
public Vessel GenerateShip(Sector sector)
{
return GenerateShip(sector, GenerateLocalCoordinates());
}
public Vessel GenerateStation(Sector sector, Vector3 localCoordinates)
{
return new Vessel(sector, localCoordinates);
}
public Vessel GenerateStation(Sector sector)
{
return GenerateStation(sector, GenerateLocalCoordinates());
}
public Vector3 GenerateLocalCoordinates()
{
double x = (rng.Randf() - 0.5) * SECTOR_SIZE.X;
double y = (rng.Randf() - 0.5) * SECTOR_SIZE.Y;
double z = (rng.Randf() - 0.5) * SECTOR_SIZE.Z;
return new(x, y, z);
}
}

View file

@ -9,12 +9,27 @@ public static class Helpers
public static bool IsInsideArea(Vector3 area, Vector3 coordinates)
{
if (coordinates.X >= area.X && coordinates.Y >= area.Y && coordinates.Z >= area.Z)
if (coordinates.X >= area.X || coordinates.Y >= area.Y || coordinates.Z >= area.Z)
{
return false;
}
if (coordinates.X < -area.X && coordinates.Y < -area.Y && coordinates.Z < -area.Z)
if (coordinates.X < -area.X || coordinates.Y < -area.Y || coordinates.Z < -area.Z)
{
return false;
}
return true;
}
public static bool IsInsideGlobalArea(Vector3Dec areaStart, Vector3Dec areaEnd, Vector3Dec coordinates)
{
if (coordinates.X >= areaEnd.X || coordinates.Y >= areaEnd.Y || coordinates.Z >= areaEnd.Z)
{
return false;
}
if (coordinates.X < areaStart.X || coordinates.Y < areaStart.Y || coordinates.Z < areaStart.Z)
{
return false;
}

View file

@ -2,15 +2,19 @@ using Godot;
public partial class Player : CharacterBody3D
{
[Export] Control GameMenu;
[Export] public float Speed = 5f;
[Export] public float JumpForce = 5f;
[Export] public float MouseSensitivity = 0.2f;
[Export] public double Speed = 5;
[Export] public double SprintMultiplier = 2;
[Export] public double JumpForce = 5;
[Export] public double MouseSensitivity = 0.2;
public Control GameMenu { get; set; }
public Character PlayerData { get; set; }
private Vector3 gravityVelocity = Vector3.Zero;
private Vector3 movementVelocity = Vector3.Zero;
private double cameraPitch = 0f;
private double cameraPitch = 0;
private Camera3D camera;
private GravityReceiver gravityReceiver;
@ -18,6 +22,14 @@ public partial class Player : CharacterBody3D
{
camera = GetNode<Camera3D>("Camera");
gravityReceiver = GetNode<GravityReceiver>("GravityReceiver");
PlayerData.UpdateNodePosition();
}
public override void _Process(double delta)
{
PlayerData.SetCoordinatesFromLocal(GlobalPosition);
PlayerData.Simulate(delta);
}
public override void _PhysicsProcess(double delta)
@ -69,7 +81,7 @@ public partial class Player : CharacterBody3D
if (IsInGravity())
{
cameraPitch = Mathf.Clamp(cameraPitch + pitchDelta, -90f, 90f);
cameraPitch = Mathf.Clamp(cameraPitch + pitchDelta, -90, 90);
}
else
{
@ -82,7 +94,7 @@ public partial class Player : CharacterBody3D
public bool IsInGravity()
{
return gravityReceiver.InGravityZone && gravityReceiver.GetGravityStrength() > 0f;
return gravityReceiver.InGravityZone && gravityReceiver.GetGravityStrength() > 0;
}
public bool IsOnGravityFloor()
@ -94,7 +106,7 @@ public partial class Player : CharacterBody3D
double alignment = collisionNormal.Dot(gravityReceiver.GetGravityDirection());
if (alignment > 0.7f)
if (alignment > 0.7)
{
return true;
}
@ -118,28 +130,28 @@ public partial class Player : CharacterBody3D
Vector3 targetUp = gravityReceiver.GetGravityDirection();
Vector3 axis = currentUp.Cross(targetUp);
if (axis.Length() < 0.00001f)
if (axis.Length() < 0.00001)
{
return;
}
double angle = currentUp.AngleTo(targetUp);
GlobalRotate(axis.Normalized(), angle * delta * 10f);
GlobalRotate(axis.Normalized(), angle * delta * 10);
}
private void ProcessCamera(double delta)
{
if (cameraPitch > 0.01f)
if (cameraPitch > 0.01)
{
cameraPitch -= 90f * delta;
cameraPitch -= 90 * delta;
}
else if (cameraPitch < -0.001f)
else if (cameraPitch < -0.001)
{
cameraPitch += 90f * delta;
cameraPitch += 90 * delta;
}
if (Mathf.Abs(cameraPitch) < 1f)
if (Mathf.Abs(cameraPitch) < 1)
{
cameraPitch = 0f;
cameraPitch = 0;
}
camera.RotationDegrees = new Vector3(cameraPitch, 0, 0);
}
@ -149,19 +161,21 @@ public partial class Player : CharacterBody3D
float inputX = Input.GetAxis("move_left", "move_right");
float inputZ = Input.GetAxis("move_forward", "move_back");
bool sprint = Input.IsActionPressed("sprint");
Vector3 direction = GlobalTransform.Basis.X * inputX + GlobalTransform.Basis.Z * inputZ;
if (direction != Vector3.Zero)
{
newMovementVelocity.X = direction.X * Speed;
newMovementVelocity.Y = direction.Y * Speed;
newMovementVelocity.Z = direction.Z * Speed;
newMovementVelocity.X = direction.X * Speed * (sprint ? SprintMultiplier : 1);
newMovementVelocity.Y = direction.Y * Speed * (sprint ? SprintMultiplier : 1);
newMovementVelocity.Z = direction.Z * Speed * (sprint ? SprintMultiplier : 1);
}
else
{
newMovementVelocity.X = 0f;
newMovementVelocity.Y = 0f;
newMovementVelocity.Z = 0f;
newMovementVelocity.X = 0;
newMovementVelocity.Y = 0;
newMovementVelocity.Z = 0;
}
if (Input.IsActionJustPressed("jump") && IsOnGravityFloor())
@ -176,19 +190,21 @@ public partial class Player : CharacterBody3D
float inputY = -Input.GetAxis("move_up", "move_down");
float inputZ = Input.GetAxis("move_forward", "move_back");
bool sprint = Input.IsActionPressed("sprint");
Vector3 direction = Transform.Basis.X * inputX + Transform.Basis.Y * inputY + Transform.Basis.Z * inputZ;
if (direction != Vector3.Zero)
{
newMovementVelocity.X = direction.X * Speed;
newMovementVelocity.Y = direction.Y * Speed;
newMovementVelocity.Z = direction.Z * Speed;
newMovementVelocity.X = direction.X * Speed * (sprint ? SprintMultiplier : 1);
newMovementVelocity.Y = direction.Y * Speed * (sprint ? SprintMultiplier : 1);
newMovementVelocity.Z = direction.Z * Speed * (sprint ? SprintMultiplier : 1);
}
else
{
newMovementVelocity.X = 0f;
newMovementVelocity.Y = 0f;
newMovementVelocity.Z = 0f;
newMovementVelocity.X = 0;
newMovementVelocity.Y = 0;
newMovementVelocity.Z = 0;
}
double inputRotateZ = Input.GetAxis("rotate_left", "rotate_right");

43
scripts/QueueManager.cs Normal file
View file

@ -0,0 +1,43 @@
using System;
using System.Collections.Concurrent;
using Godot;
public partial class QueueManager : Node
{
public static ConcurrentQueue<string> LogQueue = new();
public static ConcurrentQueue<Action> ActionQueue = new();
public static ConcurrentQueue<(Sector, GameObject)> SectorReassignQueue = new();
private readonly int sectorReassignQueueRateLimit = 500;
public override void _Process(double delta)
{
while (LogQueue.TryDequeue(out string text))
{
GD.Print(text);
}
while (ActionQueue.TryDequeue(out Action action))
{
action();
}
GD.Print(SectorReassignQueue.Count);
int sectorReassignQueueProcessed = 0;
while (
!GameManager.Singleton.simulating
&& sectorReassignQueueProcessed++ < sectorReassignQueueRateLimit
&& SectorReassignQueue.TryDequeue(out var item)
)
{
var (sector, gameObject) = item;
gameObject.CurrentSector.GameObjects.Remove(gameObject);
sector.GameObjects.Add(gameObject);
gameObject.AssignSector(sector);
}
}
}

View file

@ -0,0 +1 @@
uid://dr6y711ano07p

View file

@ -1,37 +1,95 @@
using System.Threading;
using System.Collections.Generic;
using Godot;
public class Sector
{
public Vector3I Coordinates;
public Vector3Dec GlobalStartCoordinates;
public Vector3Dec GlobalCenterCoordinates;
public Vector3Dec GlobalEndCoordinates;
public Vector3 Size;
public FastUniqueList<GameObject> GameObjects = new();
public Sector(Vector3I coordinates, Vector3 size)
public Sector(Vector3I coordinates, Vector3I offset, Vector3 size)
{
Coordinates = coordinates;
decimal startX = Coordinates.X * (decimal)size.X;
decimal startY = Coordinates.Y * (decimal)size.Y;
decimal startZ = Coordinates.Z * (decimal)size.Z;
Vector3Dec sizeDec = Vector3Dec.FromVector3(size);
decimal startX = (coordinates.X - offset.X) * sizeDec.X;
decimal startY = (coordinates.Y - offset.Y) * sizeDec.Y;
decimal startZ = (coordinates.Z - offset.Z) * sizeDec.Z;
GlobalStartCoordinates = new(startX, startY, startZ);
GlobalCenterCoordinates = new(
startX + sizeDec.X / 2,
startY + sizeDec.Y / 2,
startZ + sizeDec.Z / 2
);
GlobalEndCoordinates = new(
startX + sizeDec.X,
startY + sizeDec.Y,
startZ + sizeDec.Z
);
Size = size;
}
public Sector(int x, int y, int z, Vector3 size) : this(new(x, y, z), size) { }
public void Simulate(double delta)
{
//GD.Print(Thread.CurrentThread.ManagedThreadId);
GameObjects.ForEach(gameObject =>
{
gameObject.Simulate(delta);
});
}
public bool IsObjectInSector(GameObject gameObject)
{
return Helpers.IsInsideGlobalArea(GlobalStartCoordinates, GlobalEndCoordinates, gameObject.GlobalCoordinates);
}
public void AssignObject(GameObject gameObject)
{
QueueManager.SectorReassignQueue.Enqueue((this, gameObject));
}
public void SpawnObjects()
{
GameObjects.ForEach(GameManager.Singleton.Spawn);
}
public List<Sector> GetNeighbouringSectors()
{
List<Sector> neighbours = [];
Sector[,,] allSectors = GameManager.Singleton.GameUniverse.Sectors;
int sizeX = allSectors.GetLength(0);
int sizeY = allSectors.GetLength(1);
int sizeZ = allSectors.GetLength(2);
int startX = Mathf.Clamp(Coordinates.X - 1, 0, sizeX - 1);
int startY = Mathf.Clamp(Coordinates.Y - 1, 0, sizeY - 1);
int startZ = Mathf.Clamp(Coordinates.Z - 1, 0, sizeZ - 1);
int endX = Mathf.Clamp(Coordinates.X + 1, 0, sizeX - 1);
int endY = Mathf.Clamp(Coordinates.Y + 1, 0, sizeY - 1);
int endZ = Mathf.Clamp(Coordinates.Z + 1, 0, sizeZ - 1);
for (int x = startX; x <= endX; x++)
{
for (int y = startY; y <= endY; y++)
{
for (int z = startZ; z <= endZ; z++)
{
neighbours.Add(allSectors[x, y, z]);
}
}
}
return neighbours;
}
}

View file

@ -1,3 +1,4 @@
using System;
using Godot;
public class Universe
@ -23,4 +24,22 @@ public class Universe
{
return Sectors[x, y, z];
}
public void ForEachSector(Action<Sector> action)
{
int sizeX = Sectors.GetLength(0);
int sizeY = Sectors.GetLength(1);
int sizeZ = Sectors.GetLength(2);
for (int x = 0; x < sizeX; x++)
{
for (int y = 0; y < sizeY; y++)
{
for (int z = 0; z < sizeZ; z++)
{
action(Sectors[x, y, z]);
}
}
}
}
}

View file

@ -1,7 +1,7 @@
using System;
using Godot;
public class Vector3Dec(decimal x, decimal y, decimal z)
public readonly struct Vector3Dec(decimal x, decimal y, decimal z)
{
public static Vector3Dec Zero { get; } = new(0, 0, 0);
@ -14,6 +14,11 @@ public class Vector3Dec(decimal x, decimal y, decimal z)
return new((double)X, (double)Y, (double)Z);
}
public static Vector3Dec FromVector3(Vector3 vec)
{
return new((decimal)vec.X, (decimal)vec.Y, (decimal)vec.Z);
}
public static Vector3Dec operator +(Vector3Dec vec1, Vector3Dec vec2)
{
return new(vec1.X + vec2.X, vec1.Y + vec2.Y, vec1.Z + vec2.Z);