Compare commits

...

3 Commits

Author SHA1 Message Date
Martin Felis 4c0a2e7714 Also populate chunks with entities when chunks are being generated. 2023-11-05 21:46:28 +01:00
Martin Felis 928ddd3937 Some improvements on tile instancing. 2023-11-04 19:15:26 +01:00
Martin Felis 0663263bb7 Further cleanup. 2023-11-01 17:59:43 +01:00
9 changed files with 332 additions and 217 deletions

View File

@ -6,31 +6,33 @@ using AxialCoordDirectionPair = System.Tuple<Godot.Vector2, Godot.Vector3>;
public class HexGrid : Resource
{
private Vector2 _baseHexSize = new(1, Mathf.Sqrt(3) / 2);
private Vector2 _hexSize = new(1, Mathf.Sqrt(3) / 2);
private readonly Vector2 _baseHexSize = new(1, Mathf.Sqrt(3) / 2);
private Rect2 _boundsAxialCoords;
private Vector2 _hexScale = new(1, 1);
private Vector2 _hexSize = new(1, Mathf.Sqrt(3) / 2);
private Transform2D _hexTransform;
private Transform2D _hexTransformInv;
private HexCell _minCoords = new();
private HexCell _maxCoords = new();
private Rect2 _boundsAxialCoords;
private HexCell _minCoords = new();
public Dictionary<(Vector2, Vector3), float> Barriers = new();
public int FindPathCheckedCellCount;
public Dictionary<Vector2, float> Obstacles = new();
public Dictionary<(Vector2, Vector3), float> Barriers = new();
public float PathCostDefault = 1;
public int FindPathCheckedCellCount;
public Vector2 HexSize
public HexGrid()
{
get { return _hexSize; }
HexScale = new Vector2(1, 1);
}
public Vector2 HexSize => _hexSize;
public Vector2 HexScale
{
get { return _hexScale; }
get => _hexScale;
set
{
_hexScale = value;
@ -45,11 +47,6 @@ public class HexGrid : Resource
}
}
public HexGrid()
{
HexScale = new Vector2(1, 1);
}
public Vector2 GetHexCenter(HexCell cell)
{
return _hexTransform * cell.AxialCoords;
@ -57,21 +54,29 @@ public class HexGrid : Resource
public Vector2 GetHexCenterFromOffset(Vector2 offsetCoord)
{
HexCell cell = new HexCell();
var cell = new HexCell();
cell.OffsetCoords = offsetCoord;
return GetHexCenter(cell);
}
public Vector3 GetHexCenterVec3FromOffset(Vector2 offsetCoord)
{
var cell = new HexCell();
cell.OffsetCoords = offsetCoord;
var hexCenter = GetHexCenter(cell);
return new Vector3(hexCenter.x, 0, hexCenter.y);
}
public HexCell GetHexAtOffset(Vector2 offsetCoord)
{
HexCell cell = new HexCell();
var cell = new HexCell();
cell.OffsetCoords = offsetCoord;
return cell;
}
public HexCell GetHexAt(Vector2 planeCoord)
{
HexCell result = new HexCell(_hexTransformInv * planeCoord);
var result = new HexCell(_hexTransformInv * planeCoord);
return result;
}
@ -85,12 +90,12 @@ public class HexGrid : Resource
_minCoords = minCell;
_maxCoords = maxCell;
_boundsAxialCoords = new Rect2(_minCoords.AxialCoords,
(_maxCoords.AxialCoords - _minCoords.AxialCoords) + Vector2.One);
_maxCoords.AxialCoords - _minCoords.AxialCoords + Vector2.One);
}
public void SetBounds(HexCell center, int size)
{
Vector2 centerOffset = center.OffsetCoords;
var centerOffset = center.OffsetCoords;
SetBounds(GetHexAtOffset(centerOffset - Vector2.One * size / 2),
GetHexAtOffset(centerOffset + Vector2.One * size / 2));
}
@ -123,10 +128,7 @@ public class HexGrid : Resource
public void RemoveBarrier(HexCell cell, Vector3 directionCube)
{
if (Barriers.ContainsKey((cell.AxialCoords, directionCube)))
{
Barriers.Remove((cell.AxialCoords, directionCube));
}
if (Barriers.ContainsKey((cell.AxialCoords, directionCube))) Barriers.Remove((cell.AxialCoords, directionCube));
}
public float GetHexCost(HexCell cell)
@ -136,10 +138,7 @@ public class HexGrid : Resource
public float GetHexCost(Vector2 axialCoords)
{
if (!_boundsAxialCoords.HasPoint(axialCoords))
{
return 0;
}
if (!_boundsAxialCoords.HasPoint(axialCoords)) return 0;
float value;
return Obstacles.TryGetValue(axialCoords, out value) ? value : PathCostDefault;
@ -147,29 +146,20 @@ public class HexGrid : Resource
public float GetMoveCost(Vector2 axialCoords, Vector3 directionCube)
{
HexCell startCell = new HexCell(axialCoords);
HexCell targetCell = new HexCell(startCell.CubeCoords + directionCube);
var startCell = new HexCell(axialCoords);
var targetCell = new HexCell(startCell.CubeCoords + directionCube);
float cost = GetHexCost(axialCoords);
if (cost == 0)
{
return 0;
}
var cost = GetHexCost(axialCoords);
if (cost == 0) return 0;
cost = GetHexCost(targetCell.AxialCoords);
if (cost == 0)
{
return 0;
}
if (cost == 0) return 0;
float barrierCost;
if (Barriers.ContainsKey((axialCoords, directionCube)))
{
barrierCost = Barriers[(axialCoords, directionCube)];
if (barrierCost == 0)
{
return 0;
}
if (barrierCost == 0) return 0;
cost += barrierCost;
}
@ -177,10 +167,7 @@ public class HexGrid : Resource
if (Barriers.ContainsKey((targetCell.AxialCoords, -directionCube)))
{
barrierCost = Barriers[(targetCell.AxialCoords, -directionCube)];
if (barrierCost == 0)
{
return 0;
}
if (barrierCost == 0) return 0;
cost += barrierCost;
}
@ -193,16 +180,14 @@ public class HexGrid : Resource
{
if (GetHexCost(toCell) == 0)
{
HexCell[] line = fromCell.LineTo(toCell);
var line = fromCell.LineTo(toCell);
foreach (int i in Enumerable.Range(1, line.Length))
{
foreach (var i in Enumerable.Range(1, line.Length))
if (GetHexCost(line[i]) == 0)
{
toCell = line[i - 1];
break;
}
}
}
return toCell;
@ -210,13 +195,13 @@ public class HexGrid : Resource
public List<HexCell> FindPath(HexCell startHex, HexCell goalHex)
{
Vector2 goalAxialCoords = goalHex.AxialCoords;
var goalAxialCoords = goalHex.AxialCoords;
SimplePriorityQueue<Vector2, float> frontier = new SimplePriorityQueue<Vector2, float>();
var frontier = new SimplePriorityQueue<Vector2, float>();
frontier.Enqueue(startHex.AxialCoords, 0);
Dictionary<Vector2, Vector2> cameFrom =
var cameFrom =
new Dictionary<Vector2, Vector2>();
Dictionary<Vector2, float> costSoFar =
var costSoFar =
new Dictionary<Vector2, float>();
cameFrom.Add(startHex.AxialCoords, startHex.AxialCoords);
@ -227,20 +212,17 @@ public class HexGrid : Resource
while (frontier.Any())
{
FindPathCheckedCellCount++;
HexCell currentHex = new HexCell(frontier.Dequeue());
Vector2 currentAxial = currentHex.AxialCoords;
var currentHex = new HexCell(frontier.Dequeue());
var currentAxial = currentHex.AxialCoords;
if (currentHex == goalHex)
if (currentHex == goalHex) break;
foreach (var nextHex in currentHex.GetAllAdjacent())
{
break;
}
var nextAxial = nextHex.AxialCoords;
var nextCost = GetMoveCost(currentAxial, new HexCell(nextAxial - currentHex.AxialCoords).CubeCoords);
foreach (HexCell nextHex in currentHex.GetAllAdjacent())
{
Vector2 nextAxial = nextHex.AxialCoords;
float nextCost = GetMoveCost(currentAxial, new HexCell(nextAxial - currentHex.AxialCoords).CubeCoords);
if ((nextHex == goalHex) && (GetHexCost(nextAxial) == 0))
if (nextHex == goalHex && GetHexCost(nextAxial) == 0)
{
// Goal ist an obstacle
cameFrom[nextHex.AxialCoords] = currentHex.AxialCoords;
@ -248,16 +230,13 @@ public class HexGrid : Resource
break;
}
if (nextCost == 0)
{
continue;
}
if (nextCost == 0) continue;
nextCost += costSoFar[currentHex.AxialCoords];
if (!costSoFar.ContainsKey(nextHex.AxialCoords) || nextCost < costSoFar[nextHex.AxialCoords])
{
costSoFar[nextHex.AxialCoords] = nextCost;
float priority = nextCost + nextHex.DistanceTo(goalHex);
var priority = nextCost + nextHex.DistanceTo(goalHex);
frontier.Enqueue(nextHex.AxialCoords, priority);
cameFrom[nextHex.AxialCoords] = currentHex.AxialCoords;
@ -267,19 +246,16 @@ public class HexGrid : Resource
// GD.Print("Checked Cell Count: " + FindPathCheckedCellCount);
List<HexCell> result = new List<HexCell>();
var result = new List<HexCell>();
if (!cameFrom.ContainsKey(goalHex.AxialCoords))
{
GD.Print("Failed to find path from " + startHex + " to " + goalHex);
return result;
}
if (GetHexCost(goalAxialCoords) != 0)
{
result.Add(goalHex);
}
if (GetHexCost(goalAxialCoords) != 0) result.Add(goalHex);
HexCell pathHex = goalHex;
var pathHex = goalHex;
while (pathHex != startHex)
{
pathHex = new HexCell(cameFrom[pathHex.AxialCoords]);
@ -292,26 +268,26 @@ public class HexGrid : Resource
public List<HexCell> GetCellsForLine(Vector2 fromPlane, Vector2 toPlane)
{
List<HexCell> result = new List<HexCell>();
var result = new List<HexCell>();
float distance = (toPlane - fromPlane).Length();
Vector2 direction = (toPlane - fromPlane) / distance;
var distance = (toPlane - fromPlane).Length();
var direction = (toPlane - fromPlane) / distance;
Vector2 currentPointPlane = fromPlane;
HexCell currentCell = GetHexAt(currentPointPlane);
var currentPointPlane = fromPlane;
var currentCell = GetHexAt(currentPointPlane);
float currentDistance = 0;
do
{
result.Add(currentCell);
GetHexCenter(currentCell);
Vector2 currentPointLocal = currentPointPlane - GetHexCenter(currentCell);
var currentPointLocal = currentPointPlane - GetHexCenter(currentCell);
int neighbourIndex;
float boundaryPlaneDistance;
currentCell.QueryClosestCellBoundary(currentPointLocal, direction, out neighbourIndex,
out boundaryPlaneDistance);
currentCell = currentCell.GetAdjacent(HexCell.NeighborDirections[neighbourIndex]);
currentDistance += boundaryPlaneDistance * 1.001f;
currentPointPlane = fromPlane + direction * boundaryPlaneDistance;

View File

@ -2,7 +2,7 @@
name="PirateGame3D"
platform="Android"
runnable=true
runnable=false
custom_features=""
export_filter="all_resources"
include_filter=""
@ -15,7 +15,7 @@ script_encryption_key=""
custom_template/debug=""
custom_template/release=""
custom_build/use_custom_build=true
custom_build/use_custom_build=false
custom_build/export_format=0
custom_build/min_sdk=""
custom_build/target_sdk=""

View File

@ -132,5 +132,7 @@ common/enable_pause_aware_picking=true
[rendering]
quality/shadows/filter_mode.mobile=1
quality/directional_shadow/size.mobile=512
quality/shadow_atlas/size.mobile=1024
quality/subsurface_scattering/quality=0
environment/default_environment="res://default_env.tres"

View File

@ -352,8 +352,8 @@ public class Game : Spatial
_tileMaterial.SetShaderParam("MapAlbedoTexture", newWorldTexture);
_tileMaterial.SetShaderParam("TextureSize", (int)newWorldTexture.GetSize().x);
_tileMaterial.SetShaderParam("CoordinateOffsetU", (int)_tileInstanceManager.WorldTextureCoordinateOffset.x);
_tileMaterial.SetShaderParam("CoordinateOffsetV", (int)_tileInstanceManager.WorldTextureCoordinateOffset.y);
_tileMaterial.SetShaderParam("CoordinateOffsetU", (int)_world.WorldTextureCoordinateOffset.x);
_tileMaterial.SetShaderParam("CoordinateOffsetV", (int)_world.WorldTextureCoordinateOffset.y);
}
public void OnGoldCountChanged(int goldCount)

File diff suppressed because one or more lines are too long

View File

@ -23,6 +23,7 @@ script = ExtResource( 1 )
[node name="Mesh" type="MeshInstance" parent="." groups=["GameGeometry"]]
transform = Transform( -4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, 0, -2.5, 0 )
visible = false
mesh = ExtResource( 3 )
material/0 = ExtResource( 2 )

View File

@ -1,11 +1,20 @@
using System.Diagnostics;
using System.Linq;
using Godot;
using Godot.Collections;
using Vector2 = Godot.Vector2;
using Vector3 = Godot.Vector3;
public class TileInstanceManager : Spatial
{
private readonly Array<SceneTileChunk> _sceneTileChunks = new();
private MultiMeshInstance _tileMultiMeshInstance;
private ImageTexture _viewTileTypeTexture;
private World _world;
// other members
[Export] public bool ShowHexTiles;
[Export] public Vector2 ViewCenterPlaneCoord;
// ui elements
// scene nodes
@ -14,72 +23,6 @@ public class TileInstanceManager : Spatial
// exports
[Export] public NodePath World;
[Export] public Vector2 ViewCenterPlaneCoord;
[Export] public bool ShowHexTiles = false;
// signals
[Signal]
delegate void TileClicked(HexTile3D tile3d);
[Signal]
delegate void TileHovered(HexTile3D tile3d);
// other members
public Vector2 WorldTextureCoordinateOffset = Vector2.Zero;
private World _world;
private ImageTexture _viewTileTypeTexture;
private Image _viewTileTypeImage = new();
private class SceneTileChunk : Spatial
{
private Vector2 _chunkIndex = Vector2.Inf;
public Vector2 ChunkIndex
{
get
{
return _chunkIndex;
}
set
{
Transform chunkTransform = Transform.Identity;
Vector2 chunkOriginPlaneCoord = HexGrid.GetHexCenterFromOffset(value * global::World.ChunkSize);
chunkTransform.origin = new Vector3(chunkOriginPlaneCoord.x, 0, chunkOriginPlaneCoord.y);
Transform = chunkTransform;
_chunkIndex = value;
}
}
public Array<HexTile3D> TileNodes = new();
private PackedScene _hexTile3DScene = GD.Load<PackedScene>("res://scenes/HexTile3D.tscn");
private HexGrid HexGrid = new();
public SceneTileChunk(Vector2 chunkIndex)
{
int chunkSize = global::World.ChunkSize;
foreach (int i in Enumerable.Range(0, chunkSize))
{
foreach (int j in Enumerable.Range(0, chunkSize))
{
HexTile3D tile3D = (HexTile3D)_hexTile3DScene.Instance();
Transform tileTransform = Transform.Identity;
Vector2 centerPlaneCoord = HexGrid.GetHexCenterFromOffset(new Vector2(i, j));
tileTransform.origin = new Vector3(centerPlaneCoord.x, 0, centerPlaneCoord.y);
tile3D.Transform = tileTransform;
TileNodes.Add(tile3D);
AddChild(tile3D);
}
}
ChunkIndex = chunkIndex;
}
}
private Array<SceneTileChunk> _sceneTileChunks = new Array<SceneTileChunk>();
// Called when the node enters the scene tree for the first time.
public override void _Ready()
@ -87,6 +30,9 @@ public class TileInstanceManager : Spatial
_world = GetNode<World>(World);
_world.Connect("OnTilesChanged", this, nameof(HandleWorldTileChange));
_tileMultiMeshInstance = (MultiMeshInstance)FindNode("TileMultiMeshInstance");
Debug.Assert(_tileMultiMeshInstance != null);
}
@ -94,52 +40,43 @@ public class TileInstanceManager : Spatial
{
}
SceneTileChunk CreateSceneTileChunk(Vector2 chunkIndex)
private SceneTileChunk CreateSceneTileChunk(Vector2 chunkIndex)
{
SceneTileChunk sceneTileChunk = new SceneTileChunk(chunkIndex);
var sceneTileChunk = new SceneTileChunk(chunkIndex, _tileMultiMeshInstance);
foreach (HexTile3D hexTile3D in sceneTileChunk.TileNodes)
foreach (var hexTile3D in sceneTileChunk.TileNodes)
{
hexTile3D.Connect("TileClicked", this, nameof(OnTileClicked));
hexTile3D.Connect("TileHovered", this, nameof(OnTileHovered));
}
return sceneTileChunk;
}
SceneTileChunk FindSceneTileChunkAtIndex(Vector2 chunkIndex)
private SceneTileChunk FindSceneTileChunkAtIndex(Vector2 chunkIndex)
{
foreach (Spatial child in GetChildren())
{
SceneTileChunk sceneTileChunk = child as SceneTileChunk;
if (sceneTileChunk == null)
{
continue;
}
var sceneTileChunk = child as SceneTileChunk;
if (sceneTileChunk == null) continue;
if (sceneTileChunk.ChunkIndex == chunkIndex)
{
return sceneTileChunk;
}
if (sceneTileChunk.ChunkIndex == chunkIndex) return sceneTileChunk;
}
return null;
}
private void HandleWorldTileChange(Array<Vector2> removedChunkIndices, Array<Vector2> addedChunkIndices)
{
Array<SceneTileChunk> removedChunks = new();
foreach (Vector2 chunkIndex in removedChunkIndices)
foreach (var chunkIndex in removedChunkIndices)
{
SceneTileChunk chunk = FindSceneTileChunkAtIndex(chunkIndex);
if (chunk != null)
{
removedChunks.Add(chunk);
}
var chunk = FindSceneTileChunkAtIndex(chunkIndex);
if (chunk != null) removedChunks.Add(chunk);
}
foreach (Vector2 chunkIndex in addedChunkIndices)
foreach (var chunkIndex in addedChunkIndices)
{
SceneTileChunk sceneTileChunk = null;
if (removedChunks.Count > 0)
@ -157,30 +94,109 @@ public class TileInstanceManager : Spatial
sceneTileChunk.ChunkIndex = chunkIndex;
if (ShowHexTiles)
{
foreach (HexTile3D tile3D in sceneTileChunk.TileNodes)
{
tile3D.Transform = new Transform(Basis.Identity.Scaled(Vector3.One * 0.95f),
tile3D.Transform.origin);
}
}
// if (ShowHexTiles)
// foreach (var tile3D in sceneTileChunk.TileNodes)
// tile3D.Transform = new Transform(Basis.Identity.Scaled(Vector3.One * 0.95f),
// tile3D.Transform.origin);
_sceneTileChunks.Add(sceneTileChunk);
}
GD.Print("Removed Chunks " + removedChunkIndices.Count);
GD.Print("Added Chunks " + addedChunkIndices.Count);
GD.Print("Removed chunk count: " + removedChunks.Count);
}
public void OnTileClicked(HexTile3D tile)
{
EmitSignal("TileClicked", tile);
}
public void OnTileHovered(HexTile3D tile)
{
EmitSignal("TileHovered", tile);
}
// signals
[Signal]
private delegate void TileClicked(HexTile3D tile3d);
[Signal]
private delegate void TileHovered(HexTile3D tile3d);
private class SceneTileChunk : Spatial
{
private readonly PackedScene _hexTile3DScene = GD.Load<PackedScene>("res://scenes/HexTile3D.tscn");
private readonly MultiMeshInstance _multiMeshInstance;
private readonly HexGrid HexGrid = new();
private readonly Array<int> TileInstanceIndices = new();
public readonly Array<HexTile3D> TileNodes = new();
private Vector2 _chunkIndex = Vector2.Inf;
public SceneTileChunk(Vector2 chunkIndex, MultiMeshInstance multiMeshInstance)
{
var chunkSize = global::World.ChunkSize;
foreach (var i in Enumerable.Range(0, chunkSize))
foreach (var j in Enumerable.Range(0, chunkSize))
{
var tile3D = (HexTile3D)_hexTile3DScene.Instance();
tile3D.Cell.OffsetCoords = new Vector2(chunkIndex * global::World.ChunkSize + new Vector2(i, j));
var tileTransform = Transform.Identity;
var centerPlaneCoord = HexGrid.GetHexCenterFromOffset(new Vector2(i, j));
tileTransform.origin = new Vector3(centerPlaneCoord.x, 0, centerPlaneCoord.y);
tile3D.Transform = tileTransform;
TileNodes.Add(tile3D);
AddChild(tile3D);
}
_multiMeshInstance = multiMeshInstance;
var chunkTileCount = global::World.ChunkSize * global::World.ChunkSize;
foreach (var i in Enumerable.Range(0, chunkTileCount))
TileInstanceIndices.Add(_multiMeshInstance.Multimesh.InstanceCount + i);
_multiMeshInstance.Multimesh.InstanceCount += chunkTileCount;
_multiMeshInstance.Multimesh.VisibleInstanceCount = _multiMeshInstance.Multimesh.InstanceCount;
GD.Print("VisibleInstanceCount = " + _multiMeshInstance.Multimesh.VisibleInstanceCount);
ChunkIndex = chunkIndex;
}
public Vector2 ChunkIndex
{
get => _chunkIndex;
set
{
var chunkTransform = Transform.Identity;
var chunkOriginPlaneCoord = HexGrid.GetHexCenterFromOffset(value * global::World.ChunkSize);
chunkTransform.origin = new Vector3(chunkOriginPlaneCoord.x, 0, chunkOriginPlaneCoord.y);
Transform = chunkTransform;
_chunkIndex = value;
var tileOrientation = new Basis(Vector3.Up, 90f * Mathf.Pi / 180f);
GD.Print("Updating transforms for instances of chunk " + value + " origin: " + chunkTransform.origin);
foreach (var i in Enumerable.Range(0, TileInstanceIndices.Count))
{
var column = i % global::World.ChunkSize;
var row = i / global::World.ChunkSize;
var tilePlaneCoord =
HexGrid.GetHexCenterFromOffset(new Vector2(column, row));
_multiMeshInstance.Multimesh.SetInstanceTransform(TileInstanceIndices[i],
new Transform(tileOrientation,
chunkTransform.origin + new Vector3(tilePlaneCoord.x, 0, tilePlaneCoord.y)));
}
}
}
}
}

View File

@ -17,39 +17,46 @@ public class World : Spatial
}
// constants
public const int ChunkSize = 16;
public const int ChunkSize = 10;
public const int NumChunkRows = 3;
public const int NumChunkColumns = NumChunkRows;
private List<Vector2> _activeChunkIndices = new();
private static readonly Color RockColor = new(0.5f, 0.5f, 0.4f);
private static readonly Color GrassColor = new(0, 0.4f, 0);
private static readonly Color DarkGrassColor = new(0.05882353f, 0.5411765f, 0.05882353f);
private static readonly Color LightWaterColor = new(0.05882353f, 0.05882353f, 0.8627451f);
private readonly List<Vector2> _addedChunkIndices = new();
private readonly Godot.Collections.Dictionary<Vector2, WorldChunk> _cachedWorldChunks;
private readonly Image _heightmapImage = new();
private readonly List<Vector2> _removedChunkIndices = new();
private readonly Image _tileTypeMapImage = new();
// referenced scenes
private readonly PackedScene _worldChunkScene = GD.Load<PackedScene>("res://scenes/WorldChunk.tscn");
private List<Vector2> _activeChunkIndices = new();
private Rect2 _centerChunkRect;
// delegate void OnCoordClicked(Vector2 world_pos);
// other members
private Vector2 _centerPlaneCoord;
private readonly List<Vector2> _removedChunkIndices = new();
private TileInstanceManager _tileInstanceManager;
private readonly Image _heightmapImage = new();
private Array<Spatial> _grassAssets;
private ImageTexture _heightmapTexture;
private readonly Image _tileTypeMapImage = new();
private ImageTexture _viewTileTypeTexture;
// referenced scenes
private readonly PackedScene _worldChunkScene = GD.Load<PackedScene>("res://scenes/WorldChunk.tscn");
private OpenSimplexNoise _noiseGenerator = new();
private Array<Spatial> _rockAssets;
private TileInstanceManager _tileInstanceManager;
private Array<Spatial> _treeAssets;
private ImageTexture _viewTileTypeTexture;
public Vector2 CenterChunkIndex = Vector2.Zero;
public Spatial Chunks;
public Color DebugColor;
public HexGrid HexGrid = new();
private OpenSimplexNoise _noiseGenerator = new();
public int Seed = 0;
public GenerationState State = GenerationState.Done;
public Vector2 WorldTextureCoordinateOffset;
public World()
{
@ -69,6 +76,17 @@ public class World : Spatial
InitNoiseGenerator();
GetNode<Spatial>("Assets").Visible = false;
_rockAssets = new Array<Spatial>();
foreach (Spatial asset in GetNode<Node>("Assets/Rocks").GetChildren()) _rockAssets.Add(asset);
_grassAssets = new Array<Spatial>();
foreach (Spatial asset in GetNode<Node>("Assets/Grass").GetChildren()) _grassAssets.Add(asset);
_treeAssets = new Array<Spatial>();
foreach (Spatial asset in GetNode<Node>("Assets/Trees").GetChildren()) _treeAssets.Add(asset);
SetCenterPlaneCoord(Vector2.Zero);
}
@ -85,7 +103,7 @@ public class World : Spatial
public WorldChunk GetOrCreateWorldChunk(int xIndex, int yIndex, Color debugColor)
{
if (IsTileCached(xIndex, yIndex))
if (IsChunkCached(xIndex, yIndex))
{
var cachedChunk = _cachedWorldChunks[new Vector2(xIndex, yIndex)];
return cachedChunk;
@ -94,7 +112,7 @@ public class World : Spatial
return CreateWorldChunk(xIndex, yIndex, debugColor);
}
private bool IsTileCached(int xIndex, int yIndex)
private bool IsChunkCached(int xIndex, int yIndex)
{
return _cachedWorldChunks.ContainsKey(new Vector2(xIndex, yIndex));
}
@ -127,6 +145,78 @@ public class World : Spatial
return result;
}
private bool IsColorEqualApprox(Color colorA, Color colorB)
{
var colorDifference = new Vector3(colorA.r - colorB.r, colorA.g - colorB.g, colorA.b - colorB.b);
return colorDifference.LengthSquared() < 0.1 * 0.1;
}
private Spatial SelectAsset(Vector2 offsetCoord, Array<Spatial> assets, Random randomGenerator, double probability)
{
if (randomGenerator.NextDouble() < 1.0 - probability) return null;
var assetIndex = randomGenerator.Next(assets.Count);
var assetInstance = (Spatial)assets[assetIndex].Duplicate();
var assetTransform = Transform.Identity;
assetTransform.origin = HexGrid.GetHexCenterVec3FromOffset(offsetCoord);
// TODO: assetTransform.origin.y = GetHeightAtOffset(offsetCoord);
assetTransform.origin.y = 0;
assetTransform.basis =
assetTransform.basis.Rotated(Vector3.Up, (float)(randomGenerator.NextDouble() * Mathf.Pi * 2));
assetInstance.Transform = assetTransform;
return assetInstance;
}
private void PopulateChunk(WorldChunk chunk)
{
var environmentRandom = new Random(Seed);
var tileTypeImage = chunk.TileTypeOffscreenViewport.GetTexture().GetData();
tileTypeImage.Lock();
foreach (var textureCoordU in Enumerable.Range(0, chunk.Size))
foreach (var textureCoordV in Enumerable.Range(0, chunk.Size))
{
var colorValue = tileTypeImage.GetPixel(textureCoordU, textureCoordV);
var textureCoord = new Vector2(textureCoordU, textureCoordV);
var offsetCoord = chunk.ChunkIndex * ChunkSize + textureCoord;
if (IsColorEqualApprox(colorValue, RockColor))
{
var rockAsset = SelectAsset(offsetCoord, _rockAssets, environmentRandom, 0.15);
if (rockAsset != null) chunk.Entities.AddChild(rockAsset);
// TODO: MarkCellUnwalkable(cell);
}
else if (IsColorEqualApprox(colorValue, GrassColor) || IsColorEqualApprox(colorValue, DarkGrassColor))
{
var grassAsset = SelectAsset(offsetCoord, _grassAssets, environmentRandom, 0.15);
if (grassAsset != null) chunk.Entities.AddChild(grassAsset);
var treeAsset = SelectAsset(offsetCoord, _treeAssets, environmentRandom, 0.05);
if (treeAsset != null) chunk.Entities.AddChild(treeAsset);
// TODO: MarkCellUnwalkable(cell);
// else if (environmentRandom.NextDouble() < 0.01)
// {
// var chestAsset = (Chest)_chestScene.Instance();
// var assetTransform = Transform.Identity;
// assetTransform.origin = GetHexCenterFromOffset(offsetCoord);
// assetTransform.origin.y = GetHeightAtOffset(offsetCoord);
// chestAsset.Transform = assetTransform;
// Entities.AddChild(chestAsset);
// MarkCellUnwalkable(cell);
// }
}
// else if (IsColorWater(colorValue))
// {
// MarkCellUnwalkable(cell);
// }
}
tileTypeImage.Unlock();
}
public void UpdateCenterChunkFromPlaneCoord(Vector2 planeCoord)
{
if (State != GenerationState.Done)
@ -279,7 +369,7 @@ public class World : Spatial
_viewTileTypeTexture = new ImageTexture();
_viewTileTypeTexture.CreateFromImage(_tileTypeMapImage);
_tileInstanceManager.WorldTextureCoordinateOffset = chunkIndexSouthWest * worldChunkSize;
WorldTextureCoordinateOffset = chunkIndexSouthWest * worldChunkSize;
EmitSignal("OnWorldViewTileTypeImageChanged", _tileTypeMapImage);
EmitSignal("OnHeightmapImageChanged", _heightmapImage);
@ -294,6 +384,7 @@ public class World : Spatial
if (oldState != GenerationState.Done && State == GenerationState.Done)
{
UpdateWorldViewTexture();
EmitSignal("OnTilesChanged", _removedChunkIndices.ToArray(), _addedChunkIndices.ToArray());
}
}
@ -330,14 +421,13 @@ public class World : Spatial
if (chunk.TileTypeMapFrameCount > 0) numChunksGeneratingTileType++;
}
if (numChunksGeneratingTileType == 0)
{
State = GenerationState.Objects;
}
if (numChunksGeneratingTileType == 0) State = GenerationState.Objects;
}
else if (State == GenerationState.Objects)
{
// generate objects
foreach (var chunkIndex in _addedChunkIndices) PopulateChunk(_cachedWorldChunks[chunkIndex]);
State = GenerationState.Done;
}
}

View File

@ -4,16 +4,16 @@ using Godot;
public class WorldChunk : Spatial
{
private readonly SpatialMaterial _rectMaterial = new();
private Sprite _heightmapSprite;
private TextureRect _heightmapTextureRect;
private Sprite _noiseMask;
private Sprite _noiseSprite;
private readonly SpatialMaterial _rectMaterial = new();
private bool _showTextureOverlay;
[Export] public Vector2 ChunkIndex;
public Color DebugColor = Colors.White;
[Export] public Spatial Entities;
[Export] public Texture HeightMap;
public int HeightMapFrameCount;
@ -91,6 +91,9 @@ public class WorldChunk : Spatial
TileTypeOffscreenViewport.Size = Vector2.One * Size;
Debug.Assert(TileTypeOffscreenViewport != null);
Entities = (Spatial)FindNode("Entities");
Debug.Assert(Entities != null);
SetSize(World.ChunkSize);
}