diff --git a/JumpRoyale/src/Arena.cs b/JumpRoyale/src/Arena.cs
index 3fe82ddd..b01037f8 100644
--- a/JumpRoyale/src/Arena.cs
+++ b/JumpRoyale/src/Arena.cs
@@ -100,6 +100,16 @@ public override void _UnhandledInput(InputEvent @event)
commandHandler.SpawnFakePlayers();
}
}
+
+ // Make everyone jump, no exceptions
+ if (Input.IsPhysicalKeyPressed(Key.J))
+ {
+ foreach (Jumper jumper in ActiveJumpers.Instance.AllJumpers())
+ {
+ jumper.RandomJump();
+ jumper.FlashPlayerName();
+ }
+ }
}
///
diff --git a/JumpRoyale/src/Jumper.cs b/JumpRoyale/src/Jumper.cs
index ba9f9b40..6dc844a9 100644
--- a/JumpRoyale/src/Jumper.cs
+++ b/JumpRoyale/src/Jumper.cs
@@ -15,13 +15,16 @@ public partial class Jumper : CharacterBody2D
///
private readonly HashSet _recentPosition = [];
+ private AnimatedSprite2D _animatedSprite2D = null!;
+ private RichTextLabel _nameLabel = null!;
+ private CpuParticles2D _cpuParticles2D = null!;
+
///
/// Used to block the fadeout in some situations, e.g. at the start of the game. This is automatically set to true
/// on every jump.
///
private bool _canFadePlayerName;
private bool _lastJumpZeroAngle;
- private bool _wasOnFloor;
// Get the gravity from the project settings to be synced with RigidBody nodes.
private float _gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();
@@ -53,13 +56,37 @@ public partial class Jumper : CharacterBody2D
public void Init(int x, int y, [NotNull] PlayerData playerData)
{
PlayerData = playerData;
-
Position = new Vector2(x, y);
Name = PlayerData.Name;
+ }
+
+ public override void _Ready()
+ {
+ _animatedSprite2D = GetNode(SpriteNodeName);
+ _nameLabel = GetNode(NameNodeName);
+ _cpuParticles2D = GetNode(ParticleSystemNodeName);
SetCharacter();
SetPlayerName();
SetGlow();
+
+ _animatedSprite2D.AnimationFinished += OnSpriteAnimationFinished;
+ }
+
+ public override void _PhysicsProcess(double delta)
+ {
+ StopOnFloor();
+ ApplyInitialGravity(delta);
+ ApplyJumpVelocity();
+ RotateInAir(delta);
+ BounceOffWall();
+ PlayNotGroundedAnimation();
+ UpdateNameTransparency();
+
+ _previousXVelocity = Velocity.X;
+
+ MoveAndSlide();
+ StorePosition();
}
///
@@ -71,22 +98,17 @@ public void SetPlayerName()
// Note: ToHTML() excludes alpha component to avoid transparent names
string colorCode = Color.FromString(PlayerData.PlayerNameColor, GameConstants.DefaultNameColor).ToHtml(false);
- RichTextLabel nameLabel = GetNode(NameNodeName);
-
- nameLabel.Text = $"[center][color={colorCode}]{PlayerData.Name}[/color][/center]";
+ _nameLabel.Text = $"[center][color={colorCode}]{PlayerData.Name}[/color][/center]";
}
public void SetCrazyParticles()
{
- CpuParticles2D particles = GetGlowNode();
-
// Make sure we can repeatedly call this function without unbounded growth.
- particles.Amount = Math.Min(particles.Amount * 5, 500);
+ _cpuParticles2D.Amount = Math.Min(_cpuParticles2D.Amount * 5, 500);
}
public void SetCharacter()
{
- AnimatedSprite2D sprite = GetNode(SpriteNodeName);
int choice = PlayerData.CharacterChoice;
string gender = choice > 9 ? "f" : "m";
int charNumber = ((choice - 1) % 9 / 3) + 1;
@@ -94,11 +116,15 @@ public void SetCharacter()
GD.Print("Choice: " + choice + " Gender: " + gender + " Char: " + charNumber + " Clothing: " + clothingNumber);
- sprite.SpriteFrames = SpriteFrameCreator.Instance.GetSpriteFrames(gender, charNumber, clothingNumber);
+ _animatedSprite2D.SpriteFrames = SpriteFrameCreator.Instance.GetSpriteFrames(
+ gender,
+ charNumber,
+ clothingNumber
+ );
if (IsOnFloor())
{
- sprite.Play(JumperAnimations.AnimationIdle);
+ _animatedSprite2D.Play(JumperAnimations.AnimationIdle);
}
}
@@ -115,29 +141,21 @@ public void SetGlow()
return;
}
- CpuParticles2D particles = GetGlowNode();
Color color = Color.FromHtml(colorString);
- color.A = 1f;
- particles.SelfModulate = color;
- particles.Visible = true;
+ color.A = 1f;
+ _cpuParticles2D.SelfModulate = color;
+ _cpuParticles2D.Visible = true;
}
public void DisableGlow()
{
- CpuParticles2D particles = GetGlowNode();
-
- particles.Visible = false;
- }
-
- public override void _Ready()
- {
- GetNode(SpriteNodeName).AnimationFinished += OnSpriteAnimationFinished;
+ _cpuParticles2D.Visible = false;
}
public void RandomJump()
{
- Jump(Rng.IntRange(45, 135), Rng.IntRange(10, 100));
+ Jump(Rng.IntRange(45, 135), Rng.IntRange(75, 100));
}
public void Jump(int angle, int power)
@@ -149,11 +167,10 @@ public void Jump(int angle, int power)
_jumpVelocity.X = Mathf.Cos(Mathf.DegToRad(angle + 180));
_jumpVelocity.Y = Mathf.Sin(Mathf.DegToRad(angle + 180));
_jumpVelocity = _jumpVelocity.Normalized() * (float)normalizedPower;
-
- PlayerData.NumJumps++;
-
_canFadePlayerName = true;
_lastJumpZeroAngle = angle == 90; // 0 in the command is expressed here as 90.
+
+ PlayerData.NumJumps++;
}
}
@@ -165,104 +182,57 @@ public void DisableNameFadeout()
_canFadePlayerName = false;
}
- public void SetColor(string hexColor)
+ public void OnSpriteAnimationFinished()
{
- AnimatedSprite2D sprite = GetNode(SpriteNodeName);
-
- sprite.Modulate = Color.FromHtml(hexColor);
- sprite.Modulate = new Color(sprite.Modulate.R, sprite.Modulate.G, sprite.Modulate.B, 1f);
+ if (_animatedSprite2D.Animation == JumperAnimations.AnimationLand)
+ {
+ _animatedSprite2D.Play(JumperAnimations.AnimationIdle);
+ }
}
- public override void _PhysicsProcess(double delta)
+ ///
+ /// Resets the alpha component to 1 on player's name label and resets the name fadeout timer.
+ ///
+ public void FlashPlayerName()
{
- Vector2 velocity = Velocity;
+ SetNameAlpha(1f);
+ ResetNameTimer();
+ }
+ private void StopOnFloor()
+ {
if (IsOnFloor())
{
- velocity.Y = 0;
- velocity.X = 0;
+ Velocity = Vector2.Zero;
}
+ }
- // Add the gravity.
+ private void ApplyInitialGravity(double delta)
+ {
if (!IsOnFloor())
{
- velocity.Y += _gravity * (float)delta;
- }
-
- AnimatedSprite2D sprite = GetNode(SpriteNodeName);
-
- if (_jumpVelocity != Vector2.Zero)
- {
- velocity = _jumpVelocity;
-
- if (!_lastJumpZeroAngle)
- {
- sprite.FlipH = velocity.X < 0;
- }
-
- _jumpVelocity = Vector2.Zero;
- }
-
- if (IsOnWall())
- {
- velocity.X = _previousXVelocity * -0.75f;
+ Velocity = new(Velocity.X, Velocity.Y + _gravity * (float)delta);
}
-
- Velocity = velocity;
-
- if (Velocity.Y > 0)
- {
- sprite.Play(JumperAnimations.AnimationJump);
- }
- else if (Velocity.Y < 0)
- {
- sprite.Play(JumperAnimations.AnimationFall);
- }
-
- bool justLanded = !_wasOnFloor && IsOnFloor();
- bool stuckInAir =
- (sprite.Animation == JumperAnimations.AnimationFall || sprite.Animation == JumperAnimations.AnimationJump)
- && Velocity.Y == 0;
-
- if (justLanded || stuckInAir)
- {
- sprite.Play(JumperAnimations.AnimationLand);
- }
-
- _wasOnFloor = IsOnFloor();
-
- UpdateNameTransparency();
-
- _previousXVelocity = Velocity.X;
-
- MoveAndSlide();
- StorePosition();
}
- public void OnSpriteAnimationFinished()
+ private void ApplyJumpVelocity()
{
- AnimatedSprite2D sprite = GetNode(SpriteNodeName);
+ Velocity = !_jumpVelocity.IsEqualApprox(Vector2.Zero) ? _jumpVelocity : Velocity;
- if (sprite.Animation == JumperAnimations.AnimationLand)
+ // Flip the sprite based on our x velocity, but only if we recently jumped at a non-zero angle
+ if (!Mathf.IsZeroApprox(Velocity.X) && !_lastJumpZeroAngle)
{
- sprite.Play(JumperAnimations.AnimationIdle);
+ _animatedSprite2D.FlipH = Velocity.X < 0;
}
- }
- ///
- /// Resets the alpha component to 1 on player's name label and resets the name fadeout timer.
- ///
- public void FlashPlayerName()
- {
- SetNameAlpha(1f);
- ResetNameTimer();
+ // Reset the jump velocity to indicate that we should not continuously apply the calculated velocity
+ _jumpVelocity = Vector2.Zero;
}
private void ResetNameTimer()
{
_fontVisibilityTimerStartTime = Time.GetTicksMsec();
-
- GetNode(NameNodeName).Visible = true;
+ _nameLabel.Visible = true;
}
private void UpdateNameTransparency()
@@ -286,14 +256,47 @@ private float CalculateFontAlpha()
return Math.Max(0, 1 - diff);
}
- private CpuParticles2D GetGlowNode()
+ private void SetNameAlpha(float alpha)
{
- return GetNode(ParticleSystemNodeName);
+ _nameLabel.Modulate = new Color(1, 1, 1, alpha);
}
- private void SetNameAlpha(float alpha)
+ ///
+ /// Causes the character to rotate based on its X velocity, but only at non-zero angles.
+ ///
+ private void RotateInAir(double delta)
+ {
+ // We don't want to rotate if we just jumped straight up
+ if (_lastJumpZeroAngle)
+ {
+ // Edge case reset when we jump at the very moment we hit the floor and we keep the previous rotation
+ _animatedSprite2D.RotationDegrees = 0;
+
+ return;
+ }
+
+ // Formula to automatically calculate the rotation speed based on the character's x jump velocity. The maximum
+ // velocity is 700, but it's a bit too fast, so we want to clamp it at around 600. The formula was shortened and
+ // tweaked to always output 1 at non-zero angle with low value, with a maximum of 7 at angle of 60, which should
+ // not increase linearly, but a bit slower at the start. Assuming the full power jump.
+ // Visualization, caps at J60. Approximately every +100 velocity on the plot is the next 10 angles:
+ // https://www.wolframalpha.com/input?i=min%28max%28%284sin%28%28abs%28x%29%2F280%29+%2B+300%29%2B5%29%2C1%29%2C+7%29+%3Bx+from+0+to+700
+ float velocity = Math.Abs(Velocity.X);
+ float rotationSpeedMultiplier = (float)(4 * Math.Sin((velocity / 280) + 300) + 5);
+ float clampedMultiplier = Math.Clamp(rotationSpeedMultiplier, 1, 7);
+
+ // Calculate the rotation factor and flip the value if we are going left (rotating in the right direction)
+ float rotationFactor = 200 * (float)delta * (Velocity.X < 0 ? -1 : 1) * clampedMultiplier;
+
+ _animatedSprite2D.RotationDegrees = IsOnFloor() ? 0 : _animatedSprite2D.RotationDegrees + rotationFactor;
+ }
+
+ private void BounceOffWall()
{
- GetNode(NameNodeName).Modulate = new Color(1, 1, 1, alpha);
+ if (IsOnWall())
+ {
+ Velocity = new(_previousXVelocity * -0.75f, Velocity.Y);
+ }
}
///
@@ -329,4 +332,27 @@ private void StorePosition()
Position += Vector2.Up * 16;
}
}
+
+ private void PlayNotGroundedAnimation()
+ {
+ // Going up -> Jump, down -> Fall. Removing the check causes infinite animation start
+ if (!IsOnFloor())
+ {
+ string animation = Velocity.Y < 0 ? JumperAnimations.AnimationJump : JumperAnimations.AnimationFall;
+
+ _animatedSprite2D.Play(animation);
+ }
+
+ // Describes a situation when we stopped, but the animation is still playing the Jump/Fall frames
+ bool hasLandedButStillAnimating =
+ (
+ _animatedSprite2D.Animation == JumperAnimations.AnimationFall
+ || _animatedSprite2D.Animation == JumperAnimations.AnimationJump
+ ) && Velocity.IsEqualApprox(Vector2.Zero);
+
+ if (hasLandedButStillAnimating)
+ {
+ _animatedSprite2D.Play(JumperAnimations.AnimationLand);
+ }
+ }
}