From c7349f4ceeb9c1f97dc8f831c9f44c67694f1543 Mon Sep 17 00:00:00 2001 From: Martin Felis Date: Tue, 3 Jan 2023 17:46:55 +0100 Subject: [PATCH] WIP added navigation component, worked on ground motion component, added hexcell line to and distance functions. --- HexCell.cs | 39 +++++++++- components/GroundMotionComponent.cs | 90 +++++++++++++++++------ components/NavigationComponent.cs | 47 ++++++++++++ entities/Entity.cs | 12 +--- entities/Player.cs | 22 +++++- scenes/AdaptiveWorldStream.tscn | 15 +++- scenes/TileWorld.cs | 15 +++- tests/HexCellTests.cs | 108 ++++++++++++++++++++++++++-- 8 files changed, 301 insertions(+), 47 deletions(-) create mode 100644 components/NavigationComponent.cs diff --git a/HexCell.cs b/HexCell.cs index eb7d7e9..50cb04d 100644 --- a/HexCell.cs +++ b/HexCell.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Linq; using Godot; using Array = Godot.Collections.Array; @@ -45,6 +46,14 @@ public class HexCell : Resource CubeCoords = other.CubeCoords; } + public static HexCell FromOffsetCoords(Vector2 offsetCoords) + { + HexCell result = new HexCell(); + result.OffsetCoords = offsetCoords; + + return result; + } + private Vector3 _cubeCoords; public Vector3 CubeCoords @@ -123,8 +132,36 @@ public class HexCell : Resource return rounded; } - public HexCell getAdjacent(Vector3 dir) + public HexCell GetAdjacent(Vector3 dir) { return new HexCell(this.CubeCoords + dir); } + + public int DistanceTo(HexCell target) + { + return (int)( + Mathf.Abs(_cubeCoords.x - target.CubeCoords.x) + + Mathf.Abs(_cubeCoords.y - target.CubeCoords.y) + + Mathf.Abs(_cubeCoords.z - target.CubeCoords.z) + ) / 2; + } + + public HexCell[] LineTo(HexCell target) + { + HexCell nudgedTarget = new HexCell(); + nudgedTarget.CubeCoords = target.CubeCoords + new Vector3(1.0e-6f, 2.0e-6f, -3.0e-6f); + int steps = DistanceTo(target); + + HexCell[] path = new HexCell[steps + 1]; + + foreach (int dist in Enumerable.Range(0, steps)) + { + path[dist] = new HexCell(); + path[dist].CubeCoords = CubeCoords.LinearInterpolate(nudgedTarget.CubeCoords, (float)dist / steps); + } + + path[steps] = target; + + return path; + } } \ No newline at end of file diff --git a/components/GroundMotionComponent.cs b/components/GroundMotionComponent.cs index 5cb9108..32a38af 100644 --- a/components/GroundMotionComponent.cs +++ b/components/GroundMotionComponent.cs @@ -10,40 +10,84 @@ public class GroundMotionComponent { public float Accel = 50; public float Damping = 0.2f; - public float MaxSpeed = 2; + public float MaxSpeed = 8; - public void PhysicsProcess(float delta, Entity entity, Vector3 target_position) + + private void CalcPlaneVelocity(float delta, Entity entity, Vector3 targetPosition) { - Vector2 plane_target_vector = new Vector2(target_position.x - entity.GlobalTranslation.x, - target_position.z - entity.GlobalTranslation.z); - float distance_error = plane_target_vector.Length(); - - if (distance_error < 0.01) + Vector2 planeTargetVector = new Vector2(targetPosition.x - entity.GlobalTranslation.x, + targetPosition.z - entity.GlobalTranslation.z); + float targetDistance = planeTargetVector.Length(); + Vector2 planeTargetDirection = planeTargetVector / targetDistance; + + Vector2 planeVelocity = new Vector2(entity.Velocity.x, entity.Velocity.z); + // GD.Print("-- Step: distance: " + targetDistance + " dir: " + planeTargetDirection + " speed: " + planeVelocity.Length() + " vel dir: " + planeVelocity.Normalized()); + planeVelocity -= planeVelocity * Damping; + // GD.Print(" damp : speed: " + planeVelocity.Length() + " vel dir: " + planeVelocity.Normalized()); + + if (targetDistance < 0.01) { - entity.Velocity = Vector3.Zero; + planeVelocity = Vector2.Zero; } else { - Vector2 plane_velocity = new Vector2(entity.Velocity.x, entity.Velocity.z); - plane_velocity = plane_velocity + plane_target_vector / distance_error * Accel * delta; + planeVelocity = planeVelocity.Length() * planeTargetDirection + planeTargetDirection * Accel * delta; + // GD.Print(" accel: speed: " + planeVelocity.Length() + " vel dir: " + planeVelocity.Normalized()); - float projected_step = plane_target_vector.Dot(plane_velocity * delta); - GD.Print("Projected step: " + projected_step); - if (projected_step > 1) + float projectedStep = planeTargetDirection.Dot(planeVelocity * delta); + // GD.Print(" Projected step: " + projectedStep + " Speed: " + planeVelocity.Length() + " delta: " + delta); + if (projectedStep > targetDistance) { - plane_velocity /= projected_step; + planeVelocity *= targetDistance / projectedStep; + projectedStep = planeTargetDirection.Dot(planeVelocity * delta); + // GD.Print(" corr speed: " + planeVelocity.Length() + " step: " + projectedStep); } - - float plane_speed = plane_velocity.Length(); - if (plane_speed > MaxSpeed) + + float planeSpeed = planeVelocity.Length(); + if (planeSpeed > MaxSpeed) { - plane_velocity *= MaxSpeed / plane_speed; + planeVelocity *= MaxSpeed / planeSpeed; } - - entity.Velocity = new Vector3( - plane_velocity.x, 0, plane_velocity.y); } - entity.Velocity.x -= entity.Velocity.x * Damping; - entity.Velocity.z -= entity.Velocity.z * Damping; + entity.Velocity = new Vector3(planeVelocity.x, entity.Velocity.y, planeVelocity.y); + } + + + private void CalcVerticalVelocity(float delta, Entity entity, TileWorld tileWorld) + { + Vector3 entityVelocity = entity.Velocity; + + Vector2 currentTile = tileWorld.WorldToOffsetCoords(entity.GlobalTransform.origin); + Vector2 nextTile = tileWorld.WorldToOffsetCoords(entity.GlobalTransform.origin + entityVelocity * delta); + + if (nextTile != currentTile) + { + float currentHeight = tileWorld.GetHeightAtOffset(currentTile); + float nextHeight = tileWorld.GetHeightAtOffset(nextTile); + + bool isOnFloor = entity.IsOnFloor(); + + if (nextHeight - entity.GlobalTransform.origin.y > 0.1) + { + GD.Print("Jump!"); + entityVelocity.y = 10; + } + } + + entityVelocity.y -= 9.81f * delta; + + entity.Velocity = entityVelocity; + } + + + public void PhysicsProcess(float delta, Entity entity, Vector3 targetPosition, TileWorld tileWorld) + { + CalcPlaneVelocity(delta, entity, targetPosition); + + CalcVerticalVelocity(delta, entity, tileWorld); + + Vector3 prePhysicsVelocity = entity.Velocity; entity.Velocity = entity.MoveAndSlide(entity.Velocity); + GD.Print("Pre : speed: " + prePhysicsVelocity.Length() + " Velocity: " + prePhysicsVelocity); + GD.Print("Post: speed: " + entity.Velocity.Length() + " Velocity: " + entity.Velocity); } } \ No newline at end of file diff --git a/components/NavigationComponent.cs b/components/NavigationComponent.cs new file mode 100644 index 0000000..e97bbd3 --- /dev/null +++ b/components/NavigationComponent.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Godot; +using Vector2 = Godot.Vector2; +using Vector3 = Godot.Vector3; + +/// +/// +public class NavigationComponent +{ + public Vector3 currentGoalWorld; + public Vector2 currentGoalOffset; + public List pathOffsetCoords; + public HexCell[] path; + + private HexGrid _hexGrid; + + public void Plan(Vector3 currentPositionWorld, Vector3 targetPositionWorld, TileWorld tileWorld) + { + if ((targetPositionWorld - currentGoalWorld).LengthSquared() < 0.01) + { + return; + } + + currentGoalWorld = targetPositionWorld; + Vector2 currentPositionOffset = tileWorld.WorldToOffsetCoords(currentPositionWorld); + Vector2 currentGoalOffset = tileWorld.WorldToOffsetCoords(targetPositionWorld); + + HexCell currentCell = new HexCell(); + currentCell.OffsetCoords = currentPositionOffset; + + HexCell targetCell = new HexCell(); + targetCell.OffsetCoords = currentGoalOffset; + + path = currentCell.LineTo(targetCell); + + pathOffsetCoords = new List(); + foreach (HexCell cell in path) + { + pathOffsetCoords.Append(cell.OffsetCoords); + } + + currentGoalOffset = pathOffsetCoords[1]; + } +} \ No newline at end of file diff --git a/entities/Entity.cs b/entities/Entity.cs index 14871f2..69ea556 100644 --- a/entities/Entity.cs +++ b/entities/Entity.cs @@ -3,18 +3,10 @@ using System; public class Entity : KinematicBody { - public Vector3 Position = Vector3.Zero; - public Quat Orientation = Quat.Identity; + public Vector3 Velocity { get; set; } = Vector3.Zero; + public float RotationalVelocity { get; set; } = 0; - public Vector3 Velocity = Vector3.Zero; - public float RotationalVelocity = 0; - public override void _Ready() { } - - public override void _Process(float _delta) - { - Velocity = MoveAndSlide(Velocity); - } } \ No newline at end of file diff --git a/entities/Player.cs b/entities/Player.cs index 2cd39d3..d256e30 100644 --- a/entities/Player.cs +++ b/entities/Player.cs @@ -12,11 +12,13 @@ public class Player : Entity private WorldInfoComponent _worldInfo; private Vector2 _offsetCoord = Vector2.Zero; private GroundMotionComponent _groundMotion; + private NavigationComponent _navigationComponent; // Called when the node enters the scene tree for the first time. public override void _Ready() { _groundMotion = new GroundMotionComponent(); + _navigationComponent = new NavigationComponent(); _movable = (MovableComponent)FindNode("Movable", false); if (_movable != null) @@ -33,9 +35,27 @@ public class Player : Entity public override void _PhysicsProcess(float delta) { base._PhysicsProcess(delta); - _groundMotion.PhysicsProcess(delta, this, TargetPosition); + + // update navigation target here + + // update physics target + Vector3 targetPosition = + _worldInfo.TileWorld.GetTileWorldCenterFromOffset(_navigationComponent.currentGoalOffset); + + // compute physics movement + _groundMotion.PhysicsProcess(delta, this, targetPosition, _worldInfo.TileWorld); } + + public override void _Process(float delta) + { + if ((_navigationComponent.currentGoalWorld - TargetPosition).LengthSquared() > 0.01) + { + _navigationComponent.Plan(GlobalTransform.origin, TargetPosition, _worldInfo.TileWorld); + } + } + + public void OnPositionUpdated(Vector3 newPosition) { if (_worldInfo != null) diff --git a/scenes/AdaptiveWorldStream.tscn b/scenes/AdaptiveWorldStream.tscn index 7520505..4c9780d 100644 --- a/scenes/AdaptiveWorldStream.tscn +++ b/scenes/AdaptiveWorldStream.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=15 format=2] +[gd_scene load_steps=17 format=2] [ext_resource path="res://scenes/AdaptiveWorldStream.cs" type="Script" id=1] [ext_resource path="res://assets/CreatusPiratePack/characters/Pirate1final_0.01.glb" type="PackedScene" id=2] @@ -10,6 +10,14 @@ [ext_resource path="res://components/MovableComponent.cs" type="Script" id=8] [ext_resource path="res://scenes/TileWorld.cs" type="Script" id=9] +[sub_resource type="OpenSimplexNoise" id=10] +period = 39.6 + +[sub_resource type="NoiseTexture" id=11] +width = 100 +height = 100 +noise = SubResource( 10 ) + [sub_resource type="CubeMesh" id=1] size = Vector3( 1, 1, 1 ) @@ -183,8 +191,9 @@ text = "0" [node name="WorldTextureRect" type="TextureRect" parent="Control"] margin_left = 1.0 margin_top = 300.0 -margin_right = 101.0 -margin_bottom = 400.0 +margin_right = 513.0 +margin_bottom = 812.0 +texture = SubResource( 11 ) [node name="StreamContainer" type="Spatial" parent="."] script = ExtResource( 4 ) diff --git a/scenes/TileWorld.cs b/scenes/TileWorld.cs index 3a4231c..631c090 100644 --- a/scenes/TileWorld.cs +++ b/scenes/TileWorld.cs @@ -60,8 +60,8 @@ public class TileWorld : Spatial noise_generator.Seed = -1626828106; noise_generator.Octaves = 3; - noise_generator.Period = 5; - noise_generator.Persistence = 0.5f; + noise_generator.Period = 20; + noise_generator.Persistence = 0.1f; noise_generator.Lacunarity = 2; Heightmap.CopyFrom(noise_generator.GetImage((int)Size.x, (int)Size.y, null)); @@ -117,6 +117,17 @@ public class TileWorld : Spatial return _hexGrid.GetHexAt(new Vector2(world_coord.x, world_coord.z)).OffsetCoords; } + + public Vector3 GetTileWorldCenterFromOffset(Vector2 offset_coord) + { + Vector2 tileCenter = _hexGrid.GetHexCenterFromOffset(offset_coord); + + return new Vector3( + tileCenter.x, + GetHeightAtOffset(offset_coord), + tileCenter.y); + } + // // Called every frame. 'delta' is the elapsed time since the previous frame. // public override void _Process(float delta) // { diff --git a/tests/HexCellTests.cs b/tests/HexCellTests.cs index 54b0f66..edf1dfa 100644 --- a/tests/HexCellTests.cs +++ b/tests/HexCellTests.cs @@ -1,6 +1,8 @@ +using System; using Godot; using GoDotTest; using System.Diagnostics; +using System.Linq; public class HexCellTests : TestClass { @@ -88,21 +90,113 @@ public class HexCellTests : TestClass HexCell cell = new HexCell(new Vector2(1, 2)); // adjacent - HexCell otherCell = cell.getAdjacent(HexCell.DIR_N); + HexCell otherCell = cell.GetAdjacent(HexCell.DIR_N); Debug.Assert(otherCell.AxialCoords == new Vector2(1, 3)); - otherCell = cell.getAdjacent(HexCell.DIR_NE); + otherCell = cell.GetAdjacent(HexCell.DIR_NE); Debug.Assert(otherCell.AxialCoords == new Vector2(2, 2)); - otherCell = cell.getAdjacent(HexCell.DIR_SE); + otherCell = cell.GetAdjacent(HexCell.DIR_SE); Debug.Assert(otherCell.AxialCoords == new Vector2(2, 1)); - otherCell = cell.getAdjacent(HexCell.DIR_S); + otherCell = cell.GetAdjacent(HexCell.DIR_S); Debug.Assert(otherCell.AxialCoords == new Vector2(1, 1)); - otherCell = cell.getAdjacent(HexCell.DIR_SW); + otherCell = cell.GetAdjacent(HexCell.DIR_SW); Debug.Assert(otherCell.AxialCoords == new Vector2(0, 2)); - otherCell = cell.getAdjacent(HexCell.DIR_NW); + otherCell = cell.GetAdjacent(HexCell.DIR_NW); Debug.Assert(otherCell.AxialCoords == new Vector2(0, 3)); // not really adjacent - otherCell = cell.getAdjacent(new Vector3(-3, -3, 6)); + otherCell = cell.GetAdjacent(new Vector3(-3, -3, 6)); Debug.Assert(otherCell.AxialCoords == new Vector2(-2, -1)); } + + [Test] + public void TestDistance() + { + HexCell cell = new HexCell(); + cell.OffsetCoords = new Vector2(1, 2); + + Debug.Assert(cell.DistanceTo(new HexCell(new Vector2(0, 0))) == 3); + Debug.Assert(cell.DistanceTo(new HexCell(new Vector2(3, 4))) == 4); + Debug.Assert(cell.DistanceTo(new HexCell(new Vector2(-1, -1))) == 5); + } + + + [Test] + public void TestLineTo() + { + HexCell cell = new HexCell(); + cell.OffsetCoords = new Vector2(1, 2); + + HexCell[] path = cell.LineTo(new HexCell(5, 2)); + + HexCell[] pathExpected = + { + new HexCell(1, 2), + new HexCell(2, 2), + new HexCell(3, 2), + new HexCell(4, 2), + new HexCell(5, 2) + }; + + Debug.Assert(path.Length == pathExpected.Length); + + foreach (int index in Enumerable.Range(0, path.Length)) + { + Debug.Assert(path[index].AxialCoords == pathExpected[index].AxialCoords); + } + } + + + [Test] + public void TestLineToAngled() + { + HexCell cell = new HexCell(); + cell.OffsetCoords = new Vector2(1, 2); + + HexCell[] path = cell.LineTo(new HexCell(5, 4)); + + HexCell[] pathExpected = + { + new HexCell(1, 2), + new HexCell(2, 2), + new HexCell(2, 3), + new HexCell(3, 3), + new HexCell(4, 3), + new HexCell(4, 4), + new HexCell(5, 4) + }; + + Debug.Assert(path.Length == pathExpected.Length); + + foreach (int index in Enumerable.Range(0, path.Length)) + { + Debug.Assert(path[index].AxialCoords == pathExpected[index].AxialCoords); + } + } + + + [Test] + public void TestLineEdge() + { + HexCell cell = new HexCell(); + cell.OffsetCoords = new Vector2(1, 2); + + HexCell[] path = cell.LineTo(new HexCell(3, 4)); + + HexCell[] pathExpected = + { + new HexCell(1, 2), + new HexCell(2, 2), + new HexCell(2, 3), + new HexCell(2, 4), + new HexCell(3, 4) + }; + + Debug.Assert(path.Length == pathExpected.Length); + + foreach (int index in Enumerable.Range(0, path.Length)) + { + Debug.Print("index: " + index + " path: " + path[index].AxialCoords + " expected: " + pathExpected[index].AxialCoords); + Debug.Assert(path[index].AxialCoords == pathExpected[index].AxialCoords); + } + } } \ No newline at end of file