Compare commits

...

5 Commits

Author SHA1 Message Date
Martin Felis 1fdaa83a6e Interaction now working with streamed entities. Yay! 2023-11-10 16:26:16 +01:00
Martin Felis b968f9b3b2 Navigation with plange angle targets now functional again. 2023-11-10 12:22:17 +01:00
Martin Felis c59d92618b Tile instancing now working, also fixed navigation bounds. 2023-11-10 11:11:08 +01:00
Martin Felis eaaa5219ed Optimized HexTile highlight geometry 2023-11-10 11:10:16 +01:00
Martin Felis 1ba28ab06c HexGrid: allow setting bounds via offset coordinate rectangle. 2023-11-10 10:44:18 +01:00
20 changed files with 575 additions and 629 deletions

View File

@ -1,9 +1,14 @@
using System;
using Godot;
public static class Globals
{
public const float EpsPosition = 0.01f;
public const float EpsPositionSquared = EpsPosition * EpsPosition;
public const float EpsRadians = 0.1f * Godot.Mathf.Pi / 180f;
public const float EpsRadians = 0.1f * Mathf.Pi / 180f;
public const float EpsRadiansSquared = EpsRadians * EpsRadians;
public static float CalcPlaneAngle(Transform worldTransform)
{
return worldTransform.basis.x.SignedAngleTo(Vector3.Right.Rotated(Vector3.Up, Mathf.Pi * 0.5f), -Vector3.Up);
}
}

View File

@ -7,7 +7,8 @@ using AxialCoordDirectionPair = System.Tuple<Godot.Vector2, Godot.Vector3>;
public class HexGrid : Resource
{
private readonly Vector2 _baseHexSize = new(1, Mathf.Sqrt(3) / 2);
private Rect2 _boundsAxialCoords;
private Rect2 _boundsAxialCoords = new(-Vector2.Inf, Vector2.Inf);
private Rect2 _boundsOffsetCoords = new(-Vector2.Inf, Vector2.Inf);
private Vector2 _hexScale = new(1, 1);
private Vector2 _hexSize = new(1, Mathf.Sqrt(3) / 2);
private Transform2D _hexTransform;
@ -100,6 +101,12 @@ public class HexGrid : Resource
GetHexAtOffset(centerOffset + Vector2.One * size / 2));
}
public void SetBoundsOffset(HexCell cellSouthEast, Vector2 size)
{
_boundsOffsetCoords = new Rect2(cellSouthEast.OffsetCoords, size);
_boundsAxialCoords = new Rect2(-Vector2.Inf, Vector2.Inf);
}
public void AddObstacle(Vector2 axialCoords, float cost = 0)
{
AddObstacle(new HexCell(axialCoords), cost);
@ -140,6 +147,8 @@ public class HexGrid : Resource
{
if (!_boundsAxialCoords.HasPoint(axialCoords)) return 0;
if (!_boundsOffsetCoords.HasPoint(new HexCell(axialCoords).OffsetCoords)) return 0;
float value;
return Obstacles.TryGetValue(axialCoords, out value) ? value : PathCostDefault;
}

View File

@ -8,4 +8,4 @@ top_radius = 0.5
bottom_radius = 0.5
height = 5.0
radial_segments = 6
rings = 1
rings = 0

View File

@ -1,8 +1,4 @@
using System;
using System.Numerics;
using Godot;
using Vector2 = Godot.Vector2;
using Vector3 = Godot.Vector3;
/// <summary>
/// </summary>
@ -10,23 +6,21 @@ public class GroundMotionComponent : Component
{
public float Accel = 50;
public float Damping = 0.2f;
public float MaxSpeed = 8;
public float RotationSpeedRadPerSecond = 270 * Mathf.Pi / 180;
public Vector3 DirectionToTarget = Vector3.Zero;
public float DistanceToTarget = 0;
public float TargetAngle = 0;
public float TargetDeltaAngle = 0;
private void CalcAndApplyOrientation(float delta, Entity entity, Vector3 targetPosition, Quat targetOrientation)
public float DistanceToTarget;
public float MaxSpeed = 8;
public float RotationSpeedRadPerSecond = 270 * Mathf.Pi / 180;
public float TargetAngle;
public float TargetDeltaAngle;
private void CalcAndApplyOrientation(float delta, Entity entity, Vector3 targetPosition, float targetAngle)
{
float deltaAngleAbsolute = Mathf.Abs(TargetDeltaAngle);
if (deltaAngleAbsolute > 0.001)
{
if (RotationSpeedRadPerSecond * delta + 0.001 >= deltaAngleAbsolute)
if (RotationSpeedRadPerSecond * delta >= deltaAngleAbsolute)
{
GD.Print("Target Angle " + TargetAngle + " reached! Current Angle: " + entity.PlaneAngle + " TargetDeltaAngle = " + TargetDeltaAngle);
entity.PlaneAngle = TargetAngle;
TargetDeltaAngle = 0;
}
@ -40,24 +34,26 @@ public class GroundMotionComponent : Component
TargetDeltaAngle = 0;
}
}
private void CalcPlaneVelocity(float delta, Entity entity, Vector3 targetPosition)
{
Vector2 planeTargetDirection = new Vector2(DirectionToTarget.x, DirectionToTarget.z);
Vector2 planeVelocity = new Vector2(entity.Velocity.x, entity.Velocity.z);
// GD.Print("-- Step: distance: " + targetDistance + " dir: " + planeTargetDirection + " speed: " + planeVelocity.Length() + " vel dir: " + planeVelocity.Normalized());
planeVelocity -= planeVelocity * Damping;
// GD.Print(" damp : speed: " + planeVelocity.Length() + " vel dir: " + planeVelocity.Normalized());
if (DistanceToTarget < 0.01)
{
planeVelocity = Vector2.Zero;
} else if (TargetDeltaAngle == 0.0) {
}
else if (TargetDeltaAngle == 0.0)
{
planeVelocity = planeVelocity.Length() * planeTargetDirection + planeTargetDirection * Accel * delta;
// GD.Print(" accel: speed: " + planeVelocity.Length() + " vel dir: " + planeVelocity.Normalized());
float projectedStep = planeTargetDirection.Dot(planeVelocity * delta);
var projectedStep = planeTargetDirection.Dot(planeVelocity * delta);
// GD.Print(" Projected step: " + projectedStep + " Speed: " + planeVelocity.Length() + " delta: " + delta);
if (projectedStep > DistanceToTarget)
{
@ -65,12 +61,9 @@ public class GroundMotionComponent : Component
projectedStep = planeTargetDirection.Dot(planeVelocity * delta);
// GD.Print(" corr speed: " + planeVelocity.Length() + " step: " + projectedStep);
}
float planeSpeed = planeVelocity.Length();
if (planeSpeed > MaxSpeed)
{
planeVelocity *= MaxSpeed / planeSpeed;
}
var planeSpeed = planeVelocity.Length();
if (planeSpeed > MaxSpeed) planeVelocity *= MaxSpeed / planeSpeed;
}
else
{
@ -94,7 +87,7 @@ public class GroundMotionComponent : Component
float nextHeight = tileWorld.GetHeightAtOffset(nextTile);
bool isOnFloor = entity.IsOnFloor();
if (nextHeight - entity.GlobalTransform.origin.y > 0.1)
{
GD.Print("Jump!");
@ -107,14 +100,14 @@ public class GroundMotionComponent : Component
}
entityVelocity.y -= 9.81f * delta;
entity.Velocity = entityVelocity;
}
public void PhysicsProcess(float delta, Entity entity, Vector3 targetPosition, Quat targetOrientation, TileWorld tileWorld)
public void PhysicsProcess(float delta, Entity entity, Vector3 targetPosition, float targetAngle,
World world)
{
DirectionToTarget = (targetPosition - entity.GlobalTranslation);
DirectionToTarget = targetPosition - entity.GlobalTranslation;
DirectionToTarget.y = 0;
DistanceToTarget = DirectionToTarget.Length();
@ -122,27 +115,38 @@ public class GroundMotionComponent : Component
{
DirectionToTarget = DirectionToTarget.Normalized();
TargetAngle = Vector3.Right.SignedAngleTo(DirectionToTarget, Vector3.Up);
TargetDeltaAngle = entity.CalcShortestPlaneRotationToTargetDirection(DirectionToTarget);
TargetDeltaAngle = entity.CalcShortestPlaneRotationToTargetDirection(DirectionToTarget);
}
else
{
GD.Print("Target Position reached!");
DirectionToTarget = Vector3.Right;
TargetAngle = entity.PlaneAngle;
TargetDeltaAngle = 0;
entity.GlobalTranslation = targetPosition;
entity.Velocity = new Vector3(0, entity.Velocity.y, 0);
return;
if (entity.PlaneAngle != targetAngle)
{
Vector3 directionToTarget = Vector3.Right.Rotated(Vector3.Up, targetAngle);
TargetAngle = targetAngle;
TargetDeltaAngle = entity.CalcShortestPlaneRotationToTargetDirection(directionToTarget);
if (Mathf.Abs(TargetDeltaAngle) < Globals.EpsRadians)
{
TargetAngle = entity.PlaneAngle;
TargetDeltaAngle = 0;
return;
}
}
}
CalcAndApplyOrientation(delta, entity, targetPosition, targetOrientation);
CalcAndApplyOrientation(delta, entity, targetPosition, targetAngle);
CalcPlaneVelocity(delta, entity, targetPosition);
// CalcVerticalVelocity(delta, entity, tileWorld);
entity.Velocity = entity.MoveAndSlide(entity.Velocity);
//GD.Print("Pre : speed: " + prePhysicsVelocity.Length() + " Velocity: " + prePhysicsVelocity);
//GD.Print("Post: speed: " + entity.Velocity.Length() + " Velocity: " + entity.Velocity);
}
}

View File

@ -4,92 +4,22 @@ using System.Diagnostics;
using System.Linq;
using Godot;
using GodotComponentTest.utils;
using Vector2 = Godot.Vector2;
using Vector3 = Godot.Vector3;
/// <summary>
/// </summary>
public class NavigationComponent : Spatial
{
public class NavigationPoint
{
[Flags]
public enum NavigationFlags
{
Position = 1,
Orientation = 2
}
public Vector3 WorldPosition = Vector3.Zero;
public Quat WorldOrientation = Quat.Identity;
public readonly NavigationFlags Flags;
public NavigationPoint(Vector3 worldPosition)
{
WorldPosition = worldPosition;
Flags = NavigationFlags.Position;
}
public NavigationPoint(Quat worldOrientation)
{
WorldOrientation = worldOrientation;
Flags = NavigationFlags.Orientation;
}
public NavigationPoint(Transform worldTransform)
{
WorldPosition = worldTransform.origin;
WorldOrientation = worldTransform.basis.Quat();
Flags = NavigationFlags.Position | NavigationFlags.Orientation;
}
public bool IsReached(Transform worldTransform)
{
bool goalReached = false;
Vector2 positionError = new Vector2(WorldPosition.x - worldTransform.origin.x,
WorldPosition.z - worldTransform.origin.z);
float positionErrorSquared = positionError.LengthSquared();
worldTransform.basis.Quat();
float orientationError = Mathf.Abs(worldTransform.basis.Quat().AngleTo(WorldOrientation));
if (Flags.HasFlag(NavigationFlags.Position)
&& Flags.HasFlag(NavigationFlags.Orientation)
&& positionErrorSquared < Globals.EpsPositionSquared
&& orientationError < Globals.EpsRadians)
{
goalReached = true;
}
else if (Flags == NavigationFlags.Position &&
positionErrorSquared < Globals.EpsPositionSquared)
{
goalReached = true;
}
else if (Flags == NavigationFlags.Orientation &&
orientationError < Globals.EpsRadians)
{
goalReached = true;
}
return goalReached;
}
}
public TileWorld TileWorld { set; get; }
public Vector3 CurrentGoalPositionWorld => _currentGoalPositionWorld;
public Quat CurrentGoalOrientationWorld => _currentGoalOrientationWorld;
public World World { set; get; }
public Vector3 CurrentGoalPositionWorld { get; private set; } = Vector3.Zero;
public float CurrentGoalAngleWorld { get; private set; }
private NavigationPoint _currentGoal;
private Vector3 _currentGoalPositionWorld = Vector3.Zero;
private float _currentGoalAngleWorld = 0;
private Quat _currentGoalOrientationWorld = Quat.Identity;
private List<NavigationPoint> _planningPathWorldNavigationPoints = new List<NavigationPoint>();
private List<NavigationPoint> _planningPathSmoothedWorldNavigationPoints = new List<NavigationPoint>();
private List<NavigationPoint> _pathWorldNavigationPoints = new List<NavigationPoint>();
private List<NavigationPoint> _smoothedPathWorldNavigationPoints = new List<NavigationPoint>();
private HexCell[] _path;
private List<NavigationPoint> _pathWorldNavigationPoints = new();
private List<NavigationPoint> _planningPathSmoothedWorldNavigationPoints = new();
private List<NavigationPoint> _planningPathWorldNavigationPoints = new();
private List<NavigationPoint> _smoothedPathWorldNavigationPoints = new();
public override void _Ready()
{
@ -99,9 +29,9 @@ public class NavigationComponent : Spatial
public override void _Process(float delta)
{
Debug.Assert(TileWorld != null);
Debug.Assert(World != null);
}
public void PlanSmoothedPath(KinematicBody body, Transform fromTransformWorld, NavigationPoint navigationPoint)
{
if (navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Position)
@ -121,8 +51,8 @@ public class NavigationComponent : Spatial
public void FindPath(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld)
{
HexCell fromCell = TileWorld.HexGrid.GetHexAt(new Vector2(fromPositionWorld.x, fromPositionWorld.z));
if (TileWorld.HexGrid.GetHexCost(fromCell) == 0)
var 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<NavigationPoint>();
@ -130,11 +60,11 @@ public class NavigationComponent : Spatial
_planningPathSmoothedWorldNavigationPoints = _planningPathWorldNavigationPoints;
return;
}
HexCell toCell = TileWorld.HexGrid.GetHexAt(new Vector2(toPositionWorld.x, toPositionWorld.z));
toCell = TileWorld.HexGrid.GetClosestWalkableCell(fromCell, toCell);
if (TileWorld.HexGrid.GetHexCost(toCell) == 0)
var toCell = World.HexGrid.GetHexAt(new Vector2(toPositionWorld.x, toPositionWorld.z));
toCell = World.HexGrid.GetClosestWalkableCell(fromCell, toCell);
if (World.HexGrid.GetHexCost(toCell) == 0)
{
GD.Print("Invalid target point for FindPath(): returning empty path.");
_planningPathWorldNavigationPoints = new List<NavigationPoint>();
@ -142,25 +72,26 @@ public class NavigationComponent : Spatial
_planningPathSmoothedWorldNavigationPoints = _planningPathWorldNavigationPoints;
return;
}
TileWorld.HexGrid.SetBounds(fromCell, 40);
List<HexCell> path = TileWorld.HexGrid.FindPath(fromCell, toCell);
var path = World.HexGrid.FindPath(fromCell, toCell);
// Generate grid navigation points
_planningPathWorldNavigationPoints = new List<NavigationPoint>();
foreach (int index in Enumerable.Range(0, path.Count))
foreach (var index in Enumerable.Range(0, path.Count))
{
_planningPathWorldNavigationPoints.Add(
new NavigationPoint(TileWorld.GetHexCenterFromOffset(path[index].OffsetCoords)));
new NavigationPoint(World.HexGrid.GetHexCenterVec3FromOffset(path[index].OffsetCoords)));
}
// Ensure the last point coincides with the target position
if (_planningPathWorldNavigationPoints.Count > 0 && (_planningPathWorldNavigationPoints.Last().WorldPosition - toPositionWorld).LengthSquared() <
if (_planningPathWorldNavigationPoints.Count > 0 &&
(_planningPathWorldNavigationPoints.Last().WorldPosition - toPositionWorld).LengthSquared() <
0.5f * 0.5f)
{
_planningPathWorldNavigationPoints[_planningPathWorldNavigationPoints.Count - 1].WorldPosition = toPositionWorld;
_planningPathWorldNavigationPoints[_planningPathWorldNavigationPoints.Count - 1].WorldPosition =
toPositionWorld;
}
// Perform smoothing
_planningPathSmoothedWorldNavigationPoints = SmoothPath(body, _planningPathWorldNavigationPoints);
@ -176,18 +107,19 @@ public class NavigationComponent : Spatial
FindPath(body, fromPositionWorld, navigationPoint.WorldPosition);
_planningPathWorldNavigationPoints[_planningPathWorldNavigationPoints.Count - 1] = navigationPoint;
_planningPathSmoothedWorldNavigationPoints[_planningPathSmoothedWorldNavigationPoints.Count - 1] = navigationPoint;
_planningPathSmoothedWorldNavigationPoints[_planningPathSmoothedWorldNavigationPoints.Count - 1] =
navigationPoint;
}
public void PlanGridPath(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld)
{
Vector2 fromPositionOffset = TileWorld.WorldToOffsetCoords(fromPositionWorld);
Vector2 toPositionOffset = TileWorld.WorldToOffsetCoords(toPositionWorld);
var fromPositionOffset = World.WorldToOffsetCoords(fromPositionWorld);
var toPositionOffset = World.WorldToOffsetCoords(toPositionWorld);
HexCell fromCell = new HexCell();
var fromCell = new HexCell();
fromCell.OffsetCoords = fromPositionOffset;
HexCell toCell = new HexCell();
var toCell = new HexCell();
toCell.OffsetCoords = toPositionOffset;
_path = fromCell.LineTo(toCell);
@ -195,15 +127,15 @@ public class NavigationComponent : Spatial
_pathWorldNavigationPoints = new List<NavigationPoint>();
_pathWorldNavigationPoints.Add(
new NavigationPoint(TileWorld.GetHexCenterFromOffset(fromPositionOffset)));
new NavigationPoint(World.HexGrid.GetHexCenterVec3FromOffset(fromPositionOffset)));
foreach (int index in Enumerable.Range(1, _path.Length - 1))
foreach (var index in Enumerable.Range(1, _path.Length - 1))
{
_pathWorldNavigationPoints.Add(
new NavigationPoint(TileWorld.GetHexCenterFromOffset(_path[index].OffsetCoords)));
new NavigationPoint(World.GetHexCenterFromOffset(_path[index].OffsetCoords)));
}
if ((fromPositionWorld - TileWorld.GetHexCenterFromOffset(toCell.OffsetCoords)).LengthSquared() >
if ((fromPositionWorld - World.GetHexCenterFromOffset(toCell.OffsetCoords)).LengthSquared() >
Globals.EpsPositionSquared)
{
// Remove the last one, because it is only the position rounded to HexGrid coordinates.
@ -287,7 +219,7 @@ public class NavigationComponent : Spatial
return false;
}
public bool CheckSweptTriangleCellCollision(Vector3 startWorld, Vector3 endWorld, float radius)
{
Vector2 startPlane = new Vector2(startWorld.x, startWorld.z);
@ -295,36 +227,37 @@ public class NavigationComponent : Spatial
Vector2 directionPlane = (endPlane - startPlane).Normalized();
Vector2 sidePlane = directionPlane.Rotated(Mathf.Pi * 0.5f);
List<HexCell> cells = TileWorld.HexGrid.GetCellsForLine(startPlane + directionPlane * radius, endPlane + directionPlane * radius);
List<HexCell> cells =
World.HexGrid.GetCellsForLine(startPlane + directionPlane * radius, endPlane + directionPlane * radius);
foreach (HexCell cell in cells)
{
if (TileWorld.HexGrid.GetHexCost(cell) == 0)
if (World.HexGrid.GetHexCost(cell) == 0)
{
return true;
}
}
cells = TileWorld.HexGrid.GetCellsForLine(startPlane + sidePlane * radius, endPlane + sidePlane * radius);
cells = World.HexGrid.GetCellsForLine(startPlane + sidePlane * radius, endPlane + sidePlane * radius);
foreach (HexCell cell in cells)
{
if (TileWorld.HexGrid.GetHexCost(cell) == 0)
if (World.HexGrid.GetHexCost(cell) == 0)
{
return true;
}
}
cells = TileWorld.HexGrid.GetCellsForLine(startPlane - sidePlane * radius, endPlane - sidePlane * radius);
cells = World.HexGrid.GetCellsForLine(startPlane - sidePlane * radius, endPlane - sidePlane * radius);
foreach (HexCell cell in cells)
{
if (TileWorld.HexGrid.GetHexCost(cell) == 0)
if (World.HexGrid.GetHexCost(cell) == 0)
{
return true;
}
}
return false;
}
public List<NavigationPoint> SmoothPath(KinematicBody body, List<NavigationPoint> navigationPoints)
{
if (navigationPoints.Count <= 2)
@ -360,7 +293,7 @@ public class NavigationComponent : Spatial
continue;
}
if (endIndex == navigationPoints.Count - 1)
{
break;
@ -407,23 +340,19 @@ public class NavigationComponent : Spatial
}
_currentGoal = _pathWorldNavigationPoints[0];
_currentGoalPositionWorld = _pathWorldNavigationPoints[0].WorldPosition;
//_currentGoalOrientationWorld = Vector3.Right.SignedAngleTo(_pathWorldNavigationPoints[0].WorldOrientation);
// GD.Print("Navigation Goal: pos " + _currentGoal.WorldPosition + " " + " rot: " + _currentGoal.WorldOrientation +
// " flags: " + _currentGoal.Flags + " path length: " +
// _pathWorldNavigationPoints.Count);
CurrentGoalPositionWorld = _pathWorldNavigationPoints[0].WorldPosition;
CurrentGoalAngleWorld = _pathWorldNavigationPoints[0].WorldAngle;
}
private void ApplyExistingTransform(Transform worldTransform)
{
if (_currentGoal.Flags == NavigationPoint.NavigationFlags.Orientation)
{
_currentGoalPositionWorld = worldTransform.origin;
CurrentGoalPositionWorld = worldTransform.origin;
}
else if (_currentGoal.Flags == NavigationPoint.NavigationFlags.Position)
{
_currentGoalOrientationWorld = worldTransform.basis.Quat();
CurrentGoalAngleWorld = Globals.CalcPlaneAngle(worldTransform);
}
}
@ -434,30 +363,30 @@ public class NavigationComponent : Spatial
{
_currentGoal = new NavigationPoint(currentTransformWorld);
}
if (_pathWorldNavigationPoints.Count == 0)
{
_currentGoalOrientationWorld = currentTransformWorld.basis.Quat();
_currentGoalPositionWorld = currentTransformWorld.origin;
CurrentGoalAngleWorld = Globals.CalcPlaneAngle(currentTransformWorld);
CurrentGoalPositionWorld = currentTransformWorld.origin;
return;
}
if (_currentGoal.Flags.HasFlag(NavigationPoint.NavigationFlags.Position))
{
_currentGoalPositionWorld = _pathWorldNavigationPoints[0].WorldPosition;
CurrentGoalPositionWorld = _pathWorldNavigationPoints[0].WorldPosition;
}
else
{
_currentGoalPositionWorld = currentTransformWorld.origin;
CurrentGoalAngleWorld = Globals.CalcPlaneAngle(currentTransformWorld);
}
if (_currentGoal.Flags.HasFlag(NavigationPoint.NavigationFlags.Orientation))
{
_currentGoalOrientationWorld = _currentGoal.WorldOrientation;
CurrentGoalAngleWorld = _currentGoal.WorldAngle;
}
else
{
_currentGoalOrientationWorld = currentTransformWorld.basis.Quat();
CurrentGoalAngleWorld = Globals.CalcPlaneAngle(currentTransformWorld);
}
if (_currentGoal.IsReached(currentTransformWorld))
@ -470,11 +399,16 @@ public class NavigationComponent : Spatial
if (_pathWorldNavigationPoints.Count == 0)
{
_currentGoalOrientationWorld = currentTransformWorld.basis.Quat();
_currentGoalPositionWorld = currentTransformWorld.origin;
CurrentGoalPositionWorld = currentTransformWorld.origin;
CurrentGoalAngleWorld = Globals.CalcPlaneAngle(currentTransformWorld);
}
}
public bool IsGoalReached()
{
return _pathWorldNavigationPoints.Count == 0;
}
public void DebugDraw(Spatial parentNode, DebugGeometry debugGeometry)
{
Vector3 yOffset = Vector3.Up * 0.1f;
@ -511,7 +445,7 @@ public class NavigationComponent : Spatial
previousPoint = point.WorldPosition;
}
previousPoint = parentNode.GlobalTranslation;
foreach (NavigationPoint point in _planningPathWorldNavigationPoints)
{
@ -521,7 +455,7 @@ public class NavigationComponent : Spatial
previousPoint = point.WorldPosition;
}
previousPoint = parentNode.GlobalTranslation;
foreach (NavigationPoint point in _planningPathSmoothedWorldNavigationPoints)
{

View File

@ -1,30 +1,60 @@
using Godot;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using GodotComponentTest.entities;
using Object = Godot.Object;
using Godot;
public class TaskQueueComponent : Component
{
[Signal]
delegate void StartInteraction(Entity entity, Entity targetEntity);
private delegate void StartInteraction(Entity entity, Entity targetEntity);
public Queue<Task> Queue;
public TaskQueueComponent()
{
Queue = new Queue<Task>();
Reset();
}
public void Reset()
{
Queue.Clear();
}
public void Process(Entity entity, float delta)
{
if (Queue.Count == 0) return;
do
{
var currentTask = Queue.Peek();
var interactionTask = currentTask as InteractionTask;
if (interactionTask != null) EmitSignal("StartInteraction", entity, interactionTask.TargetEntity);
if (currentTask.PerformTask(entity, delta))
Queue.Dequeue();
else
break;
} while (Queue.Count > 0);
}
public abstract class Task : Object
{
/// <summary>
/// Executes a Task on the specified entity.
/// Executes a Task on the specified entity.
/// </summary>
/// <returns>true when the Task is complete, false otherwise</returns>
public abstract bool PerformTask(Entity entity, float delta);
}
/// <summary>
/// Makes an entity move towards a target (translation and or orientation).
/// </summary>
public class NavigationTask : Task
{
public NavigationComponent.NavigationPoint NavigationPoint;
public NavigationPoint NavigationPoint;
public bool PlanningComplete = false;
public NavigationTask(NavigationComponent.NavigationPoint navigationPoint)
public NavigationTask(NavigationPoint navigationPoint)
{
NavigationPoint = navigationPoint;
}
@ -49,45 +79,4 @@ public class TaskQueueComponent : Component
return true;
}
}
public Queue<Task> Queue;
public TaskQueueComponent()
{
Queue = new Queue<Task>();
Reset();
}
public void Reset()
{
Queue.Clear();
}
public void Process(Entity entity, float delta)
{
if (Queue.Count == 0)
{
return;
}
do
{
Task currentTask = Queue.Peek();
InteractionTask interactionTask = currentTask as InteractionTask;
if (interactionTask != null)
{
EmitSignal("StartInteraction", entity, interactionTask.TargetEntity);
}
if (currentTask.PerformTask(entity, delta))
{
Queue.Dequeue();
}
else
{
break;
}
} while (Queue.Count > 0);
}
}

View File

@ -1,20 +1,18 @@
using Godot;
using System;
public class WorldInfoComponent : Component
{
[Export] public NodePath World;
public TileWorld TileWorld;
[Export] public NodePath WorldPath;
public World World;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
TileWorld = GetNode<TileWorld>(World);
World = GetNode<World>(WorldPath);
}
public void SetWorld(TileWorld tileWorld)
public void SetWorld(World world)
{
TileWorld = tileWorld;
World = world;
}
}

View File

@ -9,7 +9,7 @@ public class Entity : KinematicBody
/** Defines the angle in plane coordinates, 0 => pointing to the right/east, pi/2 pointing up/north, range [-pi,pi]. */
public float PlaneAngle
{
get => GlobalTransform.basis.x.SignedAngleTo(Vector3.Right.Rotated(Vector3.Up, Mathf.Pi * 0.5f), -Vector3.Up);
get => Globals.CalcPlaneAngle(GlobalTransform);
set => GlobalTransform = new Transform(new Basis(Vector3.Up, value + Mathf.Pi * 0.5f), GlobalTranslation);
}

View File

@ -1,8 +1,6 @@
using Godot;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Godot;
using GodotComponentTest.components;
using GodotComponentTest.entities;
using GodotComponentTest.utils;
@ -10,52 +8,40 @@ using GodotComponentTest.utils;
public class Player : Entity, IInteractionInterface
{
// public members
[Export] public NodePath TileWorldNode;
public int GoldCount = 0;
[Export] public NodePath WorldNode;
public int GoldCount;
public TaskQueueComponent TaskQueueComponent;
public NavigationComponent Navigation
{
get { return _navigationComponent; }
}
public NavigationComponent NavigationComponent { get; private set; }
public InteractionComponent InteractionComponent { get; set; }
[Signal]
delegate void GoldCountChanged(int goldCount);
private delegate void GoldCountChanged(int goldCount);
// private members
private WorldInfoComponent _worldInfo;
private GroundMotionComponent _groundMotion;
private NavigationComponent _navigationComponent;
private Area _itemAttractorArea;
private Area _itemPickupArea;
private List<Node> _attractedItemList = new List<Node>();
private BoneAttachment _toolAttachement;
private AnimationPlayer _playerAnimationPlayer;
private readonly List<Node> _attractedItemList = new();
private AnimationTree _animationTree;
private DebugGeometry _debugGeometry;
private InteractionComponent _interactionComponent;
public InteractionComponent InteractionComponent
{
get => _interactionComponent;
set => _interactionComponent = value;
}
private GroundMotionComponent _groundMotion;
private Area _itemAttractorArea;
private Area _itemPickupArea;
private AnimationPlayer _playerAnimationPlayer;
private BoneAttachment _toolAttachement;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
_groundMotion = new GroundMotionComponent();
_worldInfo = (WorldInfoComponent)FindNode("WorldInfo", false);
_navigationComponent = (NavigationComponent)FindNode("Navigation", false);
_navigationComponent.TileWorld = GetNode<TileWorld>(TileWorldNode);
NavigationComponent = (NavigationComponent)FindNode("Navigation", false);
NavigationComponent.World = GetNode<World>(WorldNode);
TaskQueueComponent = new TaskQueueComponent();
_itemAttractorArea = (Area)FindNode("ItemAttractorArea", false);
if (_itemAttractorArea == null)
{
GD.PushWarning("No ItemAttractorArea node found for " + this.GetClass());
GD.PushWarning("No ItemAttractorArea node found for " + GetClass());
}
else
{
@ -65,27 +51,20 @@ public class Player : Entity, IInteractionInterface
_itemPickupArea = (Area)FindNode("ItemPickupArea", false);
if (_itemPickupArea == null)
{
GD.PushWarning("No ItemPickupArea node found for " + this.GetClass());
}
GD.PushWarning("No ItemPickupArea node found for " + GetClass());
else
{
_itemPickupArea.Connect("body_entered", this, nameof(OnItemPickupAreaBodyEntered));
}
_playerAnimationPlayer = GetNode<AnimationPlayer>("Geometry/AnimationPlayer");
Debug.Assert(_playerAnimationPlayer != null);
_animationTree = GetNode<AnimationTree>("Geometry/AnimationTree");
Debug.Assert(_animationTree != null);
AnimationNodeStateMachinePlayback stateMachine =
var stateMachine =
(AnimationNodeStateMachinePlayback)_animationTree.Get("parameters/playback");
stateMachine.Start("Idle");
_toolAttachement = (BoneAttachment)FindNode("ToolAttachement");
if (_toolAttachement == null)
{
GD.PushWarning("No ToolAttachement found!");
}
if (_toolAttachement == null) GD.PushWarning("No ToolAttachement found!");
_debugGeometry = (DebugGeometry)FindNode("DebugGeometry");
}
@ -95,7 +74,7 @@ public class Player : Entity, IInteractionInterface
{
base._PhysicsProcess(delta);
if (_navigationComponent == null)
if (NavigationComponent == null)
{
return;
}
@ -111,15 +90,18 @@ public class Player : Entity, IInteractionInterface
if (navigationTask != null && navigationTask.PlanningComplete == false)
{
// _navigationComponent.PlanGridPath(this, GlobalTransform, navigationTask.NavigationPoint);
_navigationComponent.PlanSmoothedPath(this, GlobalTransform, navigationTask.NavigationPoint);
NavigationComponent.PlanSmoothedPath(this, GlobalTransform, navigationTask.NavigationPoint);
_navigationComponent.ActivatePlannedPath();
NavigationComponent.ActivatePlannedPath();
navigationTask.PlanningComplete = true;
}
_navigationComponent.UpdateCurrentGoal(GlobalTransform);
_groundMotion.PhysicsProcess(delta, this, _navigationComponent.CurrentGoalPositionWorld,
_navigationComponent.CurrentGoalOrientationWorld, _worldInfo.TileWorld);
NavigationComponent.UpdateCurrentGoal(GlobalTransform);
_groundMotion.PhysicsProcess(delta, this, NavigationComponent.CurrentGoalPositionWorld,
NavigationComponent.CurrentGoalAngleWorld, _worldInfo.World);
if (NavigationComponent.IsGoalReached())
navigationTask.NavigationPoint = new NavigationPoint(GlobalTransform);
}
}
}
@ -127,9 +109,9 @@ public class Player : Entity, IInteractionInterface
public override void _Process(float delta)
{
if (_navigationComponent != null)
if (NavigationComponent != null)
{
_navigationComponent.UpdateCurrentGoal(GlobalTransform);
NavigationComponent.UpdateCurrentGoal(GlobalTransform);
}
foreach (Node node in _attractedItemList)
@ -154,23 +136,19 @@ public class Player : Entity, IInteractionInterface
_debugGeometry.Clear();
_debugGeometry.GlobalTransform = Transform.Identity;
if (_navigationComponent != null)
if (NavigationComponent != null)
{
_navigationComponent.DebugDraw(this, _debugGeometry);
return;
NavigationComponent.DebugDraw(this, _debugGeometry);
}
}
public void OnItemAttractorBodyEntered(Node node)
{
GD.Print("Item entered " + node);
_attractedItemList.Add(node);
}
public void OnItemAttractorBodyExited(Node node)
{
GD.Print("Item exited " + node);
if (node is GoldBar)
{
GoldBar bar = (GoldBar)node;
@ -198,7 +176,7 @@ public class Player : Entity, IInteractionInterface
body.QueueFree();
}
public void SetActiveTool(String toolName)
public void SetActiveTool(string toolName)
{
Debug.Assert(_toolAttachement != null);
if (toolName == "Axe")
@ -217,12 +195,12 @@ public class Player : Entity, IInteractionInterface
(AnimationNodeStateMachinePlayback)_animationTree.Get("parameters/playback");
Debug.Assert(stateMachine != null);
if (_interactionComponent.TargetEntity is Chest)
if (InteractionComponent.TargetEntity is Chest)
{
GD.Print("Player Opening Box");
stateMachine.Travel("Interaction");
}
else if (_interactionComponent.TargetEntity is Tree)
else if (InteractionComponent.TargetEntity is Tree)
{
GD.Print("Player Chopping Tree");
stateMachine.Travel("Hit");

View File

@ -22,6 +22,7 @@ public class Game : Spatial
private InteractionSystem _interactionSystem;
private HexCell _lastTile;
private Label _mouseTileCubeLabel;
private Label _mouseTileAxialLabel;
private Spatial _mouseTileHighlight;
private Label _mouseTileOffsetLabel;
private Label _mouseWorldLabel;
@ -29,9 +30,6 @@ public class Game : Spatial
private Label _numCoordsRemovedLabel;
private Label _numTilesLabel;
private Player _player;
private StreamContainer _streamContainer;
private Spatial _streamContainerActiveTiles;
private Area _streamContainerArea;
// scene nodes
private Spatial _tileHighlight;
@ -41,7 +39,6 @@ public class Game : Spatial
private TileInstanceManager _tileInstanceManager;
private ShaderMaterial _tileMaterial;
private Label _tileOffsetLabel;
private TileWorld _tileWorld;
private World _world;
private TextureRect _worldTextureRect;
@ -58,6 +55,7 @@ public class Game : Spatial
_mouseWorldLabel = debugStatsContainer.GetNode<Label>("mouse_world_label");
_mouseTileOffsetLabel = debugStatsContainer.GetNode<Label>("mouse_tile_offset_label");
_mouseTileCubeLabel = debugStatsContainer.GetNode<Label>("mouse_tile_cube_label");
_mouseTileAxialLabel = debugStatsContainer.GetNode<Label>("mouse_tile_axial_label");
_numCoordsAddedLabel = debugStatsContainer.GetNode<Label>("num_coords_added_label");
_numCoordsRemovedLabel = debugStatsContainer.GetNode<Label>("num_coords_removed_label");
@ -74,12 +72,7 @@ public class Game : Spatial
_tileHighlight = GetNode<Spatial>("TileHighlight");
_mouseTileHighlight = GetNode<Spatial>("MouseTileHighlight");
_streamContainer = (StreamContainer)FindNode("StreamContainer");
_streamContainerArea = _streamContainer.GetNode<Area>("Area");
_streamContainerActiveTiles = _streamContainer.GetNode<Spatial>("ActiveTiles");
_player = GetNode<Player>("Player");
_tileWorld = GetNode<TileWorld>("TileWorld");
_camera = (Camera)FindNode("Camera");
_cameraOffset = _camera.GlobalTranslation - _player.GlobalTranslation;
@ -88,9 +81,6 @@ public class Game : Spatial
// populate UI values
var generatorWorldSizeSlider = worldGeneratorContainer.GetNode<Slider>("HBoxContainer/WorldSizeSlider");
generatorWorldSizeSlider.Value = _tileWorld.Size;
Debug.Assert(_tileWorld != null);
// resources
_tileHighlightScene = GD.Load<PackedScene>("utils/TileHighlight.tscn");
@ -109,15 +99,7 @@ public class Game : Spatial
_interactionSystem = GetNode<InteractionSystem>("InteractionSystem");
Debug.Assert(_interactionSystem != null);
// update data
_worldTextureRect.RectSize = Vector2.One * _tileWorld.Size;
_heightTextureRect.RectSize = Vector2.One * _tileWorld.Size;
// connect signals
_streamContainerArea.Connect("input_event", this, nameof(OnAreaInputEvent));
_streamContainer.Connect("TileClicked", this, nameof(OnTileClicked));
_streamContainer.Connect("TileHovered", this, nameof(OnTileHovered));
_tileWorld.Connect("WorldGenerated", this, nameof(OnWorldGenerated));
_generateWorldButton.Connect("pressed", this, nameof(OnGenerateButton));
_player.TaskQueueComponent.Connect("StartInteraction", _interactionSystem,
nameof(_interactionSystem.OnStartInteraction));
@ -132,14 +114,13 @@ public class Game : Spatial
if (node.HasSignal("EntityClicked"))
node.Connect("EntityClicked", this, nameof(OnEntityClicked));
_world.Connect("EntityClicked", this, nameof(OnEntityClicked));
// perform dependency injection
//_streamContainer.SetWorld(_tileWorld);Clicked
var worldInfoComponent = _player.GetNode<WorldInfoComponent>("WorldInfo");
if (worldInfoComponent != null) worldInfoComponent.SetWorld(_tileWorld);
_tileWorld.Generate(_tileWorld.Size);
UpdateCurrentTile();
_streamContainer.SetCenterTile(_currentTile);
}
@ -191,15 +172,6 @@ public class Game : Spatial
tileHighlightTransform.origin.z = currentTileCenter.y;
_tileHighlight.Transform = tileHighlightTransform;
if (_currentTile.CubeCoords != _lastTile.CubeCoords)
{
_streamContainer.SetCenterTile(_currentTile);
_numTilesLabel.Text = _streamContainerActiveTiles.GetChildCount().ToString();
_numCoordsAddedLabel.Text = _streamContainer.AddedCoords.Count.ToString();
_numCoordsRemovedLabel.Text = _streamContainer.RemovedCoords.Count.ToString();
}
var cameraTransform = _camera.Transform;
cameraTransform.origin = _player.GlobalTranslation + _cameraOffset;
_camera.Transform = cameraTransform;
@ -210,14 +182,7 @@ public class Game : Spatial
{
GD.Print("Generating");
var worldSizeSlider = (Slider)FindNode("WorldSizeSlider");
if (worldSizeSlider == null)
{
GD.PrintErr("Could not find WorldSizeSlider!");
return;
}
_tileWorld.Seed = _tileWorld.Seed + 1;
_tileWorld.Generate((int)worldSizeSlider.Value);
if (worldSizeSlider == null) GD.PrintErr("Could not find WorldSizeSlider!");
}
@ -230,10 +195,8 @@ public class Game : Spatial
_mouseWorldLabel.Text = position.ToString("F3");
_mouseTileOffsetLabel.Text = cellAtCursor.OffsetCoords.ToString("N");
_mouseTileCubeLabel.Text = cellAtCursor.CubeCoords.ToString("N");
_mouseTileAxialLabel.Text = cellAtCursor.AxialCoords.ToString("N");
_mouseTileHighlight.Transform = highlightTransform;
if (inputEvent is InputEventMouseButton && ((InputEventMouseButton)inputEvent).Pressed)
_streamContainer.EmitSignal("TileClicked", _streamContainer.GetTile3dAt(cellAtCursor.OffsetCoords));
}
@ -245,7 +208,7 @@ public class Game : Spatial
_player.TaskQueueComponent.Reset();
_player.TaskQueueComponent.Queue.Enqueue(new TaskQueueComponent.NavigationTask(
new NavigationComponent.NavigationPoint(tile.GlobalTranslation)));
new NavigationPoint(tile.GlobalTranslation)));
}
public void OnTileHovered(HexTile3D tile)
@ -256,7 +219,7 @@ public class Game : Spatial
_mouseTileOffsetLabel.Text = tile.OffsetCoords.ToString("N");
_mouseTileCubeLabel.Text = tile.Cell.CubeCoords.ToString("N");
_player.Navigation.FindPath(_player, _player.GlobalTranslation, tile.GlobalTranslation);
_player.NavigationComponent.FindPath(_player, _player.GlobalTranslation, tile.GlobalTranslation);
}
public void OnEntityClicked(Entity entity)
@ -268,7 +231,7 @@ public class Game : Spatial
{
_player.TaskQueueComponent.Reset();
_player.TaskQueueComponent.Queue.Enqueue(new TaskQueueComponent.NavigationTask(
new NavigationComponent.NavigationPoint(mountPoint.GlobalTransform)));
new NavigationPoint(mountPoint.GlobalTransform)));
_player.TaskQueueComponent.Queue.Enqueue(new TaskQueueComponent.InteractionTask(entity));
}
}
@ -277,11 +240,10 @@ public class Game : Spatial
public void ResetGameState()
{
var playerStartTransform = Transform.Identity;
var height = _tileWorld.GetHeightAtOffset(new Vector2(0, 0));
playerStartTransform.origin.y = height;
playerStartTransform.origin.y = 0;
_player.Transform = playerStartTransform;
_player.TaskQueueComponent.Reset();
_player.Navigation.PlanDirectPath(_player, playerStartTransform.origin, playerStartTransform.origin,
_player.NavigationComponent.PlanDirectPath(_player, playerStartTransform.origin, playerStartTransform.origin,
playerStartTransform.basis.Quat());
_goldCountLabel.Text = "0";
@ -291,48 +253,11 @@ public class Game : Spatial
var entityTransform = entity.Transform;
var entityPlanePos = new Vector2(entityTransform.origin.x, entityTransform.origin.z);
var entityOffsetCoordinates = _hexGrid.GetHexAt(entityPlanePos).OffsetCoords;
var entityHeight = _tileWorld.GetHeightAtOffset(entityOffsetCoordinates);
entityTransform.origin.y = entityHeight;
entityTransform.origin.y = 0;
entity.Transform = entityTransform;
}
}
public void OnWorldGenerated()
{
GD.Print("Using new map. Size: " + (int)_tileWorld.ColormapImage.GetSize().x);
_goldCountLabel.Text = "0";
UpdateWorldTextures();
_streamContainer.OnWorldGenerated();
// Reset player transform to offset 0,0 and at current height
ResetGameState();
// Connect all signals of the generated world
foreach (Node node in _tileWorld.Entities.GetChildren())
if (node.HasSignal("EntityClicked"))
node.Connect("EntityClicked", this, nameof(OnEntityClicked));
}
private void UpdateWorldTextures()
{
GD.Print("Updating World textures");
var newWorldTexture = new ImageTexture();
newWorldTexture.CreateFromImage(_tileWorld.ColormapImage,
(uint)(Texture.FlagsEnum.Mipmaps | Texture.FlagsEnum.Repeat));
_worldTextureRect.Texture = newWorldTexture;
_tileMaterial.SetShaderParam("MapAlbedoTexture", newWorldTexture);
_tileMaterial.SetShaderParam("TextureSize", (int)_tileWorld.ColormapImage.GetSize().x);
var newHeightTexture = new ImageTexture();
newHeightTexture.CreateFromImage(_tileWorld.HeightmapImage,
(uint)(Texture.FlagsEnum.Mipmaps | Texture.FlagsEnum.Repeat));
_heightTextureRect.Texture = newHeightTexture;
}
private void OnHeightmapImageChanged(Image heightmapImage)
{
var newHeightmapTexture = new ImageTexture();

View File

@ -1,13 +1,11 @@
[gd_scene load_steps=24 format=2]
[gd_scene load_steps=22 format=2]
[ext_resource path="res://scenes/StreamContainer.tscn" type="PackedScene" id=1]
[ext_resource path="res://entities/Player.tscn" type="PackedScene" id=2]
[ext_resource path="res://scenes/Camera.tscn" type="PackedScene" id=3]
[ext_resource path="res://ui/EditorUI.tscn" type="PackedScene" id=4]
[ext_resource path="res://utils/TileHighlight.tscn" type="PackedScene" id=5]
[ext_resource path="res://ui/DebugStatsContainer.gd" type="Script" id=6]
[ext_resource path="res://scenes/World.cs" type="Script" id=7]
[ext_resource path="res://scenes/TileWorld.tscn" type="PackedScene" id=8]
[ext_resource path="res://scenes/Game.cs" type="Script" id=9]
[ext_resource path="res://scenes/TileInstanceManager.cs" type="Script" id=10]
[ext_resource path="res://entities/Chest.tscn" type="PackedScene" id=11]
@ -55,9 +53,6 @@ visible = false
[node name="MouseTileHighlight" parent="." instance=ExtResource( 5 )]
[node name="TileWorld" parent="." instance=ExtResource( 8 )]
GenerationMapType = 2
[node name="GameUI" type="HBoxContainer" parent="."]
anchor_left = 1.0
anchor_right = 1.0
@ -126,7 +121,7 @@ margin_bottom = 20.0
[node name="Label9" type="Label" parent="DebugContainer/DebugStatsContainer"]
visible = false
margin_top = 24.0
margin_right = 113.0
margin_right = 53.0
margin_bottom = 38.0
rect_pivot_offset = Vector2( -335, -33 )
text = "FPS"
@ -188,9 +183,10 @@ text = "0,0"
[node name="Label6" type="Label" parent="DebugContainer/DebugStatsContainer"]
visible = false
margin_top = 96.0
margin_right = 113.0
margin_bottom = 110.0
margin_left = 57.0
margin_top = 3.0
margin_right = 170.0
margin_bottom = 17.0
rect_pivot_offset = Vector2( -335, -33 )
text = "Mouse Tile Offset"
@ -218,6 +214,23 @@ margin_right = 149.0
margin_bottom = 128.0
text = "0,0,0"
[node name="Label11" type="Label" parent="DebugContainer/DebugStatsContainer"]
visible = false
margin_left = 57.0
margin_top = 3.0
margin_right = 162.0
margin_bottom = 17.0
rect_pivot_offset = Vector2( -335, -33 )
text = "Mouse Tile Axial"
[node name="mouse_tile_axial_label" type="Label" parent="DebugContainer/DebugStatsContainer"]
visible = false
margin_left = 57.0
margin_top = 3.0
margin_right = 89.0
margin_bottom = 17.0
text = "0,0,0"
[node name="Label3" type="Label" parent="DebugContainer/DebugStatsContainer"]
visible = false
margin_top = 132.0
@ -344,10 +357,6 @@ flip_v = true
[node name="EditorUI" parent="." instance=ExtResource( 4 )]
visible = false
[node name="StreamContainer" parent="." instance=ExtResource( 1 )]
visible = false
ShowHexTiles = true
[node name="Camera" parent="." instance=ExtResource( 3 )]
[node name="InteractionSystem" type="Node" parent="."]
@ -356,7 +365,10 @@ script = ExtResource( 15 )
[node name="NavigationSystem" type="Node" parent="."]
[node name="Player" parent="." instance=ExtResource( 2 )]
TileWorldNode = NodePath("../TileWorld")
WorldNode = NodePath("../World")
[node name="WorldInfo" parent="Player" index="2"]
WorldPath = NodePath("../../World")
[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 )
@ -365,6 +377,7 @@ transform = Transform( 1, 8.68458e-08, -1.04308e-07, 1.74623e-07, -1, -1.30385e-
parameters/playback = SubResource( 26 )
[node name="Entities" type="Spatial" parent="."]
visible = false
[node name="Axe" parent="Entities" instance=ExtResource( 14 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1.79762, 0, 0 )
@ -380,6 +393,7 @@ script = ExtResource( 7 )
[node name="TileInstanceManager" type="Spatial" parent="World"]
script = ExtResource( 10 )
ShowHexTiles = true
World = NodePath("..")
[node name="TileMultiMeshInstance" type="MultiMeshInstance" parent="World/TileInstanceManager"]
@ -406,11 +420,14 @@ visible = false
[node name="tree" parent="World/Assets/Trees" instance=ExtResource( 19 )]
[node name="DirectionalLight" type="DirectionalLight" parent="."]
transform = Transform( 0.328059, -0.878387, 0.347583, 0, 0.367946, 0.929847, -0.944657, -0.305045, 0.120708, 0, 6.59293, 1.20265 )
shadow_enabled = true
directional_shadow_mode = 0
[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"]
[editable path="TileWorld"]
[editable path="StreamContainer"]
[editable path="Player"]
[editable path="Player/Geometry"]

View File

@ -5,24 +5,19 @@ using Godot.Collections;
public class TileInstanceManager : Spatial
{
private readonly Array<SceneTileChunk> _sceneTileChunks = new();
private MultiMeshInstance _tileMultiMeshInstance;
private ImageTexture _viewTileTypeTexture;
private World _world;
// other members
// exports
[Export] public NodePath World;
[Export] public bool ShowHexTiles;
[Export] public Vector2 ViewCenterPlaneCoord;
// ui elements
// scene nodes
public MultiMeshInstance TileMultiMeshInstance;
// resources
// exports
[Export] public NodePath World;
// other members
private readonly Array<SceneTileChunk> _sceneTileChunks = new();
private int _usedTileInstanceIndices;
private ImageTexture _viewTileTypeTexture;
private World _world;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
@ -31,8 +26,8 @@ public class TileInstanceManager : Spatial
_world.Connect("OnTilesChanged", this, nameof(HandleWorldTileChange));
_tileMultiMeshInstance = (MultiMeshInstance)FindNode("TileMultiMeshInstance");
Debug.Assert(_tileMultiMeshInstance != null);
TileMultiMeshInstance = (MultiMeshInstance)FindNode("TileMultiMeshInstance");
Debug.Assert(TileMultiMeshInstance != null);
}
@ -42,7 +37,9 @@ public class TileInstanceManager : Spatial
private SceneTileChunk CreateSceneTileChunk(Vector2 chunkIndex)
{
var sceneTileChunk = new SceneTileChunk(chunkIndex, _tileMultiMeshInstance);
var sceneTileChunk =
new SceneTileChunk(chunkIndex, TileMultiMeshInstance, _usedTileInstanceIndices, ShowHexTiles);
_usedTileInstanceIndices += sceneTileChunk.TileNodes.Count;
foreach (var hexTile3D in sceneTileChunk.TileNodes)
{
@ -82,23 +79,15 @@ public class TileInstanceManager : Spatial
if (removedChunks.Count > 0)
{
sceneTileChunk = removedChunks[^1];
sceneTileChunk.ChunkIndex = chunkIndex;
removedChunks.RemoveAt(removedChunks.Count - 1);
GD.Print("Reused SceneTileChunk");
}
else
{
sceneTileChunk = CreateSceneTileChunk(chunkIndex);
AddChild(sceneTileChunk);
GD.Print("Created SceneTileChunk");
}
sceneTileChunk.ChunkIndex = chunkIndex;
// 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);
}
@ -128,14 +117,20 @@ public class TileInstanceManager : 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();
private readonly Array<int> _tileInstanceIndices = new();
private readonly HexGrid _hexGrid = new();
private readonly bool _showHexTiles;
public readonly Array<HexTile3D> TileNodes = new();
private Vector2 _chunkIndex = Vector2.Inf;
public SceneTileChunk(Vector2 chunkIndex, MultiMeshInstance multiMeshInstance)
public SceneTileChunk(Vector2 chunkIndex, MultiMeshInstance multiMeshInstance, int tileInstanceIndexStart,
bool showHexTiles)
{
_showHexTiles = showHexTiles;
var tileInstanceIndexStart1 = tileInstanceIndexStart;
var chunkSize = global::World.ChunkSize;
foreach (var i in Enumerable.Range(0, chunkSize))
@ -145,7 +140,7 @@ public class TileInstanceManager : Spatial
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));
var centerPlaneCoord = _hexGrid.GetHexCenterFromOffset(new Vector2(i, j));
tileTransform.origin = new Vector3(centerPlaneCoord.x, 0, centerPlaneCoord.y);
tile3D.Transform = tileTransform;
@ -157,14 +152,14 @@ public class TileInstanceManager : Spatial
var chunkTileCount = global::World.ChunkSize * global::World.ChunkSize;
Debug.Assert(tileInstanceIndexStart1 + chunkTileCount <= _multiMeshInstance.Multimesh.InstanceCount);
foreach (var i in Enumerable.Range(0, chunkTileCount))
TileInstanceIndices.Add(_multiMeshInstance.Multimesh.InstanceCount + i);
_tileInstanceIndices.Add(tileInstanceIndexStart1 + i);
_multiMeshInstance.Multimesh.InstanceCount += chunkTileCount;
// _multiMeshInstance.Multimesh.InstanceCount += chunkTileCount;
_multiMeshInstance.Multimesh.VisibleInstanceCount = _multiMeshInstance.Multimesh.InstanceCount;
GD.Print("VisibleInstanceCount = " + _multiMeshInstance.Multimesh.VisibleInstanceCount);
ChunkIndex = chunkIndex;
}
@ -175,7 +170,7 @@ public class TileInstanceManager : Spatial
set
{
var chunkTransform = Transform.Identity;
var chunkOriginPlaneCoord = HexGrid.GetHexCenterFromOffset(value * global::World.ChunkSize);
var chunkOriginPlaneCoord = _hexGrid.GetHexCenterFromOffset(value * global::World.ChunkSize);
chunkTransform.origin = new Vector3(chunkOriginPlaneCoord.x, 0, chunkOriginPlaneCoord.y);
Transform = chunkTransform;
_chunkIndex = value;
@ -184,17 +179,22 @@ public class TileInstanceManager : Spatial
GD.Print("Updating transforms for instances of chunk " + value + " origin: " + chunkTransform.origin);
foreach (var i in Enumerable.Range(0, TileInstanceIndices.Count))
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));
_hexGrid.GetHexCenterFromOffset(new Vector2(column, row));
_multiMeshInstance.Multimesh.SetInstanceTransform(TileInstanceIndices[i],
new Transform(tileOrientation,
chunkTransform.origin + new Vector3(tilePlaneCoord.x, 0, tilePlaneCoord.y)));
var hexTransform = new Transform(tileOrientation,
chunkTransform.origin + new Vector3(tilePlaneCoord.x, 0, tilePlaneCoord.y));
if (_showHexTiles)
hexTransform = new Transform(tileOrientation.Scaled(Vector3.One * 0.95f),
hexTransform.origin);
_multiMeshInstance.Multimesh.SetInstanceTransform(_tileInstanceIndices[i], hexTransform);
}
}
}

View File

@ -1,14 +1,26 @@
using Godot;
using System;
using System.Linq;
using System.Diagnostics;
using System.Linq;
using Godot;
using Godot.Collections;
using Namespace;
using Vector2 = Godot.Vector2;
using Vector3 = Godot.Vector3;
public class TileWorld : Spatial
{
// exports
[Export] public bool DebugMap;
[ExportFlagsEnum(typeof(MapType))] public MapType GenerationMapType = MapType.Debug;
[Export] public int Size = 64;
// constants
private static readonly Color RockColor = new(0.5f, 0.5f, 0.4f);
private static readonly Color GrassColor = new(0, 0.4f, 0);
private PackedScene _chestScene = GD.Load<PackedScene>("res://entities/Chest.tscn");
private GenerationState _currentGenerationState = GenerationState.Heightmap;
private Spatial _environmentNode;
private readonly Array<Spatial> _grassAssets = new();
private enum GenerationState
{
Heightmap,
@ -17,51 +29,37 @@ public class TileWorld : Spatial
Done
}
// constants
private static readonly Color RockColor = new Color(0.5f, 0.5f, 0.4f);
private static readonly Color GrassColor = new Color(0, 0.4f, 0);
private GenerationState _currentGenerationState = GenerationState.Heightmap;
// signals
[Signal]
delegate void WorldGenerated();
private delegate void WorldGenerated();
// public members
public enum MapType
{
Noise,
Debug,
Flat,
Flat
}
[ExportFlagsEnum(typeof(MapType))] public MapType GenerationMapType = MapType.Debug;
[Export] public int Size = 64;
[Export] public bool DebugMap;
public float HeightScale = 1.0f;
public Image HeightmapImage;
public Image ColormapImage;
public Spatial Entities;
public Image HeightmapImage;
public float HeightScale = 1.0f;
public HexGrid HexGrid;
public Image NavigationmapImage;
public int Seed = 0;
public Spatial Entities;
public HexGrid HexGrid;
// private members
private int _halfSize;
private Random _tileTypeRandom;
private Viewport _worldOffscreenViewport;
private TextureRect _worldOffscreenTextureRect;
private Viewport _heightmapOffscreenViewport;
private TextureRect _heightmapOffscreenTextureRect;
private Array<Spatial> _rockAssets = new();
private Array<Spatial> _grassAssets = new();
private Array<Spatial> _treeAssets = new();
private PackedScene _chestScene = GD.Load<PackedScene>("res://entities/Chest.tscn");
private Spatial _environmentNode;
private Viewport _heightmapOffscreenViewport;
private int _resizeExtraFrameCount;
private bool _resizeTriggered;
private int _resizeExtraFrameCount = 0;
private readonly Array<Spatial> _rockAssets = new();
private Random _tileTypeRandom;
private readonly Array<Spatial> _treeAssets = new();
private TextureRect _worldOffscreenTextureRect;
private Viewport _worldOffscreenViewport;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
@ -149,10 +147,6 @@ public class TileWorld : Spatial
_heightmapOffscreenViewport.Size = new Vector2(size, size);
_halfSize = Mathf.RoundToInt(size) / 2;
HexGrid.SetBounds(
TextureCoordToCell(new Vector2(0, 0)),
TextureCoordToCell(new Vector2(size, size))
);
HexGrid.Obstacles.Clear();
HexGrid.Barriers.Clear();
@ -282,7 +276,7 @@ public class TileWorld : Spatial
EmitSignal("WorldGenerated");
}
Spatial SelectAsset(Vector2 offsetCoord, Array<Spatial> assets, Random randomGenerator, double probability)
private Spatial SelectAsset(Vector2 offsetCoord, Array<Spatial> assets, Random randomGenerator, double probability)
{
if (randomGenerator.NextDouble() < 1.0 - probability)
{
@ -301,13 +295,13 @@ public class TileWorld : Spatial
return assetInstance;
}
bool IsColorEqualApprox(Color colorA, Color colorB)
private bool IsColorEqualApprox(Color colorA, Color colorB)
{
Vector3 colorDifference = new Vector3(colorA.r - colorB.r, colorA.g - colorB.g, colorA.b - colorB.b);
return colorDifference.LengthSquared() < 0.1 * 0.1;
}
bool IsColorWater(Color color)
private bool IsColorWater(Color color)
{
return (color.r == 0 && color.g == 0 && color.b > 0.01);
}
@ -319,7 +313,7 @@ public class TileWorld : Spatial
NavigationmapImage.SetPixelv(OffsetToTextureCoord(cell.OffsetCoords), Colors.Red);
NavigationmapImage.Unlock();
}
private void PopulateEnvironment()
{
Random environmentRandom = new Random(Seed);
@ -461,16 +455,13 @@ public class TileWorld : Spatial
Vector2 textureCoord = OffsetToTextureCoord(offsetCoord);
float heightmapHeight = (HeightmapImage.GetPixel((int)textureCoord.x, (int)(textureCoord.y)).r - 0.5f) * HeightScale;
float heightmapHeight = (HeightmapImage.GetPixel((int)textureCoord.x, (int)(textureCoord.y)).r - 0.5f) *
HeightScale;
if (heightmapHeight <= 0)
{
heightmapHeight -= 0.03f;
}
else
{
heightmapHeight = 0f;
}
return heightmapHeight;
}

View File

@ -13,11 +13,6 @@
[node name="TileWorld" type="Spatial"]
script = ExtResource( 1 )
[node name="DirectionalLight" type="DirectionalLight" parent="."]
transform = Transform( 0.328059, -0.878387, 0.347583, 0, 0.367946, 0.929847, -0.944657, -0.305045, 0.120708, 0, 6.59293, 1.20265 )
shadow_enabled = true
directional_shadow_mode = 0
[node name="WorldOffscreenViewport" type="Viewport" parent="."]
size = Vector2( 100, 100 )
own_world = true

View File

@ -17,7 +17,7 @@ public class World : Spatial
}
// constants
public const int ChunkSize = 10;
public const int ChunkSize = 18;
public const int NumChunkRows = 3;
public const int NumChunkColumns = NumChunkRows;
private static readonly Color RockColor = new(0.5f, 0.5f, 0.4f);
@ -41,6 +41,8 @@ public class World : Spatial
// other members
private Vector2 _centerPlaneCoord;
private Vector2 _chunkIndexNorthEast;
private Vector2 _chunkIndexSouthWest;
private Array<Spatial> _grassAssets;
private ImageTexture _heightmapTexture;
@ -58,6 +60,26 @@ public class World : Spatial
public GenerationState State = GenerationState.Done;
public Vector2 WorldTextureCoordinateOffset;
// ui elements
// scene nodes
// resources
// exports
// [Export] public Vector2 Size = new Vector2(1, 1);
// signals
[Signal]
private delegate void OnTilesChanged(Array<Vector2> removedChunkIndices, Array<Vector2> addedChunkIndices);
[Signal]
private delegate void OnWorldViewTileTypeImageChanged(Image viewTileTypeImage);
[Signal]
private delegate void OnHeightmapImageChanged(Image heightmapImage);
public World()
{
Debug.Assert(ChunkSize % 2 == 0);
@ -73,6 +95,8 @@ public class World : Spatial
_tileInstanceManager = (TileInstanceManager)FindNode("TileInstanceManager");
Debug.Assert(_tileInstanceManager != null);
_tileInstanceManager.TileMultiMeshInstance.Multimesh.InstanceCount =
ChunkSize * ChunkSize * NumChunkColumns * NumChunkRows;
InitNoiseGenerator();
@ -194,8 +218,14 @@ public class World : Spatial
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);
Tree treeAsset = SelectAsset(offsetCoord, _treeAssets, environmentRandom, 0.05) as Tree;
if (treeAsset != null)
{
chunk.Entities.AddChild(treeAsset);
treeAsset.Connect("EntityClicked", this, nameof(OnEntityClicked));
}
// TODO: MarkCellUnwalkable(cell);
// else if (environmentRandom.NextDouble() < 0.01)
// {
@ -217,6 +247,16 @@ public class World : Spatial
tileTypeImage.Unlock();
}
public Vector2 WorldToOffsetCoords(Vector3 fromPositionWorld)
{
return HexGrid.GetHexAt(new Vector2(fromPositionWorld.x, fromPositionWorld.z)).OffsetCoords;
}
public Vector3 GetHexCenterFromOffset(Vector2 fromPositionOffset)
{
return HexGrid.GetHexCenterVec3FromOffset(fromPositionOffset);
}
public void UpdateCenterChunkFromPlaneCoord(Vector2 planeCoord)
{
if (State != GenerationState.Done)
@ -326,7 +366,14 @@ public class World : Spatial
public void SetCenterPlaneCoord(Vector2 centerPlaneCoord)
{
if (!_centerChunkRect.HasPoint(centerPlaneCoord)) UpdateCenterChunkFromPlaneCoord(centerPlaneCoord);
if (!_centerChunkRect.HasPoint(centerPlaneCoord))
{
UpdateCenterChunkFromPlaneCoord(centerPlaneCoord);
UpdateChunkBounds();
UpdateNavigationBounds();
}
}
private void UpdateWorldViewTexture()
@ -340,18 +387,10 @@ public class World : Spatial
_tileTypeMapImage.Create(worldChunkSize * numWorldChunkColumns, worldChunkSize * numWorldChunkRows, false,
Image.Format.Rgba8);
var chunkIndexSouthWest = Vector2.Inf;
var chunkIndexNorthEast = -Vector2.Inf;
foreach (var chunkIndex in _activeChunkIndices)
{
var worldChunk = GetOrCreateWorldChunk((int)chunkIndex.x, (int)chunkIndex.y, Colors.White);
if (chunkIndex.x <= chunkIndexSouthWest.x && chunkIndex.y <= chunkIndexSouthWest.y)
chunkIndexSouthWest = chunkIndex;
else if (chunkIndex.x >= chunkIndexNorthEast.x && chunkIndex.y >= chunkIndexNorthEast.y)
chunkIndexNorthEast = chunkIndex;
_heightmapImage.BlendRect(
worldChunk.HeightmapOffscreenViewport.GetTexture().GetData(),
new Rect2(Vector2.Zero, Vector2.One * worldChunkSize),
@ -369,12 +408,42 @@ public class World : Spatial
_viewTileTypeTexture = new ImageTexture();
_viewTileTypeTexture.CreateFromImage(_tileTypeMapImage);
WorldTextureCoordinateOffset = chunkIndexSouthWest * worldChunkSize;
WorldTextureCoordinateOffset = _chunkIndexSouthWest * worldChunkSize;
EmitSignal("OnWorldViewTileTypeImageChanged", _tileTypeMapImage);
EmitSignal("OnHeightmapImageChanged", _heightmapImage);
}
private void UpdateChunkBounds()
{
_chunkIndexSouthWest = Vector2.Inf;
_chunkIndexNorthEast = -Vector2.Inf;
foreach (var chunkIndex in _activeChunkIndices)
{
var worldChunk = GetOrCreateWorldChunk((int)chunkIndex.x, (int)chunkIndex.y, Colors.White);
if (chunkIndex.x <= _chunkIndexSouthWest.x && chunkIndex.y <= _chunkIndexSouthWest.y)
_chunkIndexSouthWest = chunkIndex;
else if (chunkIndex.x >= _chunkIndexNorthEast.x && chunkIndex.y >= _chunkIndexNorthEast.y)
_chunkIndexNorthEast = chunkIndex;
}
}
private void UpdateNavigationBounds()
{
var 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.
var cellNorthEast = HexGrid.GetHexAtOffset(_chunkIndexNorthEast * ChunkSize + Vector2.One * (ChunkSize - 1));
var centerCell =
HexGrid.GetHexAtOffset(((cellNorthEast.OffsetCoords - cellSouthWest.OffsetCoords) / 2).Round());
var numCells = ChunkSize * Math.Max(NumChunkColumns, NumChunkRows);
HexGrid.SetBoundsOffset(cellSouthWest, ChunkSize * new Vector2(NumChunkColumns, NumChunkRows));
}
public override void _Process(float delta)
{
var oldState = State;
@ -431,23 +500,4 @@ public class World : Spatial
State = GenerationState.Done;
}
}
// ui elements
// scene nodes
// resources
// exports
// [Export] public Vector2 Size = new Vector2(1, 1);
// signals
[Signal]
private delegate void OnTilesChanged(Array<Vector2> removedChunkIndices, Array<Vector2> addedChunkIndices);
[Signal]
private delegate void OnWorldViewTileTypeImageChanged(Image viewTileTypeImage);
[Signal]
private delegate void OnHeightmapImageChanged(Image heightmapImage);
}

View File

@ -1,32 +1,7 @@
using Godot;
using System;
using Array = Godot.Collections.Array;
public class EditorUI : Control
{
// exported members
[Export] public NodePath World;
[Export] public NodePath StreamContainer;
// public members
public Vector2 currentTileOffset = Vector2.Zero;
// private members
private Button _resetButton;
private Button _grassButton;
private Button _sandButton;
private Button _waterButton;
private Button _obstacleButton;
private Button _navigateButton;
private ShaderMaterial _tileMaterial;
private CheckBox _gameGeometryCheckBox;
private CheckBox _physicsGeometryCheckBox;
private CheckBox _navigationGeometryCheckBox;
private TileWorld _tileWorld;
private StreamContainer _streamContainer;
public enum InputMode
{
None,
@ -36,40 +11,59 @@ public class EditorUI : Control
Obstacle,
Navigate
}
private CheckBox _gameGeometryCheckBox;
private Button _grassButton;
private Button _navigateButton;
private CheckBox _navigationGeometryCheckBox;
private Button _obstacleButton;
private CheckBox _physicsGeometryCheckBox;
// private members
private Button _resetButton;
private Button _sandButton;
private StreamContainer _streamContainer;
private ShaderMaterial _tileMaterial;
private TileWorld _tileWorld;
private Button _waterButton;
public InputMode CurrentInputMode = InputMode.None;
// exported members
// public members
public Vector2 currentTileOffset = Vector2.Zero;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
_tileWorld = (TileWorld) GetNode(World);
_streamContainer = (StreamContainer)GetNode(StreamContainer);
_tileMaterial = GD.Load<ShaderMaterial>("materials/HexTileTextureLookup.tres");
// signals
_resetButton = (Button) FindNode("ResetButton");
_resetButton = (Button)FindNode("ResetButton");
_resetButton.Connect("pressed", this, nameof(OnResetButton));
_grassButton = (Button) FindNode("GrassButton");
_grassButton = (Button)FindNode("GrassButton");
_grassButton.Connect("pressed", this, nameof(OnGrassButton));
_sandButton = (Button) FindNode("SandButton");
_sandButton = (Button)FindNode("SandButton");
_sandButton.Connect("pressed", this, nameof(OnSandButton));
_waterButton = (Button) FindNode("WaterButton");
_waterButton = (Button)FindNode("WaterButton");
_waterButton.Connect("pressed", this, nameof(OnWaterButton));
_obstacleButton = (Button) FindNode("ObstacleButton");
_obstacleButton = (Button)FindNode("ObstacleButton");
_obstacleButton.Connect("pressed", this, nameof(OnObstacleButton));
_navigateButton = (Button) FindNode("NavigateButton");
_navigateButton = (Button)FindNode("NavigateButton");
_navigateButton.Connect("pressed", this, nameof(OnNavigateButton));
_gameGeometryCheckBox = (CheckBox)FindNode("GameGeometryCheckBox");
_gameGeometryCheckBox.Connect("toggled", this, nameof(OnGameGeometryCheckBoxToggled));
_physicsGeometryCheckBox = (CheckBox)FindNode("PhysicsGeometryCheckBox");
_physicsGeometryCheckBox.Connect("toggled", this, nameof(OnPhysicsGeometryCheckBoxToggled));
_navigationGeometryCheckBox = (CheckBox)FindNode("NavigationGeometryCheckBox");
_navigationGeometryCheckBox.Connect("toggled", this, nameof(OnNavigationGeometryCheckBoxToggled));
}
@ -90,53 +84,41 @@ public class EditorUI : Control
public void OnSandButton()
{
CurrentInputMode = InputMode.Sand;
}
public void OnWaterButton()
{
CurrentInputMode = InputMode.Water;
}
public void OnObstacleButton()
{
CurrentInputMode = InputMode.Obstacle;
}
public void OnNavigateButton()
{
CurrentInputMode = InputMode.Navigate;
}
public void OnGameGeometryCheckBoxToggled(bool pressed)
{
Array gameGeometries = GetTree().GetNodesInGroup("GameGeometry");
var gameGeometries = GetTree().GetNodesInGroup("GameGeometry");
foreach (Spatial mesh in gameGeometries)
{
if (mesh != null)
{
mesh.Visible = pressed;
}
}
}
public void OnPhysicsGeometryCheckBoxToggled(bool pressed)
{
Array physicsGeometries = GetTree().GetNodesInGroup("PhysicsGeometry");
var physicsGeometries = GetTree().GetNodesInGroup("PhysicsGeometry");
foreach (Spatial mesh in physicsGeometries)
{
if (mesh != null)
{
mesh.Visible = pressed;
}
}
}
public void OnNavigationGeometryCheckBoxToggled(bool pressed)
{
UpdateTileMaterial();
@ -161,23 +143,26 @@ public class EditorUI : Control
_tileMaterial.SetShaderParam("TextureSize", (int)_tileWorld.ColormapImage.GetSize().x);
}
}
public void OnTileClicked(Vector2 offsetCoord)
{
switch (CurrentInputMode)
{
case InputMode.Grass:_tileWorld.SetTileColorAtOffset(currentTileOffset, Colors.Green);
break;
case InputMode.Water:_tileWorld.SetTileColorAtOffset(currentTileOffset, Colors.Blue);
break;
case InputMode.Sand:_tileWorld.SetTileColorAtOffset(currentTileOffset, Colors.Yellow);
break;
case InputMode.Grass:
_tileWorld.SetTileColorAtOffset(currentTileOffset, Colors.Green);
break;
case InputMode.Water:
_tileWorld.SetTileColorAtOffset(currentTileOffset, Colors.Blue);
break;
case InputMode.Sand:
_tileWorld.SetTileColorAtOffset(currentTileOffset, Colors.Yellow);
break;
case InputMode.Obstacle:
_tileWorld.MarkCellUnwalkable(HexCell.FromOffsetCoords(offsetCoord));
break;
break;
}
UpdateTileMaterial();
}
}
}

View File

@ -9,54 +9,54 @@ using NodePair = System.Tuple<Godot.Node, Godot.Node>;
public class InteractionSystem : Node
{
private List<NodePair> _interactionPairs;
private List<InteractionComponent> _activeInteractions;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
_interactionPairs = new List<NodePair>();
_activeInteractions = new List<InteractionComponent>();
}
public override void _Process(float delta)
{
base._Process(delta);
List<NodePair> invalidInteractionPairs = new List<NodePair>();
List<InteractionComponent> endedInteractions = new List<InteractionComponent>();
foreach (Tuple<Node, Node> pair in _interactionPairs)
foreach (InteractionComponent interaction in _activeInteractions)
{
Node nodeA = pair.Item1;
Node nodeB = pair.Item2;
Spatial owningEntity = interaction.OwningEntity;
Spatial targetEntity = interaction.TargetEntity;
if (nodeA == null || nodeA.IsQueuedForDeletion() || nodeB == null || nodeB.IsQueuedForDeletion()) {
invalidInteractionPairs.Add(pair);
if (owningEntity == null || owningEntity.IsQueuedForDeletion() || targetEntity == null || targetEntity.IsQueuedForDeletion())
{
interaction.hasStopped = true;
}
if (nodeA == null || nodeA.IsQueuedForDeletion())
if (interaction.hasStopped)
{
IInteractionInterface interactableB = nodeB as IInteractionInterface;
if (interactableB != null)
{
interactableB.OnInteractionEnd();
interactableB.InteractionComponent = null;
}
}
if (nodeB == null || nodeB.IsQueuedForDeletion())
{
IInteractionInterface interactableA = nodeA as IInteractionInterface;
IInteractionInterface interactableA = owningEntity as IInteractionInterface;
if (interactableA != null)
{
interactableA.OnInteractionEnd();
interactableA.InteractionComponent = null;
}
IInteractionInterface interactableB = targetEntity as IInteractionInterface;
if (interactableB != null)
{
interactableB.OnInteractionEnd();
interactableB.InteractionComponent = null;
}
endedInteractions.Add(interaction);
}
}
foreach (NodePair pair in invalidInteractionPairs)
foreach (InteractionComponent interaction in endedInteractions)
{
_interactionPairs.Remove(pair);
_activeInteractions.Remove(interaction);
}
}
@ -71,8 +71,7 @@ public class InteractionSystem : Node
interactionComponent.EmitSignal("InteractionStart");
NodePair pair = new NodePair(owningEntity, targetEntity);
_interactionPairs.Add(new NodePair(owningEntity, targetEntity));
_activeInteractions.Add(interactionComponent);
}
private static void ConnectInteractionSignals(Entity entity, InteractionComponent interactionComponent)

View File

@ -1,17 +1,14 @@
using Godot;
using GoDotTest;
using System.Diagnostics;
using System.Linq;
using Xunit;
public class NavigationComponentTests : TestClass
{
private Node _testScene = null;
private TileWorld _tileWorld;
private NavigationComponent _navigationComponent;
private KinematicBody _kinematicBody;
private readonly Node _testScene;
private PackedScene _tileWorldScene = GD.Load<PackedScene>("res://scenes/TileWorld.tscn");
private readonly PackedScene _WorldScene = GD.Load<PackedScene>("res://scenes/World.tscn");
private KinematicBody _kinematicBody;
private NavigationComponent _navigationComponent;
private World _world;
public NavigationComponentTests(Node testScene) : base(testScene)
{
@ -21,11 +18,11 @@ public class NavigationComponentTests : TestClass
[Setup]
public void Setup()
{
_tileWorld = (TileWorld)_tileWorldScene.Instance();
_tileWorld.HexGrid = new HexGrid();
_testScene.AddChild(_tileWorld);
_world = (World)_WorldScene.Instance();
_world.HexGrid = new HexGrid();
_testScene.AddChild(_world);
_kinematicBody = new KinematicBody();
_navigationComponent = new NavigationComponent();
_navigationComponent.TileWorld = _tileWorld;
_navigationComponent.World = _world;
}
}

70
utils/NavigationPoint.cs Normal file
View File

@ -0,0 +1,70 @@
using System;
using Godot;
public class NavigationPoint
{
[Flags]
public enum NavigationFlags
{
Position = 1,
Orientation = 2
}
public readonly NavigationFlags Flags;
public Quat WorldOrientation = Quat.Identity;
public float WorldAngle;
public Vector3 WorldPosition = Vector3.Zero;
public NavigationPoint(Vector3 worldPosition)
{
WorldPosition = worldPosition;
Flags = NavigationFlags.Position;
}
public NavigationPoint(Quat worldOrientation)
{
WorldOrientation = worldOrientation;
Flags = NavigationFlags.Orientation;
}
public NavigationPoint(Transform worldTransform)
{
WorldPosition = worldTransform.origin;
WorldOrientation = worldTransform.basis.Quat();
WorldAngle = Globals.CalcPlaneAngle(worldTransform);
Flags = NavigationFlags.Position | NavigationFlags.Orientation;
}
public bool IsReached(Transform worldTransform)
{
var goalReached = false;
var positionError = new Vector2(WorldPosition.x - worldTransform.origin.x,
WorldPosition.z - worldTransform.origin.z);
var positionErrorSquared = positionError.LengthSquared();
worldTransform.basis.Quat();
var orientationError = Mathf.Abs(worldTransform.basis.Quat().AngleTo(WorldOrientation));
var angleError = Mathf.Abs(Globals.CalcPlaneAngle(worldTransform) - WorldAngle);
if (Flags.HasFlag(NavigationFlags.Position)
&& Flags.HasFlag(NavigationFlags.Orientation)
&& positionErrorSquared < Globals.EpsPositionSquared
&& angleError < Globals.EpsRadians)
{
goalReached = true;
}
else if (Flags == NavigationFlags.Position &&
positionErrorSquared < Globals.EpsPositionSquared)
{
goalReached = true;
}
else if (Flags == NavigationFlags.Orientation &&
angleError < Globals.EpsRadians)
{
goalReached = true;
}
return goalReached;
}
}

View File

@ -9,7 +9,7 @@ top_radius = 0.5
bottom_radius = 0.5
height = 0.1
radial_segments = 6
rings = 1
rings = 0
[sub_resource type="SpatialMaterial" id=4]
render_priority = 1