Sprite frames by code

An eight frame walk cycle painted into Image buffers at startup and fed to an AnimatedSprite2D. The strip at the bottom is the SpriteFrames itself.

The editor above is running this project. Change a line and it reloads.

Godot classes used

AnimatedSprite2DSpriteFramesImageTexture

The code

scripts/main.gd
extends Node

const FRAME_SIZE: int = 24
const FRAME_COUNT: int = 8
const FPS: float = 10.0
const STRIP_Y: float = 570.0
const STRIP_STEP: float = 96.0

const SKIN: Color = Color('#8da5f3')
const LEG: Color = Color('#5f74b8')
const EYE: Color = Color('#12141a')

var sprite: AnimatedSprite2D
var highlight: ColorRect


func _ready() -> void:
	_add_background()
	_add_label('every frame is an Image painted pixel by pixel at startup', Vector2(40, 40))

	var frames: SpriteFrames = SpriteFrames.new()
	frames.set_animation_speed('default', FPS)
	frames.set_animation_loop('default', true)

	for i in FRAME_COUNT:
		frames.add_frame('default', _make_frame(i))

	sprite = AnimatedSprite2D.new()
	sprite.sprite_frames = frames
	sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
	sprite.scale = Vector2(11, 11)
	sprite.position = Vector2(640, 300)
	add_child(sprite)
	sprite.play()

	_add_strip(frames)

	print('the strip below is the SpriteFrames, the highlight is the frame on screen')


func _process(_delta: float) -> void:
	highlight.position.x = _strip_x(sprite.frame) - STRIP_STEP * 0.5


func _make_frame(index: int) -> ImageTexture:
	var image: Image = Image.create(FRAME_SIZE, FRAME_SIZE, false, Image.FORMAT_RGBA8)
	image.fill(Color(0, 0, 0, 0))

	var phase: float = TAU * index / FRAME_COUNT
	var bob: int = int(round(sin(phase) * 1.5))
	var swing: int = int(round(sin(phase) * 2.0))
	var body: Vector2 = Vector2(12, 11 + bob)

	_fill_rect(image, Vector2i(8 + swing, 17), Vector2i(3, 6), LEG)
	_fill_rect(image, Vector2i(14 - swing, 17), Vector2i(3, 6), LEG)

	for y in FRAME_SIZE:
		for x in FRAME_SIZE:
			if Vector2(x, y).distance_to(body) <= 7.2:
				image.set_pixel(x, y, SKIN)

	_fill_rect(image, Vector2i(9, 9 + bob), Vector2i(2, 2), EYE)
	_fill_rect(image, Vector2i(14, 9 + bob), Vector2i(2, 2), EYE)

	return ImageTexture.create_from_image(image)


func _fill_rect(image: Image, at: Vector2i, size: Vector2i, color: Color) -> void:
	for y in range(at.y, min(at.y + size.y, FRAME_SIZE)):
		for x in range(at.x, min(at.x + size.x, FRAME_SIZE)):
			image.set_pixel(x, y, color)


func _add_strip(frames: SpriteFrames) -> void:
	highlight = ColorRect.new()
	highlight.color = Color('#2a2f3d')
	highlight.size = Vector2(STRIP_STEP, STRIP_STEP)
	highlight.position = Vector2(_strip_x(0) - STRIP_STEP * 0.5, STRIP_Y - STRIP_STEP * 0.5)
	add_child(highlight)

	for i in FRAME_COUNT:
		var thumb: Sprite2D = Sprite2D.new()
		thumb.texture = frames.get_frame_texture('default', i)
		thumb.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
		thumb.scale = Vector2(3, 3)
		thumb.position = Vector2(_strip_x(i), STRIP_Y)
		add_child(thumb)


func _strip_x(index: int) -> float:
	return 640.0 + (index - (FRAME_COUNT - 1) * 0.5) * STRIP_STEP


func _add_background() -> void:
	var background: ColorRect = ColorRect.new()
	background.color = Color('#12141a')
	add_child(background)
	background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)


func _add_label(text: String, at: Vector2) -> void:
	var label: Label = Label.new()
	label.text = text
	label.position = at
	label.add_theme_color_override('font_color', Color('#999999'))
	add_child(label)

More Animation examples