Add initial project files and implement CameraController and World classes
This commit is contained in:
289
Source/World.cs
Normal file
289
Source/World.cs
Normal file
@@ -0,0 +1,289 @@
|
||||
using Godot;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Godot.Collections;
|
||||
|
||||
public enum DebugType
|
||||
{
|
||||
None,
|
||||
Slope,
|
||||
Height,
|
||||
Temperature,
|
||||
Moisture
|
||||
}
|
||||
|
||||
[Tool]
|
||||
public partial class World : Node2D
|
||||
{
|
||||
|
||||
|
||||
private int _chunksX = 32;
|
||||
[Export]
|
||||
public int ChunkXCount
|
||||
{
|
||||
get => _chunksX;
|
||||
set
|
||||
{
|
||||
_chunksX = value;
|
||||
LoadTiles();
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2I ChunksPerAxis
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
if (HeightMapImage == null)
|
||||
{
|
||||
return new Vector2I(_chunksX, _chunksX);
|
||||
}
|
||||
|
||||
Vector2I heightMapResolution = new Vector2I(HeightMapImage.GetWidth(), HeightMapImage.GetHeight());
|
||||
int chunksY = (int)(heightMapResolution.Y / (heightMapResolution.X / _chunksX));
|
||||
return new Vector2I(_chunksX, chunksY);
|
||||
}
|
||||
}
|
||||
|
||||
private int _tileSize = 96;
|
||||
[Export]
|
||||
public int TileSize
|
||||
{
|
||||
get => _tileSize;
|
||||
set
|
||||
{
|
||||
_tileSize = value;
|
||||
LoadTiles();
|
||||
}
|
||||
}
|
||||
|
||||
[Export]
|
||||
public Texture2D HeightMapTexture;
|
||||
|
||||
private float _rockThreshold = 0.05f;
|
||||
[Export]
|
||||
public float RockThreshold
|
||||
{
|
||||
get => _rockThreshold;
|
||||
set
|
||||
{
|
||||
_rockThreshold = value;
|
||||
LoadTiles();
|
||||
}
|
||||
}
|
||||
|
||||
private float _snowHeightThreshold = 0.8f;
|
||||
[Export]
|
||||
public float SnowHeightThreshold
|
||||
{
|
||||
get => _snowHeightThreshold;
|
||||
set
|
||||
{
|
||||
_snowHeightThreshold = value;
|
||||
LoadTiles();
|
||||
}
|
||||
}
|
||||
|
||||
private float _waterThreshold = 0.0f;
|
||||
[Export]
|
||||
public float WaterThreshold
|
||||
{
|
||||
get => _waterThreshold;
|
||||
set
|
||||
{
|
||||
_waterThreshold = value;
|
||||
LoadTiles();
|
||||
}
|
||||
}
|
||||
|
||||
private float _desertTemperatureThreshold = 0.7f;
|
||||
[Export]
|
||||
public float DesertTemperatureThreshold
|
||||
{
|
||||
get => _desertTemperatureThreshold;
|
||||
set
|
||||
{
|
||||
_desertTemperatureThreshold = value;
|
||||
LoadTiles();
|
||||
}
|
||||
}
|
||||
|
||||
private float _desertMoistureThreshold = 0.3f;
|
||||
[Export]
|
||||
public float DesertMoistureThreshold
|
||||
{
|
||||
get => _desertMoistureThreshold;
|
||||
set
|
||||
{
|
||||
_desertMoistureThreshold = value;
|
||||
LoadTiles();
|
||||
}
|
||||
}
|
||||
|
||||
private float _temperatureNoiseFrequency = 0.01f;
|
||||
[Export]
|
||||
public float TemperatureNoiseFrequency
|
||||
{
|
||||
get => _temperatureNoiseFrequency;
|
||||
set
|
||||
{
|
||||
_temperatureNoiseFrequency = value;
|
||||
LoadTiles();
|
||||
}
|
||||
}
|
||||
|
||||
private float _moistureNoiseFrequency = 0.01f;
|
||||
[Export]
|
||||
public float MoistureNoiseFrequency
|
||||
{
|
||||
get => _moistureNoiseFrequency;
|
||||
set
|
||||
{
|
||||
_moistureNoiseFrequency = value;
|
||||
LoadTiles();
|
||||
}
|
||||
}
|
||||
|
||||
private DebugType _debugType = DebugType.None;
|
||||
[Export]
|
||||
public DebugType DebugType
|
||||
{
|
||||
get => _debugType;
|
||||
set
|
||||
{
|
||||
_debugType = value;
|
||||
LoadTiles();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
ConcurrentDictionary<Vector2I, Tile> _tiles = new ConcurrentDictionary<Vector2I, Tile>();
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
public World()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
|
||||
// If the height map image is not assigned, log an error and return
|
||||
if (HeightMapTexture == null)
|
||||
{
|
||||
GD.PrintErr("Height map image is not assigned.");
|
||||
return;
|
||||
}
|
||||
|
||||
LoadTiles();
|
||||
}
|
||||
|
||||
private void LoadTiles()
|
||||
{
|
||||
// Check if HeightMapImage is available
|
||||
if (HeightMapImage == null)
|
||||
{
|
||||
GD.PrintErr("Cannot load tiles: HeightMapImage is null");
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel any existing generation
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
var cancellationToken = _cancellationTokenSource.Token;
|
||||
|
||||
// Clear existing tiles
|
||||
foreach (var tile in _tiles.Values)
|
||||
{
|
||||
tile.Visible = false;
|
||||
|
||||
// Delete and delete its children
|
||||
foreach (Node child in tile.GetChildren())
|
||||
{
|
||||
child.QueueFree();
|
||||
}
|
||||
tile.QueueFree();
|
||||
|
||||
}
|
||||
_tiles.Clear();
|
||||
|
||||
int scale = 1;
|
||||
|
||||
// Store local copies of parameters to avoid them changing mid-generation
|
||||
int tileSize = TileSize;
|
||||
int chunksX = _chunksX;
|
||||
|
||||
// Get the resolution of the height map image
|
||||
Vector2I heightMapResolution = new Vector2I(HeightMapImage.GetWidth(), HeightMapImage.GetHeight());
|
||||
|
||||
// Calculate the number of chunks in the Y direction based on the aspect ratio
|
||||
int chunksY = (int)(heightMapResolution.Y / (heightMapResolution.X / _chunksX));
|
||||
|
||||
// Loop through each chunk position and create a Tile
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
for (int x = 0; x < chunksX; x++)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested) return;
|
||||
|
||||
Parallel.For(0, chunksY, new ParallelOptions { CancellationToken = cancellationToken }, y =>
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested) return;
|
||||
|
||||
Vector2I chunkId = new Vector2I(x, y);
|
||||
|
||||
Tile tile = new Tile(new Vector2I(tileSize, tileSize), chunkId, this);
|
||||
tile.CancellationToken = cancellationToken;
|
||||
tile.Position = new Vector2(chunkId.X * tileSize, chunkId.Y * tileSize) * scale;
|
||||
tile.Scale = new Vector2(scale, scale);
|
||||
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
CallDeferred("add_child", tile);
|
||||
_tiles[chunkId] = tile;
|
||||
GD.Print("Loaded tile at chunk " + chunkId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
GD.Print("Tile generation cancelled");
|
||||
}
|
||||
}, cancellationToken);
|
||||
|
||||
}
|
||||
|
||||
private Image _heightMapCache;
|
||||
public Image HeightMapImage
|
||||
{
|
||||
get
|
||||
{
|
||||
// If not cached, load and cache it
|
||||
if (_heightMapCache == null && HeightMapTexture != null)
|
||||
{
|
||||
_heightMapCache = HeightMapTexture.GetImage();
|
||||
|
||||
// If GetImage() returns null, try loading directly from file
|
||||
if (_heightMapCache == null && HeightMapTexture.ResourcePath != "")
|
||||
{
|
||||
_heightMapCache = Image.LoadFromFile(HeightMapTexture.ResourcePath);
|
||||
|
||||
if (_heightMapCache == null)
|
||||
{
|
||||
GD.PushError($"Could not load heightmap from {HeightMapTexture.ResourcePath}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _heightMapCache;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user