revamped megajump and air control

This commit is contained in:
2025-08-07 16:13:21 +02:00
parent f905e55f65
commit 4f9005d016
2 changed files with 189 additions and 88 deletions

View File

@ -50,11 +50,18 @@ blend_mode = 1
[node name="Player" type="CharacterBody3D"] [node name="Player" type="CharacterBody3D"]
script = ExtResource("1_poq2x") script = ExtResource("1_poq2x")
AccelerationSpeedFactorFloor = 3.0 WalkSpeed = 7.5
AccelerationAir = 2.0
DecelerationAir = 0.1
Weight = 5.0 Weight = 5.0
SimpleJumpStartVelocity = 7.0 SimpleJumpStartVelocity = 8.0
HangTimeInFrames = 3 SimpleJumpHangTimeInFrames = 3
GravityLesseningFactorUpward = 2.5 SimpleJumpGravityLesseningFactor = 2.5
DoubleJumpStartVelocity = 15.0
DoubleJumpGravityLesseningFactor = 1.2
MegaJumpStartVelocity = 30.0
MegaJumpHangTimeInFrames = 15
MegaJumpGravityLesseningFactor = 1.2
JumpFromDashSpeedFactor = 4.0 JumpFromDashSpeedFactor = 4.0
WallHugHorizontalDeceleration = 3.0 WallHugHorizontalDeceleration = 3.0
MaxJumpBoostAfterDashing = 0.7 MaxJumpBoostAfterDashing = 0.7
@ -452,18 +459,42 @@ initial_state = NodePath("SimpleJump")
[node name="SimpleJump" type="Node" parent="StateChart/Root/Movement/Jump"] [node name="SimpleJump" type="Node" parent="StateChart/Root/Movement/Jump"]
script = ExtResource("27_34snm") script = ExtResource("27_34snm")
[node name="OnMegajump" type="Node" parent="StateChart/Root/Movement/Jump/SimpleJump"]
script = ExtResource("28_n7qhm")
to = NodePath("../../MegaJump")
event = &"megajump"
delay_in_seconds = "0.0"
[node name="OnJumpEnded" type="Node" parent="StateChart/Root/Movement/Jump/SimpleJump"] [node name="OnJumpEnded" type="Node" parent="StateChart/Root/Movement/Jump/SimpleJump"]
script = ExtResource("28_n7qhm") script = ExtResource("28_n7qhm")
to = NodePath("../../../Airborne/DoubleJumpEnabled") to = NodePath("../../../Airborne/DoubleJumpEnabled")
event = &"jump_ended" event = &"jump_ended"
delay_in_seconds = "0.0" delay_in_seconds = "0.0"
[node name="Double" type="Node" parent="StateChart/Root/Movement/Jump"] [node name="DoubleJump" type="Node" parent="StateChart/Root/Movement/Jump"]
script = ExtResource("27_34snm") script = ExtResource("27_34snm")
[node name="Empowered" type="Node" parent="StateChart/Root/Movement/Jump"] [node name="OnMegajump" type="Node" parent="StateChart/Root/Movement/Jump/DoubleJump"]
script = ExtResource("28_n7qhm")
to = NodePath("../../MegaJump")
event = &"megajump"
delay_in_seconds = "0.0"
[node name="OnJumpEnded" type="Node" parent="StateChart/Root/Movement/Jump/DoubleJump"]
script = ExtResource("28_n7qhm")
to = NodePath("../../../Airborne/Falling")
event = &"jump_ended"
delay_in_seconds = "0.0"
[node name="MegaJump" type="Node" parent="StateChart/Root/Movement/Jump"]
script = ExtResource("27_34snm") script = ExtResource("27_34snm")
[node name="OnJumpEnded" type="Node" parent="StateChart/Root/Movement/Jump/MegaJump"]
script = ExtResource("28_n7qhm")
to = NodePath("../../../Airborne/Falling")
event = &"jump_ended"
delay_in_seconds = "0.0"
[node name="Grounded" type="Node" parent="StateChart/Root/Movement"] [node name="Grounded" type="Node" parent="StateChart/Root/Movement"]
script = ExtResource("27_34snm") script = ExtResource("27_34snm")
@ -475,7 +506,7 @@ delay_in_seconds = "0.0"
[node name="OnMegajump" type="Node" parent="StateChart/Root/Movement/Grounded"] [node name="OnMegajump" type="Node" parent="StateChart/Root/Movement/Grounded"]
script = ExtResource("28_n7qhm") script = ExtResource("28_n7qhm")
to = NodePath("../../Airborne/Falling") to = NodePath("../../Jump/MegaJump")
event = &"megajump" event = &"megajump"
delay_in_seconds = "0.0" delay_in_seconds = "0.0"
@ -493,6 +524,12 @@ initial_state = NodePath("CoyoteEnabled")
script = ExtResource("41_ruloh") script = ExtResource("41_ruloh")
default_state = NodePath("../CoyoteEnabled") default_state = NodePath("../CoyoteEnabled")
[node name="OnMegajump" type="Node" parent="StateChart/Root/Movement/Airborne"]
script = ExtResource("28_n7qhm")
to = NodePath("../../Jump/MegaJump")
event = &"megajump"
delay_in_seconds = "0.0"
[node name="OnGrounded" type="Node" parent="StateChart/Root/Movement/Airborne"] [node name="OnGrounded" type="Node" parent="StateChart/Root/Movement/Airborne"]
script = ExtResource("28_n7qhm") script = ExtResource("28_n7qhm")
to = NodePath("../../Grounded") to = NodePath("../../Grounded")
@ -531,7 +568,7 @@ script = ExtResource("27_34snm")
[node name="OnJump" type="Node" parent="StateChart/Root/Movement/Airborne/DoubleJumpEnabled"] [node name="OnJump" type="Node" parent="StateChart/Root/Movement/Airborne/DoubleJumpEnabled"]
script = ExtResource("28_n7qhm") script = ExtResource("28_n7qhm")
to = NodePath("../../Falling") to = NodePath("../../../Jump/DoubleJump")
event = &"jump" event = &"jump"
delay_in_seconds = "0.0" delay_in_seconds = "0.0"

View File

@ -60,12 +60,14 @@ public partial class PlayerController : CharacterBody3D
[Export(PropertyHint.Range, "0,20,0.1,or_greater")] [Export(PropertyHint.Range, "0,20,0.1,or_greater")]
public float WalkSpeed { get; set; } = 7.0f; public float WalkSpeed { get; set; } = 7.0f;
[Export(PropertyHint.Range, "0,10,0.1,or_greater")] [Export(PropertyHint.Range, "0,10,0.1,or_greater")]
public float AccelerationSpeedFactorFloor = 5.0f; public float AccelerationFloor = 5.0f;
[Export(PropertyHint.Range, "0,10,0.1,or_greater")] [Export(PropertyHint.Range, "0,10,0.1,or_greater")]
public float DecelerationSpeedFactorFloor = 5.0f; public float DecelerationFloor = 5.0f;
[ExportGroup("Air")] [ExportGroup("Air")]
[Export(PropertyHint.Range, "0,10,0.1,or_greater")] [Export(PropertyHint.Range, "0,10,0.1,or_greater")]
public float DecelerationSpeedFactorAir = 1.0f; public float AccelerationAir = 3.0f;
[Export(PropertyHint.Range, "0,10,0.01,or_greater")]
public float DecelerationAir = 1.0f;
[Export(PropertyHint.Range, "0,10,0.01,or_greater")] [Export(PropertyHint.Range, "0,10,0.01,or_greater")]
public float Weight { get; set; } = 3.0f; public float Weight { get; set; } = 3.0f;
@ -77,9 +79,29 @@ public partial class PlayerController : CharacterBody3D
[Export(PropertyHint.Range, "0,100,1,or_greater")] [Export(PropertyHint.Range, "0,100,1,or_greater")]
public float SimpleJumpStartVelocity { get; set; } = 3.0f; public float SimpleJumpStartVelocity { get; set; } = 3.0f;
[Export(PropertyHint.Range, "0,10,1,or_greater")] [Export(PropertyHint.Range, "0,10,1,or_greater")]
public int HangTimeInFrames { get; set; } = 5; public int SimpleJumpHangTimeInFrames { get; set; } = 5;
[Export(PropertyHint.Range, "1,10,0.1,or_greater")] [Export(PropertyHint.Range, "1,10,0.1,or_greater")]
public float GravityLesseningFactorUpward { get; set; } = 3f; public float SimpleJumpGravityLesseningFactor { get; set; } = 3f;
// Double jump
[ExportSubgroup("Double jump")]
[Export(PropertyHint.Range, "0,100,1,or_greater")]
public float DoubleJumpStartVelocity { get; set; } = 10.0f;
[Export(PropertyHint.Range, "0,10,1,or_greater")]
public int DoubleJumpHangTimeInFrames { get; set; } = 5;
[Export(PropertyHint.Range, "1,10,0.1,or_greater")]
public float DoubleJumpGravityLesseningFactor { get; set; } = 3f;
// Mega jump
[ExportSubgroup("Mega jump")]
[Export(PropertyHint.Range, "0,100,1,or_greater")]
public float MegaJumpStartVelocity { get; set; } = 10.0f;
[Export(PropertyHint.Range, "0,10,1,or_greater")]
public int MegaJumpHangTimeInFrames { get; set; } = 5;
[Export(PropertyHint.Range, "1,10,0.1,or_greater")]
public float MegaJumpGravityLesseningFactor { get; set; } = 3f;
// Other jump // Other jump
[ExportSubgroup("Other jump")] [ExportSubgroup("Other jump")]
@ -153,6 +175,8 @@ public partial class PlayerController : CharacterBody3D
private StateChartState _airborne; private StateChartState _airborne;
private StateChartState _coyoteEnabled; private StateChartState _coyoteEnabled;
private StateChartState _simpleJump; private StateChartState _simpleJump;
private StateChartState _doubleJump;
private StateChartState _megaJump;
private StateChartState _doubleJumpEnabled; private StateChartState _doubleJumpEnabled;
private StateChartState _onWall; private StateChartState _onWall;
private StateChartState _onWallHugCanceled; private StateChartState _onWallHugCanceled;
@ -230,6 +254,8 @@ public partial class PlayerController : CharacterBody3D
_airborne = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne")); _airborne = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne"));
_coyoteEnabled = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne/CoyoteEnabled")); _coyoteEnabled = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne/CoyoteEnabled"));
_simpleJump = StateChartState.Of(GetNode("StateChart/Root/Movement/Jump/SimpleJump")); _simpleJump = StateChartState.Of(GetNode("StateChart/Root/Movement/Jump/SimpleJump"));
_doubleJump = StateChartState.Of(GetNode("StateChart/Root/Movement/Jump/DoubleJump"));
_megaJump = StateChartState.Of(GetNode("StateChart/Root/Movement/Jump/MegaJump"));
_doubleJumpEnabled = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne/DoubleJumpEnabled")); _doubleJumpEnabled = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne/DoubleJumpEnabled"));
_onWall = StateChartState.Of(GetNode("StateChart/Root/Movement/OnWall")); _onWall = StateChartState.Of(GetNode("StateChart/Root/Movement/OnWall"));
_onWallHugging = StateChartState.Of(GetNode("StateChart/Root/Movement/OnWall/Hugging")); _onWallHugging = StateChartState.Of(GetNode("StateChart/Root/Movement/OnWall/Hugging"));
@ -311,18 +337,56 @@ public partial class PlayerController : CharacterBody3D
// _empowerOff.StateEntered += EmpowerStopped; // _empowerOff.StateEntered += EmpowerStopped;
// _empowerTimeDownscale.Timeout += EmpowerTimerTimeout; // _empowerTimeDownscale.Timeout += EmpowerTimerTimeout;
_powerFull.StateEntered += StopPowerCooldown;
_powerFull.StateExited += StartPowerCooldown; _powerFull.StateExited += StartPowerCooldown;
_powerRecharging.StateEntered += StartPowerCooldown; _powerRecharging.StateEntered += StartPowerCooldown;
_powerFull.StateEntered += StopPowerCooldown;
_powerCooldownTimer.Timeout += PowerCooldownExpired; _powerCooldownTimer.Timeout += PowerCooldownExpired;
_powerRecharging.StateProcessing += PowerRecharging; _powerRecharging.StateProcessing += PowerRecharging;
_powerExpired.StateProcessing += PowerRecharging; _powerExpired.StateProcessing += PowerRecharging;
_simpleJump.StateEntered += OnSimpleJumpStarted; _simpleJump.StateEntered += OnSimpleJumpStarted;
_simpleJump.StatePhysicsProcessing += HandleSimpleJump; _simpleJump.StatePhysicsProcessing += HandleSimpleJump;
_simpleJump.StateExited += OnSimpleJumpEnded;
_doubleJump.StateEntered += OnDoubleJumpStarted;
_doubleJump.StatePhysicsProcessing += HandleDoubleJump;
_megaJump.StateEntered += OnMegaJumpStarted;
_megaJump.StatePhysicsProcessing += HandleMegaJump;
} }
// Physics processes
public void HandleGrounded(float delta)
{
MoveOnGround(delta);
_canDash = true;
if (!isOnFloorCustom())
_playerState.SendEvent("start_falling");
}
public void HandleAirborne(float delta)
{
MoveInAir(delta);
if (isOnFloorCustom())
_playerState.SendEvent("grounded");
if (WallHugSystem.IsWallHugging() && Velocity.Y < 0)
_playerState.SendEvent("wall_hug");
}
public void HandleWallHugging(float delta)
{
WallHug(delta);
if (isOnFloorCustom())
_playerState.SendEvent("grounded");
if (!WallHugSystem.IsWallHugging())
_playerState.SendEvent("start_falling");
}
public void HandleWallHanging(float delta)
{
WallHang(delta);
}
// Jump
private int _framesSinceJumpAtApex = 0;
public void OnInputJumpStarted() public void OnInputJumpStarted()
{ {
if (CanMantle()) if (CanMantle())
@ -352,6 +416,11 @@ public partial class PlayerController : CharacterBody3D
// else if (_onWall.Active) // else if (_onWall.Active)
// JumpFromWall(_empowerOn.Active); // JumpFromWall(_empowerOn.Active);
if (_empowerOn.Active && CanPerformEmpoweredAction())
{
_playerState.SendEvent("megajump");
return;
}
_playerState.SendEvent("jump"); _playerState.SendEvent("jump");
} }
@ -364,31 +433,66 @@ public partial class PlayerController : CharacterBody3D
_playerState.SendEvent("jump_ended"); _playerState.SendEvent("jump_ended");
} }
private int _framesSinceJumpAtApex = 0; public void OnJumpStarted(float verticalVelocity)
public void OnSimpleJumpStarted()
{ {
_framesSinceJumpAtApex = 0; _framesSinceJumpAtApex = 0;
SetVerticalVelocity(verticalVelocity);
}
public void OnSimpleJumpStarted()
{
OnJumpStarted(SimpleJumpStartVelocity);
}
public void OnDoubleJumpStarted()
{
OnJumpStarted(DoubleJumpStartVelocity);
}
public void OnMegaJumpStarted()
{
OnJumpStarted(MegaJumpStartVelocity);
}
public Vector3 ComputeHVelocity(float delta, float accelerationFactor, float decelerationFactor)
{
Vector3 direction = HeadSystem.Transform.Basis * _inputMove;
var acceleration = direction.Length() > 0 ? accelerationFactor : decelerationFactor;
float xAcceleration = Mathf.Lerp(Velocity.X, direction.X * _targetSpeed, delta * acceleration);
float zAcceleration = Mathf.Lerp(Velocity.Z, direction.Z * _targetSpeed, delta * acceleration);
return new Vector3(xAcceleration, 0, zAcceleration);
}
public Vector3 ComputeHVelocityGround(float delta)
{
return ComputeHVelocity(delta, AccelerationFloor, DecelerationFloor);
}
public Vector3 ComputeHVelocityAir(float delta)
{
return ComputeHVelocity(delta, AccelerationAir, DecelerationAir);
}
public void SetVerticalVelocity(float verticalVelocity)
{
Velocity = new Vector3( Velocity = new Vector3(
x: Velocity.X, x: Velocity.X,
y: SimpleJumpStartVelocity, y: verticalVelocity,
z: Velocity.Z); z: Velocity.Z);
} }
public void HandleSimpleJump(float delta) public void HandleJump(float delta, float gravityFactor, int hangFrames)
{ {
// Update horizontal velocity
var horizontalVelocity = ComputeHVelocityAir(delta);
Velocity = new Vector3(horizontalVelocity.X, Velocity.Y, horizontalVelocity.Z);
// Hang time at the top of the jump // Hang time at the top of the jump
if (Velocity.Y <= Mathf.Epsilon) if (Velocity.Y <= Mathf.Epsilon)
{ {
_framesSinceJumpAtApex++; _framesSinceJumpAtApex++;
Velocity = new Vector3( SetVerticalVelocity(0);
x: Velocity.X,
y: 0,
z: Velocity.Z);
} }
// Cancel gravity on jump apex // Cancel gravity on jump apex
var gravity = CalculateGravityForce() / GravityLesseningFactorUpward; var gravity = CalculateGravityForce() / gravityFactor;
var isAtApex = _framesSinceJumpAtApex > 0; var isAtApex = _framesSinceJumpAtApex > 0;
if (isAtApex) if (isAtApex)
{ {
@ -396,19 +500,27 @@ public partial class PlayerController : CharacterBody3D
} }
// Update velocity accordingly // Update velocity accordingly
var newVerticalSpeed = Velocity.Y - gravity * delta; var newVerticalSpeed = Velocity.Y - gravity * delta;
Velocity = new Vector3( SetVerticalVelocity(newVerticalSpeed);
x: Velocity.X, if (IsHeadTouchingCeiling())
y: newVerticalSpeed, {
z: Velocity.Z); SetVerticalVelocity(Velocity.Y - 2.0f);
}
// Move back to Airborne state management when starting to go down again // Move back to Airborne state management when starting to go down again
if (_framesSinceJumpAtApex > HangTimeInFrames) if (_framesSinceJumpAtApex > hangFrames)
_playerState.SendEvent("jump_ended"); _playerState.SendEvent("jump_ended");
} }
public void HandleSimpleJump(float delta)
public void OnSimpleJumpEnded()
{ {
HandleJump(delta, SimpleJumpGravityLesseningFactor, SimpleJumpHangTimeInFrames);
}
public void HandleDoubleJump(float delta)
{
HandleJump(delta, DoubleJumpGravityLesseningFactor, DoubleJumpHangTimeInFrames);
}
public void HandleMegaJump(float delta)
{
HandleJump(delta, MegaJumpGravityLesseningFactor, MegaJumpHangTimeInFrames);
} }
public void PowerRecharging(float delta) public void PowerRecharging(float delta)
@ -777,36 +889,6 @@ public partial class PlayerController : CharacterBody3D
DashSystem.PrepareDash(); DashSystem.PrepareDash();
} }
// Physics processes
public void HandleGrounded(float delta)
{
MoveOnGround(delta);
_canDash = true;
if (!isOnFloorCustom())
_playerState.SendEvent("start_falling");
}
public void HandleAirborne(float delta)
{
MoveInAir(delta);
if (isOnFloorCustom())
_playerState.SendEvent("grounded");
if (WallHugSystem.IsWallHugging() && Velocity.Y < 0)
_playerState.SendEvent("wall_hug");
}
public void HandleWallHugging(float delta)
{
WallHug(delta);
if (isOnFloorCustom())
_playerState.SendEvent("grounded");
if (!WallHugSystem.IsWallHugging())
_playerState.SendEvent("start_falling");
}
public void HandleWallHanging(float delta)
{
WallHang(delta);
}
/////////////////////////// ///////////////////////////
// Stateless logic //////// // Stateless logic ////////
@ -819,33 +901,15 @@ public partial class PlayerController : CharacterBody3D
public void MoveOnGround(double delta) public void MoveOnGround(double delta)
{ {
Vector3 direction = HeadSystem.Transform.Basis * _inputMove; var horizontalVelocity = ComputeHVelocityGround((float) delta);
Velocity = new Vector3(horizontalVelocity.X, Velocity.Y, horizontalVelocity.Z);
var accelerationFactor = direction.Length() > 0 ? AccelerationSpeedFactorFloor : DecelerationSpeedFactorFloor;
float xAcceleration = Mathf.Lerp(Velocity.X, direction.X * _targetSpeed,
(float)delta * accelerationFactor);
float zAcceleration = Mathf.Lerp(Velocity.Z, direction.Z * _targetSpeed,
(float)delta * accelerationFactor);
Velocity = new Vector3(xAcceleration, Velocity.Y, zAcceleration);
} }
public void MoveInAir(double delta) public void MoveInAir(double delta)
{ {
Velocity = new Vector3( var horizontalVelocity = ComputeHVelocityAir((float) delta);
x: Velocity.X, var verticalVelocity = Velocity.Y - (CalculateGravityForce() * (float)delta);
y: Velocity.Y - (CalculateGravityForce() * (float)delta), Velocity = new Vector3(horizontalVelocity.X, verticalVelocity, horizontalVelocity.Z);
z: Velocity.Z);
// The code below is required to quickly adjust player's position on Y-axis when there's a ceiling on the
// trajectory of player's jump and player is standing
if (IsHeadTouchingCeiling())
{
Velocity = new Vector3(
x: Velocity.X,
y: Velocity.Y - 2.0f,
z: Velocity.Z);
}
} }
public void WallHug(float delta) public void WallHug(float delta)