SwingPivot: Procedural Swing Animation


Like what I'm doing? What about join me as a Patron and have a lot of perks and rewards?

Become a Patron

Hello there!

I just finished refactoring and redesigning the Star-riding mechanic. I changed the animation direction, from frame-by-frame to cutout animation. If you want to know how I make Cutout Animation characters, you can check out the Bard's Lesson Cutout Animation post I made, make sure to check out the comments, there are some useful tips there as well.

Due to that change a whole new world of possibilities opened, I was able to separate the sprites and animate them independently, which led to the following awesome swing effect when the mouse rides a star:

Alright let's dig into the SwingPivot to see how I made this effect, by the way the source code is at the bottom of the post as well.

The SwingPivot Class

The idea behind this class is that it will give an inertia looking to its children.  So it would pick a movement velocity, i.e. a Vector2, and would rotate according to the movement direction, flipping horizontally depending on the horizontal axis of the movement. It also takes an angle offset, it is useful depending on the children's angle, for instance the Astromouse technically is rotated 90 degrees. It also has a is_swingging control variable and a damping, which is used to damp the swing to its resting angle.


Swinging

To make the swing movement is very simple:

  1. Get the movement's angle
  2. Add the angle offset
  3. Set the rotation to be the resulting angle

Getting the Movement's Angle

Every Vector2 has a method that will convert it from a Vector to an angle in radians. Well, since I can't reason in radians, I convert it to degrees to reason about it better.


Adding the offset

I think this is not as important but it is a corner case for me, this helped make the Astromouse angle follows the movement angle. I think this could be 

Applying the movement's angle and flip

Then we just need to apply the resulting angle as the rotation of the SwingPivot and flip horizontally if needed. The flip happens depending on the movement horizontal velocity. If it is towards the right, no flip, otherwise flip

That's all for the swing, quite easy to be honest. The really hard part is to make it rest.

Resting

I couldn't figure out a way to make the SwingPivot rest by itself, I tried using a threshold, etc...no success. It would be good if I figure out how to make it rest by itself, so outside classes would just have to care about feeding it with a movement velocity. In any case, the rest method has to be called from outside, as the swing...I should really figure out how to encapsulate that.

To rest there are way more to care about

  1. The resting angle, i.e. the angle the character shall stay when the movement stops
  2. Which Cartesian quadrant the current rotation's angle of the SwingPivot lays on  During the tutorial I just realised the only corner case where it should be 360 degrees is when the movement is to left-down so I took rid of the quadrant thing
  3. The amount in percentage that the SwingPivot will offset between the current rotation and the resting angle, i.e. the damping

Getting the Rest Angle

The resting angle is, for the context of the game, always either 0 or 360, this is because of how the resting interpolates between the current angle and the resting angle. It could, in theory, always be 0 degrees, but if the rotation was in 270 degrees the character would make almost a whole spin, instead of rest towards the angle, so in that case it should interpolate to 360 which is the closest to 270.

So to make the SwingPivot rest proper, we must return the either if it shall rest to 0.0, spinning to the left, or to 360, spinning to the right. For that we just need to check if the movement is towards the left and towards the bottom. To apply this filtering I just made a ternary operation, I think it is quite readable, so is OK to me, but I'd like to know your opinion on that.

Damping to the rest position

To make the SwingPivot damp to its rest position is quite tricky. I didn't want to use any process function for that, like _physics_process, instead if it isn't swinging, remember the is_swinging control variable, it should damp to said rest position. To do that I used a while loop which yields for the physics_frame to render, on each iteration of this loop it would linearly interpolate the SwingPivot's rotation towards the rest angle by a given percentage, stored in the damping. That's why the damping has a max of 1.0, which is 100% per frame, meaning it would immediately rotate to the rest angle.

And that's all, that's how I achieved this awesome swinging effect to make it feel like the Astromouse is taking a ride on the stars.

Here is the source code if you want to just copy-paste it

extends Position2D
"""
Swing following a movement direction to give an inertia aspect
"""
export (float) var angle_offset_degrees = 90
export (float, 1.0) var damping = 0.02
var movement_velocity = Vector2(0, 0)
var is_swinging = false
func swing():
    is_swinging = true
    var movement_angle_degrees = rad2deg(movement_velocity.angle())
    
    var target_angle_degrees = movement_angle_degrees + angle_offset_degrees
    rotation_degrees = target_angle_degrees
    flip_horizontally()
func rest():
    is_swinging = false
    damp_to_rest_angle()
func damp_to_rest_angle():
    var rest_angle_degrees = get_rest_angle_degrees()
    while not is_swinging:
        rotation_degrees = lerp(rotation_degrees, rest_angle_degrees, damping)
        yield(get_tree(), "physics_frame")
func flip_horizontally():
    var is_flipped = movement_velocity.x < 0
    if is_flipped:
        scale.x = -1
    else:
        scale.x = 1
func get_rest_angle_degrees():
    var is_movement_bottom_left = movement_velocity.y > 0 and movement_velocity.x < 0     
    var rest_angle = 0.0 if not is_movement_bottom_left else 360.0
    return rest_angle

That's it, keep developing and until the next time!

Get Moon Cheeser

Download NowName your own price

Leave a comment

Log in with itch.io to leave a comment.