diff --git a/main.tscn b/main.tscn index fa91c6c..5c9c62c 100644 --- a/main.tscn +++ b/main.tscn @@ -145,7 +145,7 @@ use_collision = true size = Vector3(2.14108, 52.2441, 99.9347) [node name="CSGBox3D14" type="CSGBox3D" parent="Greybox"] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -17.534, 10.8779, 27.1386) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -17.534, 10.8458, 27.1386) use_collision = true size = Vector3(1.10791, 1.71436, 25.7228) @@ -155,9 +155,9 @@ use_collision = true size = Vector3(76.2239, 21.6211, 2.62695) [node name="CSGBox3D16" type="CSGBox3D" parent="Greybox"] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -14.0259, 22.0567, -5.15678) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -13.8856, 21.8279, -5.20281) use_collision = true -size = Vector3(74.1406, 1, 25.474) +size = Vector3(74.1406, 1, 25.6403) [node name="CSGBox3D17" type="CSGBox3D" parent="Greybox"] transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -22.0327, 10.786, -16.8929) @@ -170,4 +170,4 @@ use_collision = true size = Vector3(1.10791, 1.71436, 23.4043) [node name="Player" parent="." instance=ExtResource("1_ig7tw")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.35171, 0.0273495, 22.1779) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 30.2019, 12.6118, 13.3006) diff --git a/player_controller/PlayerController.tscn b/player_controller/PlayerController.tscn index c5ba0c9..00a737d 100644 --- a/player_controller/PlayerController.tscn +++ b/player_controller/PlayerController.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=27 format=3 uid="uid://bei4nhkf8lwdo"] +[gd_scene load_steps=26 format=3 uid="uid://bei4nhkf8lwdo"] [ext_resource type="Script" uid="uid://bbbrf5ckydfna" path="res://player_controller/Scripts/PlayerController.cs" id="1_poq2x"] [ext_resource type="Material" uid="uid://dtq8i1ka1f2pn" path="res://player_controller/Assets/Materials/Health/CameraVignette.tres" id="2_6hee7"] @@ -8,13 +8,13 @@ [ext_resource type="Script" uid="uid://vuq8rjq3vegn" path="res://player_controller/Scripts/Stamina.cs" id="6_lxtc4"] [ext_resource type="Script" uid="uid://cwbvxlfvmocc1" path="res://player_controller/Scripts/StairsSystem.cs" id="7_bmt5a"] [ext_resource type="Script" uid="uid://dd1yrt7eiiyf4" path="res://player_controller/Scripts/CapsuleCollider.cs" id="8_lmtjd"] +[ext_resource type="PackedScene" uid="uid://wq1okogkhc5l" path="res://systems/mantle_system.tscn" id="8_qu4wy"] [ext_resource type="Script" uid="uid://bt0xv2q8iv1vn" path="res://player_controller/Scripts/Gravity.cs" id="9_lsueh"] [ext_resource type="Script" uid="uid://dwoppk8j5fxeg" path="res://player_controller/Scripts/DashSystem.cs" id="9_qu4wy"] [ext_resource type="Script" uid="uid://g8idirw62qe0" path="res://player_controller/Scripts/Bobbing.cs" id="10_7wk1w"] [ext_resource type="Script" uid="uid://c6bx47wr7fbdm" path="res://player_controller/Scripts/Mouse.cs" id="11_huhen"] [ext_resource type="Script" uid="uid://b6k73aj5povgv" path="res://player_controller/Scripts/FieldOfView.cs" id="12_m2mxi"] [ext_resource type="Script" uid="uid://bt8flen3mi28r" path="res://player_controller/Scripts/AnimationPlayer.cs" id="13_vnh4e"] -[ext_resource type="Script" uid="uid://bja6tis1vaysu" path="res://player_controller/Scripts/MantleSystem.cs" id="14_4coqe"] [sub_resource type="CapsuleMesh" id="CapsuleMesh_xc2g5"] @@ -25,10 +25,7 @@ shader = ExtResource("4_jyscr") shader_parameter/limit = 0.0 shader_parameter/blur = 0.0 -[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_4coqe"] - -[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_qu4wy"] -height = 1.5 +[sub_resource type="SphereShape3D" id="SphereShape3D_qu4wy"] [sub_resource type="SphereMesh" id="SphereMesh_qu4wy"] @@ -194,29 +191,14 @@ target_position = Vector3(0, -0.55, 0) [node name="StairsBelowRayCast3D" type="RayCast3D" parent="."] target_position = Vector3(0, -0.75, 0) -[node name="MantleSystem" type="Node3D" parent="."] -script = ExtResource("14_4coqe") -MantleEndLocationDistanceFromWall = 0.2 -MantleHeightCastStart = 3.0 - -[node name="MantleCast3D" type="ShapeCast3D" parent="."] -shape = SubResource("CapsuleShape3D_4coqe") -target_position = Vector3(0, 0, 0) -debug_shape_custom_color = Color(1, 0, 0, 1) - -[node name="WallInFrontCast3D" type="ShapeCast3D" parent="."] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) -shape = SubResource("CapsuleShape3D_qu4wy") -target_position = Vector3(0, 0, -1.5) -max_results = 1 -debug_shape_custom_color = Color(0.911631, 0.11884, 0.656218, 1) +[node name="MantleSystem" parent="." instance=ExtResource("8_qu4wy")] [node name="DashSystem" type="Node3D" parent="."] script = ExtResource("9_qu4wy") [node name="DashCast3D" type="ShapeCast3D" parent="DashSystem"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.68, 0) -shape = SubResource("CapsuleShape3D_qu4wy") +shape = SubResource("SphereShape3D_qu4wy") target_position = Vector3(0, 0, -20) max_results = 1 collision_mask = 2 @@ -226,6 +208,8 @@ debug_shape_custom_color = Color(0.911631, 0.11884, 0.656218, 1) mesh = SubResource("SphereMesh_qu4wy") surface_material_override/0 = SubResource("StandardMaterial3D_v31n3") +[node name="MantleSystem" parent="DashSystem" instance=ExtResource("8_qu4wy")] + [node name="StairsSystem" type="Node3D" parent="."] script = ExtResource("7_bmt5a") diff --git a/player_controller/Scripts/DashSystem.cs b/player_controller/Scripts/DashSystem.cs index c12bd85..8e057b6 100644 --- a/player_controller/Scripts/DashSystem.cs +++ b/player_controller/Scripts/DashSystem.cs @@ -1,9 +1,10 @@ using Godot; -using RustyOptions; namespace PolarBears.PlayerControllerAddon; -public record DashLocation(Result Result, bool HasHit); +public record DashComputation(bool HasHit, Vector3 Location, Vector3 CollisionPoint, Vector3 CollisionNormal); + +public record DashResolve(bool EndWithMantle, Vector3 DashLocation, Vector3 MantleLocation); public partial class DashSystem: Node3D { @@ -15,24 +16,35 @@ public partial class DashSystem: Node3D private Camera3D _camera; private MeshInstance3D _dashTarget; + private MantleSystem _mantleSystem; + public void Init(Node3D head, Camera3D camera) { _dashCast3D = GetNode("DashCast3D"); _head = head; _camera = camera; + + _mantleSystem = GetNode("MantleSystem"); + _mantleSystem.Init(this); + _dashTarget = GetNode("DashTarget"); _dashTarget.SetVisible(false); } - private DashLocation ComputeDashLocation() + private DashComputation ComputeDashLocation() { - var dashLocation = _dashCast3D.IsColliding() - ? _dashCast3D.GetCollisionPoint(0) - : _dashCast3D.ToGlobal(_dashCast3D.TargetPosition); - return new DashLocation(Result.Ok(dashLocation), _dashCast3D.IsColliding()); + if (!_dashCast3D.IsColliding()) + { + return new DashComputation(false, _dashCast3D.ToGlobal(_dashCast3D.TargetPosition), Vector3.Zero, Vector3.Zero); + } + var collisionPoint = _dashCast3D.GetCollisionPoint(0); + var collisionNormal = _dashCast3D.GetCollisionNormal(0); + var collisionShape = (SphereShape3D) _dashCast3D.GetShape(); + var centerSphereLocation = collisionPoint + collisionNormal * collisionShape.Radius; + return new DashComputation(true, centerSphereLocation, collisionPoint, collisionNormal); } - public Result PrepareDash() + public DashResolve PrepareDash() { _dashTarget.SetVisible(false); @@ -41,16 +53,23 @@ public partial class DashSystem: Node3D _head.Rotation.Y, _camera.Rotation.Z)); - var (result, hasHit) = ComputeDashLocation(); + var (hasHit, location, collisionPoint, collisionNormal) = ComputeDashLocation(); + + var shouldMantle = false; + var mantleLocation = Vector3.Zero; + if (hasHit && Mathf.Abs(collisionNormal.Y) < 0.01f) + { + var mantleResult = _mantleSystem.FindMantleLocationAtPoint(collisionPoint, collisionNormal); + shouldMantle = mantleResult.IsSome(out mantleLocation); + } - var targetColor = hasHit ? new Color(0.2f, 0.2f, 1f) : new Color(1f, 1f, 1f); + var targetColor = shouldMantle ? new Color(0.2f, 0.2f, 1f) : new Color(1f, 1f, 1f); var targetMaterial = (StandardMaterial3D) _dashTarget.GetSurfaceOverrideMaterial(0); - targetMaterial.AlbedoColor = targetColor; - + targetMaterial.SetAlbedo(targetColor); _dashTarget.SetVisible(true); - _dashTarget.SetGlobalPosition(result.Unwrap()); + _dashTarget.SetGlobalPosition(location); - return result; + return new DashResolve(shouldMantle, location, mantleLocation); } public void Dash() diff --git a/player_controller/Scripts/MantleSystem.cs b/player_controller/Scripts/MantleSystem.cs index 4f806a3..8a171c6 100644 --- a/player_controller/Scripts/MantleSystem.cs +++ b/player_controller/Scripts/MantleSystem.cs @@ -1,13 +1,14 @@ -using Godot; +using System; +using Godot; using RustyOptions; namespace PolarBears.PlayerControllerAddon; public partial class MantleSystem: Node3D { - [Export(PropertyHint.Range, "0,2,0.1,or_greater")] + [Export(PropertyHint.Range, "0,2,0.1,suffix:m,or_greater")] public float MantleEndLocationDistanceFromWall { get; set; } = 1f; - [Export(PropertyHint.Range, "0,10,0.1,or_greater")] + [Export(PropertyHint.Range, "0,10,0.1,suffix:m,or_greater")] public float MantleHeightCastStart { get; set; } = 2f; [Export(PropertyHint.Range, "0,10,0.01,suffix:m,or_greater")] public float MaxStepHeight = 0.5f; @@ -15,15 +16,16 @@ public partial class MantleSystem: Node3D private Node3D _head; private ShapeCast3D _wallInFrontCast3D; private ShapeCast3D _mantleCast3D; + private RayCast3D _mantleCheckCast3D; - public void Init(ShapeCast3D wallInFrontCast3D, Node3D head, ShapeCast3D mantleCast3D) + public void Init(Node3D head) { - _wallInFrontCast3D = wallInFrontCast3D; _head = head; - _mantleCast3D = mantleCast3D; + _wallInFrontCast3D = GetNode("WallInFrontCast3D"); + _mantleCast3D = GetNode("MantleCast3D"); } - public Result CheckWallInFront() + public Option FindMantleInFrontOfPlayer() { _wallInFrontCast3D.SetRotation(new Vector3( _wallInFrontCast3D.Rotation.X, @@ -32,17 +34,25 @@ public partial class MantleSystem: Node3D if (!_wallInFrontCast3D.IsColliding()) { - return Result.Err("No collision found"); + return Option.None; } var collisionPoint = _wallInFrontCast3D.GetCollisionPoint(0); - var horizontalEndLocation = collisionPoint - _wallInFrontCast3D.GetCollisionNormal(0) * MantleEndLocationDistanceFromWall; + var collisionNormal = _wallInFrontCast3D.GetCollisionNormal(0); + return FindMantleLocationAtPoint(collisionPoint, collisionNormal); + } + + public Option FindMantleLocationAtPoint(Vector3 point, Vector3 wallNormal) + { + var horizontalEndLocation = point - wallNormal * MantleEndLocationDistanceFromWall; var shapeCastStartLocation = horizontalEndLocation + Vector3.Up * MantleHeightCastStart; _mantleCast3D.SetGlobalPosition(shapeCastStartLocation); var targetLocation = Vector3.Down * MantleHeightCastStart + Vector3.Up * MaxStepHeight; _mantleCast3D.SetTargetPosition(targetLocation); - - return _mantleCast3D.IsColliding() ? Result.Ok(_mantleCast3D.GetCollisionPoint(0)) : Result.Err("No collision found"); + + if (_mantleCast3D.IsColliding() && _mantleCast3D.GetCollisionNormal(0).Y > 0.9f) + return Option.Some(_mantleCast3D.GetCollisionPoint(0)); + return Option.None; } } diff --git a/player_controller/Scripts/PlayerController.cs b/player_controller/Scripts/PlayerController.cs index a937500..69a1ca1 100644 --- a/player_controller/Scripts/PlayerController.cs +++ b/player_controller/Scripts/PlayerController.cs @@ -1,4 +1,7 @@ +using System.Collections.Generic; +using System.Runtime.InteropServices.JavaScript; using Godot; +using RustyOptions; namespace PolarBears.PlayerControllerAddon; @@ -34,7 +37,15 @@ public partial class PlayerController : CharacterBody3D private bool _canDoubleJump = true; private bool _movementEnabled = true; + + private bool _isTweening = false; + private record TweenInputs(Vector3 Location, float Duration); + + private Queue _tweenInputs = new Queue(); + + private bool _shouldMantle = false; private Vector3 _dashLocation = Vector3.Zero; + private Vector3 _mantleLocation = Vector3.Zero; private float _currentSpeed; @@ -65,8 +76,6 @@ public partial class PlayerController : CharacterBody3D RayCast3D stairsBelowRayCast3D = GetNode("StairsBelowRayCast3D"); RayCast3D stairsAheadRayCast3D = GetNode("StairsAheadRayCast3D"); - ShapeCast3D wallInFrontCast3D = GetNode("WallInFrontCast3D"); - ShapeCast3D mantleCast3D = GetNode("MantleCast3D"); Node3D cameraSmooth = GetNode("Head/CameraSmooth"); @@ -100,7 +109,7 @@ public partial class PlayerController : CharacterBody3D StairsSystem.Init(stairsBelowRayCast3D, stairsAheadRayCast3D, cameraSmooth); MantleSystem = GetNode("MantleSystem"); - MantleSystem.Init(wallInFrontCast3D, Head, mantleCast3D); + MantleSystem.Init(Head); DashSystem = GetNode("DashSystem"); DashSystem.Init(Head, camera); @@ -139,32 +148,58 @@ public partial class PlayerController : CharacterBody3D _movementEnabled = true; } - private void TweenToLocation(Vector3 location, float duration) + public void EndTween() { - Tween tween = GetTree().CreateTween(); - var callback = new Callable(this, MethodName.EnableMovement); + EnableMovement(); + _isTweening = false; + } + + private void TweenToLocation(TweenInputs inputs) + { + var (location, duration) = inputs; + + var tween = GetTree().CreateTween(); + var callback = new Callable(this, MethodName.EndTween); tween.TweenProperty(this, "position", location, duration); tween.TweenCallback(callback); DisableMovement(); + _isTweening = true; tween.Play(); } + private void QueueTween(TweenInputs inputs) + { + _tweenInputs.Enqueue(inputs); + } + + private void QueueTween(Vector3 location, float duration) + { + QueueTween(new TweenInputs(location, duration)); + } + public override void _PhysicsProcess(double delta) { + if (_tweenInputs.Count > 0 && !_isTweening) + TweenToLocation(_tweenInputs.Dequeue()); + if (Input.IsActionPressed("aim_dash")) { - _dashLocation = DashSystem.PrepareDash().Unwrap(); + (_shouldMantle, _dashLocation, _mantleLocation) = DashSystem.PrepareDash(); } if (Input.IsActionJustReleased("aim_dash")) { DashSystem.Dash(); - TweenToLocation(_dashLocation, 0.1f); + QueueTween(_dashLocation, 0.1f); + if (_shouldMantle) + { + QueueTween(_mantleLocation, 0.1f); + } } - var mantleLocationResult = MantleSystem.CheckWallInFront(); + var mantleLocationResult = MantleSystem.FindMantleInFrontOfPlayer(); if (isOnFloorCustom()) { _lastFrameWasOnFloor = Engine.GetPhysicsFrames(); @@ -189,10 +224,10 @@ public partial class PlayerController : CharacterBody3D && !doesCapsuleHaveCrouchingHeight && !isPlayerDead) { - if (mantleLocationResult.IsOk(out var mantleLocation)) + if (mantleLocationResult.IsSome(out var mantleLocation)) { var duration = 0.1f * mantleLocation.DistanceTo(Position); - TweenToLocation(mantleLocation, duration); + QueueTween(mantleLocation, duration); } else if (isOnFloorCustom()) { diff --git a/systems/mantle_system.tscn b/systems/mantle_system.tscn new file mode 100644 index 0000000..a7796dd --- /dev/null +++ b/systems/mantle_system.tscn @@ -0,0 +1,28 @@ +[gd_scene load_steps=4 format=3 uid="uid://wq1okogkhc5l"] + +[ext_resource type="Script" uid="uid://bja6tis1vaysu" path="res://player_controller/Scripts/MantleSystem.cs" id="1_o1m1l"] + +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_4coqe"] + +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_qu4wy"] +height = 1.5 + +[node name="MantleSystem" type="Node3D"] +script = ExtResource("1_o1m1l") +MantleEndLocationDistanceFromWall = 0.2 +MantleHeightCastStart = 3.0 + +[node name="MantleCast3D" type="ShapeCast3D" parent="."] +shape = SubResource("CapsuleShape3D_4coqe") +target_position = Vector3(0, 0, 0) +max_results = 1 +collision_mask = 2 +debug_shape_custom_color = Color(1, 0, 0, 1) + +[node name="WallInFrontCast3D" type="ShapeCast3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) +shape = SubResource("CapsuleShape3D_qu4wy") +target_position = Vector3(0, 0, -1.5) +max_results = 1 +collision_mask = 2 +debug_shape_custom_color = Color(0.911631, 0.11884, 0.656218, 1)