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"]
script = ExtResource("1_poq2x")
AccelerationSpeedFactorFloor = 3.0
WalkSpeed = 7.5
AccelerationAir = 2.0
DecelerationAir = 0.1
Weight = 5.0
SimpleJumpStartVelocity = 7.0
HangTimeInFrames = 3
GravityLesseningFactorUpward = 2.5
SimpleJumpStartVelocity = 8.0
SimpleJumpHangTimeInFrames = 3
SimpleJumpGravityLesseningFactor = 2.5
DoubleJumpStartVelocity = 15.0
DoubleJumpGravityLesseningFactor = 1.2
MegaJumpStartVelocity = 30.0
MegaJumpHangTimeInFrames = 15
MegaJumpGravityLesseningFactor = 1.2
JumpFromDashSpeedFactor = 4.0
WallHugHorizontalDeceleration = 3.0
MaxJumpBoostAfterDashing = 0.7
@ -452,18 +459,42 @@ initial_state = NodePath("SimpleJump")
[node name="SimpleJump" type="Node" parent="StateChart/Root/Movement/Jump"]
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"]
script = ExtResource("28_n7qhm")
to = NodePath("../../../Airborne/DoubleJumpEnabled")
event = &"jump_ended"
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")
[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")
[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"]
script = ExtResource("27_34snm")
@ -475,7 +506,7 @@ delay_in_seconds = "0.0"
[node name="OnMegajump" type="Node" parent="StateChart/Root/Movement/Grounded"]
script = ExtResource("28_n7qhm")
to = NodePath("../../Airborne/Falling")
to = NodePath("../../Jump/MegaJump")
event = &"megajump"
delay_in_seconds = "0.0"
@ -493,6 +524,12 @@ initial_state = NodePath("CoyoteEnabled")
script = ExtResource("41_ruloh")
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"]
script = ExtResource("28_n7qhm")
to = NodePath("../../Grounded")
@ -531,7 +568,7 @@ script = ExtResource("27_34snm")
[node name="OnJump" type="Node" parent="StateChart/Root/Movement/Airborne/DoubleJumpEnabled"]
script = ExtResource("28_n7qhm")
to = NodePath("../../Falling")
to = NodePath("../../../Jump/DoubleJump")
event = &"jump"
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")]
public float WalkSpeed { get; set; } = 7.0f;
[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")]
public float DecelerationSpeedFactorFloor = 5.0f;
public float DecelerationFloor = 5.0f;
[ExportGroup("Air")]
[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")]
public float Weight { get; set; } = 3.0f;
@ -77,9 +79,29 @@ public partial class PlayerController : CharacterBody3D
[Export(PropertyHint.Range, "0,100,1,or_greater")]
public float SimpleJumpStartVelocity { get; set; } = 3.0f;
[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")]
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
[ExportSubgroup("Other jump")]
@ -153,6 +175,8 @@ public partial class PlayerController : CharacterBody3D
private StateChartState _airborne;
private StateChartState _coyoteEnabled;
private StateChartState _simpleJump;
private StateChartState _doubleJump;
private StateChartState _megaJump;
private StateChartState _doubleJumpEnabled;
private StateChartState _onWall;
private StateChartState _onWallHugCanceled;
@ -230,6 +254,8 @@ public partial class PlayerController : CharacterBody3D
_airborne = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne"));
_coyoteEnabled = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne/CoyoteEnabled"));
_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"));
_onWall = StateChartState.Of(GetNode("StateChart/Root/Movement/OnWall"));
_onWallHugging = StateChartState.Of(GetNode("StateChart/Root/Movement/OnWall/Hugging"));
@ -311,18 +337,56 @@ public partial class PlayerController : CharacterBody3D
// _empowerOff.StateEntered += EmpowerStopped;
// _empowerTimeDownscale.Timeout += EmpowerTimerTimeout;
_powerFull.StateEntered += StopPowerCooldown;
_powerFull.StateExited += StartPowerCooldown;
_powerRecharging.StateEntered += StartPowerCooldown;
_powerFull.StateEntered += StopPowerCooldown;
_powerCooldownTimer.Timeout += PowerCooldownExpired;
_powerRecharging.StateProcessing += PowerRecharging;
_powerExpired.StateProcessing += PowerRecharging;
_simpleJump.StateEntered += OnSimpleJumpStarted;
_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()
{
if (CanMantle())
@ -352,6 +416,11 @@ public partial class PlayerController : CharacterBody3D
// else if (_onWall.Active)
// JumpFromWall(_empowerOn.Active);
if (_empowerOn.Active && CanPerformEmpoweredAction())
{
_playerState.SendEvent("megajump");
return;
}
_playerState.SendEvent("jump");
}
@ -364,31 +433,66 @@ public partial class PlayerController : CharacterBody3D
_playerState.SendEvent("jump_ended");
}
private int _framesSinceJumpAtApex = 0;
public void OnSimpleJumpStarted()
public void OnJumpStarted(float verticalVelocity)
{
_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(
x: Velocity.X,
y: SimpleJumpStartVelocity,
y: verticalVelocity,
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
if (Velocity.Y <= Mathf.Epsilon)
{
_framesSinceJumpAtApex++;
Velocity = new Vector3(
x: Velocity.X,
y: 0,
z: Velocity.Z);
SetVerticalVelocity(0);
}
// Cancel gravity on jump apex
var gravity = CalculateGravityForce() / GravityLesseningFactorUpward;
var gravity = CalculateGravityForce() / gravityFactor;
var isAtApex = _framesSinceJumpAtApex > 0;
if (isAtApex)
{
@ -396,19 +500,27 @@ public partial class PlayerController : CharacterBody3D
}
// Update velocity accordingly
var newVerticalSpeed = Velocity.Y - gravity * delta;
Velocity = new Vector3(
x: Velocity.X,
y: newVerticalSpeed,
z: Velocity.Z);
// Move back to Airborne state management when starting to go down again
if (_framesSinceJumpAtApex > HangTimeInFrames)
_playerState.SendEvent("jump_ended");
SetVerticalVelocity(newVerticalSpeed);
if (IsHeadTouchingCeiling())
{
SetVerticalVelocity(Velocity.Y - 2.0f);
}
public void OnSimpleJumpEnded()
// Move back to Airborne state management when starting to go down again
if (_framesSinceJumpAtApex > hangFrames)
_playerState.SendEvent("jump_ended");
}
public void HandleSimpleJump(float delta)
{
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)
@ -777,36 +889,6 @@ public partial class PlayerController : CharacterBody3D
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 ////////
@ -819,33 +901,15 @@ public partial class PlayerController : CharacterBody3D
public void MoveOnGround(double delta)
{
Vector3 direction = HeadSystem.Transform.Basis * _inputMove;
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);
var horizontalVelocity = ComputeHVelocityGround((float) delta);
Velocity = new Vector3(horizontalVelocity.X, Velocity.Y, horizontalVelocity.Z);
}
public void MoveInAir(double delta)
{
Velocity = new Vector3(
x: Velocity.X,
y: Velocity.Y - (CalculateGravityForce() * (float)delta),
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);
}
var horizontalVelocity = ComputeHVelocityAir((float) delta);
var verticalVelocity = Velocity.Y - (CalculateGravityForce() * (float)delta);
Velocity = new Vector3(horizontalVelocity.X, verticalVelocity, horizontalVelocity.Z);
}
public void WallHug(float delta)