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>
|
||||||
/// </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);
|
|
||||||
|
|
||||||
|
if (_planningPathSmoothedWorldNavigationPoints.Count > 0) {
|
||||||
_planningPathWorldNavigationPoints[_planningPathWorldNavigationPoints.Count - 1] = navigationPoint;
|
_planningPathWorldNavigationPoints[_planningPathWorldNavigationPoints.Count - 1] = navigationPoint;
|
||||||
_planningPathSmoothedWorldNavigationPoints[_planningPathSmoothedWorldNavigationPoints.Count - 1] =
|
_planningPathSmoothedWorldNavigationPoints[_planningPathSmoothedWorldNavigationPoints.Count - 1] =
|
||||||
navigationPoint;
|
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);
|
||||||
|
|
|
@ -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()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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();
|
||||||
|
|
|
@ -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=""
|
||||||
|
|
398
scenes/World.cs
398
scenes/World.cs
|
@ -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,39 +210,38 @@ 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) chunk.Entities.AddChild(rockAsset);
|
if (rockAsset != null) {
|
||||||
// TODO: MarkCellUnwalkable(cell);
|
chunk.Entities.AddChild(rockAsset);
|
||||||
|
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)
|
||||||
// {
|
// {
|
||||||
|
@ -264,30 +259,23 @@ public class World : Spatial
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
HexCell centerOffsetCoord = HexGrid.GetHexAt(planeCoord);
|
|
||||||
return (centerOffsetCoord.OffsetCoords / ChunkSize).Floor();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetCenterPlaneCoord(Vector2 centerPlaneCoord)
|
|
||||||
{
|
private Vector2 GetChunkTupleFromPlaneCoord(Vector2 planeCoord) {
|
||||||
if (!_centerChunkRect.HasPoint(centerPlaneCoord))
|
HexCell hexCell = HexGrid.GetHexAt(planeCoord);
|
||||||
{
|
return GetChunkIndexFromOffsetCoord(hexCell.OffsetCoords);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2 GetChunkIndexFromOffsetCoord(Vector2 offsetCoord) {
|
||||||
|
return (offsetCoord / ChunkSize).Floor();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateGenerationState()
|
while (_removedSpatialNodes.Count > 0) {
|
||||||
{
|
GD.Print("Queueing deletion of " + _removedSpatialNodes[0]);
|
||||||
FrameCounter++;
|
_removedSpatialNodes[0].QueueFree();
|
||||||
|
_removedSpatialNodes.RemoveAt(0);
|
||||||
if (State == GenerationState.Heightmap)
|
}
|
||||||
{
|
|
||||||
int numChunksGeneratingHeightmap = 0;
|
|
||||||
foreach (Vector2 chunkIndex in _addedChunkIndices)
|
|
||||||
{
|
|
||||||
WorldChunk chunk = _cachedWorldChunks[chunkIndex];
|
|
||||||
if (chunk.HeightMapFrameCount > 0) numChunksGeneratingHeightmap++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (numChunksGeneratingHeightmap == 0)
|
private void UpdateGenerationState() {
|
||||||
{
|
if (State == GenerationState.Heightmap) {
|
||||||
|
int numChunksGeneratingHeightmap = 0;
|
||||||
|
foreach (Vector2 chunkIndex in _addedChunkIndices) {
|
||||||
|
WorldChunk chunk = _cachedWorldChunks[chunkIndex];
|
||||||
|
if (chunk.HeightMapFrameCount > 0) {
|
||||||
|
numChunksGeneratingHeightmap++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
@ -152,19 +147,18 @@ public class WorldChunk : Spatial
|
||||||
}
|
}
|
||||||
|
|
||||||
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));
|
||||||
|
@ -179,18 +173,18 @@ public class WorldChunk : Spatial
|
||||||
|
|
||||||
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,26 +245,30 @@ 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();
|
||||||
ImageTexture imageTexture = new();
|
ImageTexture imageTexture = new();
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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));
|
||||||
|
|
Loading…
Reference in New Issue