Add initial project files and implement CameraController and World classes
This commit is contained in:
4
.editorconfig
Normal file
4
.editorconfig
Normal file
@@ -0,0 +1,4 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Normalize EOL for all files that Git considers text files.
|
||||
* text=auto eol=lf
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
/android/
|
||||
13
.idea/.idea.Frontiers/.idea/.gitignore
generated
vendored
Normal file
13
.idea/.idea.Frontiers/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Rider ignored files
|
||||
/projectSettingsUpdater.xml
|
||||
/.idea.Frontiers.iml
|
||||
/modules.xml
|
||||
/contentModel.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
1
.idea/.idea.Frontiers/.idea/.name
generated
Normal file
1
.idea/.idea.Frontiers/.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
||||
Frontiers
|
||||
4
.idea/.idea.Frontiers/.idea/encodings.xml
generated
Normal file
4
.idea/.idea.Frontiers/.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||
</project>
|
||||
8
.idea/.idea.Frontiers/.idea/indexLayout.xml
generated
Normal file
8
.idea/.idea.Frontiers/.idea/indexLayout.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
||||
10
.idea/.idea.Frontiers/.idea/libraries/GdSdk_Master.xml
generated
Normal file
10
.idea/.idea.Frontiers/.idea/libraries/GdSdk_Master.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<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>
|
||||
6
.idea/.idea.Frontiers/.idea/vcs.xml
generated
Normal file
6
.idea/.idea.Frontiers/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
6
Frontiers.csproj
Normal file
6
Frontiers.csproj
Normal file
@@ -0,0 +1,6 @@
|
||||
<Project Sdk="Godot.NET.Sdk/4.4.1">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
2
Frontiers.csproj.DotSettings
Normal file
2
Frontiers.csproj.DotSettings
Normal file
@@ -0,0 +1,2 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=source/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
19
Frontiers.sln
Normal file
19
Frontiers.sln
Normal file
@@ -0,0 +1,19 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2012
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frontiers", "Frontiers.csproj", "{F33DFCC0-769F-49F6-9FEF-F64EF49FBEAD}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
ExportDebug|Any CPU = ExportDebug|Any CPU
|
||||
ExportRelease|Any CPU = ExportRelease|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{F33DFCC0-769F-49F6-9FEF-F64EF49FBEAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F33DFCC0-769F-49F6-9FEF-F64EF49FBEAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F33DFCC0-769F-49F6-9FEF-F64EF49FBEAD}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
|
||||
{F33DFCC0-769F-49F6-9FEF-F64EF49FBEAD}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
|
||||
{F33DFCC0-769F-49F6-9FEF-F64EF49FBEAD}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
|
||||
{F33DFCC0-769F-49F6-9FEF-F64EF49FBEAD}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
2
Frontiers.sln.DotSettings.user
Normal file
2
Frontiers.sln.DotSettings.user
Normal file
@@ -0,0 +1,2 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F6780d13016c376c4491c5618b257d84da7eacf747ed2719783e775546b79b_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
||||
117
Source/CameraController.cs
Normal file
117
Source/CameraController.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using Godot;
|
||||
|
||||
public partial class CameraController : Camera2D
|
||||
{
|
||||
[Export] public float ZoomSpeed = 1.1f;
|
||||
[Export] public float MinZoom = 0.01f;
|
||||
[Export] public float MaxZoom = 5.0f;
|
||||
[Export] public bool EnableRotation = false;
|
||||
[Export] public float RotationSpeed = 0.01f;
|
||||
[Export] public float PanSmoothing = 0.1f;
|
||||
|
||||
private bool _isDragging = false;
|
||||
private Vector2 _dragStartMousePos;
|
||||
private Vector2 _dragStartCameraPos;
|
||||
private bool _isRotating = false;
|
||||
private Vector2 _lastMousePosition;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
Enabled = true;
|
||||
}
|
||||
|
||||
public override void _UnhandledInput(InputEvent @event)
|
||||
{
|
||||
HandleZoom(@event);
|
||||
HandlePanning(@event);
|
||||
HandleRotation(@event);
|
||||
}
|
||||
|
||||
private void HandleZoom(InputEvent @event)
|
||||
{
|
||||
if (@event is InputEventMouseButton mouseButton && mouseButton.Pressed)
|
||||
{
|
||||
Vector2 mouseWorldPos = GetGlobalMousePosition();
|
||||
|
||||
if (mouseButton.ButtonIndex == MouseButton.WheelUp)
|
||||
{
|
||||
var newZoom = Zoom * ZoomSpeed;
|
||||
if (newZoom.X <= MaxZoom)
|
||||
{
|
||||
ZoomToPoint(newZoom, mouseWorldPos);
|
||||
}
|
||||
}
|
||||
else if (mouseButton.ButtonIndex == MouseButton.WheelDown)
|
||||
{
|
||||
var newZoom = Zoom / ZoomSpeed;
|
||||
if (newZoom.X >= MinZoom)
|
||||
{
|
||||
ZoomToPoint(newZoom, mouseWorldPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ZoomToPoint(Vector2 newZoom, Vector2 worldPoint)
|
||||
{
|
||||
Vector2 viewportCenter = GetViewportRect().Size / 2;
|
||||
Vector2 offsetFromCenter = (GetGlobalMousePosition() - GlobalPosition);
|
||||
|
||||
Zoom = newZoom;
|
||||
|
||||
Vector2 newOffsetFromCenter = offsetFromCenter / (newZoom.X / Zoom.X);
|
||||
GlobalPosition += offsetFromCenter - newOffsetFromCenter;
|
||||
}
|
||||
|
||||
private void HandlePanning(InputEvent @event)
|
||||
{
|
||||
if (@event is InputEventMouseButton mouseButton)
|
||||
{
|
||||
if (mouseButton.ButtonIndex == MouseButton.Left)
|
||||
{
|
||||
if (mouseButton.Pressed)
|
||||
{
|
||||
_isDragging = true;
|
||||
_dragStartMousePos = mouseButton.GlobalPosition;
|
||||
_dragStartCameraPos = GlobalPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
_isDragging = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (@event is InputEventMouseMotion mouseMotion && _isDragging)
|
||||
{
|
||||
Vector2 mouseDelta = _dragStartMousePos - mouseMotion.GlobalPosition;
|
||||
GlobalPosition = _dragStartCameraPos + mouseDelta / Zoom;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleRotation(InputEvent @event)
|
||||
{
|
||||
if (!EnableRotation) return;
|
||||
|
||||
if (@event is InputEventMouseButton mouseButton)
|
||||
{
|
||||
if (mouseButton.ButtonIndex == MouseButton.Right)
|
||||
{
|
||||
if (mouseButton.Pressed)
|
||||
{
|
||||
_isRotating = true;
|
||||
_lastMousePosition = mouseButton.GlobalPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
_isRotating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (@event is InputEventMouseMotion mouseMotion && _isRotating)
|
||||
{
|
||||
var delta = _lastMousePosition - mouseMotion.GlobalPosition;
|
||||
Rotation += delta.X * RotationSpeed;
|
||||
_lastMousePosition = mouseMotion.GlobalPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
Source/CameraController.cs.uid
Normal file
1
Source/CameraController.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ceahximwi24jm
|
||||
284
Source/Tile.cs
Normal file
284
Source/Tile.cs
Normal file
@@ -0,0 +1,284 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Godot;
|
||||
|
||||
public partial class Tile : Node2D
|
||||
{
|
||||
private Vector2I _size;
|
||||
private Vector2I _chunkId;
|
||||
private World _world;
|
||||
public CancellationToken CancellationToken { get; set; }
|
||||
|
||||
public Tile(Vector2I size, Vector2I chunkId, World world)
|
||||
{
|
||||
_size = size;
|
||||
_chunkId = chunkId;
|
||||
_world = world;
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
loadTile();
|
||||
}
|
||||
|
||||
void loadTile()
|
||||
{
|
||||
|
||||
Image image = Image.CreateEmpty(_size.X, _size.Y, false, Image.Format.Rgba8);
|
||||
|
||||
Vector2I heightMapResolution = new Vector2I(
|
||||
_world.HeightMapImage.GetWidth(),
|
||||
_world.HeightMapImage.GetHeight()
|
||||
);
|
||||
|
||||
Vector2I mapResolution = _world.ChunksPerAxis * _world.TileSize;
|
||||
Vector2I tileOffset = new Vector2I(
|
||||
_chunkId.X * _world.TileSize,
|
||||
_chunkId.Y * _world.TileSize
|
||||
);
|
||||
|
||||
// Store parameters locally to avoid changes during generation
|
||||
float rockThreshold = _world.RockThreshold;
|
||||
float snowHeightThreshold = _world.SnowHeightThreshold;
|
||||
float waterThreshold = _world.WaterThreshold;
|
||||
float desertMoistureThreshold = _world.DesertMoistureThreshold;
|
||||
float desertTemperatureThreshold = _world.DesertTemperatureThreshold;
|
||||
|
||||
DebugType debugType = _world.DebugType;
|
||||
|
||||
// Run in thread
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
for (int x = 0; x < _size.X; x++)
|
||||
{
|
||||
if (CancellationToken.IsCancellationRequested) return;
|
||||
|
||||
for (int y = 0; y < _size.Y; y++)
|
||||
{
|
||||
if (CancellationToken.IsCancellationRequested) return;
|
||||
|
||||
Vector2I pixelOffset = new Vector2I(
|
||||
x + tileOffset.X,
|
||||
y + tileOffset.Y
|
||||
);
|
||||
|
||||
float heightValue = GetValueAtMapPosition(pixelOffset);
|
||||
float heatValue = GetHeatAtMapPosition(pixelOffset);
|
||||
float moistureValue = GetMoistureAtMapPosition(pixelOffset);
|
||||
|
||||
// Get the max difference between the coord and its neighbors
|
||||
float maxDiff = 0.0f;
|
||||
float centerHeight = heightValue;
|
||||
bool bordersSea = false;
|
||||
for (int offsetX = -1; offsetX <= 1; offsetX++)
|
||||
{
|
||||
for (int offsetY = -1; offsetY <= 1; offsetY++)
|
||||
{
|
||||
if (offsetX == 0 && offsetY == 0)
|
||||
continue;
|
||||
|
||||
Vector2I neighborCoords = new Vector2I(
|
||||
pixelOffset.X + offsetX,
|
||||
pixelOffset.Y + offsetY
|
||||
);
|
||||
|
||||
float neighborHeight = GetValueAtMapPosition(neighborCoords);
|
||||
|
||||
if (IsSeaAtMapPosition(neighborCoords, waterThreshold))
|
||||
{
|
||||
bordersSea = true;
|
||||
}
|
||||
|
||||
float diff = Mathf.Abs(centerHeight - neighborHeight);
|
||||
if (diff > maxDiff)
|
||||
{
|
||||
maxDiff = diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (debugType == DebugType.None)
|
||||
{
|
||||
|
||||
// Grass (default)
|
||||
image.SetPixel(x, y, Color.FromString("#bed58a", Colors.Purple));
|
||||
|
||||
if (IsSeaAtMapPosition(pixelOffset, waterThreshold))
|
||||
{
|
||||
// Water
|
||||
image.SetPixel(x, y, Color.FromString("#4380b0", Colors.Purple));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (moistureValue < desertMoistureThreshold && heatValue > desertTemperatureThreshold)
|
||||
{
|
||||
// Desert
|
||||
image.SetPixel(x, y, Color.FromString("#edc9af", Colors.Purple));
|
||||
}
|
||||
|
||||
if (bordersSea)
|
||||
{
|
||||
// Beach
|
||||
image.SetPixel(x, y, Color.FromString("#808080", Colors.Purple));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (heightValue > snowHeightThreshold)
|
||||
{
|
||||
// Snow
|
||||
image.SetPixel(x, y, Color.FromString("#f4f4f4", Colors.Purple));
|
||||
}
|
||||
|
||||
// We want steep areas to be grey for rock.
|
||||
if (maxDiff > rockThreshold)
|
||||
{
|
||||
// Steep area
|
||||
image.SetPixel(x, y, Color.FromString("#e4d3a6", Colors.Purple));
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
else if (debugType == DebugType.Height)
|
||||
{
|
||||
float grayValue = heightValue;
|
||||
image.SetPixel(x, y, new Color(grayValue, grayValue, grayValue));
|
||||
}
|
||||
else if (debugType == DebugType.Slope)
|
||||
{
|
||||
float grayValue = maxDiff * 5.0f;
|
||||
image.SetPixel(x, y, new Color(grayValue, grayValue, grayValue));
|
||||
} else if (debugType == DebugType.Temperature)
|
||||
{
|
||||
image.SetPixel(x, y, new Color(heatValue, heatValue, heatValue));
|
||||
} else if (debugType == DebugType.Moisture)
|
||||
{
|
||||
image.SetPixel(x, y, new Color(moistureValue, moistureValue, moistureValue));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// Create a texture from the image and assign it to a Sprite2D
|
||||
Texture2D texture = ImageTexture.CreateFromImage(image);
|
||||
Sprite2D sprite = new Sprite2D();
|
||||
sprite.Texture = texture;
|
||||
sprite.TextureFilter = TextureFilterEnum.Nearest;
|
||||
CallDeferred("add_child", sprite);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Generation was cancelled, do nothing
|
||||
}
|
||||
}, CancellationToken);
|
||||
|
||||
}
|
||||
|
||||
// Terrain Query Methods
|
||||
private float GetValueAtMapPosition(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(
|
||||
_world.HeightMapImage.GetWidth(),
|
||||
_world.HeightMapImage.GetHeight()
|
||||
);
|
||||
|
||||
Vector2I mapResolution = _world.ChunksPerAxis * _world.TileSize;
|
||||
|
||||
Vector2 mapDelta = (Vector2) position / (Vector2) mapResolution;
|
||||
|
||||
Vector2I heightMapCoords = new Vector2I(
|
||||
(int)(mapDelta.X * heightMapResolution.X),
|
||||
(int)(mapDelta.Y * heightMapResolution.Y)
|
||||
);
|
||||
|
||||
Color hmColor = _world.HeightMapImage.GetPixel(heightMapCoords.X, heightMapCoords.Y);
|
||||
|
||||
return hmColor.R;
|
||||
}
|
||||
|
||||
private bool isSeaAtMapPosition(Vector2I position)
|
||||
{
|
||||
float heightValue = GetValueAtMapPosition(position);
|
||||
return heightValue <= _world.WaterThreshold;
|
||||
}
|
||||
|
||||
private bool IsSeaAtMapPosition(Vector2I position, float waterThreshold)
|
||||
{
|
||||
float heightValue = GetValueAtMapPosition(position);
|
||||
return heightValue <= waterThreshold;
|
||||
}
|
||||
|
||||
private FastNoiseLite _heatNoise = null;
|
||||
private float GetHeatAtMapPosition(Vector2I position)
|
||||
{
|
||||
|
||||
if (_heatNoise == null)
|
||||
{
|
||||
_heatNoise = new FastNoiseLite();
|
||||
_heatNoise.SetNoiseType(FastNoiseLite.NoiseTypeEnum.Perlin);
|
||||
_heatNoise.SetFrequency(1/_world.TemperatureNoiseFrequency);
|
||||
_heatNoise.SetSeed(1738);
|
||||
}
|
||||
|
||||
float heat = _heatNoise.GetNoise2D(position.X, position.Y);
|
||||
heat *= 0.2f;
|
||||
|
||||
// Fall off towards the poles. Use sine function for a globe effect.
|
||||
float mapResolutionY = _world.ChunksPerAxis.Y * _world.TileSize;
|
||||
float latitudeFactor = Mathf.Sin(Mathf.Pi * position.Y / mapResolutionY);
|
||||
heat = latitudeFactor + heat;
|
||||
heat = Mathf.Clamp(
|
||||
heat,
|
||||
0.0f,
|
||||
1.0f
|
||||
);
|
||||
|
||||
return heat;
|
||||
}
|
||||
|
||||
private FastNoiseLite _moistureNoise = null;
|
||||
private float GetMoistureAtMapPosition(Vector2I position)
|
||||
{
|
||||
|
||||
if (_moistureNoise == null)
|
||||
{
|
||||
_moistureNoise = new FastNoiseLite();
|
||||
_moistureNoise.SetNoiseType(FastNoiseLite.NoiseTypeEnum.Perlin);
|
||||
_moistureNoise.SetFrequency(1/_world.MoistureNoiseFrequency);
|
||||
_moistureNoise.SetSeed(1337);
|
||||
}
|
||||
|
||||
float moisture = _moistureNoise.GetNoise2D(position.X, position.Y);
|
||||
moisture *= 0.2f;
|
||||
|
||||
// Fall off towards the poles. Use sine function for a globe effect.
|
||||
float mapResolutionY = _world.ChunksPerAxis.Y * _world.TileSize;
|
||||
float latitudeFactor = Mathf.Sin(Mathf.Pi * position.Y / mapResolutionY);
|
||||
moisture = latitudeFactor + moisture;
|
||||
moisture = Mathf.Clamp(
|
||||
moisture,
|
||||
0.0f,
|
||||
1.0f
|
||||
);
|
||||
|
||||
|
||||
return moisture;
|
||||
}
|
||||
|
||||
}
|
||||
1
Source/Tile.cs.uid
Normal file
1
Source/Tile.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://d1af74387s3fk
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
Source/World.cs.uid
Normal file
1
Source/World.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://btqabtn0awg6k
|
||||
BIN
assets/World_Elevation_Map_8_bit_(World_Height_map)_(alterative_version).png
LFS
Normal file
BIN
assets/World_Elevation_Map_8_bit_(World_Height_map)_(alterative_version).png
LFS
Normal file
Binary file not shown.
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dteqog3a5k8qx"
|
||||
path="res://.godot/imported/World_Elevation_Map_8_bit_(World_Height_map)_(alterative_version).png-f039a3c89cf0c7ad5d067a47b56cbf8e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/World_Elevation_Map_8_bit_(World_Height_map)_(alterative_version).png"
|
||||
dest_files=["res://.godot/imported/World_Elevation_Map_8_bit_(World_Height_map)_(alterative_version).png-f039a3c89cf0c7ad5d067a47b56cbf8e.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
assets/World_elevation_map.png
LFS
Normal file
BIN
assets/World_elevation_map.png
LFS
Normal file
Binary file not shown.
34
assets/World_elevation_map.png.import
Normal file
34
assets/World_elevation_map.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cthrxmyms6rxj"
|
||||
path="res://.godot/imported/World_elevation_map.png-ffb15608784624fa8e00c287e76ba4a3.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/World_elevation_map.png"
|
||||
dest_files=["res://.godot/imported/World_elevation_map.png-ffb15608784624fa8e00c287e76ba4a3.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
assets/gebco_08_rev_elev_21600x10800.png
LFS
Normal file
BIN
assets/gebco_08_rev_elev_21600x10800.png
LFS
Normal file
Binary file not shown.
34
assets/gebco_08_rev_elev_21600x10800.png.import
Normal file
34
assets/gebco_08_rev_elev_21600x10800.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cfq68ehcuvoya"
|
||||
path="res://.godot/imported/gebco_08_rev_elev_21600x10800.png-f55daf6de01cca738c16dcab8f2b72f3.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/gebco_08_rev_elev_21600x10800.png"
|
||||
dest_files=["res://.godot/imported/gebco_08_rev_elev_21600x10800.png-f55daf6de01cca738c16dcab8f2b72f3.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
1
icon.svg
Normal file
1
icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>
|
||||
|
After Width: | Height: | Size: 994 B |
37
icon.svg.import
Normal file
37
icon.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://coj5oieh63qxm"
|
||||
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
21
main.tscn
Normal file
21
main.tscn
Normal file
@@ -0,0 +1,21 @@
|
||||
[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" uid="uid://ceahximwi24jm" 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"]
|
||||
|
||||
[node name="Main" type="Node2D"]
|
||||
|
||||
[node name="CameraController" type="Camera2D" parent="."]
|
||||
script = ExtResource("3_camera")
|
||||
|
||||
[node name="World" type="Node2D" parent="."]
|
||||
script = ExtResource("1_0xm2m")
|
||||
ChunkXCount = 16
|
||||
TileSize = 128
|
||||
HeightMapTexture = ExtResource("3_h2yge")
|
||||
RockThreshold = 0.04
|
||||
SnowHeightThreshold = 0.32
|
||||
WaterThreshold = 0.24
|
||||
DesertTemperatureThreshold = 0.82
|
||||
DesertMoistureThreshold = 0.985
|
||||
20
project.godot
Normal file
20
project.godot
Normal file
@@ -0,0 +1,20 @@
|
||||
; Engine configuration file.
|
||||
; It's best edited using the editor UI and not directly,
|
||||
; since the parameters that go here are not all obvious.
|
||||
;
|
||||
; Format:
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=5
|
||||
|
||||
[application]
|
||||
|
||||
config/name="Frontiers"
|
||||
run/main_scene="uid://c10nqwr7qp0ai"
|
||||
config/features=PackedStringArray("4.4", "C#", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[dotnet]
|
||||
|
||||
project/assembly_name="Frontiers"
|
||||
Reference in New Issue
Block a user