From 2109c6b6ecf4b922528ad02d614232eed3ebcf6a Mon Sep 17 00:00:00 2001 From: Martin Felis Date: Sat, 18 Nov 2023 22:11:34 +0100 Subject: [PATCH] Moved path planning to world class. - Tree entities now are obstacles in navigation planning. - Once chopped obstacles are removed. --- components/NavigationComponent.cs | 248 +++++++----------- entities/Entity.cs | 24 +- entities/Tree.cs | 49 ++-- export_presets.cfg | 2 +- scenes/World.cs | 414 ++++++++++++++++++------------ scenes/WorldChunk.cs | 145 +++++------ systems/InteractionSystem.cs | 58 ++--- 7 files changed, 452 insertions(+), 488 deletions(-) diff --git a/components/NavigationComponent.cs b/components/NavigationComponent.cs index 9323f65..fcd2ff9 100644 --- a/components/NavigationComponent.cs +++ b/components/NavigationComponent.cs @@ -7,8 +7,7 @@ using GodotComponentTest.utils; /// /// -public class NavigationComponent : Spatial -{ +public class NavigationComponent : Spatial { public World World { set; get; } public Vector3 CurrentGoalPositionWorld { get; private set; } = Vector3.Zero; public float CurrentGoalAngleWorld { get; private set; } @@ -21,39 +20,29 @@ public class NavigationComponent : Spatial private List _planningPathWorldNavigationPoints = new(); private List _smoothedPathWorldNavigationPoints = new(); - public override void _Ready() - { + public override void _Ready() { base._Ready(); _pathWorldNavigationPoints = new List(); } - public override void _Process(float delta) - { + public override void _Process(float delta) { Debug.Assert(World != null); } - public void PlanSmoothedPath(KinematicBody body, Transform fromTransformWorld, NavigationPoint navigationPoint) - { + public void PlanSmoothedPath(Entity body, Transform fromTransformWorld, NavigationPoint navigationPoint) { if (navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Position) - && navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Orientation)) - { + && navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Orientation)) { FindPath(body, fromTransformWorld.origin, navigationPoint); - } - else if (navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Position)) - { + } else if (navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Position)) { FindPath(body, fromTransformWorld.origin, navigationPoint.WorldPosition); - } - else - { + } else { throw new NotImplementedException(); } } - public void FindPath(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld) - { - var fromCell = World.HexGrid.GetHexAt(new Vector2(fromPositionWorld.x, fromPositionWorld.z)); - if (World.HexGrid.GetHexCost(fromCell) == 0) - { + public void FindPath(Entity entity, Vector3 fromPositionWorld, Vector3 toPositionWorld) { + HexCell fromCell = World.HexGrid.GetHexAt(new Vector2(fromPositionWorld.x, fromPositionWorld.z)); + if (World.HexGrid.GetHexCost(fromCell) == 0) { GD.Print("Invalid starting point for FindPath(): returning empty path."); _planningPathWorldNavigationPoints = new List(); _planningPathWorldNavigationPoints.Add(new NavigationPoint(fromPositionWorld)); @@ -61,11 +50,10 @@ public class NavigationComponent : Spatial return; } - var toCell = World.HexGrid.GetHexAt(new Vector2(toPositionWorld.x, toPositionWorld.z)); + HexCell toCell = World.HexGrid.GetHexAt(new Vector2(toPositionWorld.x, toPositionWorld.z)); toCell = World.HexGrid.GetClosestWalkableCell(fromCell, toCell); - if (World.HexGrid.GetHexCost(toCell) == 0) - { + if (World.HexGrid.GetHexCost(toCell) == 0) { GD.Print("Invalid target point for FindPath(): returning empty path."); _planningPathWorldNavigationPoints = new List(); _planningPathWorldNavigationPoints.Add(new NavigationPoint(fromPositionWorld)); @@ -73,12 +61,11 @@ public class NavigationComponent : Spatial return; } - var path = World.HexGrid.FindPath(fromCell, toCell); + List path = World.FindPath(entity, fromCell, toCell); // Generate grid navigation points _planningPathWorldNavigationPoints = new List(); - foreach (var index in Enumerable.Range(0, path.Count)) - { + foreach (int index in Enumerable.Range(0, path.Count)) { _planningPathWorldNavigationPoints.Add( new NavigationPoint(World.HexGrid.GetHexCenterVec3FromOffset(path[index].OffsetCoords))); } @@ -86,40 +73,38 @@ public class NavigationComponent : Spatial // Ensure the last point coincides with the target position if (_planningPathWorldNavigationPoints.Count > 0 && (_planningPathWorldNavigationPoints.Last().WorldPosition - toPositionWorld).LengthSquared() < - 0.5f * 0.5f) - { + 0.5f * 0.5f) { _planningPathWorldNavigationPoints[_planningPathWorldNavigationPoints.Count - 1].WorldPosition = toPositionWorld; } // Perform smoothing - _planningPathSmoothedWorldNavigationPoints = SmoothPath(body, _planningPathWorldNavigationPoints); + _planningPathSmoothedWorldNavigationPoints = SmoothPath(entity, _planningPathWorldNavigationPoints); // Ensure starting point is the current position - if (_planningPathSmoothedWorldNavigationPoints.Count > 0) - { + if (_planningPathSmoothedWorldNavigationPoints.Count > 0) { _planningPathSmoothedWorldNavigationPoints[0] = new NavigationPoint(fromPositionWorld); } } - public void FindPath(KinematicBody body, Vector3 fromPositionWorld, NavigationPoint navigationPoint) - { - FindPath(body, fromPositionWorld, navigationPoint.WorldPosition); + public void FindPath(Entity entity, Vector3 fromPositionWorld, NavigationPoint navigationPoint) { + FindPath(entity, fromPositionWorld, navigationPoint.WorldPosition); - _planningPathWorldNavigationPoints[_planningPathWorldNavigationPoints.Count - 1] = navigationPoint; - _planningPathSmoothedWorldNavigationPoints[_planningPathSmoothedWorldNavigationPoints.Count - 1] = - navigationPoint; + if (_planningPathSmoothedWorldNavigationPoints.Count > 0) { + _planningPathWorldNavigationPoints[_planningPathWorldNavigationPoints.Count - 1] = navigationPoint; + _planningPathSmoothedWorldNavigationPoints[_planningPathSmoothedWorldNavigationPoints.Count - 1] = + navigationPoint; + } } - public void PlanGridPath(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld) - { - var fromPositionOffset = World.WorldToOffsetCoords(fromPositionWorld); - var toPositionOffset = World.WorldToOffsetCoords(toPositionWorld); + public void PlanGridPath(Entity entity, Vector3 fromPositionWorld, Vector3 toPositionWorld) { + Vector2 fromPositionOffset = World.WorldToOffsetCoords(fromPositionWorld); + Vector2 toPositionOffset = World.WorldToOffsetCoords(toPositionWorld); - var fromCell = new HexCell(); + HexCell fromCell = new(); fromCell.OffsetCoords = fromPositionOffset; - var toCell = new HexCell(); + HexCell toCell = new(); toCell.OffsetCoords = toPositionOffset; _path = fromCell.LineTo(toCell); @@ -129,26 +114,23 @@ public class NavigationComponent : Spatial _pathWorldNavigationPoints.Add( new NavigationPoint(World.HexGrid.GetHexCenterVec3FromOffset(fromPositionOffset))); - foreach (var index in Enumerable.Range(1, _path.Length - 1)) - { + foreach (int index in Enumerable.Range(1, _path.Length - 1)) { _pathWorldNavigationPoints.Add( new NavigationPoint(World.GetHexCenterFromOffset(_path[index].OffsetCoords))); } if ((fromPositionWorld - World.GetHexCenterFromOffset(toCell.OffsetCoords)).LengthSquared() > Globals.EpsPositionSquared) - { // Remove the last one, because it is only the position rounded to HexGrid coordinates. - if (_pathWorldNavigationPoints.Count > 0) - { + { + if (_pathWorldNavigationPoints.Count > 0) { _pathWorldNavigationPoints.RemoveAt(_pathWorldNavigationPoints.Count - 1); } } _pathWorldNavigationPoints.Add(new NavigationPoint(toPositionWorld)); - if (_pathWorldNavigationPoints.Count > 2) - { - _smoothedPathWorldNavigationPoints = SmoothPath(body, _pathWorldNavigationPoints); + if (_pathWorldNavigationPoints.Count > 2) { + _smoothedPathWorldNavigationPoints = SmoothPath(entity, _pathWorldNavigationPoints); _pathWorldNavigationPoints = _smoothedPathWorldNavigationPoints; } @@ -156,36 +138,28 @@ public class NavigationComponent : Spatial } - public void PlanGridPath(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld, - Quat toWorldOrientation) - { - PlanGridPath(body, fromPositionWorld, toPositionWorld); + public void PlanGridPath(Entity entity, Vector3 fromPositionWorld, Vector3 toPositionWorld, + Quat toWorldOrientation) { + PlanGridPath(entity, fromPositionWorld, toPositionWorld); _pathWorldNavigationPoints.Add(new NavigationPoint(toWorldOrientation)); } - public void PlanGridPath(KinematicBody body, Transform fromTransformWorld, NavigationPoint navigationPoint) - { + public void PlanGridPath(Entity entity, Transform fromTransformWorld, NavigationPoint navigationPoint) { if (navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Position) - && navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Orientation)) - { - PlanGridPath(body, fromTransformWorld.origin, navigationPoint.WorldPosition, + && navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Orientation)) { + PlanGridPath(entity, fromTransformWorld.origin, navigationPoint.WorldPosition, navigationPoint.WorldOrientation); - } - else if (navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Position)) - { - PlanGridPath(body, fromTransformWorld.origin, navigationPoint.WorldPosition); - } - else - { + } else if (navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Position)) { + PlanGridPath(entity, fromTransformWorld.origin, navigationPoint.WorldPosition); + } else { throw new NotImplementedException(); } } - public void PlanDirectPath(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld) - { + public void PlanDirectPath(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld) { _pathWorldNavigationPoints.Clear(); _pathWorldNavigationPoints.Add(new NavigationPoint(toPositionWorld)); @@ -194,23 +168,20 @@ public class NavigationComponent : Spatial public void PlanDirectPath(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld, - Quat toWorldOrientation) - { + Quat toWorldOrientation) { PlanDirectPath(body, fromPositionWorld, toPositionWorld); _pathWorldNavigationPoints.Add(new NavigationPoint(toWorldOrientation)); } - public bool HasPathCollision(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld) - { + public bool HasPathCollision(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld) { Vector3 fromPositionLocal = GlobalTransform.XformInv(fromPositionWorld); Vector3 toPositionLocal = GlobalTransform.XformInv(toPositionWorld); Vector3 relativeVelocity = GlobalTransform.basis.Xform(toPositionLocal - fromPositionLocal); KinematicCollision moveCollision = body.MoveAndCollide(relativeVelocity, true, true, true); - if (moveCollision != null) - { + if (moveCollision != null) { Spatial colliderSpatial = moveCollision.Collider as Spatial; // GD.Print("Found collision: " + moveCollision.Collider + " (" + colliderSpatial.Name + ")"); return true; @@ -220,37 +191,30 @@ public class NavigationComponent : Spatial } - public bool CheckSweptTriangleCellCollision(Vector3 startWorld, Vector3 endWorld, float radius) - { - Vector2 startPlane = new Vector2(startWorld.x, startWorld.z); - Vector2 endPlane = new Vector2(endWorld.x, endWorld.z); + public bool CheckSweptTriangleCellCollision(Vector3 startWorld, Vector3 endWorld, float radius) { + Vector2 startPlane = new(startWorld.x, startWorld.z); + Vector2 endPlane = new(endWorld.x, endWorld.z); Vector2 directionPlane = (endPlane - startPlane).Normalized(); Vector2 sidePlane = directionPlane.Rotated(Mathf.Pi * 0.5f); List cells = World.HexGrid.GetCellsForLine(startPlane + directionPlane * radius, endPlane + directionPlane * radius); - foreach (HexCell cell in cells) - { - if (World.HexGrid.GetHexCost(cell) == 0) - { + foreach (HexCell cell in cells) { + if (World.HexGrid.GetHexCost(cell) == 0) { return true; } } cells = World.HexGrid.GetCellsForLine(startPlane + sidePlane * radius, endPlane + sidePlane * radius); - foreach (HexCell cell in cells) - { - if (World.HexGrid.GetHexCost(cell) == 0) - { + foreach (HexCell cell in cells) { + if (World.HexGrid.GetHexCost(cell) == 0) { return true; } } cells = World.HexGrid.GetCellsForLine(startPlane - sidePlane * radius, endPlane - sidePlane * radius); - foreach (HexCell cell in cells) - { - if (World.HexGrid.GetHexCost(cell) == 0) - { + foreach (HexCell cell in cells) { + if (World.HexGrid.GetHexCost(cell) == 0) { return true; } } @@ -258,29 +222,24 @@ public class NavigationComponent : Spatial return false; } - public List SmoothPath(KinematicBody body, List navigationPoints) - { - if (navigationPoints.Count <= 2) - { + public List SmoothPath(KinematicBody body, List navigationPoints) { + if (navigationPoints.Count <= 2) { return navigationPoints; } Vector3 bodyGlobalTranslation = body.GlobalTranslation; - List smoothedPath = new List(); + List smoothedPath = new(); int startIndex = 0; int endIndex = navigationPoints.Count > 1 ? 1 : 0; smoothedPath.Add(navigationPoints[startIndex]); Vector3 startPoint = navigationPoints[startIndex].WorldPosition; - while (endIndex != navigationPoints.Count) - { + while (endIndex != navigationPoints.Count) { Vector3 endPoint = navigationPoints[endIndex].WorldPosition; - if (CheckSweptTriangleCellCollision(startPoint, endPoint, 0.27f)) - { - if (endIndex - startIndex == 1) - { + if (CheckSweptTriangleCellCollision(startPoint, endPoint, 0.27f)) { + if (endIndex - startIndex == 1) { GD.Print("Aborting SmoothPath: input path passes through collision geometry."); body.GlobalTranslation = bodyGlobalTranslation; return smoothedPath; @@ -294,8 +253,7 @@ public class NavigationComponent : Spatial continue; } - if (endIndex == navigationPoints.Count - 1) - { + if (endIndex == navigationPoints.Count - 1) { break; } @@ -308,34 +266,25 @@ public class NavigationComponent : Spatial return smoothedPath; } - public void PlanDirectPath(KinematicBody body, Transform fromTransformWorld, NavigationPoint navigationPoint) - { + public void PlanDirectPath(KinematicBody body, Transform fromTransformWorld, NavigationPoint navigationPoint) { if (navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Position) - && navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Orientation)) - { + && navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Orientation)) { PlanDirectPath(body, fromTransformWorld.origin, navigationPoint.WorldPosition, navigationPoint.WorldOrientation); - } - else if (navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Position)) - { + } else if (navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Position)) { PlanDirectPath(body, fromTransformWorld.origin, navigationPoint.WorldPosition); - } - else - { + } else { throw new NotImplementedException(); } } - public void ActivatePlannedPath() - { + public void ActivatePlannedPath() { _pathWorldNavigationPoints = _planningPathSmoothedWorldNavigationPoints; UpdateCurrentGoal(); } - private void UpdateCurrentGoal() - { - if (_pathWorldNavigationPoints.Count == 0) - { + private void UpdateCurrentGoal() { + if (_pathWorldNavigationPoints.Count == 0) { return; } @@ -344,73 +293,56 @@ public class NavigationComponent : Spatial CurrentGoalAngleWorld = _pathWorldNavigationPoints[0].WorldAngle; } - private void ApplyExistingTransform(Transform worldTransform) - { - if (_currentGoal.Flags == NavigationPoint.NavigationFlags.Orientation) - { + private void ApplyExistingTransform(Transform worldTransform) { + if (_currentGoal.Flags == NavigationPoint.NavigationFlags.Orientation) { CurrentGoalPositionWorld = worldTransform.origin; - } - else if (_currentGoal.Flags == NavigationPoint.NavigationFlags.Position) - { + } else if (_currentGoal.Flags == NavigationPoint.NavigationFlags.Position) { CurrentGoalAngleWorld = Globals.CalcPlaneAngle(worldTransform); } } - public void UpdateCurrentGoal(Transform currentTransformWorld) - { - if (_currentGoal == null) - { + public void UpdateCurrentGoal(Transform currentTransformWorld) { + if (_currentGoal == null) { _currentGoal = new NavigationPoint(currentTransformWorld); } - if (_pathWorldNavigationPoints.Count == 0) - { + if (_pathWorldNavigationPoints.Count == 0) { CurrentGoalAngleWorld = Globals.CalcPlaneAngle(currentTransformWorld); CurrentGoalPositionWorld = currentTransformWorld.origin; return; } - if (_currentGoal.Flags.HasFlag(NavigationPoint.NavigationFlags.Position)) - { + if (_currentGoal.Flags.HasFlag(NavigationPoint.NavigationFlags.Position)) { CurrentGoalPositionWorld = _pathWorldNavigationPoints[0].WorldPosition; - } - else - { + } else { CurrentGoalAngleWorld = Globals.CalcPlaneAngle(currentTransformWorld); } - if (_currentGoal.Flags.HasFlag(NavigationPoint.NavigationFlags.Orientation)) - { + if (_currentGoal.Flags.HasFlag(NavigationPoint.NavigationFlags.Orientation)) { CurrentGoalAngleWorld = _currentGoal.WorldAngle; - } - else - { + } else { CurrentGoalAngleWorld = Globals.CalcPlaneAngle(currentTransformWorld); } - if (_currentGoal.IsReached(currentTransformWorld)) - { + if (_currentGoal.IsReached(currentTransformWorld)) { _pathWorldNavigationPoints.RemoveAt(0); UpdateCurrentGoal(); ApplyExistingTransform(currentTransformWorld); } - if (_pathWorldNavigationPoints.Count == 0) - { + if (_pathWorldNavigationPoints.Count == 0) { CurrentGoalPositionWorld = currentTransformWorld.origin; CurrentGoalAngleWorld = Globals.CalcPlaneAngle(currentTransformWorld); } } - public bool IsGoalReached() - { + public bool IsGoalReached() { return _pathWorldNavigationPoints.Count == 0; } - public void DebugDraw(Spatial parentNode, DebugGeometry debugGeometry) - { + public void DebugDraw(Spatial parentNode, DebugGeometry debugGeometry) { Vector3 yOffset = Vector3.Up * 0.1f; debugGeometry.GlobalTransform = Transform.Identity; @@ -428,8 +360,7 @@ public class NavigationComponent : Spatial debugGeometry.PopTransform(); Vector3 previousPoint = parentNode.GlobalTranslation; - foreach (NavigationPoint point in _pathWorldNavigationPoints) - { + foreach (NavigationPoint point in _pathWorldNavigationPoints) { debugGeometry.AddVertex(previousPoint + yOffset); debugGeometry.AddVertex(point.WorldPosition + yOffset); @@ -437,8 +368,7 @@ public class NavigationComponent : Spatial } previousPoint = parentNode.GlobalTranslation; - foreach (NavigationPoint point in _smoothedPathWorldNavigationPoints) - { + foreach (NavigationPoint point in _smoothedPathWorldNavigationPoints) { debugGeometry.SetColor(new Color(0, 0, 1)); debugGeometry.AddVertex(previousPoint + yOffset); debugGeometry.AddVertex(point.WorldPosition + yOffset); @@ -447,8 +377,7 @@ public class NavigationComponent : Spatial } previousPoint = parentNode.GlobalTranslation; - foreach (NavigationPoint point in _planningPathWorldNavigationPoints) - { + foreach (NavigationPoint point in _planningPathWorldNavigationPoints) { debugGeometry.SetColor(new Color(1, 0, 1)); debugGeometry.AddVertex(previousPoint + yOffset); debugGeometry.AddVertex(point.WorldPosition + yOffset); @@ -457,8 +386,7 @@ public class NavigationComponent : Spatial } previousPoint = parentNode.GlobalTranslation; - foreach (NavigationPoint point in _planningPathSmoothedWorldNavigationPoints) - { + foreach (NavigationPoint point in _planningPathSmoothedWorldNavigationPoints) { debugGeometry.SetColor(new Color(1, 1, 0)); debugGeometry.AddVertex(previousPoint + yOffset); debugGeometry.AddVertex(point.WorldPosition + yOffset); diff --git a/entities/Entity.cs b/entities/Entity.cs index 3f13a0c..bda848f 100644 --- a/entities/Entity.cs +++ b/entities/Entity.cs @@ -1,30 +1,24 @@ using Godot; -using System; -public class Entity : KinematicBody -{ +public class Entity : KinematicBody { public Vector3 Velocity { get; set; } = Vector3.Zero; public float RotationalVelocity { get; set; } = 0; - /** Defines the angle in plane coordinates, 0 => pointing to the right/east, pi/2 pointing up/north, range [-pi,pi]. */ - public float PlaneAngle - { + /** + * Defines the angle in plane coordinates, 0 => pointing to the right/east, pi/2 pointing up/north, range [-pi,pi]. + */ + public float PlaneAngle { get => Globals.CalcPlaneAngle(GlobalTransform); set => GlobalTransform = new Transform(new Basis(Vector3.Up, value + Mathf.Pi * 0.5f), GlobalTranslation); } - - public float CalcShortestPlaneRotationToTargetDirection(Vector3 globalTargetDirection) - { + + public float CalcShortestPlaneRotationToTargetDirection(Vector3 globalTargetDirection) { float angleToTarget = Vector3.Right.SignedAngleTo(globalTargetDirection, Vector3.Up); float currentAngle = PlaneAngle; float delta = angleToTarget - currentAngle; - - delta += (delta > Mathf.Pi) ? -Mathf.Pi * 2 : (delta < -Mathf.Pi) ? Mathf.Pi * 2 : 0; + + delta += delta > Mathf.Pi ? -Mathf.Pi * 2 : delta < -Mathf.Pi ? Mathf.Pi * 2 : 0; return delta; } - - public override void _Ready() - { - } } \ No newline at end of file diff --git a/entities/Tree.cs b/entities/Tree.cs index 2c8c728..28c0283 100644 --- a/entities/Tree.cs +++ b/entities/Tree.cs @@ -4,8 +4,7 @@ using Godot; using GodotComponentTest.components; using GodotComponentTest.entities; -public class Tree : StaticBody, IInteractionInterface -{ +public class Tree : StaticBody, IInteractionInterface { [Export] public float ChopDuration = 2; public bool IsMouseOver; public InteractionComponent InteractionComponent { get; set; } @@ -18,9 +17,11 @@ public class Tree : StaticBody, IInteractionInterface [Signal] public delegate void EntityClicked(Entity entity); + [Signal] + public delegate void TreeChopped(Tree entity); + // Called when the node enters the scene tree for the first time. - public override void _Ready() - { + public override void _Ready() { _geometry = GetNode("Geometry/tree"); Debug.Assert(_geometry != null); _animationPlayer = GetNode("AnimationPlayer"); @@ -33,56 +34,43 @@ public class Tree : StaticBody, IInteractionInterface Connect("mouse_exited", this, nameof(OnAreaMouseExited)); } - - public override void _Process(float delta) - { + public override void _Process(float delta) { base._Process(delta); - if (_isBeingChopped) - { + if (_isBeingChopped) { _health = Math.Max(0, _health - delta * 100 / ChopDuration); } - if (_health == 0) - { + if (_health == 0) { InteractionComponent.EndInteraction(); InteractionComponent.TargetEntity = null; - QueueFree(); + EmitSignal("TreeChopped", this); } } public void OnAreaInputEvent(Node camera, InputEvent inputEvent, Vector3 position, Vector3 normal, - int shapeIndex) - { - if (IsMouseOver && inputEvent is InputEventMouseButton) - { + int shapeIndex) { + if (IsMouseOver && inputEvent is InputEventMouseButton) { InputEventMouseButton mouseButtonEvent = (InputEventMouseButton)inputEvent; - if (mouseButtonEvent.ButtonIndex == 1 && mouseButtonEvent.Pressed) - { + if (mouseButtonEvent.ButtonIndex == 1 && mouseButtonEvent.Pressed) { EmitSignal("EntityClicked", this); } } } - - public void OnAreaMouseEntered() - { + public void OnAreaMouseEntered() { IsMouseOver = true; - SpatialMaterial overrideMaterial = new SpatialMaterial(); + SpatialMaterial overrideMaterial = new(); overrideMaterial.AlbedoColor = new Color(1, 0, 0); _geometry.MaterialOverride = overrideMaterial; } - - public void OnAreaMouseExited() - { + public void OnAreaMouseExited() { IsMouseOver = false; _geometry.MaterialOverride = null; } - - public void OnInteractionStart() - { + public void OnInteractionStart() { GD.Print("Starting tree animationplayer"); _animationPlayer.CurrentAnimation = "TreeShake"; _animationPlayer.Seek(0); @@ -90,11 +78,10 @@ public class Tree : StaticBody, IInteractionInterface _isBeingChopped = true; } - public void OnInteractionEnd() - { + public void OnInteractionEnd() { _animationPlayer.CurrentAnimation = "Idle"; _animationPlayer.Seek(0); _animationPlayer.Stop(); _isBeingChopped = false; } -} +} \ No newline at end of file diff --git a/export_presets.cfg b/export_presets.cfg index 9ca3e63..08b8d15 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -30,7 +30,7 @@ keystore/release="/home/martin/projects/GodotComponentTest/android/keystore/debu keystore/release_user="androiddebugkey" keystore/release_password="android" one_click_deploy/clear_previous_install=true -version/code=1 +version/code=5 version/name="1.0" package/unique_name="org.godotengine.$genname" package/name="" diff --git a/scenes/World.cs b/scenes/World.cs index 3d65aca..66e4550 100644 --- a/scenes/World.cs +++ b/scenes/World.cs @@ -4,11 +4,10 @@ using System.Diagnostics; using System.Linq; using Godot; using Godot.Collections; +using Priority_Queue; -public class World : Spatial -{ - public enum GenerationState - { +public class World : Spatial { + public enum GenerationState { Undefined, Heightmap, TileType, @@ -17,7 +16,7 @@ public class World : Spatial } // constants - public const int ChunkSize = 12; + public const int ChunkSize = 14; public const int NumChunkRows = 3; public const int NumChunkColumns = NumChunkRows; private static readonly Color RockColor = new(0.5f, 0.5f, 0.4f); @@ -27,17 +26,17 @@ public class World : Spatial private readonly Godot.Collections.Dictionary _cachedWorldChunks; private readonly List _addedChunkIndices = new(); - private readonly List _unusedWorldChunks = new(); + private readonly List _deactivatedWorldChunks = new(); private readonly Image _heightmapImage = new(); private readonly List _removedChunkIndices = new(); private readonly Image _tileTypeMapImage = new(); - private int FrameCounter; // referenced scenes private readonly PackedScene _worldChunkScene = GD.Load("res://scenes/WorldChunk.tscn"); private List _activeChunkIndices = new(); private Rect2 _centerChunkRect; + private readonly List _removedSpatialNodes = new(); // delegate void OnCoordClicked(Vector2 world_pos); @@ -93,16 +92,14 @@ public class World : Spatial [Signal] private delegate void TileHovered(HexTile3D tile3d); - public World() - { + public World() { Debug.Assert(ChunkSize % 2 == 0); _cachedWorldChunks = new Godot.Collections.Dictionary(); } // Called when the node enters the scene tree for the first time. - public override void _Ready() - { + public override void _Ready() { Chunks = (Spatial)FindNode("Chunks"); Debug.Assert(Chunks != null); @@ -117,19 +114,24 @@ public class World : Spatial GetNode("Assets").Visible = false; _rockAssets = new Array(); - foreach (Spatial asset in GetNode("Assets/Rocks").GetChildren()) _rockAssets.Add(asset); + foreach (Spatial asset in GetNode("Assets/Rocks").GetChildren()) { + _rockAssets.Add(asset); + } _grassAssets = new Array(); - foreach (Spatial asset in GetNode("Assets/Grass").GetChildren()) _grassAssets.Add(asset); + foreach (Spatial asset in GetNode("Assets/Grass").GetChildren()) { + _grassAssets.Add(asset); + } _treeAssets = new Array(); - foreach (Spatial asset in GetNode("Assets/Trees").GetChildren()) _treeAssets.Add(asset); + foreach (Spatial asset in GetNode("Assets/Trees").GetChildren()) { + _treeAssets.Add(asset); + } SetCenterPlaneCoord(Vector2.Zero); } - public void InitNoiseGenerator() - { + public void InitNoiseGenerator() { _noiseGenerator = new OpenSimplexNoise(); _noiseGenerator.Seed = Seed; @@ -139,22 +141,17 @@ public class World : Spatial _noiseGenerator.Lacunarity = 2; } - public WorldChunk GetOrCreateWorldChunk(Vector2 chunkIndex, Color debugColor) - { + public WorldChunk GetOrCreateWorldChunk(Vector2 chunkIndex, Color debugColor) { WorldChunk chunk; - if (IsChunkCached(chunkIndex)) + if (IsChunkCached(chunkIndex)) { return _cachedWorldChunks[chunkIndex]; - - if (_unusedWorldChunks.Count > 0) - { - chunk = _unusedWorldChunks.First(); - _unusedWorldChunks.RemoveAt(0); - - GD.Print("Reusing chunk from former index " + chunk.ChunkIndex + " at new index " + chunkIndex); } - else - { + + if (_deactivatedWorldChunks.Count > 0) { + chunk = _deactivatedWorldChunks.First(); + _deactivatedWorldChunks.RemoveAt(0); + } else { chunk = CreateWorldChunk(chunkIndex, debugColor); } @@ -163,14 +160,12 @@ public class World : Spatial return chunk; } - private bool IsChunkCached(Vector2 chunkIndex) - { + private bool IsChunkCached(Vector2 chunkIndex) { return _cachedWorldChunks.ContainsKey(chunkIndex); } - private WorldChunk CreateWorldChunk(Vector2 chunkIndex, Color debugColor) - { + private WorldChunk CreateWorldChunk(Vector2 chunkIndex, Color debugColor) { WorldChunk result = (WorldChunk)_worldChunkScene.Instance(); Chunks.AddChild(result); result.Connect("TileClicked", this, nameof(OnTileClicked)); @@ -191,15 +186,16 @@ public class World : Spatial return result; } - private bool IsColorEqualApprox(Color colorA, Color colorB) - { + private bool IsColorEqualApprox(Color colorA, Color colorB) { Vector3 colorDifference = new(colorA.r - colorB.r, colorA.g - colorB.g, colorA.b - colorB.b); return colorDifference.LengthSquared() < 0.1 * 0.1; } - private Spatial SelectAsset(Vector2 textureCoord, Array assets, Random randomGenerator, double probability) - { - if (randomGenerator.NextDouble() < 1.0 - probability) return null; + private Spatial SelectAsset(Vector2 textureCoord, Array assets, Random randomGenerator, + double probability) { + if (randomGenerator.NextDouble() < 1.0 - probability) { + return null; + } int assetIndex = randomGenerator.Next(assets.Count); Spatial assetInstance = (Spatial)assets[assetIndex].Duplicate(); @@ -214,41 +210,40 @@ public class World : Spatial return assetInstance; } - private void PopulateChunk(WorldChunk chunk) - { + private void PopulateChunk(WorldChunk chunk) { Random environmentRandom = new(Seed); - Image tileTypeImage = chunk.TileTypeOffscreenViewport.GetTexture().GetData(); - tileTypeImage.Lock(); + chunk.CreateUnlockedTileTypeImage(); - foreach (int textureCoordU in Enumerable.Range(0, chunk.Size)) - foreach (int textureCoordV in Enumerable.Range(0, chunk.Size)) - { - Color colorValue = tileTypeImage.GetPixel(textureCoordU, textureCoordV); - Vector2 textureCoord = new(textureCoordU, textureCoordV); - Vector2 offsetCoord = chunk.ChunkIndex * ChunkSize + textureCoord; + foreach (int textureCoordU in Enumerable.Range(0, chunk.Size)) { + foreach (int textureCoordV in Enumerable.Range(0, chunk.Size)) { + Color colorValue = chunk.TileTypeImage.GetPixel(textureCoordU, textureCoordV); + Vector2 textureCoord = new(textureCoordU, textureCoordV); + Vector2 offsetCoord = chunk.ChunkIndex * ChunkSize + textureCoord; - if (IsColorEqualApprox(colorValue, RockColor)) - { - Spatial rockAsset = SelectAsset(textureCoord, _rockAssets, environmentRandom, 0.15); - if (rockAsset != null) chunk.Entities.AddChild(rockAsset); - // TODO: MarkCellUnwalkable(cell); - } - else if (IsColorEqualApprox(colorValue, GrassColor) || IsColorEqualApprox(colorValue, DarkGrassColor)) - { - Spatial grassAsset = SelectAsset(textureCoord, _grassAssets, environmentRandom, 0.15); - if (grassAsset != null) chunk.Entities.AddChild(grassAsset); + if (IsColorEqualApprox(colorValue, RockColor)) { + Spatial rockAsset = SelectAsset(textureCoord, _rockAssets, environmentRandom, 0.15); + if (rockAsset != null) { + chunk.Entities.AddChild(rockAsset); + MarkCellUnwalkable(HexGrid.GetHexAtOffset(offsetCoord)); + } + } else if (IsColorEqualApprox(colorValue, GrassColor) || + IsColorEqualApprox(colorValue, DarkGrassColor)) { + Spatial grassAsset = SelectAsset(textureCoord, _grassAssets, environmentRandom, 0.15); + if (grassAsset != null) { + chunk.Entities.AddChild(grassAsset); + } - Tree treeAsset = SelectAsset(textureCoord, _treeAssets, environmentRandom, 0.05) as Tree; - if (treeAsset != null) - { - chunk.Entities.AddChild(treeAsset); - treeAsset.Connect("EntityClicked", this, nameof(OnEntityClicked)); - } + Tree treeAsset = SelectAsset(textureCoord, _treeAssets, environmentRandom, 0.05) as Tree; + if (treeAsset != null) { + chunk.Entities.AddChild(treeAsset); + treeAsset.Connect("EntityClicked", this, nameof(OnEntityClicked)); + treeAsset.Connect("TreeChopped", this, nameof(OnBlockingSpatialRemoved)); + MarkCellUnwalkable(HexGrid.GetHexAtOffset(offsetCoord)); + } - - // TODO: MarkCellUnwalkable(cell); - // else if (environmentRandom.NextDouble() < 0.01) + // TODO: MarkCellUnwalkable(cell); + // else if (environmentRandom.NextDouble() < 0.01) // { // var chestAsset = (Chest)_chestScene.Instance(); // var assetTransform = Transform.Identity; @@ -258,36 +253,29 @@ public class World : Spatial // Entities.AddChild(chestAsset); // MarkCellUnwalkable(cell); // } - } + } // else if (IsColorWater(colorValue)) // { // MarkCellUnwalkable(cell); // } + } } - - tileTypeImage.Unlock(); } - public Vector2 WorldToOffsetCoords(Vector3 fromPositionWorld) - { + public Vector2 WorldToOffsetCoords(Vector3 fromPositionWorld) { return HexGrid.GetHexAt(new Vector2(fromPositionWorld.x, fromPositionWorld.z)).OffsetCoords; } - public Vector3 GetHexCenterFromOffset(Vector2 fromPositionOffset) - { + public Vector3 GetHexCenterFromOffset(Vector2 fromPositionOffset) { return HexGrid.GetHexCenterVec3FromOffset(fromPositionOffset); } - public void UpdateCenterChunkFromPlaneCoord(Vector2 planeCoord) - { - if (State != GenerationState.Done) - { + public void UpdateCenterChunkFromPlaneCoord(Vector2 planeCoord) { + if (State != GenerationState.Done) { GD.PrintErr("Cannot update chunk to new planeCoord " + planeCoord + ": Chunk generation not yet finished!"); return; } - GD.Print("Update Chunks: " + FrameCounter); - // mark all chunks as retired Godot.Collections.Dictionary oldCachedChunks = new(_cachedWorldChunks); @@ -319,15 +307,16 @@ public class World : Spatial _activeChunkIndices.Add(CenterChunkIndex + new Vector2(+1, +1)); // clear unused chunks - _unusedWorldChunks.Clear(); + _deactivatedWorldChunks.Clear(); _addedChunkIndices.Clear(); - foreach (Vector2 oldChunkIndex in oldCachedChunks.Keys) - if (!_activeChunkIndices.Contains(oldChunkIndex)) + foreach (Vector2 oldChunkIndex in oldCachedChunks.Keys) { + if (!_activeChunkIndices.Contains(oldChunkIndex)) { DeactivateChunk(oldCachedChunks[oldChunkIndex]); + } + } - foreach (Vector2 activeChunkIndex in _activeChunkIndices) - { + foreach (Vector2 activeChunkIndex in _activeChunkIndices) { WorldChunk chunk = GetOrCreateWorldChunk(activeChunkIndex, new Color(GD.Randf(), GD.Randf(), GD.Randf())); _cachedWorldChunks[activeChunkIndex] = chunk; @@ -335,34 +324,29 @@ public class World : Spatial Debug.Assert(_activeChunkIndices.Count == NumChunkRows * NumChunkColumns); - foreach (Vector2 chunkKey in _activeChunkIndices) - if (!oldCachedChunks.ContainsKey(chunkKey)) - { + foreach (Vector2 chunkKey in _activeChunkIndices) { + if (!oldCachedChunks.ContainsKey(chunkKey)) { ActivateChunk(_cachedWorldChunks[chunkKey], chunkKey); State = GenerationState.Heightmap; } + } } - private void ActivateChunk(WorldChunk chunk, Vector2 chunkIndex) - { + private void ActivateChunk(WorldChunk chunk, Vector2 chunkIndex) { chunk.SetChunkIndex(chunkIndex, HexGrid); chunk.UpdateTileTransforms(); _addedChunkIndices.Add(chunk.ChunkIndex); - GD.Print("Generating noise for chunk " + chunk.ChunkIndex); GenerateChunkNoiseMap(chunk); } - private void DeactivateChunk(WorldChunk chunk) - { - GD.Print("Clearing chunk index: " + chunk.ChunkIndex); + private void DeactivateChunk(WorldChunk chunk) { _cachedWorldChunks.Remove(chunk.ChunkIndex); chunk.ClearContent(); - _unusedWorldChunks.Add(chunk); + _deactivatedWorldChunks.Add(chunk); } - private void GenerateChunkNoiseMap(WorldChunk chunk) - { + private void GenerateChunkNoiseMap(WorldChunk chunk) { Vector2 chunkIndex = chunk.ChunkIndex; ImageTexture noiseImageTexture = new(); @@ -372,27 +356,29 @@ public class World : Spatial chunk.SetNoisemap(noiseImageTexture); } - private void RemoveChunk(Vector2 cachedChunkKey) - { + private void RemoveChunk(Vector2 cachedChunkKey) { _cachedWorldChunks.Remove(cachedChunkKey); _removedChunkIndices.Add(cachedChunkKey); - foreach (WorldChunk chunk in Chunks.GetChildren()) - if (chunk.ChunkIndex == new Vector2(cachedChunkKey.x, cachedChunkKey.y)) + foreach (WorldChunk chunk in Chunks.GetChildren()) { + if (chunk.ChunkIndex == new Vector2(cachedChunkKey.x, cachedChunkKey.y)) { chunk.QueueFree(); + } + } } - private Vector2 GetChunkTupleFromPlaneCoord(Vector2 planeCoord) - { - HexCell centerOffsetCoord = HexGrid.GetHexAt(planeCoord); - return (centerOffsetCoord.OffsetCoords / ChunkSize).Floor(); + private Vector2 GetChunkTupleFromPlaneCoord(Vector2 planeCoord) { + HexCell hexCell = HexGrid.GetHexAt(planeCoord); + return GetChunkIndexFromOffsetCoord(hexCell.OffsetCoords); } - public void SetCenterPlaneCoord(Vector2 centerPlaneCoord) - { - if (!_centerChunkRect.HasPoint(centerPlaneCoord)) - { + private Vector2 GetChunkIndexFromOffsetCoord(Vector2 offsetCoord) { + return (offsetCoord / ChunkSize).Floor(); + } + + public void SetCenterPlaneCoord(Vector2 centerPlaneCoord) { + if (!_centerChunkRect.HasPoint(centerPlaneCoord)) { UpdateCenterChunkFromPlaneCoord(centerPlaneCoord); UpdateChunkBounds(); @@ -401,8 +387,7 @@ public class World : Spatial } } - private void UpdateWorldViewTexture() - { + private void UpdateWorldViewTexture() { int worldChunkSize = ChunkSize; int numWorldChunkRows = NumChunkRows; int numWorldChunkColumns = NumChunkColumns; @@ -412,8 +397,7 @@ public class World : Spatial _tileTypeMapImage.Create(worldChunkSize * numWorldChunkColumns, worldChunkSize * numWorldChunkRows, false, Image.Format.Rgba8); - foreach (Vector2 chunkIndex in _activeChunkIndices) - { + foreach (Vector2 chunkIndex in _activeChunkIndices) { WorldChunk worldChunk = GetOrCreateWorldChunk(chunkIndex, Colors.White); _heightmapImage.BlendRect( @@ -439,113 +423,207 @@ public class World : Spatial EmitSignal("OnHeightmapImageChanged", _heightmapImage); } - private void UpdateChunkBounds() - { + private void UpdateChunkBounds() { _chunkIndexSouthWest = Vector2.Inf; _chunkIndexNorthEast = -Vector2.Inf; - foreach (Vector2 chunkIndex in _activeChunkIndices) - { - WorldChunk worldChunk = GetOrCreateWorldChunk(chunkIndex, Colors.White); - - if (chunkIndex.x <= _chunkIndexSouthWest.x && chunkIndex.y <= _chunkIndexSouthWest.y) + foreach (Vector2 chunkIndex in _activeChunkIndices) { + if (chunkIndex.x <= _chunkIndexSouthWest.x && chunkIndex.y <= _chunkIndexSouthWest.y) { _chunkIndexSouthWest = chunkIndex; - else if (chunkIndex.x >= _chunkIndexNorthEast.x && chunkIndex.y >= _chunkIndexNorthEast.y) + } else if (chunkIndex.x >= _chunkIndexNorthEast.x && chunkIndex.y >= _chunkIndexNorthEast.y) { _chunkIndexNorthEast = chunkIndex; + } } } - private void UpdateNavigationBounds() - { + private void UpdateNavigationBounds() { HexCell cellSouthWest = HexGrid.GetHexAtOffset(_chunkIndexSouthWest * ChunkSize); - // Chunks have their cells ordered from south west (0,0) to north east (ChunkSize, ChunkSize). For the - // north east cell we have to add the chunk size to get to the actual corner cell. - HexCell cellNorthEast = - HexGrid.GetHexAtOffset(_chunkIndexNorthEast * ChunkSize + Vector2.One * (ChunkSize - 1)); - - HexCell centerCell = - HexGrid.GetHexAtOffset(((cellNorthEast.OffsetCoords - cellSouthWest.OffsetCoords) / 2).Round()); - int numCells = ChunkSize * Math.Max(NumChunkColumns, NumChunkRows); - HexGrid.SetBoundsOffset(cellSouthWest, ChunkSize * new Vector2(NumChunkColumns, NumChunkRows)); } - public override void _Process(float delta) - { + public void MarkCellUnwalkable(HexCell cell) { + HexGrid.AddObstacle(cell); + } + + public float GetHexCost(Entity entity, HexCell cell) { + float nextHexCost = HexGrid.GetHexCost(cell); + if (nextHexCost != 0) { + Vector2 nextOffset = cell.OffsetCoords; + Vector2 chunkIndex = GetChunkIndexFromOffsetCoord(nextOffset); + WorldChunk chunk = _cachedWorldChunks[chunkIndex]; + Vector2 textureCoordinate = nextOffset - Vector2.One * ChunkSize * chunkIndex; + + Color tileTypeColor = chunk.TileTypeImage.GetPixel((int)textureCoordinate.x, (int)textureCoordinate.y); + if (!IsColorEqualApprox(tileTypeColor, GrassColor) && + !IsColorEqualApprox(tileTypeColor, DarkGrassColor)) { + nextHexCost = 0; + } + } + + return nextHexCost; + } + + public float GetMoveCost(Entity entity, HexCell currentHex, HexCell nextHex) { + if (GetHexCost(entity, nextHex) == 0) { + return 0; + } + + return HexGrid.GetMoveCost(currentHex.AxialCoords, + new HexCell(nextHex.AxialCoords - currentHex.AxialCoords).CubeCoords); + } + + public List FindPath(Entity entity, HexCell startHex, HexCell goalHex) { + if (State != GenerationState.Done) { + return new List(); + } + + Vector2 goalAxialCoords = goalHex.AxialCoords; + + SimplePriorityQueue frontier = new(); + frontier.Enqueue(startHex.AxialCoords, 0); + System.Collections.Generic.Dictionary cameFrom = new(); + System.Collections.Generic.Dictionary costSoFar = new(); + + cameFrom.Add(startHex.AxialCoords, startHex.AxialCoords); + costSoFar.Add(startHex.AxialCoords, 0); + + int FindPathCheckedCellCount = 0; + + while (frontier.Any()) { + FindPathCheckedCellCount++; + HexCell currentHex = new(frontier.Dequeue()); + Vector2 currentAxial = currentHex.AxialCoords; + + if (currentHex == goalHex) { + break; + } + + foreach (HexCell nextHex in currentHex.GetAllAdjacent()) { + Vector2 nextAxial = nextHex.AxialCoords; + + float moveCost = GetMoveCost(entity, currentHex, nextHex); + + if (nextHex == goalHex && moveCost == 0 && GetHexCost(entity, nextHex) == 0) { + // Goal ist an obstacle + cameFrom[nextHex.AxialCoords] = currentHex.AxialCoords; + frontier.Clear(); + break; + } + + if (moveCost == 0) { + continue; + } + + moveCost += costSoFar[currentHex.AxialCoords]; + if (!costSoFar.ContainsKey(nextHex.AxialCoords) || moveCost < costSoFar[nextHex.AxialCoords]) { + costSoFar[nextHex.AxialCoords] = moveCost; + float priority = moveCost + nextHex.DistanceTo(goalHex); + + frontier.Enqueue(nextHex.AxialCoords, priority); + cameFrom[nextHex.AxialCoords] = currentHex.AxialCoords; + } + } + } + + // GD.Print("Checked Cell Count: " + FindPathCheckedCellCount); + + List result = new(); + if (!cameFrom.ContainsKey(goalHex.AxialCoords)) { + GD.Print("Failed to find path from " + startHex + " to " + goalHex); + return result; + } + + if (HexGrid.GetHexCost(goalAxialCoords) != 0) { + result.Add(goalHex); + } + + HexCell pathHex = goalHex; + while (pathHex != startHex) { + pathHex = new HexCell(cameFrom[pathHex.AxialCoords]); + result.Insert(0, pathHex); + } + + return result; + } + + public override void _Process(float delta) { GenerationState oldState = State; UpdateGenerationState(); - if (oldState != GenerationState.Done && State == GenerationState.Done) + if (oldState != GenerationState.Done && State == GenerationState.Done) { UpdateWorldViewTexture(); + } + + while (_removedSpatialNodes.Count > 0) { + GD.Print("Queueing deletion of " + _removedSpatialNodes[0]); + _removedSpatialNodes[0].QueueFree(); + _removedSpatialNodes.RemoveAt(0); + } } - private void UpdateGenerationState() - { - FrameCounter++; - - if (State == GenerationState.Heightmap) - { + private void UpdateGenerationState() { + if (State == GenerationState.Heightmap) { int numChunksGeneratingHeightmap = 0; - foreach (Vector2 chunkIndex in _addedChunkIndices) - { + foreach (Vector2 chunkIndex in _addedChunkIndices) { WorldChunk chunk = _cachedWorldChunks[chunkIndex]; - if (chunk.HeightMapFrameCount > 0) numChunksGeneratingHeightmap++; + if (chunk.HeightMapFrameCount > 0) { + numChunksGeneratingHeightmap++; + } } - if (numChunksGeneratingHeightmap == 0) - { + if (numChunksGeneratingHeightmap == 0) { // assign height map images - foreach (Vector2 chunkIndex in _addedChunkIndices) - { + foreach (Vector2 chunkIndex in _addedChunkIndices) { WorldChunk chunk = _cachedWorldChunks[chunkIndex]; chunk.SetHeightmap(chunk.HeightmapOffscreenViewport.GetTexture()); } - GD.Print("Switching to TileType Generation: " + FrameCounter); State = GenerationState.TileType; } - } - else if (State == GenerationState.TileType) - { + } else if (State == GenerationState.TileType) { int numChunksGeneratingTileType = 0; - foreach (Vector2 chunkIndex in _addedChunkIndices) - { + foreach (Vector2 chunkIndex in _addedChunkIndices) { WorldChunk chunk = _cachedWorldChunks[chunkIndex]; - if (chunk.TileTypeMapFrameCount > 0) numChunksGeneratingTileType++; + if (chunk.TileTypeMapFrameCount > 0) { + numChunksGeneratingTileType++; + } } - if (numChunksGeneratingTileType == 0) - { - GD.Print("Switching to Object Generation: " + FrameCounter); + if (numChunksGeneratingTileType == 0) { State = GenerationState.Objects; } - } - else if (State == GenerationState.Objects) - { + } else if (State == GenerationState.Objects) { // generate objects - foreach (Vector2 chunkIndex in _addedChunkIndices) + foreach (Vector2 chunkIndex in _addedChunkIndices) { PopulateChunk(_cachedWorldChunks[chunkIndex]); + } _addedChunkIndices.Clear(); - GD.Print("Generation done: " + FrameCounter); State = GenerationState.Done; } } - private void OnEntityClicked(Entity entity) - { + private void OnEntityClicked(Entity entity) { EmitSignal("EntityClicked", entity); } - public void OnTileClicked(HexTile3D tile) - { + public void OnTileClicked(HexTile3D tile) { EmitSignal("TileClicked", tile); } - public void OnTileHovered(HexTile3D tile) - { + public void OnTileHovered(HexTile3D tile) { EmitSignal("TileHovered", tile); } + + public void OnBlockingSpatialRemoved(Spatial spatialNode) { + if (spatialNode.IsQueuedForDeletion()) { + return; + } + + HexGrid.RemoveObstacle(HexGrid.GetHexAt(new Vector2(spatialNode.GlobalTranslation.x, + spatialNode.GlobalTranslation.z))); + _removedSpatialNodes.Add(spatialNode); + } } \ No newline at end of file diff --git a/scenes/WorldChunk.cs b/scenes/WorldChunk.cs index 7e06846..8dbcc42 100644 --- a/scenes/WorldChunk.cs +++ b/scenes/WorldChunk.cs @@ -3,8 +3,7 @@ using System.Linq; using Godot; using Godot.Collections; -public class WorldChunk : Spatial -{ +public class WorldChunk : Spatial { private readonly PackedScene _hexTile3DScene = GD.Load("res://scenes/HexTile3D.tscn"); private MultiMeshInstance _multiMeshInstance; private readonly Array _tileInstanceIndices = new(); @@ -28,6 +27,7 @@ public class WorldChunk : Spatial [Export] public Texture HeightMap; public int HeightMapFrameCount; + public Image TileTypeImage; public Viewport HeightmapOffscreenViewport; [Export] public Texture NavigationMap; @@ -56,32 +56,30 @@ public class WorldChunk : Spatial public int TileTypeMapFrameCount; public Viewport TileTypeOffscreenViewport; - public WorldChunk() - { - } + public WorldChunk() { } - public WorldChunk(int size) - { + public WorldChunk(int size) { SetSize(size); } [Export] - public bool ShowTextureOverlay - { + public bool ShowTextureOverlay { get => _showTextureOverlay; - set - { - if (PlaneRectMesh != null) PlaneRectMesh.Visible = value; + set { + if (PlaneRectMesh != null) { + PlaneRectMesh.Visible = value; + } } } // Called when the node enters the scene tree for the first time. - public override void _Ready() - { + public override void _Ready() { PlaneRectMesh = (MeshInstance)FindNode("PlaneRectMesh"); Debug.Assert(PlaneRectMesh != null); - if (PlaneRectMesh.Visible) _showTextureOverlay = true; + if (PlaneRectMesh.Visible) { + _showTextureOverlay = true; + } Transform planeRectTransform = Transform.Identity; planeRectTransform = @@ -116,12 +114,10 @@ public class WorldChunk : Spatial SetSize(World.ChunkSize); } - public void SetSize(int size) - { + public void SetSize(int size) { Size = size; - if (TileTypeOffscreenViewport != null) - { + if (TileTypeOffscreenViewport != null) { TileTypeOffscreenViewport.Size = Vector2.One * size; HeightmapOffscreenViewport.Size = Vector2.One * size; _noiseMask.Transform = Transform2D.Identity.Scaled(Vector2.One * size / _noiseMask.Texture.GetSize().x); @@ -131,8 +127,7 @@ public class WorldChunk : Spatial } } - public void SetChunkIndex(Vector2 chunkIndex, HexGrid hexGrid) - { + public void SetChunkIndex(Vector2 chunkIndex, HexGrid hexGrid) { ChunkIndex = chunkIndex; float chunkSize = World.ChunkSize; @@ -148,49 +143,48 @@ public class WorldChunk : Spatial new Vector2(localPlaneCoordSouthWest.x, localPlaneCoordNorthEast.y), new Vector2(localPlaneCoordNorthEast.x - localPlaneCoordSouthWest.x, localPlaneCoordSouthWest.y - localPlaneCoordNorthEast.y) - ); + ); } public void InitializeTileInstances(Vector2 chunkIndex, MultiMeshInstance multiMeshInstance, - int tileInstanceIndexStart) - { + int tileInstanceIndexStart) { _multiMeshInstance = multiMeshInstance; _tileInstanceIndices.Clear(); int chunkSize = World.ChunkSize; - foreach (Spatial node in Tiles.GetChildren()) + foreach (Spatial node in Tiles.GetChildren()) { node.QueueFree(); + } - foreach (int i in Enumerable.Range(0, chunkSize)) - foreach (int j in Enumerable.Range(0, chunkSize)) - { - HexTile3D tile3D = (HexTile3D)_hexTile3DScene.Instance(); - tile3D.Connect("TileClicked", this, nameof(OnTileClicked)); - tile3D.Connect("TileHovered", this, nameof(OnTileHovered)); + foreach (int i in Enumerable.Range(0, chunkSize)) { + foreach (int j in Enumerable.Range(0, chunkSize)) { + HexTile3D tile3D = (HexTile3D)_hexTile3DScene.Instance(); + tile3D.Connect("TileClicked", this, nameof(OnTileClicked)); + tile3D.Connect("TileHovered", this, nameof(OnTileHovered)); - tile3D.Cell.OffsetCoords = new Vector2(chunkIndex * World.ChunkSize + new Vector2(i, j)); - _tileInstanceIndices.Add(tileInstanceIndexStart + _tileInstanceIndices.Count); + tile3D.Cell.OffsetCoords = new Vector2(chunkIndex * World.ChunkSize + new Vector2(i, j)); + _tileInstanceIndices.Add(tileInstanceIndexStart + _tileInstanceIndices.Count); - Transform tileTransform = Transform.Identity; - Vector2 centerPlaneCoord = _hexGrid.GetHexCenterFromOffset(new Vector2(i, j)); - tileTransform.origin = new Vector3(centerPlaneCoord.x, 0, centerPlaneCoord.y); - tile3D.Transform = tileTransform; + Transform tileTransform = Transform.Identity; + Vector2 centerPlaneCoord = _hexGrid.GetHexCenterFromOffset(new Vector2(i, j)); + tileTransform.origin = new Vector3(centerPlaneCoord.x, 0, centerPlaneCoord.y); + tile3D.Transform = tileTransform; - Tiles.AddChild(tile3D); + Tiles.AddChild(tile3D); + } } _multiMeshInstance.Multimesh.VisibleInstanceCount = _multiMeshInstance.Multimesh.InstanceCount; } - public void ClearContent() - { - foreach (Spatial child in Entities.GetChildren()) + public void ClearContent() { + foreach (Spatial child in Entities.GetChildren()) { child.QueueFree(); + } } - public void UpdateTileTransforms() - { + public void UpdateTileTransforms() { Transform chunkTransform = Transform.Identity; Vector2 chunkOriginPlaneCoord = _hexGrid.GetHexCenterFromOffset(ChunkIndex * World.ChunkSize); chunkTransform.origin = new Vector3(chunkOriginPlaneCoord.x, 0, chunkOriginPlaneCoord.y); @@ -198,10 +192,7 @@ public class WorldChunk : Spatial Basis tileOrientation = new(Vector3.Up, 90f * Mathf.Pi / 180f); - GD.Print("Updating transforms for instances of chunk " + ChunkIndex + " origin: " + chunkTransform.origin); - - foreach (int i in Enumerable.Range(0, _tileInstanceIndices.Count)) - { + foreach (int i in Enumerable.Range(0, _tileInstanceIndices.Count)) { int column = i % World.ChunkSize; int row = i / World.ChunkSize; @@ -211,17 +202,17 @@ public class WorldChunk : Spatial Transform hexTransform = new(tileOrientation, chunkTransform.origin + new Vector3(tilePlaneCoord.x, 0, tilePlaneCoord.y)); - if (_showHexTiles) + if (_showHexTiles) { hexTransform = new Transform(tileOrientation.Scaled(Vector3.One * 0.95f), hexTransform.origin); + } _multiMeshInstance.Multimesh.SetInstanceTransform(_tileInstanceIndices[i], hexTransform); } } // other members - public void SaveToFile(string chunkName) - { + public void SaveToFile(string chunkName) { Image image = new(); image.CreateFromData(Size, Size, false, Image.Format.Rgba8, TileTypeMap.GetData().GetData()); @@ -234,13 +225,10 @@ public class WorldChunk : Spatial image.SavePng(chunkName + "_heightMap.png"); } - public void LoadFromFile(string chunkName) - { - } + public void LoadFromFile(string chunkName) { } - public void SetNoisemap(Texture texture) - { + public void SetNoisemap(Texture texture) { _noiseSprite.Texture = texture; _noiseSprite.Transform = Transform2D.Identity.Scaled(HeightmapOffscreenViewport.Size / _noiseSprite.Texture.GetSize().x); @@ -248,8 +236,7 @@ public class WorldChunk : Spatial HeightMapFrameCount = 1; } - public void SetHeightmap(Texture texture) - { + public void SetHeightmap(Texture texture) { _heightmapSprite.Texture = texture; _heightmapSprite.Transform = Transform2D.Identity.Scaled(TileTypeOffscreenViewport.Size / _heightmapSprite.Texture.GetSize()); @@ -258,25 +245,29 @@ public class WorldChunk : Spatial TileTypeMapFrameCount = 1; } - public override void _Process(float delta) - { + public void CreateUnlockedTileTypeImage() { + TileTypeImage = TileTypeOffscreenViewport.GetTexture().GetData(); + TileTypeImage.Lock(); + } + + public override void _Process(float delta) { Texture tileTypeTexture = TileTypeOffscreenViewport.GetTexture(); - if (NoiseTextureCheckerboardOverlay) - { + if (NoiseTextureCheckerboardOverlay) { Image tileTypeImage = tileTypeTexture.GetData(); tileTypeImage.Lock(); - foreach (int i in Enumerable.Range(0, Size)) - foreach (int j in Enumerable.Range(0, Size)) - { - Vector2 textureCoord = new(i, j); - Color baseColor = tileTypeImage.GetPixelv(textureCoord); + foreach (int i in Enumerable.Range(0, Size)) { + foreach (int j in Enumerable.Range(0, Size)) { + Vector2 textureCoord = new(i, j); + Color baseColor = tileTypeImage.GetPixelv(textureCoord); - if ((i + j) % 2 == 0) - tileTypeImage.SetPixelv(textureCoord, baseColor); - else - tileTypeImage.SetPixelv(textureCoord, baseColor * 0.6f); + if ((i + j) % 2 == 0) { + tileTypeImage.SetPixelv(textureCoord, baseColor); + } else { + tileTypeImage.SetPixelv(textureCoord, baseColor * 0.6f); + } + } } tileTypeImage.Unlock(); @@ -295,24 +286,26 @@ public class WorldChunk : Spatial //RectMaterial.Uv1Triplanar = true; PlaneRectMesh.SetSurfaceMaterial(0, _rectMaterial); - if (HeightMapFrameCount == 0) HeightmapOffscreenViewport.RenderTargetUpdateMode = Viewport.UpdateMode.Disabled; + if (HeightMapFrameCount == 0) { + HeightmapOffscreenViewport.RenderTargetUpdateMode = Viewport.UpdateMode.Disabled; + } HeightMapFrameCount = HeightMapFrameCount > 0 ? HeightMapFrameCount - 1 : 0; - if (TileTypeMapFrameCount == 0) TileTypeOffscreenViewport.RenderTargetUpdateMode = Viewport.UpdateMode.Disabled; + if (TileTypeMapFrameCount == 0) { + TileTypeOffscreenViewport.RenderTargetUpdateMode = Viewport.UpdateMode.Disabled; + } TileTypeMapFrameCount = TileTypeMapFrameCount > 0 ? TileTypeMapFrameCount - 1 : 0; PlaneRectMesh.MaterialOverride = null; } - public void OnTileClicked(HexTile3D tile) - { + public void OnTileClicked(HexTile3D tile) { EmitSignal("TileClicked", tile); } - public void OnTileHovered(HexTile3D tile) - { + public void OnTileHovered(HexTile3D tile) { EmitSignal("TileHovered", tile); } } \ No newline at end of file diff --git a/systems/InteractionSystem.cs b/systems/InteractionSystem.cs index 01b2710..aa186f8 100644 --- a/systems/InteractionSystem.cs +++ b/systems/InteractionSystem.cs @@ -1,84 +1,68 @@ -using Godot; -using System; using System.Collections.Generic; -using Godot.Collections; +using Godot; using GodotComponentTest.components; using GodotComponentTest.entities; -using Array = System.Array; using NodePair = System.Tuple; -public class InteractionSystem : Node -{ +public class InteractionSystem : Node { private List _activeInteractions; // Called when the node enters the scene tree for the first time. - public override void _Ready() - { + public override void _Ready() { _activeInteractions = new List(); } - public override void _Process(float delta) - { + public override void _Process(float delta) { base._Process(delta); - List invalidInteractionPairs = new List(); - List endedInteractions = new List(); + List endedInteractions = new(); - foreach (InteractionComponent interaction in _activeInteractions) - { + foreach (InteractionComponent interaction in _activeInteractions) { Spatial owningEntity = interaction.OwningEntity; Spatial targetEntity = interaction.TargetEntity; - if (owningEntity == null || owningEntity.IsQueuedForDeletion() || targetEntity == null || targetEntity.IsQueuedForDeletion()) - { + if (owningEntity == null || owningEntity.IsQueuedForDeletion() || targetEntity == null || + targetEntity.IsQueuedForDeletion()) { interaction.hasStopped = true; } - - if (interaction.hasStopped) - { + + if (interaction.hasStopped) { IInteractionInterface interactableA = owningEntity as IInteractionInterface; - if (interactableA != null) - { + if (interactableA != null) { interactableA.OnInteractionEnd(); interactableA.InteractionComponent = null; } - + IInteractionInterface interactableB = targetEntity as IInteractionInterface; - if (interactableB != null) - { + if (interactableB != null) { interactableB.OnInteractionEnd(); interactableB.InteractionComponent = null; } - + endedInteractions.Add(interaction); } } - + foreach (InteractionComponent interaction in endedInteractions) - { _activeInteractions.Remove(interaction); - } } - public void OnStartInteraction(Entity owningEntity, Entity targetEntity) - { - InteractionComponent interactionComponent = new InteractionComponent(); + public void OnStartInteraction(Entity owningEntity, Entity targetEntity) { + InteractionComponent interactionComponent = new(); interactionComponent.OwningEntity = owningEntity; interactionComponent.TargetEntity = targetEntity; - + ConnectInteractionSignals(owningEntity, interactionComponent); ConnectInteractionSignals(targetEntity, interactionComponent); - + interactionComponent.EmitSignal("InteractionStart"); _activeInteractions.Add(interactionComponent); } - private static void ConnectInteractionSignals(Entity entity, InteractionComponent interactionComponent) - { + private static void ConnectInteractionSignals(Entity entity, InteractionComponent interactionComponent) { IInteractionInterface interactable = entity as IInteractionInterface; - if (interactable != null) - { + if (interactable != null) { interactable.InteractionComponent = interactionComponent; interactionComponent.Connect("InteractionStart", entity, nameof(interactable.OnInteractionStart)); interactionComponent.Connect("InteractionEnd", entity, nameof(interactable.OnInteractionEnd));