Moved path planning to world class.

- Tree entities now are obstacles in navigation planning.
 - Once chopped obstacles are removed.
main
Martin Felis 2023-11-18 22:11:34 +01:00
parent a37b028b39
commit 2109c6b6ec
7 changed files with 452 additions and 488 deletions

View File

@ -7,8 +7,7 @@ using GodotComponentTest.utils;
/// <summary> /// <summary>
/// </summary> /// </summary>
public class NavigationComponent : Spatial public class NavigationComponent : Spatial {
{
public World World { set; get; } public World World { set; get; }
public Vector3 CurrentGoalPositionWorld { get; private set; } = Vector3.Zero; public Vector3 CurrentGoalPositionWorld { get; private set; } = Vector3.Zero;
public float CurrentGoalAngleWorld { get; private set; } public float CurrentGoalAngleWorld { get; private set; }
@ -21,39 +20,29 @@ public class NavigationComponent : Spatial
private List<NavigationPoint> _planningPathWorldNavigationPoints = new(); private List<NavigationPoint> _planningPathWorldNavigationPoints = new();
private List<NavigationPoint> _smoothedPathWorldNavigationPoints = new(); private List<NavigationPoint> _smoothedPathWorldNavigationPoints = new();
public override void _Ready() public override void _Ready() {
{
base._Ready(); base._Ready();
_pathWorldNavigationPoints = new List<NavigationPoint>(); _pathWorldNavigationPoints = new List<NavigationPoint>();
} }
public override void _Process(float delta) public override void _Process(float delta) {
{
Debug.Assert(World != null); 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) if (navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Position)
&& navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Orientation)) && navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Orientation)) {
{
FindPath(body, fromTransformWorld.origin, navigationPoint); 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); FindPath(body, fromTransformWorld.origin, navigationPoint.WorldPosition);
} } else {
else
{
throw new NotImplementedException(); throw new NotImplementedException();
} }
} }
public void FindPath(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld) public void FindPath(Entity entity, Vector3 fromPositionWorld, Vector3 toPositionWorld) {
{ HexCell fromCell = World.HexGrid.GetHexAt(new Vector2(fromPositionWorld.x, fromPositionWorld.z));
var fromCell = World.HexGrid.GetHexAt(new Vector2(fromPositionWorld.x, fromPositionWorld.z)); if (World.HexGrid.GetHexCost(fromCell) == 0) {
if (World.HexGrid.GetHexCost(fromCell) == 0)
{
GD.Print("Invalid starting point for FindPath(): returning empty path."); GD.Print("Invalid starting point for FindPath(): returning empty path.");
_planningPathWorldNavigationPoints = new List<NavigationPoint>(); _planningPathWorldNavigationPoints = new List<NavigationPoint>();
_planningPathWorldNavigationPoints.Add(new NavigationPoint(fromPositionWorld)); _planningPathWorldNavigationPoints.Add(new NavigationPoint(fromPositionWorld));
@ -61,11 +50,10 @@ public class NavigationComponent : Spatial
return; 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); 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."); GD.Print("Invalid target point for FindPath(): returning empty path.");
_planningPathWorldNavigationPoints = new List<NavigationPoint>(); _planningPathWorldNavigationPoints = new List<NavigationPoint>();
_planningPathWorldNavigationPoints.Add(new NavigationPoint(fromPositionWorld)); _planningPathWorldNavigationPoints.Add(new NavigationPoint(fromPositionWorld));
@ -73,12 +61,11 @@ public class NavigationComponent : Spatial
return; return;
} }
var path = World.HexGrid.FindPath(fromCell, toCell); List<HexCell> path = World.FindPath(entity, fromCell, toCell);
// Generate grid navigation points // Generate grid navigation points
_planningPathWorldNavigationPoints = new List<NavigationPoint>(); _planningPathWorldNavigationPoints = new List<NavigationPoint>();
foreach (var index in Enumerable.Range(0, path.Count)) foreach (int index in Enumerable.Range(0, path.Count)) {
{
_planningPathWorldNavigationPoints.Add( _planningPathWorldNavigationPoints.Add(
new NavigationPoint(World.HexGrid.GetHexCenterVec3FromOffset(path[index].OffsetCoords))); 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 // Ensure the last point coincides with the target position
if (_planningPathWorldNavigationPoints.Count > 0 && if (_planningPathWorldNavigationPoints.Count > 0 &&
(_planningPathWorldNavigationPoints.Last().WorldPosition - toPositionWorld).LengthSquared() < (_planningPathWorldNavigationPoints.Last().WorldPosition - toPositionWorld).LengthSquared() <
0.5f * 0.5f) 0.5f * 0.5f) {
{
_planningPathWorldNavigationPoints[_planningPathWorldNavigationPoints.Count - 1].WorldPosition = _planningPathWorldNavigationPoints[_planningPathWorldNavigationPoints.Count - 1].WorldPosition =
toPositionWorld; toPositionWorld;
} }
// Perform smoothing // Perform smoothing
_planningPathSmoothedWorldNavigationPoints = SmoothPath(body, _planningPathWorldNavigationPoints); _planningPathSmoothedWorldNavigationPoints = SmoothPath(entity, _planningPathWorldNavigationPoints);
// Ensure starting point is the current position // Ensure starting point is the current position
if (_planningPathSmoothedWorldNavigationPoints.Count > 0) if (_planningPathSmoothedWorldNavigationPoints.Count > 0) {
{
_planningPathSmoothedWorldNavigationPoints[0] = new NavigationPoint(fromPositionWorld); _planningPathSmoothedWorldNavigationPoints[0] = new NavigationPoint(fromPositionWorld);
} }
} }
public void FindPath(KinematicBody body, Vector3 fromPositionWorld, NavigationPoint navigationPoint) public void FindPath(Entity entity, Vector3 fromPositionWorld, NavigationPoint navigationPoint) {
{ FindPath(entity, fromPositionWorld, navigationPoint.WorldPosition);
FindPath(body, fromPositionWorld, navigationPoint.WorldPosition);
_planningPathWorldNavigationPoints[_planningPathWorldNavigationPoints.Count - 1] = navigationPoint; if (_planningPathSmoothedWorldNavigationPoints.Count > 0) {
_planningPathSmoothedWorldNavigationPoints[_planningPathSmoothedWorldNavigationPoints.Count - 1] = _planningPathWorldNavigationPoints[_planningPathWorldNavigationPoints.Count - 1] = navigationPoint;
navigationPoint; _planningPathSmoothedWorldNavigationPoints[_planningPathSmoothedWorldNavigationPoints.Count - 1] =
navigationPoint;
}
} }
public void PlanGridPath(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld) public void PlanGridPath(Entity entity, Vector3 fromPositionWorld, Vector3 toPositionWorld) {
{ Vector2 fromPositionOffset = World.WorldToOffsetCoords(fromPositionWorld);
var fromPositionOffset = World.WorldToOffsetCoords(fromPositionWorld); Vector2 toPositionOffset = World.WorldToOffsetCoords(toPositionWorld);
var toPositionOffset = World.WorldToOffsetCoords(toPositionWorld);
var fromCell = new HexCell(); HexCell fromCell = new();
fromCell.OffsetCoords = fromPositionOffset; fromCell.OffsetCoords = fromPositionOffset;
var toCell = new HexCell(); HexCell toCell = new();
toCell.OffsetCoords = toPositionOffset; toCell.OffsetCoords = toPositionOffset;
_path = fromCell.LineTo(toCell); _path = fromCell.LineTo(toCell);
@ -129,26 +114,23 @@ public class NavigationComponent : Spatial
_pathWorldNavigationPoints.Add( _pathWorldNavigationPoints.Add(
new NavigationPoint(World.HexGrid.GetHexCenterVec3FromOffset(fromPositionOffset))); 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( _pathWorldNavigationPoints.Add(
new NavigationPoint(World.GetHexCenterFromOffset(_path[index].OffsetCoords))); new NavigationPoint(World.GetHexCenterFromOffset(_path[index].OffsetCoords)));
} }
if ((fromPositionWorld - World.GetHexCenterFromOffset(toCell.OffsetCoords)).LengthSquared() > if ((fromPositionWorld - World.GetHexCenterFromOffset(toCell.OffsetCoords)).LengthSquared() >
Globals.EpsPositionSquared) Globals.EpsPositionSquared)
{
// Remove the last one, because it is only the position rounded to HexGrid coordinates. // 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.RemoveAt(_pathWorldNavigationPoints.Count - 1);
} }
} }
_pathWorldNavigationPoints.Add(new NavigationPoint(toPositionWorld)); _pathWorldNavigationPoints.Add(new NavigationPoint(toPositionWorld));
if (_pathWorldNavigationPoints.Count > 2) if (_pathWorldNavigationPoints.Count > 2) {
{ _smoothedPathWorldNavigationPoints = SmoothPath(entity, _pathWorldNavigationPoints);
_smoothedPathWorldNavigationPoints = SmoothPath(body, _pathWorldNavigationPoints);
_pathWorldNavigationPoints = _smoothedPathWorldNavigationPoints; _pathWorldNavigationPoints = _smoothedPathWorldNavigationPoints;
} }
@ -156,36 +138,28 @@ public class NavigationComponent : Spatial
} }
public void PlanGridPath(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld, public void PlanGridPath(Entity entity, Vector3 fromPositionWorld, Vector3 toPositionWorld,
Quat toWorldOrientation) Quat toWorldOrientation) {
{ PlanGridPath(entity, fromPositionWorld, toPositionWorld);
PlanGridPath(body, fromPositionWorld, toPositionWorld);
_pathWorldNavigationPoints.Add(new NavigationPoint(toWorldOrientation)); _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) if (navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Position)
&& navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Orientation)) && navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Orientation)) {
{ PlanGridPath(entity, fromTransformWorld.origin, navigationPoint.WorldPosition,
PlanGridPath(body, fromTransformWorld.origin, navigationPoint.WorldPosition,
navigationPoint.WorldOrientation); navigationPoint.WorldOrientation);
} } else if (navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Position)) {
else if (navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Position)) PlanGridPath(entity, fromTransformWorld.origin, navigationPoint.WorldPosition);
{ } else {
PlanGridPath(body, fromTransformWorld.origin, navigationPoint.WorldPosition);
}
else
{
throw new NotImplementedException(); throw new NotImplementedException();
} }
} }
public void PlanDirectPath(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld) public void PlanDirectPath(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld) {
{
_pathWorldNavigationPoints.Clear(); _pathWorldNavigationPoints.Clear();
_pathWorldNavigationPoints.Add(new NavigationPoint(toPositionWorld)); _pathWorldNavigationPoints.Add(new NavigationPoint(toPositionWorld));
@ -194,23 +168,20 @@ public class NavigationComponent : Spatial
public void PlanDirectPath(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld, public void PlanDirectPath(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld,
Quat toWorldOrientation) Quat toWorldOrientation) {
{
PlanDirectPath(body, fromPositionWorld, toPositionWorld); PlanDirectPath(body, fromPositionWorld, toPositionWorld);
_pathWorldNavigationPoints.Add(new NavigationPoint(toWorldOrientation)); _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 fromPositionLocal = GlobalTransform.XformInv(fromPositionWorld);
Vector3 toPositionLocal = GlobalTransform.XformInv(toPositionWorld); Vector3 toPositionLocal = GlobalTransform.XformInv(toPositionWorld);
Vector3 relativeVelocity = GlobalTransform.basis.Xform(toPositionLocal - fromPositionLocal); Vector3 relativeVelocity = GlobalTransform.basis.Xform(toPositionLocal - fromPositionLocal);
KinematicCollision moveCollision = body.MoveAndCollide(relativeVelocity, true, true, true); KinematicCollision moveCollision = body.MoveAndCollide(relativeVelocity, true, true, true);
if (moveCollision != null) if (moveCollision != null) {
{
Spatial colliderSpatial = moveCollision.Collider as Spatial; Spatial colliderSpatial = moveCollision.Collider as Spatial;
// GD.Print("Found collision: " + moveCollision.Collider + " (" + colliderSpatial.Name + ")"); // GD.Print("Found collision: " + moveCollision.Collider + " (" + colliderSpatial.Name + ")");
return true; return true;
@ -220,37 +191,30 @@ public class NavigationComponent : Spatial
} }
public bool CheckSweptTriangleCellCollision(Vector3 startWorld, Vector3 endWorld, float radius) public bool CheckSweptTriangleCellCollision(Vector3 startWorld, Vector3 endWorld, float radius) {
{ Vector2 startPlane = new(startWorld.x, startWorld.z);
Vector2 startPlane = new Vector2(startWorld.x, startWorld.z); Vector2 endPlane = new(endWorld.x, endWorld.z);
Vector2 endPlane = new Vector2(endWorld.x, endWorld.z);
Vector2 directionPlane = (endPlane - startPlane).Normalized(); Vector2 directionPlane = (endPlane - startPlane).Normalized();
Vector2 sidePlane = directionPlane.Rotated(Mathf.Pi * 0.5f); Vector2 sidePlane = directionPlane.Rotated(Mathf.Pi * 0.5f);
List<HexCell> cells = List<HexCell> cells =
World.HexGrid.GetCellsForLine(startPlane + directionPlane * radius, endPlane + directionPlane * radius); World.HexGrid.GetCellsForLine(startPlane + directionPlane * radius, endPlane + directionPlane * radius);
foreach (HexCell cell in cells) foreach (HexCell cell in cells) {
{ if (World.HexGrid.GetHexCost(cell) == 0) {
if (World.HexGrid.GetHexCost(cell) == 0)
{
return true; return true;
} }
} }
cells = World.HexGrid.GetCellsForLine(startPlane + sidePlane * radius, endPlane + sidePlane * radius); cells = World.HexGrid.GetCellsForLine(startPlane + sidePlane * radius, endPlane + sidePlane * radius);
foreach (HexCell cell in cells) foreach (HexCell cell in cells) {
{ if (World.HexGrid.GetHexCost(cell) == 0) {
if (World.HexGrid.GetHexCost(cell) == 0)
{
return true; return true;
} }
} }
cells = World.HexGrid.GetCellsForLine(startPlane - sidePlane * radius, endPlane - sidePlane * radius); cells = World.HexGrid.GetCellsForLine(startPlane - sidePlane * radius, endPlane - sidePlane * radius);
foreach (HexCell cell in cells) foreach (HexCell cell in cells) {
{ if (World.HexGrid.GetHexCost(cell) == 0) {
if (World.HexGrid.GetHexCost(cell) == 0)
{
return true; return true;
} }
} }
@ -258,29 +222,24 @@ public class NavigationComponent : Spatial
return false; return false;
} }
public List<NavigationPoint> SmoothPath(KinematicBody body, List<NavigationPoint> navigationPoints) public List<NavigationPoint> SmoothPath(KinematicBody body, List<NavigationPoint> navigationPoints) {
{ if (navigationPoints.Count <= 2) {
if (navigationPoints.Count <= 2)
{
return navigationPoints; return navigationPoints;
} }
Vector3 bodyGlobalTranslation = body.GlobalTranslation; Vector3 bodyGlobalTranslation = body.GlobalTranslation;
List<NavigationPoint> smoothedPath = new List<NavigationPoint>(); List<NavigationPoint> smoothedPath = new();
int startIndex = 0; int startIndex = 0;
int endIndex = navigationPoints.Count > 1 ? 1 : 0; int endIndex = navigationPoints.Count > 1 ? 1 : 0;
smoothedPath.Add(navigationPoints[startIndex]); smoothedPath.Add(navigationPoints[startIndex]);
Vector3 startPoint = navigationPoints[startIndex].WorldPosition; Vector3 startPoint = navigationPoints[startIndex].WorldPosition;
while (endIndex != navigationPoints.Count) while (endIndex != navigationPoints.Count) {
{
Vector3 endPoint = navigationPoints[endIndex].WorldPosition; Vector3 endPoint = navigationPoints[endIndex].WorldPosition;
if (CheckSweptTriangleCellCollision(startPoint, endPoint, 0.27f)) if (CheckSweptTriangleCellCollision(startPoint, endPoint, 0.27f)) {
{ if (endIndex - startIndex == 1) {
if (endIndex - startIndex == 1)
{
GD.Print("Aborting SmoothPath: input path passes through collision geometry."); GD.Print("Aborting SmoothPath: input path passes through collision geometry.");
body.GlobalTranslation = bodyGlobalTranslation; body.GlobalTranslation = bodyGlobalTranslation;
return smoothedPath; return smoothedPath;
@ -294,8 +253,7 @@ public class NavigationComponent : Spatial
continue; continue;
} }
if (endIndex == navigationPoints.Count - 1) if (endIndex == navigationPoints.Count - 1) {
{
break; break;
} }
@ -308,34 +266,25 @@ public class NavigationComponent : Spatial
return smoothedPath; 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) 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, PlanDirectPath(body, fromTransformWorld.origin, navigationPoint.WorldPosition,
navigationPoint.WorldOrientation); navigationPoint.WorldOrientation);
} } else if (navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Position)) {
else if (navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Position))
{
PlanDirectPath(body, fromTransformWorld.origin, navigationPoint.WorldPosition); PlanDirectPath(body, fromTransformWorld.origin, navigationPoint.WorldPosition);
} } else {
else
{
throw new NotImplementedException(); throw new NotImplementedException();
} }
} }
public void ActivatePlannedPath() public void ActivatePlannedPath() {
{
_pathWorldNavigationPoints = _planningPathSmoothedWorldNavigationPoints; _pathWorldNavigationPoints = _planningPathSmoothedWorldNavigationPoints;
UpdateCurrentGoal(); UpdateCurrentGoal();
} }
private void UpdateCurrentGoal() private void UpdateCurrentGoal() {
{ if (_pathWorldNavigationPoints.Count == 0) {
if (_pathWorldNavigationPoints.Count == 0)
{
return; return;
} }
@ -344,73 +293,56 @@ public class NavigationComponent : Spatial
CurrentGoalAngleWorld = _pathWorldNavigationPoints[0].WorldAngle; CurrentGoalAngleWorld = _pathWorldNavigationPoints[0].WorldAngle;
} }
private void ApplyExistingTransform(Transform worldTransform) private void ApplyExistingTransform(Transform worldTransform) {
{ if (_currentGoal.Flags == NavigationPoint.NavigationFlags.Orientation) {
if (_currentGoal.Flags == NavigationPoint.NavigationFlags.Orientation)
{
CurrentGoalPositionWorld = worldTransform.origin; CurrentGoalPositionWorld = worldTransform.origin;
} } else if (_currentGoal.Flags == NavigationPoint.NavigationFlags.Position) {
else if (_currentGoal.Flags == NavigationPoint.NavigationFlags.Position)
{
CurrentGoalAngleWorld = Globals.CalcPlaneAngle(worldTransform); CurrentGoalAngleWorld = Globals.CalcPlaneAngle(worldTransform);
} }
} }
public void UpdateCurrentGoal(Transform currentTransformWorld) public void UpdateCurrentGoal(Transform currentTransformWorld) {
{ if (_currentGoal == null) {
if (_currentGoal == null)
{
_currentGoal = new NavigationPoint(currentTransformWorld); _currentGoal = new NavigationPoint(currentTransformWorld);
} }
if (_pathWorldNavigationPoints.Count == 0) if (_pathWorldNavigationPoints.Count == 0) {
{
CurrentGoalAngleWorld = Globals.CalcPlaneAngle(currentTransformWorld); CurrentGoalAngleWorld = Globals.CalcPlaneAngle(currentTransformWorld);
CurrentGoalPositionWorld = currentTransformWorld.origin; CurrentGoalPositionWorld = currentTransformWorld.origin;
return; return;
} }
if (_currentGoal.Flags.HasFlag(NavigationPoint.NavigationFlags.Position)) if (_currentGoal.Flags.HasFlag(NavigationPoint.NavigationFlags.Position)) {
{
CurrentGoalPositionWorld = _pathWorldNavigationPoints[0].WorldPosition; CurrentGoalPositionWorld = _pathWorldNavigationPoints[0].WorldPosition;
} } else {
else
{
CurrentGoalAngleWorld = Globals.CalcPlaneAngle(currentTransformWorld); CurrentGoalAngleWorld = Globals.CalcPlaneAngle(currentTransformWorld);
} }
if (_currentGoal.Flags.HasFlag(NavigationPoint.NavigationFlags.Orientation)) if (_currentGoal.Flags.HasFlag(NavigationPoint.NavigationFlags.Orientation)) {
{
CurrentGoalAngleWorld = _currentGoal.WorldAngle; CurrentGoalAngleWorld = _currentGoal.WorldAngle;
} } else {
else
{
CurrentGoalAngleWorld = Globals.CalcPlaneAngle(currentTransformWorld); CurrentGoalAngleWorld = Globals.CalcPlaneAngle(currentTransformWorld);
} }
if (_currentGoal.IsReached(currentTransformWorld)) if (_currentGoal.IsReached(currentTransformWorld)) {
{
_pathWorldNavigationPoints.RemoveAt(0); _pathWorldNavigationPoints.RemoveAt(0);
UpdateCurrentGoal(); UpdateCurrentGoal();
ApplyExistingTransform(currentTransformWorld); ApplyExistingTransform(currentTransformWorld);
} }
if (_pathWorldNavigationPoints.Count == 0) if (_pathWorldNavigationPoints.Count == 0) {
{
CurrentGoalPositionWorld = currentTransformWorld.origin; CurrentGoalPositionWorld = currentTransformWorld.origin;
CurrentGoalAngleWorld = Globals.CalcPlaneAngle(currentTransformWorld); CurrentGoalAngleWorld = Globals.CalcPlaneAngle(currentTransformWorld);
} }
} }
public bool IsGoalReached() public bool IsGoalReached() {
{
return _pathWorldNavigationPoints.Count == 0; return _pathWorldNavigationPoints.Count == 0;
} }
public void DebugDraw(Spatial parentNode, DebugGeometry debugGeometry) public void DebugDraw(Spatial parentNode, DebugGeometry debugGeometry) {
{
Vector3 yOffset = Vector3.Up * 0.1f; Vector3 yOffset = Vector3.Up * 0.1f;
debugGeometry.GlobalTransform = Transform.Identity; debugGeometry.GlobalTransform = Transform.Identity;
@ -428,8 +360,7 @@ public class NavigationComponent : Spatial
debugGeometry.PopTransform(); debugGeometry.PopTransform();
Vector3 previousPoint = parentNode.GlobalTranslation; Vector3 previousPoint = parentNode.GlobalTranslation;
foreach (NavigationPoint point in _pathWorldNavigationPoints) foreach (NavigationPoint point in _pathWorldNavigationPoints) {
{
debugGeometry.AddVertex(previousPoint + yOffset); debugGeometry.AddVertex(previousPoint + yOffset);
debugGeometry.AddVertex(point.WorldPosition + yOffset); debugGeometry.AddVertex(point.WorldPosition + yOffset);
@ -437,8 +368,7 @@ public class NavigationComponent : Spatial
} }
previousPoint = parentNode.GlobalTranslation; previousPoint = parentNode.GlobalTranslation;
foreach (NavigationPoint point in _smoothedPathWorldNavigationPoints) foreach (NavigationPoint point in _smoothedPathWorldNavigationPoints) {
{
debugGeometry.SetColor(new Color(0, 0, 1)); debugGeometry.SetColor(new Color(0, 0, 1));
debugGeometry.AddVertex(previousPoint + yOffset); debugGeometry.AddVertex(previousPoint + yOffset);
debugGeometry.AddVertex(point.WorldPosition + yOffset); debugGeometry.AddVertex(point.WorldPosition + yOffset);
@ -447,8 +377,7 @@ public class NavigationComponent : Spatial
} }
previousPoint = parentNode.GlobalTranslation; previousPoint = parentNode.GlobalTranslation;
foreach (NavigationPoint point in _planningPathWorldNavigationPoints) foreach (NavigationPoint point in _planningPathWorldNavigationPoints) {
{
debugGeometry.SetColor(new Color(1, 0, 1)); debugGeometry.SetColor(new Color(1, 0, 1));
debugGeometry.AddVertex(previousPoint + yOffset); debugGeometry.AddVertex(previousPoint + yOffset);
debugGeometry.AddVertex(point.WorldPosition + yOffset); debugGeometry.AddVertex(point.WorldPosition + yOffset);
@ -457,8 +386,7 @@ public class NavigationComponent : Spatial
} }
previousPoint = parentNode.GlobalTranslation; previousPoint = parentNode.GlobalTranslation;
foreach (NavigationPoint point in _planningPathSmoothedWorldNavigationPoints) foreach (NavigationPoint point in _planningPathSmoothedWorldNavigationPoints) {
{
debugGeometry.SetColor(new Color(1, 1, 0)); debugGeometry.SetColor(new Color(1, 1, 0));
debugGeometry.AddVertex(previousPoint + yOffset); debugGeometry.AddVertex(previousPoint + yOffset);
debugGeometry.AddVertex(point.WorldPosition + yOffset); debugGeometry.AddVertex(point.WorldPosition + yOffset);

View File

@ -1,30 +1,24 @@
using Godot; using Godot;
using System;
public class Entity : KinematicBody public class Entity : KinematicBody {
{
public Vector3 Velocity { get; set; } = Vector3.Zero; public Vector3 Velocity { get; set; } = Vector3.Zero;
public float RotationalVelocity { get; set; } = 0; 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); get => Globals.CalcPlaneAngle(GlobalTransform);
set => GlobalTransform = new Transform(new Basis(Vector3.Up, value + Mathf.Pi * 0.5f), GlobalTranslation); 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 angleToTarget = Vector3.Right.SignedAngleTo(globalTargetDirection, Vector3.Up);
float currentAngle = PlaneAngle; float currentAngle = PlaneAngle;
float delta = angleToTarget - currentAngle; 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; return delta;
} }
public override void _Ready()
{
}
} }

View File

@ -4,8 +4,7 @@ using Godot;
using GodotComponentTest.components; using GodotComponentTest.components;
using GodotComponentTest.entities; using GodotComponentTest.entities;
public class Tree : StaticBody, IInteractionInterface public class Tree : StaticBody, IInteractionInterface {
{
[Export] public float ChopDuration = 2; [Export] public float ChopDuration = 2;
public bool IsMouseOver; public bool IsMouseOver;
public InteractionComponent InteractionComponent { get; set; } public InteractionComponent InteractionComponent { get; set; }
@ -18,9 +17,11 @@ public class Tree : StaticBody, IInteractionInterface
[Signal] [Signal]
public delegate void EntityClicked(Entity entity); 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. // Called when the node enters the scene tree for the first time.
public override void _Ready() public override void _Ready() {
{
_geometry = GetNode<MeshInstance>("Geometry/tree"); _geometry = GetNode<MeshInstance>("Geometry/tree");
Debug.Assert(_geometry != null); Debug.Assert(_geometry != null);
_animationPlayer = GetNode<AnimationPlayer>("AnimationPlayer"); _animationPlayer = GetNode<AnimationPlayer>("AnimationPlayer");
@ -33,56 +34,43 @@ public class Tree : StaticBody, IInteractionInterface
Connect("mouse_exited", this, nameof(OnAreaMouseExited)); Connect("mouse_exited", this, nameof(OnAreaMouseExited));
} }
public override void _Process(float delta) {
public override void _Process(float delta)
{
base._Process(delta); base._Process(delta);
if (_isBeingChopped) if (_isBeingChopped) {
{
_health = Math.Max(0, _health - delta * 100 / ChopDuration); _health = Math.Max(0, _health - delta * 100 / ChopDuration);
} }
if (_health == 0) if (_health == 0) {
{
InteractionComponent.EndInteraction(); InteractionComponent.EndInteraction();
InteractionComponent.TargetEntity = null; InteractionComponent.TargetEntity = null;
QueueFree(); EmitSignal("TreeChopped", this);
} }
} }
public void OnAreaInputEvent(Node camera, InputEvent inputEvent, Vector3 position, Vector3 normal, public void OnAreaInputEvent(Node camera, InputEvent inputEvent, Vector3 position, Vector3 normal,
int shapeIndex) int shapeIndex) {
{ if (IsMouseOver && inputEvent is InputEventMouseButton) {
if (IsMouseOver && inputEvent is InputEventMouseButton)
{
InputEventMouseButton mouseButtonEvent = (InputEventMouseButton)inputEvent; InputEventMouseButton mouseButtonEvent = (InputEventMouseButton)inputEvent;
if (mouseButtonEvent.ButtonIndex == 1 && mouseButtonEvent.Pressed) if (mouseButtonEvent.ButtonIndex == 1 && mouseButtonEvent.Pressed) {
{
EmitSignal("EntityClicked", this); EmitSignal("EntityClicked", this);
} }
} }
} }
public void OnAreaMouseEntered() {
public void OnAreaMouseEntered()
{
IsMouseOver = true; IsMouseOver = true;
SpatialMaterial overrideMaterial = new SpatialMaterial(); SpatialMaterial overrideMaterial = new();
overrideMaterial.AlbedoColor = new Color(1, 0, 0); overrideMaterial.AlbedoColor = new Color(1, 0, 0);
_geometry.MaterialOverride = overrideMaterial; _geometry.MaterialOverride = overrideMaterial;
} }
public void OnAreaMouseExited() {
public void OnAreaMouseExited()
{
IsMouseOver = false; IsMouseOver = false;
_geometry.MaterialOverride = null; _geometry.MaterialOverride = null;
} }
public void OnInteractionStart() {
public void OnInteractionStart()
{
GD.Print("Starting tree animationplayer"); GD.Print("Starting tree animationplayer");
_animationPlayer.CurrentAnimation = "TreeShake"; _animationPlayer.CurrentAnimation = "TreeShake";
_animationPlayer.Seek(0); _animationPlayer.Seek(0);
@ -90,8 +78,7 @@ public class Tree : StaticBody, IInteractionInterface
_isBeingChopped = true; _isBeingChopped = true;
} }
public void OnInteractionEnd() public void OnInteractionEnd() {
{
_animationPlayer.CurrentAnimation = "Idle"; _animationPlayer.CurrentAnimation = "Idle";
_animationPlayer.Seek(0); _animationPlayer.Seek(0);
_animationPlayer.Stop(); _animationPlayer.Stop();

View File

@ -30,7 +30,7 @@ keystore/release="/home/martin/projects/GodotComponentTest/android/keystore/debu
keystore/release_user="androiddebugkey" keystore/release_user="androiddebugkey"
keystore/release_password="android" keystore/release_password="android"
one_click_deploy/clear_previous_install=true one_click_deploy/clear_previous_install=true
version/code=1 version/code=5
version/name="1.0" version/name="1.0"
package/unique_name="org.godotengine.$genname" package/unique_name="org.godotengine.$genname"
package/name="" package/name=""

View File

@ -4,11 +4,10 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using Godot; using Godot;
using Godot.Collections; using Godot.Collections;
using Priority_Queue;
public class World : Spatial public class World : Spatial {
{ public enum GenerationState {
public enum GenerationState
{
Undefined, Undefined,
Heightmap, Heightmap,
TileType, TileType,
@ -17,7 +16,7 @@ public class World : Spatial
} }
// constants // constants
public const int ChunkSize = 12; public const int ChunkSize = 14;
public const int NumChunkRows = 3; public const int NumChunkRows = 3;
public const int NumChunkColumns = NumChunkRows; public const int NumChunkColumns = NumChunkRows;
private static readonly Color RockColor = new(0.5f, 0.5f, 0.4f); 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<Vector2, WorldChunk> _cachedWorldChunks; private readonly Godot.Collections.Dictionary<Vector2, WorldChunk> _cachedWorldChunks;
private readonly List<Vector2> _addedChunkIndices = new(); private readonly List<Vector2> _addedChunkIndices = new();
private readonly List<WorldChunk> _unusedWorldChunks = new(); private readonly List<WorldChunk> _deactivatedWorldChunks = new();
private readonly Image _heightmapImage = new(); private readonly Image _heightmapImage = new();
private readonly List<Vector2> _removedChunkIndices = new(); private readonly List<Vector2> _removedChunkIndices = new();
private readonly Image _tileTypeMapImage = new(); private readonly Image _tileTypeMapImage = new();
private int FrameCounter;
// referenced scenes // referenced scenes
private readonly PackedScene _worldChunkScene = GD.Load<PackedScene>("res://scenes/WorldChunk.tscn"); private readonly PackedScene _worldChunkScene = GD.Load<PackedScene>("res://scenes/WorldChunk.tscn");
private List<Vector2> _activeChunkIndices = new(); private List<Vector2> _activeChunkIndices = new();
private Rect2 _centerChunkRect; private Rect2 _centerChunkRect;
private readonly List<Spatial> _removedSpatialNodes = new();
// delegate void OnCoordClicked(Vector2 world_pos); // delegate void OnCoordClicked(Vector2 world_pos);
@ -93,16 +92,14 @@ public class World : Spatial
[Signal] [Signal]
private delegate void TileHovered(HexTile3D tile3d); private delegate void TileHovered(HexTile3D tile3d);
public World() public World() {
{
Debug.Assert(ChunkSize % 2 == 0); Debug.Assert(ChunkSize % 2 == 0);
_cachedWorldChunks = new Godot.Collections.Dictionary<Vector2, WorldChunk>(); _cachedWorldChunks = new Godot.Collections.Dictionary<Vector2, WorldChunk>();
} }
// Called when the node enters the scene tree for the first time. // Called when the node enters the scene tree for the first time.
public override void _Ready() public override void _Ready() {
{
Chunks = (Spatial)FindNode("Chunks"); Chunks = (Spatial)FindNode("Chunks");
Debug.Assert(Chunks != null); Debug.Assert(Chunks != null);
@ -117,19 +114,24 @@ public class World : Spatial
GetNode<Spatial>("Assets").Visible = false; GetNode<Spatial>("Assets").Visible = false;
_rockAssets = new Array<Spatial>(); _rockAssets = new Array<Spatial>();
foreach (Spatial asset in GetNode<Node>("Assets/Rocks").GetChildren()) _rockAssets.Add(asset); foreach (Spatial asset in GetNode<Node>("Assets/Rocks").GetChildren()) {
_rockAssets.Add(asset);
}
_grassAssets = new Array<Spatial>(); _grassAssets = new Array<Spatial>();
foreach (Spatial asset in GetNode<Node>("Assets/Grass").GetChildren()) _grassAssets.Add(asset); foreach (Spatial asset in GetNode<Node>("Assets/Grass").GetChildren()) {
_grassAssets.Add(asset);
}
_treeAssets = new Array<Spatial>(); _treeAssets = new Array<Spatial>();
foreach (Spatial asset in GetNode<Node>("Assets/Trees").GetChildren()) _treeAssets.Add(asset); foreach (Spatial asset in GetNode<Node>("Assets/Trees").GetChildren()) {
_treeAssets.Add(asset);
}
SetCenterPlaneCoord(Vector2.Zero); SetCenterPlaneCoord(Vector2.Zero);
} }
public void InitNoiseGenerator() public void InitNoiseGenerator() {
{
_noiseGenerator = new OpenSimplexNoise(); _noiseGenerator = new OpenSimplexNoise();
_noiseGenerator.Seed = Seed; _noiseGenerator.Seed = Seed;
@ -139,22 +141,17 @@ public class World : Spatial
_noiseGenerator.Lacunarity = 2; _noiseGenerator.Lacunarity = 2;
} }
public WorldChunk GetOrCreateWorldChunk(Vector2 chunkIndex, Color debugColor) public WorldChunk GetOrCreateWorldChunk(Vector2 chunkIndex, Color debugColor) {
{
WorldChunk chunk; WorldChunk chunk;
if (IsChunkCached(chunkIndex)) if (IsChunkCached(chunkIndex)) {
return _cachedWorldChunks[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); chunk = CreateWorldChunk(chunkIndex, debugColor);
} }
@ -163,14 +160,12 @@ public class World : Spatial
return chunk; return chunk;
} }
private bool IsChunkCached(Vector2 chunkIndex) private bool IsChunkCached(Vector2 chunkIndex) {
{
return _cachedWorldChunks.ContainsKey(chunkIndex); return _cachedWorldChunks.ContainsKey(chunkIndex);
} }
private WorldChunk CreateWorldChunk(Vector2 chunkIndex, Color debugColor) private WorldChunk CreateWorldChunk(Vector2 chunkIndex, Color debugColor) {
{
WorldChunk result = (WorldChunk)_worldChunkScene.Instance(); WorldChunk result = (WorldChunk)_worldChunkScene.Instance();
Chunks.AddChild(result); Chunks.AddChild(result);
result.Connect("TileClicked", this, nameof(OnTileClicked)); result.Connect("TileClicked", this, nameof(OnTileClicked));
@ -191,15 +186,16 @@ public class World : Spatial
return result; 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); Vector3 colorDifference = new(colorA.r - colorB.r, colorA.g - colorB.g, colorA.b - colorB.b);
return colorDifference.LengthSquared() < 0.1 * 0.1; return colorDifference.LengthSquared() < 0.1 * 0.1;
} }
private Spatial SelectAsset(Vector2 textureCoord, Array<Spatial> assets, Random randomGenerator, double probability) private Spatial SelectAsset(Vector2 textureCoord, Array<Spatial> assets, Random randomGenerator,
{ double probability) {
if (randomGenerator.NextDouble() < 1.0 - probability) return null; if (randomGenerator.NextDouble() < 1.0 - probability) {
return null;
}
int assetIndex = randomGenerator.Next(assets.Count); int assetIndex = randomGenerator.Next(assets.Count);
Spatial assetInstance = (Spatial)assets[assetIndex].Duplicate(); Spatial assetInstance = (Spatial)assets[assetIndex].Duplicate();
@ -214,41 +210,40 @@ public class World : Spatial
return assetInstance; return assetInstance;
} }
private void PopulateChunk(WorldChunk chunk) private void PopulateChunk(WorldChunk chunk) {
{
Random environmentRandom = new(Seed); Random environmentRandom = new(Seed);
Image tileTypeImage = chunk.TileTypeOffscreenViewport.GetTexture().GetData(); chunk.CreateUnlockedTileTypeImage();
tileTypeImage.Lock();
foreach (int textureCoordU in Enumerable.Range(0, chunk.Size)) foreach (int textureCoordU in Enumerable.Range(0, chunk.Size)) {
foreach (int textureCoordV in Enumerable.Range(0, chunk.Size)) foreach (int textureCoordV in Enumerable.Range(0, chunk.Size)) {
{ Color colorValue = chunk.TileTypeImage.GetPixel(textureCoordU, textureCoordV);
Color colorValue = tileTypeImage.GetPixel(textureCoordU, textureCoordV); Vector2 textureCoord = new(textureCoordU, textureCoordV);
Vector2 textureCoord = new(textureCoordU, textureCoordV); Vector2 offsetCoord = chunk.ChunkIndex * ChunkSize + textureCoord;
Vector2 offsetCoord = chunk.ChunkIndex * ChunkSize + textureCoord;
if (IsColorEqualApprox(colorValue, RockColor)) if (IsColorEqualApprox(colorValue, RockColor)) {
{ Spatial rockAsset = SelectAsset(textureCoord, _rockAssets, environmentRandom, 0.15);
Spatial rockAsset = SelectAsset(textureCoord, _rockAssets, environmentRandom, 0.15); if (rockAsset != null) {
if (rockAsset != null) chunk.Entities.AddChild(rockAsset); chunk.Entities.AddChild(rockAsset);
// TODO: MarkCellUnwalkable(cell); MarkCellUnwalkable(HexGrid.GetHexAtOffset(offsetCoord));
} }
else if (IsColorEqualApprox(colorValue, GrassColor) || IsColorEqualApprox(colorValue, DarkGrassColor)) } else if (IsColorEqualApprox(colorValue, GrassColor) ||
{ IsColorEqualApprox(colorValue, DarkGrassColor)) {
Spatial grassAsset = SelectAsset(textureCoord, _grassAssets, environmentRandom, 0.15); Spatial grassAsset = SelectAsset(textureCoord, _grassAssets, environmentRandom, 0.15);
if (grassAsset != null) chunk.Entities.AddChild(grassAsset); if (grassAsset != null) {
chunk.Entities.AddChild(grassAsset);
}
Tree treeAsset = SelectAsset(textureCoord, _treeAssets, environmentRandom, 0.05) as Tree; Tree treeAsset = SelectAsset(textureCoord, _treeAssets, environmentRandom, 0.05) as Tree;
if (treeAsset != null) if (treeAsset != null) {
{ chunk.Entities.AddChild(treeAsset);
chunk.Entities.AddChild(treeAsset); treeAsset.Connect("EntityClicked", this, nameof(OnEntityClicked));
treeAsset.Connect("EntityClicked", this, nameof(OnEntityClicked)); treeAsset.Connect("TreeChopped", this, nameof(OnBlockingSpatialRemoved));
} MarkCellUnwalkable(HexGrid.GetHexAtOffset(offsetCoord));
}
// TODO: MarkCellUnwalkable(cell);
// TODO: MarkCellUnwalkable(cell); // else if (environmentRandom.NextDouble() < 0.01)
// else if (environmentRandom.NextDouble() < 0.01)
// { // {
// var chestAsset = (Chest)_chestScene.Instance(); // var chestAsset = (Chest)_chestScene.Instance();
// var assetTransform = Transform.Identity; // var assetTransform = Transform.Identity;
@ -258,36 +253,29 @@ public class World : Spatial
// Entities.AddChild(chestAsset); // Entities.AddChild(chestAsset);
// MarkCellUnwalkable(cell); // MarkCellUnwalkable(cell);
// } // }
} }
// else if (IsColorWater(colorValue)) // else if (IsColorWater(colorValue))
// { // {
// MarkCellUnwalkable(cell); // MarkCellUnwalkable(cell);
// } // }
}
} }
tileTypeImage.Unlock();
} }
public Vector2 WorldToOffsetCoords(Vector3 fromPositionWorld) public Vector2 WorldToOffsetCoords(Vector3 fromPositionWorld) {
{
return HexGrid.GetHexAt(new Vector2(fromPositionWorld.x, fromPositionWorld.z)).OffsetCoords; return HexGrid.GetHexAt(new Vector2(fromPositionWorld.x, fromPositionWorld.z)).OffsetCoords;
} }
public Vector3 GetHexCenterFromOffset(Vector2 fromPositionOffset) public Vector3 GetHexCenterFromOffset(Vector2 fromPositionOffset) {
{
return HexGrid.GetHexCenterVec3FromOffset(fromPositionOffset); return HexGrid.GetHexCenterVec3FromOffset(fromPositionOffset);
} }
public void UpdateCenterChunkFromPlaneCoord(Vector2 planeCoord) public void UpdateCenterChunkFromPlaneCoord(Vector2 planeCoord) {
{ if (State != GenerationState.Done) {
if (State != GenerationState.Done)
{
GD.PrintErr("Cannot update chunk to new planeCoord " + planeCoord + ": Chunk generation not yet finished!"); GD.PrintErr("Cannot update chunk to new planeCoord " + planeCoord + ": Chunk generation not yet finished!");
return; return;
} }
GD.Print("Update Chunks: " + FrameCounter);
// mark all chunks as retired // mark all chunks as retired
Godot.Collections.Dictionary<Vector2, WorldChunk> oldCachedChunks = new(_cachedWorldChunks); Godot.Collections.Dictionary<Vector2, WorldChunk> oldCachedChunks = new(_cachedWorldChunks);
@ -319,15 +307,16 @@ public class World : Spatial
_activeChunkIndices.Add(CenterChunkIndex + new Vector2(+1, +1)); _activeChunkIndices.Add(CenterChunkIndex + new Vector2(+1, +1));
// clear unused chunks // clear unused chunks
_unusedWorldChunks.Clear(); _deactivatedWorldChunks.Clear();
_addedChunkIndices.Clear(); _addedChunkIndices.Clear();
foreach (Vector2 oldChunkIndex in oldCachedChunks.Keys) foreach (Vector2 oldChunkIndex in oldCachedChunks.Keys) {
if (!_activeChunkIndices.Contains(oldChunkIndex)) if (!_activeChunkIndices.Contains(oldChunkIndex)) {
DeactivateChunk(oldCachedChunks[oldChunkIndex]); DeactivateChunk(oldCachedChunks[oldChunkIndex]);
}
}
foreach (Vector2 activeChunkIndex in _activeChunkIndices) foreach (Vector2 activeChunkIndex in _activeChunkIndices) {
{
WorldChunk chunk = GetOrCreateWorldChunk(activeChunkIndex, WorldChunk chunk = GetOrCreateWorldChunk(activeChunkIndex,
new Color(GD.Randf(), GD.Randf(), GD.Randf())); new Color(GD.Randf(), GD.Randf(), GD.Randf()));
_cachedWorldChunks[activeChunkIndex] = chunk; _cachedWorldChunks[activeChunkIndex] = chunk;
@ -335,34 +324,29 @@ public class World : Spatial
Debug.Assert(_activeChunkIndices.Count == NumChunkRows * NumChunkColumns); Debug.Assert(_activeChunkIndices.Count == NumChunkRows * NumChunkColumns);
foreach (Vector2 chunkKey in _activeChunkIndices) foreach (Vector2 chunkKey in _activeChunkIndices) {
if (!oldCachedChunks.ContainsKey(chunkKey)) if (!oldCachedChunks.ContainsKey(chunkKey)) {
{
ActivateChunk(_cachedWorldChunks[chunkKey], chunkKey); ActivateChunk(_cachedWorldChunks[chunkKey], chunkKey);
State = GenerationState.Heightmap; State = GenerationState.Heightmap;
} }
}
} }
private void ActivateChunk(WorldChunk chunk, Vector2 chunkIndex) private void ActivateChunk(WorldChunk chunk, Vector2 chunkIndex) {
{
chunk.SetChunkIndex(chunkIndex, HexGrid); chunk.SetChunkIndex(chunkIndex, HexGrid);
chunk.UpdateTileTransforms(); chunk.UpdateTileTransforms();
_addedChunkIndices.Add(chunk.ChunkIndex); _addedChunkIndices.Add(chunk.ChunkIndex);
GD.Print("Generating noise for chunk " + chunk.ChunkIndex);
GenerateChunkNoiseMap(chunk); GenerateChunkNoiseMap(chunk);
} }
private void DeactivateChunk(WorldChunk chunk) private void DeactivateChunk(WorldChunk chunk) {
{
GD.Print("Clearing chunk index: " + chunk.ChunkIndex);
_cachedWorldChunks.Remove(chunk.ChunkIndex); _cachedWorldChunks.Remove(chunk.ChunkIndex);
chunk.ClearContent(); chunk.ClearContent();
_unusedWorldChunks.Add(chunk); _deactivatedWorldChunks.Add(chunk);
} }
private void GenerateChunkNoiseMap(WorldChunk chunk) private void GenerateChunkNoiseMap(WorldChunk chunk) {
{
Vector2 chunkIndex = chunk.ChunkIndex; Vector2 chunkIndex = chunk.ChunkIndex;
ImageTexture noiseImageTexture = new(); ImageTexture noiseImageTexture = new();
@ -372,27 +356,29 @@ public class World : Spatial
chunk.SetNoisemap(noiseImageTexture); chunk.SetNoisemap(noiseImageTexture);
} }
private void RemoveChunk(Vector2 cachedChunkKey) private void RemoveChunk(Vector2 cachedChunkKey) {
{
_cachedWorldChunks.Remove(cachedChunkKey); _cachedWorldChunks.Remove(cachedChunkKey);
_removedChunkIndices.Add(cachedChunkKey); _removedChunkIndices.Add(cachedChunkKey);
foreach (WorldChunk chunk in Chunks.GetChildren()) foreach (WorldChunk chunk in Chunks.GetChildren()) {
if (chunk.ChunkIndex == new Vector2(cachedChunkKey.x, cachedChunkKey.y)) if (chunk.ChunkIndex == new Vector2(cachedChunkKey.x, cachedChunkKey.y)) {
chunk.QueueFree(); chunk.QueueFree();
}
}
} }
private Vector2 GetChunkTupleFromPlaneCoord(Vector2 planeCoord) private Vector2 GetChunkTupleFromPlaneCoord(Vector2 planeCoord) {
{ HexCell hexCell = HexGrid.GetHexAt(planeCoord);
HexCell centerOffsetCoord = HexGrid.GetHexAt(planeCoord); return GetChunkIndexFromOffsetCoord(hexCell.OffsetCoords);
return (centerOffsetCoord.OffsetCoords / ChunkSize).Floor();
} }
public void SetCenterPlaneCoord(Vector2 centerPlaneCoord) private Vector2 GetChunkIndexFromOffsetCoord(Vector2 offsetCoord) {
{ return (offsetCoord / ChunkSize).Floor();
if (!_centerChunkRect.HasPoint(centerPlaneCoord)) }
{
public void SetCenterPlaneCoord(Vector2 centerPlaneCoord) {
if (!_centerChunkRect.HasPoint(centerPlaneCoord)) {
UpdateCenterChunkFromPlaneCoord(centerPlaneCoord); UpdateCenterChunkFromPlaneCoord(centerPlaneCoord);
UpdateChunkBounds(); UpdateChunkBounds();
@ -401,8 +387,7 @@ public class World : Spatial
} }
} }
private void UpdateWorldViewTexture() private void UpdateWorldViewTexture() {
{
int worldChunkSize = ChunkSize; int worldChunkSize = ChunkSize;
int numWorldChunkRows = NumChunkRows; int numWorldChunkRows = NumChunkRows;
int numWorldChunkColumns = NumChunkColumns; int numWorldChunkColumns = NumChunkColumns;
@ -412,8 +397,7 @@ public class World : Spatial
_tileTypeMapImage.Create(worldChunkSize * numWorldChunkColumns, worldChunkSize * numWorldChunkRows, false, _tileTypeMapImage.Create(worldChunkSize * numWorldChunkColumns, worldChunkSize * numWorldChunkRows, false,
Image.Format.Rgba8); Image.Format.Rgba8);
foreach (Vector2 chunkIndex in _activeChunkIndices) foreach (Vector2 chunkIndex in _activeChunkIndices) {
{
WorldChunk worldChunk = GetOrCreateWorldChunk(chunkIndex, Colors.White); WorldChunk worldChunk = GetOrCreateWorldChunk(chunkIndex, Colors.White);
_heightmapImage.BlendRect( _heightmapImage.BlendRect(
@ -439,113 +423,207 @@ public class World : Spatial
EmitSignal("OnHeightmapImageChanged", _heightmapImage); EmitSignal("OnHeightmapImageChanged", _heightmapImage);
} }
private void UpdateChunkBounds() private void UpdateChunkBounds() {
{
_chunkIndexSouthWest = Vector2.Inf; _chunkIndexSouthWest = Vector2.Inf;
_chunkIndexNorthEast = -Vector2.Inf; _chunkIndexNorthEast = -Vector2.Inf;
foreach (Vector2 chunkIndex in _activeChunkIndices) foreach (Vector2 chunkIndex in _activeChunkIndices) {
{ if (chunkIndex.x <= _chunkIndexSouthWest.x && chunkIndex.y <= _chunkIndexSouthWest.y) {
WorldChunk worldChunk = GetOrCreateWorldChunk(chunkIndex, Colors.White);
if (chunkIndex.x <= _chunkIndexSouthWest.x && chunkIndex.y <= _chunkIndexSouthWest.y)
_chunkIndexSouthWest = chunkIndex; _chunkIndexSouthWest = chunkIndex;
else if (chunkIndex.x >= _chunkIndexNorthEast.x && chunkIndex.y >= _chunkIndexNorthEast.y) } else if (chunkIndex.x >= _chunkIndexNorthEast.x && chunkIndex.y >= _chunkIndexNorthEast.y) {
_chunkIndexNorthEast = chunkIndex; _chunkIndexNorthEast = chunkIndex;
}
} }
} }
private void UpdateNavigationBounds() private void UpdateNavigationBounds() {
{
HexCell cellSouthWest = HexGrid.GetHexAtOffset(_chunkIndexSouthWest * ChunkSize); 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)); 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<HexCell> FindPath(Entity entity, HexCell startHex, HexCell goalHex) {
if (State != GenerationState.Done) {
return new List<HexCell>();
}
Vector2 goalAxialCoords = goalHex.AxialCoords;
SimplePriorityQueue<Vector2, float> frontier = new();
frontier.Enqueue(startHex.AxialCoords, 0);
System.Collections.Generic.Dictionary<Vector2, Vector2> cameFrom = new();
System.Collections.Generic.Dictionary<Vector2, float> 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<HexCell> 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; GenerationState oldState = State;
UpdateGenerationState(); UpdateGenerationState();
if (oldState != GenerationState.Done && State == GenerationState.Done) if (oldState != GenerationState.Done && State == GenerationState.Done) {
UpdateWorldViewTexture(); UpdateWorldViewTexture();
}
while (_removedSpatialNodes.Count > 0) {
GD.Print("Queueing deletion of " + _removedSpatialNodes[0]);
_removedSpatialNodes[0].QueueFree();
_removedSpatialNodes.RemoveAt(0);
}
} }
private void UpdateGenerationState() private void UpdateGenerationState() {
{ if (State == GenerationState.Heightmap) {
FrameCounter++;
if (State == GenerationState.Heightmap)
{
int numChunksGeneratingHeightmap = 0; int numChunksGeneratingHeightmap = 0;
foreach (Vector2 chunkIndex in _addedChunkIndices) foreach (Vector2 chunkIndex in _addedChunkIndices) {
{
WorldChunk chunk = _cachedWorldChunks[chunkIndex]; WorldChunk chunk = _cachedWorldChunks[chunkIndex];
if (chunk.HeightMapFrameCount > 0) numChunksGeneratingHeightmap++; if (chunk.HeightMapFrameCount > 0) {
numChunksGeneratingHeightmap++;
}
} }
if (numChunksGeneratingHeightmap == 0) if (numChunksGeneratingHeightmap == 0) {
{
// assign height map images // assign height map images
foreach (Vector2 chunkIndex in _addedChunkIndices) foreach (Vector2 chunkIndex in _addedChunkIndices) {
{
WorldChunk chunk = _cachedWorldChunks[chunkIndex]; WorldChunk chunk = _cachedWorldChunks[chunkIndex];
chunk.SetHeightmap(chunk.HeightmapOffscreenViewport.GetTexture()); chunk.SetHeightmap(chunk.HeightmapOffscreenViewport.GetTexture());
} }
GD.Print("Switching to TileType Generation: " + FrameCounter);
State = GenerationState.TileType; State = GenerationState.TileType;
} }
} } else if (State == GenerationState.TileType) {
else if (State == GenerationState.TileType)
{
int numChunksGeneratingTileType = 0; int numChunksGeneratingTileType = 0;
foreach (Vector2 chunkIndex in _addedChunkIndices) foreach (Vector2 chunkIndex in _addedChunkIndices) {
{
WorldChunk chunk = _cachedWorldChunks[chunkIndex]; WorldChunk chunk = _cachedWorldChunks[chunkIndex];
if (chunk.TileTypeMapFrameCount > 0) numChunksGeneratingTileType++; if (chunk.TileTypeMapFrameCount > 0) {
numChunksGeneratingTileType++;
}
} }
if (numChunksGeneratingTileType == 0) if (numChunksGeneratingTileType == 0) {
{
GD.Print("Switching to Object Generation: " + FrameCounter);
State = GenerationState.Objects; State = GenerationState.Objects;
} }
} } else if (State == GenerationState.Objects) {
else if (State == GenerationState.Objects)
{
// generate objects // generate objects
foreach (Vector2 chunkIndex in _addedChunkIndices) foreach (Vector2 chunkIndex in _addedChunkIndices) {
PopulateChunk(_cachedWorldChunks[chunkIndex]); PopulateChunk(_cachedWorldChunks[chunkIndex]);
}
_addedChunkIndices.Clear(); _addedChunkIndices.Clear();
GD.Print("Generation done: " + FrameCounter);
State = GenerationState.Done; State = GenerationState.Done;
} }
} }
private void OnEntityClicked(Entity entity) private void OnEntityClicked(Entity entity) {
{
EmitSignal("EntityClicked", entity); EmitSignal("EntityClicked", entity);
} }
public void OnTileClicked(HexTile3D tile) public void OnTileClicked(HexTile3D tile) {
{
EmitSignal("TileClicked", tile); EmitSignal("TileClicked", tile);
} }
public void OnTileHovered(HexTile3D tile) public void OnTileHovered(HexTile3D tile) {
{
EmitSignal("TileHovered", 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);
}
} }

View File

@ -3,8 +3,7 @@ using System.Linq;
using Godot; using Godot;
using Godot.Collections; using Godot.Collections;
public class WorldChunk : Spatial public class WorldChunk : Spatial {
{
private readonly PackedScene _hexTile3DScene = GD.Load<PackedScene>("res://scenes/HexTile3D.tscn"); private readonly PackedScene _hexTile3DScene = GD.Load<PackedScene>("res://scenes/HexTile3D.tscn");
private MultiMeshInstance _multiMeshInstance; private MultiMeshInstance _multiMeshInstance;
private readonly Array<int> _tileInstanceIndices = new(); private readonly Array<int> _tileInstanceIndices = new();
@ -28,6 +27,7 @@ public class WorldChunk : Spatial
[Export] public Texture HeightMap; [Export] public Texture HeightMap;
public int HeightMapFrameCount; public int HeightMapFrameCount;
public Image TileTypeImage;
public Viewport HeightmapOffscreenViewport; public Viewport HeightmapOffscreenViewport;
[Export] public Texture NavigationMap; [Export] public Texture NavigationMap;
@ -56,32 +56,30 @@ public class WorldChunk : Spatial
public int TileTypeMapFrameCount; public int TileTypeMapFrameCount;
public Viewport TileTypeOffscreenViewport; public Viewport TileTypeOffscreenViewport;
public WorldChunk() public WorldChunk() { }
{
}
public WorldChunk(int size) public WorldChunk(int size) {
{
SetSize(size); SetSize(size);
} }
[Export] [Export]
public bool ShowTextureOverlay public bool ShowTextureOverlay {
{
get => _showTextureOverlay; get => _showTextureOverlay;
set set {
{ if (PlaneRectMesh != null) {
if (PlaneRectMesh != null) PlaneRectMesh.Visible = value; PlaneRectMesh.Visible = value;
}
} }
} }
// Called when the node enters the scene tree for the first time. // Called when the node enters the scene tree for the first time.
public override void _Ready() public override void _Ready() {
{
PlaneRectMesh = (MeshInstance)FindNode("PlaneRectMesh"); PlaneRectMesh = (MeshInstance)FindNode("PlaneRectMesh");
Debug.Assert(PlaneRectMesh != null); Debug.Assert(PlaneRectMesh != null);
if (PlaneRectMesh.Visible) _showTextureOverlay = true; if (PlaneRectMesh.Visible) {
_showTextureOverlay = true;
}
Transform planeRectTransform = Transform.Identity; Transform planeRectTransform = Transform.Identity;
planeRectTransform = planeRectTransform =
@ -116,12 +114,10 @@ public class WorldChunk : Spatial
SetSize(World.ChunkSize); SetSize(World.ChunkSize);
} }
public void SetSize(int size) public void SetSize(int size) {
{
Size = size; Size = size;
if (TileTypeOffscreenViewport != null) if (TileTypeOffscreenViewport != null) {
{
TileTypeOffscreenViewport.Size = Vector2.One * size; TileTypeOffscreenViewport.Size = Vector2.One * size;
HeightmapOffscreenViewport.Size = Vector2.One * size; HeightmapOffscreenViewport.Size = Vector2.One * size;
_noiseMask.Transform = Transform2D.Identity.Scaled(Vector2.One * size / _noiseMask.Texture.GetSize().x); _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; ChunkIndex = chunkIndex;
float chunkSize = World.ChunkSize; float chunkSize = World.ChunkSize;
@ -148,49 +143,48 @@ public class WorldChunk : Spatial
new Vector2(localPlaneCoordSouthWest.x, localPlaneCoordNorthEast.y), new Vector2(localPlaneCoordSouthWest.x, localPlaneCoordNorthEast.y),
new Vector2(localPlaneCoordNorthEast.x - localPlaneCoordSouthWest.x, new Vector2(localPlaneCoordNorthEast.x - localPlaneCoordSouthWest.x,
localPlaneCoordSouthWest.y - localPlaneCoordNorthEast.y) localPlaneCoordSouthWest.y - localPlaneCoordNorthEast.y)
); );
} }
public void InitializeTileInstances(Vector2 chunkIndex, MultiMeshInstance multiMeshInstance, public void InitializeTileInstances(Vector2 chunkIndex, MultiMeshInstance multiMeshInstance,
int tileInstanceIndexStart) int tileInstanceIndexStart) {
{
_multiMeshInstance = multiMeshInstance; _multiMeshInstance = multiMeshInstance;
_tileInstanceIndices.Clear(); _tileInstanceIndices.Clear();
int chunkSize = World.ChunkSize; int chunkSize = World.ChunkSize;
foreach (Spatial node in Tiles.GetChildren()) foreach (Spatial node in Tiles.GetChildren()) {
node.QueueFree(); node.QueueFree();
}
foreach (int i in Enumerable.Range(0, chunkSize)) foreach (int i in Enumerable.Range(0, chunkSize)) {
foreach (int j in Enumerable.Range(0, chunkSize)) foreach (int j in Enumerable.Range(0, chunkSize)) {
{ HexTile3D tile3D = (HexTile3D)_hexTile3DScene.Instance();
HexTile3D tile3D = (HexTile3D)_hexTile3DScene.Instance(); tile3D.Connect("TileClicked", this, nameof(OnTileClicked));
tile3D.Connect("TileClicked", this, nameof(OnTileClicked)); tile3D.Connect("TileHovered", this, nameof(OnTileHovered));
tile3D.Connect("TileHovered", this, nameof(OnTileHovered));
tile3D.Cell.OffsetCoords = new Vector2(chunkIndex * World.ChunkSize + new Vector2(i, j)); tile3D.Cell.OffsetCoords = new Vector2(chunkIndex * World.ChunkSize + new Vector2(i, j));
_tileInstanceIndices.Add(tileInstanceIndexStart + _tileInstanceIndices.Count); _tileInstanceIndices.Add(tileInstanceIndexStart + _tileInstanceIndices.Count);
Transform tileTransform = Transform.Identity; Transform tileTransform = Transform.Identity;
Vector2 centerPlaneCoord = _hexGrid.GetHexCenterFromOffset(new Vector2(i, j)); Vector2 centerPlaneCoord = _hexGrid.GetHexCenterFromOffset(new Vector2(i, j));
tileTransform.origin = new Vector3(centerPlaneCoord.x, 0, centerPlaneCoord.y); tileTransform.origin = new Vector3(centerPlaneCoord.x, 0, centerPlaneCoord.y);
tile3D.Transform = tileTransform; tile3D.Transform = tileTransform;
Tiles.AddChild(tile3D); Tiles.AddChild(tile3D);
}
} }
_multiMeshInstance.Multimesh.VisibleInstanceCount = _multiMeshInstance.Multimesh.InstanceCount; _multiMeshInstance.Multimesh.VisibleInstanceCount = _multiMeshInstance.Multimesh.InstanceCount;
} }
public void ClearContent() public void ClearContent() {
{ foreach (Spatial child in Entities.GetChildren()) {
foreach (Spatial child in Entities.GetChildren())
child.QueueFree(); child.QueueFree();
}
} }
public void UpdateTileTransforms() public void UpdateTileTransforms() {
{
Transform chunkTransform = Transform.Identity; Transform chunkTransform = Transform.Identity;
Vector2 chunkOriginPlaneCoord = _hexGrid.GetHexCenterFromOffset(ChunkIndex * World.ChunkSize); Vector2 chunkOriginPlaneCoord = _hexGrid.GetHexCenterFromOffset(ChunkIndex * World.ChunkSize);
chunkTransform.origin = new Vector3(chunkOriginPlaneCoord.x, 0, chunkOriginPlaneCoord.y); 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); 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 column = i % World.ChunkSize;
int row = i / World.ChunkSize; int row = i / World.ChunkSize;
@ -211,17 +202,17 @@ public class WorldChunk : Spatial
Transform hexTransform = new(tileOrientation, Transform hexTransform = new(tileOrientation,
chunkTransform.origin + new Vector3(tilePlaneCoord.x, 0, tilePlaneCoord.y)); chunkTransform.origin + new Vector3(tilePlaneCoord.x, 0, tilePlaneCoord.y));
if (_showHexTiles) if (_showHexTiles) {
hexTransform = new Transform(tileOrientation.Scaled(Vector3.One * 0.95f), hexTransform = new Transform(tileOrientation.Scaled(Vector3.One * 0.95f),
hexTransform.origin); hexTransform.origin);
}
_multiMeshInstance.Multimesh.SetInstanceTransform(_tileInstanceIndices[i], hexTransform); _multiMeshInstance.Multimesh.SetInstanceTransform(_tileInstanceIndices[i], hexTransform);
} }
} }
// other members // other members
public void SaveToFile(string chunkName) public void SaveToFile(string chunkName) {
{
Image image = new(); Image image = new();
image.CreateFromData(Size, Size, false, Image.Format.Rgba8, TileTypeMap.GetData().GetData()); image.CreateFromData(Size, Size, false, Image.Format.Rgba8, TileTypeMap.GetData().GetData());
@ -234,13 +225,10 @@ public class WorldChunk : Spatial
image.SavePng(chunkName + "_heightMap.png"); 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.Texture = texture;
_noiseSprite.Transform = _noiseSprite.Transform =
Transform2D.Identity.Scaled(HeightmapOffscreenViewport.Size / _noiseSprite.Texture.GetSize().x); Transform2D.Identity.Scaled(HeightmapOffscreenViewport.Size / _noiseSprite.Texture.GetSize().x);
@ -248,8 +236,7 @@ public class WorldChunk : Spatial
HeightMapFrameCount = 1; HeightMapFrameCount = 1;
} }
public void SetHeightmap(Texture texture) public void SetHeightmap(Texture texture) {
{
_heightmapSprite.Texture = texture; _heightmapSprite.Texture = texture;
_heightmapSprite.Transform = _heightmapSprite.Transform =
Transform2D.Identity.Scaled(TileTypeOffscreenViewport.Size / _heightmapSprite.Texture.GetSize()); Transform2D.Identity.Scaled(TileTypeOffscreenViewport.Size / _heightmapSprite.Texture.GetSize());
@ -258,25 +245,29 @@ public class WorldChunk : Spatial
TileTypeMapFrameCount = 1; 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(); Texture tileTypeTexture = TileTypeOffscreenViewport.GetTexture();
if (NoiseTextureCheckerboardOverlay) if (NoiseTextureCheckerboardOverlay) {
{
Image tileTypeImage = tileTypeTexture.GetData(); Image tileTypeImage = tileTypeTexture.GetData();
tileTypeImage.Lock(); tileTypeImage.Lock();
foreach (int i in Enumerable.Range(0, Size)) foreach (int i in Enumerable.Range(0, Size)) {
foreach (int j in Enumerable.Range(0, Size)) foreach (int j in Enumerable.Range(0, Size)) {
{ Vector2 textureCoord = new(i, j);
Vector2 textureCoord = new(i, j); Color baseColor = tileTypeImage.GetPixelv(textureCoord);
Color baseColor = tileTypeImage.GetPixelv(textureCoord);
if ((i + j) % 2 == 0) if ((i + j) % 2 == 0) {
tileTypeImage.SetPixelv(textureCoord, baseColor); tileTypeImage.SetPixelv(textureCoord, baseColor);
else } else {
tileTypeImage.SetPixelv(textureCoord, baseColor * 0.6f); tileTypeImage.SetPixelv(textureCoord, baseColor * 0.6f);
}
}
} }
tileTypeImage.Unlock(); tileTypeImage.Unlock();
@ -295,24 +286,26 @@ public class WorldChunk : Spatial
//RectMaterial.Uv1Triplanar = true; //RectMaterial.Uv1Triplanar = true;
PlaneRectMesh.SetSurfaceMaterial(0, _rectMaterial); 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; 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; TileTypeMapFrameCount = TileTypeMapFrameCount > 0 ? TileTypeMapFrameCount - 1 : 0;
PlaneRectMesh.MaterialOverride = null; PlaneRectMesh.MaterialOverride = null;
} }
public void OnTileClicked(HexTile3D tile) public void OnTileClicked(HexTile3D tile) {
{
EmitSignal("TileClicked", tile); EmitSignal("TileClicked", tile);
} }
public void OnTileHovered(HexTile3D tile) public void OnTileHovered(HexTile3D tile) {
{
EmitSignal("TileHovered", tile); EmitSignal("TileHovered", tile);
} }
} }

View File

@ -1,51 +1,40 @@
using Godot;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Godot.Collections; using Godot;
using GodotComponentTest.components; using GodotComponentTest.components;
using GodotComponentTest.entities; using GodotComponentTest.entities;
using Array = System.Array;
using NodePair = System.Tuple<Godot.Node, Godot.Node>; using NodePair = System.Tuple<Godot.Node, Godot.Node>;
public class InteractionSystem : Node public class InteractionSystem : Node {
{
private List<InteractionComponent> _activeInteractions; private List<InteractionComponent> _activeInteractions;
// Called when the node enters the scene tree for the first time. // Called when the node enters the scene tree for the first time.
public override void _Ready() public override void _Ready() {
{
_activeInteractions = new List<InteractionComponent>(); _activeInteractions = new List<InteractionComponent>();
} }
public override void _Process(float delta) public override void _Process(float delta) {
{
base._Process(delta); base._Process(delta);
List<NodePair> invalidInteractionPairs = new List<NodePair>(); List<InteractionComponent> endedInteractions = new();
List<InteractionComponent> endedInteractions = new List<InteractionComponent>();
foreach (InteractionComponent interaction in _activeInteractions) foreach (InteractionComponent interaction in _activeInteractions) {
{
Spatial owningEntity = interaction.OwningEntity; Spatial owningEntity = interaction.OwningEntity;
Spatial targetEntity = interaction.TargetEntity; 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; interaction.hasStopped = true;
} }
if (interaction.hasStopped) if (interaction.hasStopped) {
{
IInteractionInterface interactableA = owningEntity as IInteractionInterface; IInteractionInterface interactableA = owningEntity as IInteractionInterface;
if (interactableA != null) if (interactableA != null) {
{
interactableA.OnInteractionEnd(); interactableA.OnInteractionEnd();
interactableA.InteractionComponent = null; interactableA.InteractionComponent = null;
} }
IInteractionInterface interactableB = targetEntity as IInteractionInterface; IInteractionInterface interactableB = targetEntity as IInteractionInterface;
if (interactableB != null) if (interactableB != null) {
{
interactableB.OnInteractionEnd(); interactableB.OnInteractionEnd();
interactableB.InteractionComponent = null; interactableB.InteractionComponent = null;
} }
@ -55,14 +44,11 @@ public class InteractionSystem : Node
} }
foreach (InteractionComponent interaction in endedInteractions) foreach (InteractionComponent interaction in endedInteractions)
{
_activeInteractions.Remove(interaction); _activeInteractions.Remove(interaction);
}
} }
public void OnStartInteraction(Entity owningEntity, Entity targetEntity) public void OnStartInteraction(Entity owningEntity, Entity targetEntity) {
{ InteractionComponent interactionComponent = new();
InteractionComponent interactionComponent = new InteractionComponent();
interactionComponent.OwningEntity = owningEntity; interactionComponent.OwningEntity = owningEntity;
interactionComponent.TargetEntity = targetEntity; interactionComponent.TargetEntity = targetEntity;
@ -74,11 +60,9 @@ public class InteractionSystem : Node
_activeInteractions.Add(interactionComponent); _activeInteractions.Add(interactionComponent);
} }
private static void ConnectInteractionSignals(Entity entity, InteractionComponent interactionComponent) private static void ConnectInteractionSignals(Entity entity, InteractionComponent interactionComponent) {
{
IInteractionInterface interactable = entity as IInteractionInterface; IInteractionInterface interactable = entity as IInteractionInterface;
if (interactable != null) if (interactable != null) {
{
interactable.InteractionComponent = interactionComponent; interactable.InteractionComponent = interactionComponent;
interactionComponent.Connect("InteractionStart", entity, nameof(interactable.OnInteractionStart)); interactionComponent.Connect("InteractionStart", entity, nameof(interactable.OnInteractionStart));
interactionComponent.Connect("InteractionEnd", entity, nameof(interactable.OnInteractionEnd)); interactionComponent.Connect("InteractionEnd", entity, nameof(interactable.OnInteractionEnd));