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