Recover Animation #
TIL. If I want to have the player move back to their default pose gradually I can use the animation track’s Capture mode.
Here, I have no keyframes at t=0 but I do have keyframes at t=0.5. With the track mode set to Capture, I can play the “recover” animation any time and it’ll interpolate from the current values—no matter what they are—to whatever I set in my keyframes here.
Perfect for the little sword animation I’m working with.
$ convert -crop 150x100+200+193 +repage -resize 320x -delay 6 -loop 0 frame*.png anim.gif
$ gifsicle anim.gif `seq -f "#%g" 0 4 196` -O2 -o shorter.gif
I’ve got three chained attacks but for now we’ll focus on the forehand and backhand swing. If the player presses the attack button during the forehand then a backhand swing will follow. Otherwise it’ll recover.
After the backhand attack it’ll recover either way.
I don’t need to specify separate recovery animations for both attacks, which is great because there will be other positions I want to recover from.
That makes me think… I can probably have the attack animation interrupted by hitting something and play the recovery. That might be cool.
Maybe I can use it for the really solid, armored enemies. Or when swinging a mace. Or hitting a wall. I’ll keep it in the toolbox for later.
Learning from the Godot Platformer 2D Example #
https://godotengine.org/asset-library/asset/2727
Surprisingly, it doesn’t use @export var
. It sets up some references using the $ syntax:
@onready var sprite := $Sprite2D as Sprite2D
Weird.
And what is this?
@onready var gun = sprite.get_node(^"Gun") as Gun
It’s also in the docs.
Reckon it’s syntactic sugar like the dollar sign $
for quickly fetching references to nodes. Can’t find documentation anywhere… it is called a caret, isn’t it?
Terminal velocity. I could use that.
Input.get_axis
and Input.get_vector
will make my code a lot smaller. Don’t have to if/else every input direction and then normalize. Great if I’m making small games from scratch over and over.
Here’s the player movement along the x-axis.
const WALK_SPEED := 300.0
...
var direction := Input.get_axis("move_left", "move_right") * WALK_SPEED
velocity.x = move_toward(velocity.x, direction, ACCELERATION_SPEED * delta)
Checking for a value near zero:
if not is_zero_approx(velocity.x):
if velocity.x > 0.0:
sprite.scale.x = 1.0
else:
sprite.scale.x = -1.0
For vertical movement, jumping just sets the vertical velocity to a constant. Huh. For some reason I thought it’d be more complicated than that.
Interestingly, the double jump multiplies the horizontal speed by 2.5. Never realized that’s going on but I’m sure I’m grateful for it. (-:
Yeah, I tried running the demo again and I can definitely see it now that I’m looking for it. Cool.
There is one more tweak to jumping behaviour:
elif Input.is_action_just_released("jump") and velocity.y < 0.0:
# The player let go of jump early, reduce vertical momentum.
velocity.y *= 0.6
So the player presses jump, the vertical velocity is set to a large negative number. In this case -725.0
. If the player releases the jump button before—gravity is 2,100 pixels per second squared. So the player has to release within one frame to get the reduction in jump height.
Why not just check whether a frame has passed since jump was pressed?
The only reason I could think of is if the project gravity setting was changed to something floatier. But then I realized it’s not just one frame.
velocity.y = minf(TERMINAL_VELOCITY, velocity.y + gravity * delta)
I’m getting 0.008333
for delta so the player has way more than one frame to get the jump reduction. I tried with floaty physics as well and I can actually get the character to reach a few different heights by holding it down for different amounts of time.
This is neat, too:
Once the bullet has been around for a while, the “destroy” animation is played. It changes the alpha of its CPUParticles2D instance (using modulate) to 0 to get a fade out before calling queue_free()
. A one-second, one-shot timer triggers the destroy animation.
The bullet also has a sprite with a CanvasItem material used to enable “Add” blend mode.
Coin is a good lesson, here, too. Look how much the animation player does:
When the “picked” animation is played it fades out, moves upward (Sprite2D:position), turns off monitoring so it can’t be picked up again before it removes itself, and plays the pickup audio clip.
AnimationPlayers are also used to control the platform positions as they move up and down on a loop. The platforms are AnimatableBody2Ds.
The nice sway from all the shrubs and trees comes from a single shader. I’ve been shying away from shaders so far but avoidance is a sure sign that I really should have a peek:
// original wind shader from https://github.com/Maujoe/godot-simple-wind-shader-2d/tree/master/assets/maujoe.simple_wind_shader_2d
// original script modified by HungryProton so that the assets are moving differently : https://pastebin.com/VL3AfV8D
//
// speed - The speed of the wind movement.
// minStrength - The minimal strength of the wind movement.
// maxStrength - The maximal strength of the wind movement.
// strengthScale - Scalefactor for the wind strength.
// interval - The time between minimal and maximal strength changes.
// detail - The detail (number of waves) of the wind movement.
// distortion - The strength of geometry distortion.
// heightOffset - The height where the wind begins to move. By default 0.0.
shader_type canvas_item;
render_mode blend_mix;
// Wind settings.
uniform float speed = 1.0;
uniform float minStrength : hint_range(0.0, 1.0) = 0.05;
uniform float maxStrength : hint_range(0.0, 1.0) = 0.01;
uniform float strengthScale = 100.0;
uniform float interval = 3.5;
uniform float detail = 1.0;
uniform float distortion : hint_range(0.0, 1.0);
uniform float heightOffset : hint_range(0.0, 1.0);
// With the offset value, you can if you want different moves for each asset. Just put a random value (1, 2, 3) in the editor. Don't forget to mark the material as unique if you use this
uniform float offset = 0;
float getWind(vec2 vertex, vec2 uv, float time){
float diff = pow(maxStrength - minStrength, 2.0);
float strength = clamp(minStrength + diff + sin(time / interval) * diff, minStrength, maxStrength) * strengthScale;
float wind = (sin(time) + cos(time * detail)) * strength * max(0.0, (1.0-uv.y) - heightOffset);
return wind;
}
void vertex() {
vec4 pos = MODEL_MATRIX * vec4(0.0, 0.0, 0.0, 1.0);
float time = TIME * speed + offset;
//float time = TIME * speed + pos.x * pos.y ; not working when moving...
VERTEX.x += getWind(VERTEX.xy, UV, time);
}
Okay, three shader types: spatial for 3D, canvas_item which I assume is 2D, and particles for particles, which can be either.
The Shading Language page in the docs doesn’t actually have a list of render modes but blend_mix is the default blend mode, I believe.
uniform
is the shader language equivalent to @export var
in GD script. Those variables will show up in the editor.
The getWind()
method is using sine waves to determine the strength of the wind at a given point.
There are three processor functions that can be overridden, vertex
, fragment
(modify each pixel), light
(modify each pixel and light).
The vertex()
processor here does something with a MODEL_MATRIX I don’t understand, but then it discards the value anyway so it doesn’t matter. VERTEX isn’t used as input, either, though its x value is modified to shift the pixel a little left or a little right. The only built-in input that gets used is UV.
You would think it would need to know the pixel’s current value in order to set the new value so it would need VERTEX.xy
. At least that’s what I thought, but then it’s not a fragment shader it’s a vertex shader. I think all we’re really doing is moving the corners of the image around a little bit. I’m assuming the pixels are interpolated automagically.
So why do we need UV.y
? It looks like it’s just being used to change the timing of the wind in the y axis. The top of the tree shifts a little, then the middle shifts as well, then finally the bottom.
Swapping UV.y
for VERTEX.y
still works but the movement is exaggerated. There’s a (1.0 - uv.y)
that tells me UV coordinates are in (0, 1) whereas VERTEX coordinates are … I guess in screen space?
One more thing… I was wondering what this arrow is in the platform scene.
That’s part of a CollisionShape2D when one way collisions are on. I don’t know if you can rotate it separately from the collision shape, though. I guess for a rectangle it doesn’t matter. Probably not a thing to have a collision direction that is not perpendicular to one of the sides.