Also populate chunks with entities when chunks are being generated.

WorldChunkRefactoring
Martin Felis 2023-11-05 21:46:28 +01:00
parent 928ddd3937
commit 4c0a2e7714
7 changed files with 197 additions and 100 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,30 +180,28 @@ 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;
}
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,20 +268,20 @@ 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;

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

@ -1,4 +1,4 @@
[gd_scene load_steps=19 format=2]
[gd_scene load_steps=24 format=2]
[ext_resource path="res://scenes/StreamContainer.tscn" type="PackedScene" id=1]
[ext_resource path="res://entities/Player.tscn" type="PackedScene" id=2]
@ -15,6 +15,11 @@
[ext_resource path="res://assets/Environment/HexTileMesh.tres" type="CylinderMesh" id=13]
[ext_resource path="res://entities/Axe.tscn" type="PackedScene" id=14]
[ext_resource path="res://systems/InteractionSystem.cs" type="Script" id=15]
[ext_resource path="res://entities/rockA.tscn" type="PackedScene" id=16]
[ext_resource path="res://entities/rockC.tscn" type="PackedScene" id=17]
[ext_resource path="res://entities/rockB.tscn" type="PackedScene" id=18]
[ext_resource path="res://entities/Tree.tscn" type="PackedScene" id=19]
[ext_resource path="res://assets/Environment/grassLarge.tscn" type="PackedScene" id=20]
[sub_resource type="Animation" id=25]
resource_name = "FlashLabel"
@ -35,7 +40,9 @@ tracks/0/keys = {
[sub_resource type="AnimationNodeStateMachinePlayback" id=26]
[sub_resource type="MultiMesh" id=27]
color_format = 1
transform_format = 1
custom_data_format = 1
visible_instance_count = 0
mesh = ExtResource( 13 )
@ -351,9 +358,6 @@ script = ExtResource( 15 )
[node name="Player" parent="." instance=ExtResource( 2 )]
TileWorldNode = NodePath("../TileWorld")
[node name="Skeleton" parent="Player/Geometry/Armature" index="0"]
bones/4/bound_children = [ ]
[node name="ToolAttachement" parent="Player/Geometry/Armature/Skeleton" index="5"]
transform = Transform( 1, 8.68458e-08, -1.04308e-07, 1.74623e-07, -1, -1.30385e-07, 1.41561e-07, 1.50874e-07, -1, -0.72, 0.45, 3.28113e-08 )
@ -382,6 +386,26 @@ World = NodePath("..")
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -2.5, 0 )
multimesh = SubResource( 27 )
[node name="Assets" type="Spatial" parent="World"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -5, 0 )
visible = false
[node name="Rocks" type="Spatial" parent="World/Assets"]
[node name="rockA" parent="World/Assets/Rocks" instance=ExtResource( 16 )]
[node name="rockB" parent="World/Assets/Rocks" instance=ExtResource( 18 )]
[node name="rockC" parent="World/Assets/Rocks" instance=ExtResource( 17 )]
[node name="Grass" type="Spatial" parent="World/Assets"]
[node name="grassLarge" parent="World/Assets/Grass" instance=ExtResource( 20 )]
[node name="Trees" type="Spatial" parent="World/Assets"]
[node name="tree" parent="World/Assets/Trees" instance=ExtResource( 19 )]
[connection signal="toggled" from="DebugContainer/DebugStatsContainer/DebugMenuButton" to="DebugContainer/DebugStatsContainer" method="_on_DebugMenuButton_toggled"]
[connection signal="value_changed" from="Generator Container/WorldGeneratorContainer/HBoxContainer/WorldSizeSlider" to="Generator Container/WorldGeneratorContainer" method="_on_HSlider_value_changed"]
[connection signal="toggled" from="Generator Container/WorldGeneratorContainer/ShowTexturesCheckButton" to="Generator Container/WorldGeneratorContainer" method="_on_ShowTexturesCheckButton_toggled"]

View File

@ -142,6 +142,7 @@ public class TileInstanceManager : Spatial
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));
@ -181,7 +182,7 @@ public class TileInstanceManager : Spatial
var tileOrientation = new Basis(Vector3.Up, 90f * Mathf.Pi / 180f);
GD.Print("Updating transforms for instances of chunk " + value);
GD.Print("Updating transforms for instances of chunk " + value + " origin: " + chunkTransform.origin);
foreach (var i in Enumerable.Range(0, TileInstanceIndices.Count))
{
@ -190,6 +191,7 @@ public class TileInstanceManager : Spatial
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,16 +17,18 @@ 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 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
@ -39,10 +41,13 @@ public class World : Spatial
// other members
private Vector2 _centerPlaneCoord;
private Array<Spatial> _grassAssets;
private ImageTexture _heightmapTexture;
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;
@ -71,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);
}
@ -129,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)
@ -338,6 +426,8 @@ public class World : Spatial
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);
}