Update project to use Godot.NET.Sdk 4.3.0 and improve terrain generation performance

This commit is contained in:
ImBenji
2025-10-18 18:26:55 +01:00
parent ca3f88cf17
commit f7de1c67af
7 changed files with 93 additions and 55 deletions

View File

@@ -1 +0,0 @@
Frontiers

View File

@@ -1,10 +0,0 @@
<component name="libraryTable">
<library name="GdSdk Master" type="GdScript">
<properties path="$PROJECT_DIR$/../Library/Caches/JetBrains/Rider2025.1/projects/.idea.frontiers.5687bf8b/sdk/GdSdk Master" version="Master" date="2024-06-01T15:14:16.000+02:00" />
<CLASSES />
<JAVADOC />
<SOURCES>
<root url="file://$PROJECT_DIR$/../Library/Caches/JetBrains/Rider2025.1/projects/.idea.frontiers.5687bf8b/sdk/GdSdk Master" />
</SOURCES>
</library>
</component>

View File

@@ -1,4 +1,4 @@
<Project Sdk="Godot.NET.Sdk/4.4.1"> <Project Sdk="Godot.NET.Sdk/4.3.0">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading> <EnableDynamicLoading>true</EnableDynamicLoading>

View File

@@ -48,12 +48,18 @@ public partial class Tile : Node2D
DebugType debugType = _world.DebugType; DebugType debugType = _world.DebugType;
// Run in thread // Use limited parallelism instead of unlimited Task.Run
var parallelOptions = new ParallelOptions
{
MaxDegreeOfParallelism = Math.Max(1, (int)(System.Environment.ProcessorCount * 0.5)),
CancellationToken = CancellationToken
};
Task.Run(() => Task.Run(() =>
{ {
try try
{ {
for (int x = 0; x < _size.X; x++) Parallel.For(0, _size.X, parallelOptions, x =>
{ {
if (CancellationToken.IsCancellationRequested) return; if (CancellationToken.IsCancellationRequested) return;
@@ -88,7 +94,7 @@ public partial class Tile : Node2D
float neighborHeight = GetValueAtMapPosition(neighborCoords); float neighborHeight = GetValueAtMapPosition(neighborCoords);
if (IsSeaAtMapPosition(neighborCoords, waterThreshold)) if (IsSeaAtMapPosition(neighborCoords))
{ {
bordersSea = true; bordersSea = true;
} }
@@ -109,14 +115,14 @@ public partial class Tile : Node2D
// Grass (default) // Grass (default)
image.SetPixel(x, y, Color.FromString("#bed58a", Colors.Purple)); image.SetPixel(x, y, Color.FromString("#bed58a", Colors.Purple));
if (IsSeaAtMapPosition(pixelOffset, waterThreshold)) if (IsSeaAtMapPosition(pixelOffset))
{ {
// Water // Water
image.SetPixel(x, y, Color.FromString("#4380b0", Colors.Purple)); image.SetPixel(x, y, Color.FromString("#4380b0", Colors.Purple));
continue; continue;
} }
if (moistureValue < desertMoistureThreshold && heatValue > desertTemperatureThreshold) if (isDesertAtMapPosition(pixelOffset))
{ {
// Desert // Desert
image.SetPixel(x, y, Color.FromString("#edc9af", Colors.Purple)); image.SetPixel(x, y, Color.FromString("#edc9af", Colors.Purple));
@@ -129,7 +135,7 @@ public partial class Tile : Node2D
continue; continue;
} }
if (heightValue > snowHeightThreshold) if (heightValue > snowHeightThreshold && !isDesertAtMapPosition(pixelOffset))
{ {
// Snow // Snow
image.SetPixel(x, y, Color.FromString("#f4f4f4", Colors.Purple)); image.SetPixel(x, y, Color.FromString("#f4f4f4", Colors.Purple));
@@ -165,7 +171,7 @@ public partial class Tile : Node2D
} }
} });
if (!CancellationToken.IsCancellationRequested) if (!CancellationToken.IsCancellationRequested)
{ {
@@ -186,13 +192,8 @@ public partial class Tile : Node2D
} }
// Terrain Query Methods // Terrain Query Methods
private float GetValueAtMapPosition(Vector2I position) private Vector2I ToHeightMapCoordinates(Vector2I position)
{ {
// Clamp position to map bounds
position.X = Mathf.Clamp(position.X, 0, _world.ChunksPerAxis.X * _world.TileSize - 1);
position.Y = Mathf.Clamp(position.Y, 0, _world.ChunksPerAxis.Y * _world.TileSize - 1);
Vector2I heightMapResolution = new Vector2I( Vector2I heightMapResolution = new Vector2I(
_world.HeightMapImage.GetWidth(), _world.HeightMapImage.GetWidth(),
_world.HeightMapImage.GetHeight() _world.HeightMapImage.GetHeight()
@@ -207,6 +208,19 @@ public partial class Tile : Node2D
(int)(mapDelta.Y * heightMapResolution.Y) (int)(mapDelta.Y * heightMapResolution.Y)
); );
// Clamp to valid range
heightMapCoords.X = Mathf.Clamp(heightMapCoords.X, 0, heightMapResolution.X - 1);
heightMapCoords.Y = Mathf.Clamp(heightMapCoords.Y, 0, heightMapResolution.Y - 1);
return heightMapCoords;
}
private float GetValueAtMapPosition(Vector2I position)
{
Vector2I heightMapCoords = ToHeightMapCoordinates(position);
Color hmColor = _world.HeightMapImage.GetPixel(heightMapCoords.X, heightMapCoords.Y); Color hmColor = _world.HeightMapImage.GetPixel(heightMapCoords.X, heightMapCoords.Y);
return hmColor.R; return hmColor.R;
@@ -218,10 +232,17 @@ public partial class Tile : Node2D
return heightValue <= _world.WaterThreshold; return heightValue <= _world.WaterThreshold;
} }
private bool IsSeaAtMapPosition(Vector2I position, float waterThreshold) private bool isDesertAtMapPosition(Vector2I position)
{
float heatValue = GetHeatAtMapPosition(position);
float moistureValue = GetMoistureAtMapPosition(position);
return moistureValue < _world.DesertMoistureThreshold && heatValue > _world.DesertTemperatureThreshold;
}
private bool IsSeaAtMapPosition(Vector2I position)
{ {
float heightValue = GetValueAtMapPosition(position); float heightValue = GetValueAtMapPosition(position);
return heightValue <= waterThreshold; return heightValue <= _world.WaterThreshold;
} }
private FastNoiseLite _heatNoise = null; private FastNoiseLite _heatNoise = null;
@@ -233,21 +254,21 @@ public partial class Tile : Node2D
_heatNoise = new FastNoiseLite(); _heatNoise = new FastNoiseLite();
_heatNoise.SetNoiseType(FastNoiseLite.NoiseTypeEnum.Perlin); _heatNoise.SetNoiseType(FastNoiseLite.NoiseTypeEnum.Perlin);
_heatNoise.SetFrequency(1/_world.TemperatureNoiseFrequency); _heatNoise.SetFrequency(1/_world.TemperatureNoiseFrequency);
_heatNoise.SetFractalType(FastNoiseLite.FractalTypeEnum.Fbm);
_heatNoise.SetFractalOctaves(12);
_heatNoise.SetSeed(1738); _heatNoise.SetSeed(1738);
} }
float heat = _heatNoise.GetNoise2D(position.X, position.Y); Vector2I heightMapCoords = ToHeightMapCoordinates(position);
heat *= 0.2f;
float heat = _heatNoise.GetNoise2D(heightMapCoords.X, heightMapCoords.Y);
heat *= _world.TemperatureNoiseAmplitude;
// Fall off towards the poles. Use sine function for a globe effect. // Fall off towards the poles. Use sine function for a globe effect.
float mapResolutionY = _world.ChunksPerAxis.Y * _world.TileSize; float mapResolutionY = _world.HeightMapImage.GetHeight();
float latitudeFactor = Mathf.Sin(Mathf.Pi * position.Y / mapResolutionY); float latitudeFactor = Mathf.Sin(Mathf.Pi * heightMapCoords.Y / mapResolutionY);
heat = latitudeFactor + heat; heat = latitudeFactor + heat;
heat = Mathf.Clamp(
heat,
0.0f,
1.0f
);
return heat; return heat;
} }
@@ -261,21 +282,20 @@ public partial class Tile : Node2D
_moistureNoise = new FastNoiseLite(); _moistureNoise = new FastNoiseLite();
_moistureNoise.SetNoiseType(FastNoiseLite.NoiseTypeEnum.Perlin); _moistureNoise.SetNoiseType(FastNoiseLite.NoiseTypeEnum.Perlin);
_moistureNoise.SetFrequency(1/_world.MoistureNoiseFrequency); _moistureNoise.SetFrequency(1/_world.MoistureNoiseFrequency);
_moistureNoise.SetFractalType(FastNoiseLite.FractalTypeEnum.Fbm);
_moistureNoise.SetFractalOctaves(12);
_moistureNoise.SetSeed(1337); _moistureNoise.SetSeed(1337);
} }
float moisture = _moistureNoise.GetNoise2D(position.X, position.Y); Vector2I heightMapCoords = ToHeightMapCoordinates(position);
moisture *= 0.2f;
float moisture = _moistureNoise.GetNoise2D(heightMapCoords.X, heightMapCoords.Y);
moisture *= _world.MoistureNoiseAmplitude;
// Fall off towards the poles. Use sine function for a globe effect. // Fall off towards the poles. Use sine function for a globe effect.
float mapResolutionY = _world.ChunksPerAxis.Y * _world.TileSize; float mapResolutionY = _world.HeightMapImage.GetHeight();
float latitudeFactor = Mathf.Sin(Mathf.Pi * position.Y / mapResolutionY); float latitudeFactor = Mathf.Sin(Mathf.Pi * heightMapCoords.Y / mapResolutionY);
moisture = latitudeFactor + moisture; moisture = latitudeFactor + moisture;
moisture = Mathf.Clamp(
moisture,
0.0f,
1.0f
);
return moisture; return moisture;

View File

@@ -6,6 +6,7 @@ using System.Diagnostics;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Godot.Collections; using Godot.Collections;
using Environment = Godot.Environment;
public enum DebugType public enum DebugType
{ {
@@ -148,6 +149,30 @@ public partial class World : Node2D
} }
} }
private float _TemperatureNoiseAmplitude = 0.1f;
[Export]
public float TemperatureNoiseAmplitude
{
get => _TemperatureNoiseAmplitude;
set
{
_TemperatureNoiseAmplitude = value;
LoadTiles();
}
}
private float _MoistureNoiseAmplitude = 0.1f;
[Export]
public float MoistureNoiseAmplitude
{
get => _MoistureNoiseAmplitude;
set
{
_MoistureNoiseAmplitude = value;
LoadTiles();
}
}
private DebugType _debugType = DebugType.None; private DebugType _debugType = DebugType.None;
[Export] [Export]
public DebugType DebugType public DebugType DebugType
@@ -162,8 +187,10 @@ public partial class World : Node2D
ConcurrentDictionary<Vector2I, Tile> _tiles = new ConcurrentDictionary<Vector2I, Tile>(); ConcurrentDictionary<Vector2I, Tile> _tiles = new ConcurrentDictionary<Vector2I, Tile>();
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
private static readonly int MaxDegreeOfParallelism = Math.Max(1, (int)(System.Environment.ProcessorCount * 0.5));
public World() public World()
{ {
@@ -233,7 +260,7 @@ public partial class World : Node2D
{ {
if (cancellationToken.IsCancellationRequested) return; if (cancellationToken.IsCancellationRequested) return;
Parallel.For(0, chunksY, new ParallelOptions { CancellationToken = cancellationToken }, y => for (int y = 0; y < chunksY; y++)
{ {
if (cancellationToken.IsCancellationRequested) return; if (cancellationToken.IsCancellationRequested) return;
@@ -248,14 +275,14 @@ public partial class World : Node2D
{ {
CallDeferred("add_child", tile); CallDeferred("add_child", tile);
_tiles[chunkId] = tile; _tiles[chunkId] = tile;
GD.Print("Loaded tile at chunk " + chunkId); // GD.Print("Loaded tile at chunk " + chunkId);
}
} }
});
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
GD.Print("Tile generation cancelled"); // GD.Print("Tile generation cancelled");
} }
}, cancellationToken); }, cancellationToken);

View File

@@ -1,7 +1,7 @@
[gd_scene load_steps=4 format=3 uid="uid://c10nqwr7qp0ai"] [gd_scene load_steps=4 format=3 uid="uid://c10nqwr7qp0ai"]
[ext_resource type="Script" uid="uid://btqabtn0awg6k" path="res://Source/World.cs" id="1_0xm2m"] [ext_resource type="Script" path="res://Source/World.cs" id="1_0xm2m"]
[ext_resource type="Script" uid="uid://ceahximwi24jm" path="res://Source/CameraController.cs" id="3_camera"] [ext_resource type="Script" path="res://Source/CameraController.cs" id="3_camera"]
[ext_resource type="Texture2D" uid="uid://dteqog3a5k8qx" path="res://assets/World_Elevation_Map_8_bit_(World_Height_map)_(alterative_version).png" id="3_h2yge"] [ext_resource type="Texture2D" uid="uid://dteqog3a5k8qx" path="res://assets/World_Elevation_Map_8_bit_(World_Height_map)_(alterative_version).png" id="3_h2yge"]
[node name="Main" type="Node2D"] [node name="Main" type="Node2D"]
@@ -17,5 +17,7 @@ HeightMapTexture = ExtResource("3_h2yge")
RockThreshold = 0.04 RockThreshold = 0.04
SnowHeightThreshold = 0.32 SnowHeightThreshold = 0.32
WaterThreshold = 0.24 WaterThreshold = 0.24
DesertTemperatureThreshold = 0.82 DesertTemperatureThreshold = 0.84
DesertMoistureThreshold = 0.985 DesertMoistureThreshold = 0.96
TemperatureNoiseFrequency = 1500.0
MoistureNoiseFrequency = 1500.0

View File

@@ -12,7 +12,7 @@ config_version=5
config/name="Frontiers" config/name="Frontiers"
run/main_scene="uid://c10nqwr7qp0ai" run/main_scene="uid://c10nqwr7qp0ai"
config/features=PackedStringArray("4.4", "C#", "Forward Plus") config/features=PackedStringArray("4.3", "C#", "Forward Plus")
config/icon="res://icon.svg" config/icon="res://icon.svg"
[dotnet] [dotnet]