How would I make a modular animated sprite?

Godot Version

4.5.1

Question

As the title says, I’d like to make a scalable animated sprite like the ones you’d find in Celeste.


How is this effect achieved? I know that celeste was made with Unity but surely there would be some way to parody the ability to scale blocks as big as you’d like them to be.
Specifically what I’m talking about couldn’t be achieved by a tilemap because I would want the sprite to react to the player interacting with it.

There are lots of ways to do this. You can use is_on_wall(), you can use a RayCast2D, a ShapeCast2D, or an Area2D on the player to detect the wall and act appropriately for the climbing bit.

To make the animated sprite change inside, add an ShapeCast2D on the sprite that is larger than the block that can detect the character and use the collision points to detect the player in relation to the whole thing.

Also, you can do this in a TileMapLayer. You can make a special scene that goes in the TileMapLayer. The wall climbing you can do just by defining physics on the tiles.

I’d start with the climb functionality first. You can look up videos on wall running, wall sliding, and/or wall jumping in Godot if you can’t find any on wall climbing.

I realize now how deceptive the first image can be. My problem isn’t in the functionality of letting the player climb/interact with objects (I ended up using different collision layers so that I didn’t need to bother with finding custom data from tilemaps). I want to figure out how to make a scalable sprite that can look nice and not stretched in any assortment of sizes (32 by 64), (16 by 128) ect. without needing to make too many sprites for each configuration of size.

Only through shaders, you could sample a repeating texture by world position.

shader_type canvas_item;
render_mode world_vertex_coords;

varying vec2 uv_planar;
void vertex() {
	uv_planar = VERTEX.xy * TEXTURE_PIXEL_SIZE;
}

void fragment() {
	COLOR = texture(TEXTURE, uv_planar);
}
2 Likes

Well since my last post I actually played that level in Celeste for the first time. So for the stars, that’s actually a Parallax2D node behind the tile, where the tile center is transparent. Then when the character lands on top that looks like a shader effect. And when the character dashes through, that looks like a different animation player for the character dash and a shader effect to show the player’s passage.

Post a video. Not everyone played Celeste.

2 Likes

It’s currently on sale on Steam for $4.99, which is why I’m playing it.

Hope you enjoy it! It’s one of my favorites.

2 Likes

This code works if there isn’t any animation. Is there a way to make this shader work with animation?

shader_type canvas_item;
render_mode world_vertex_coords;

varying vec2 uv_planar;
void vertex() {
uv_planar = VERTEX.xy * TEXTURE_PIXEL_SIZE;
}

void fragment() {
COLOR = texture(TEXTURE, uv_planar);
}

For a spritesheet animation you could use this shader, using fract more instead of relying on repeating textures.

shader_type canvas_item;
render_mode world_vertex_coords;

uniform uint hframes = 1;
uniform uint vframes = 1;

uniform uint current_frame = 0;

varying vec2 uv_planar;
void vertex() {
	uv_planar = VERTEX.xy * TEXTURE_PIXEL_SIZE;
}

void fragment() {
	vec2 wh = vec2(uvec2(hframes, vframes));
	vec2 xy = vec2(uvec2(current_frame % vframes, current_frame / vframes)) / wh;
	
	vec2 uv_animated = fract(uv_planar * wh) / wh + fract(xy);
	COLOR = texture(TEXTURE, uv_animated);
}
2 Likes

Thank you! Worked like a charm.

PS: If anyone is having trouble with this; the shader only works if you have both vertical and horizontal frames.

2 Likes

I have no idea what you’re talking about because I am one of the approximately 98% of people who have not played Celeste.