0
\$\begingroup\$

I would like to use the technique described here to create a player sprite with unlimited clothing and equipment options (like in Stardew Valley).

To elaborate: I want to have a sprite sheet for the player sprite's body, some number of sprite sheets for clothing items the player sprite could wear, and some number of sprite sheets for tools the player sprite could carry. Once the (human) player has selected the desired body, clothing, and tool, I want to layer and bake the chosen items into one sprite sheet.

In Godot Engine, how is this kind of baking accomplished at runtime?

\$\endgroup\$
1
  • \$\begingroup\$ Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. \$\endgroup\$
    – Community Bot
    Sep 28 at 10:36
3
\$\begingroup\$

So you let the player select a series of sprite sheets. And there is some order in which you overlay them to create the sprite sheet of the player.


Image manipulation at runtime

We can create an Image:

var image = Image.new() 
image.create(256,256,false,Image.FORMAT_RGBA8)

Change 256,256 with the appropriate size.

There the format is important. You want a format that has an alpha channel, otherwise, you will not be able to draw with transparency.


Now we need the image we are going to draw (the first sprite sheet). We can use load or preload to get it. For example:

var icon := preload("res://icon.png")

We usually import as textures, but we want an image. So we use get_data():

var source := preload("res://icon.png").get_data()

Then we can draw it with blit_rect:

image.blit_rect(source, Rect2(Vector2.ZERO, source.get_size()), Vector2.ZERO)

There the Rect2 is the source area (the area of the image we are drawing that we want to draw) which goes from Vector2.ZERO to the size of the image. The other Vector2.ZERO is the position where we want to draw it.

Now the idea is that you will do that for all the sprite sheets that you need to draw.


Note: depending on the format of the image you want to draw, you may have to convert it first:

source.convert(Image.FORMAT_RGBA8)

Also, usually, the images are imported in a compressed format that cannot be converted directly, and thus you would have to decompress first:

source.decompress()
source.convert(Image.FORMAT_RGBA8)

If the blit_rect line is giving you problems, try that.


Using a Viewport texture

If you found the above approach too limiting. You can add a Viewport with a Camera2D. Then to get the texture from the Viewport, you can use viewport.get_texture() (and, of course, you can get an Image from it with .get_data()).

Thus, you can add to the Viewport anything you want to draw. Which means that you can add multiple nodes to compose the image, with their transforms and modulations. Or it could be a single one which uses the methods described in Custom drawing in 2D, or CanvasItem shaders (For example, you may allow the user to do a hue rotation of a sprite sheet, implemented in a shader). Or combinations of those techniques.


Creating sprite frames for animation

Once you are done, you can create an ImageTexture from the Image, like this:

image_texture = ImageTexture.new()
image_texture.create_from_image(image)

Since we are talking of sprite sheet, we can create AtlasTextures:

func get_texture_frame(row, column):
    var t = AtlasTexture.new()
    t.atlas = image_texture
    t.region = (
            column * SPRITE_WIDTH,
            row * SPRITE_HEIGHT,
            SPRITE_WIDTH,
            SPRITE_HEIGHT
        )
    return t

Then we can add the AtlasTextures to a SpriteFrames with add_frame to use them in an AnimatedSprite.

Creating a SpriteFrames in code looks something like this:

var frames := SpriteFrames.new()
frames.add_animation("animation_name")
frames.set_animation_speed("animation_name", frames_per_second)
frames.add_frame("animation_name", get_texture_frame(0, 0))
frames.add_frame("animation_name", get_texture_frame(0, 1))
# …

Persistent storage

If you want to save an Image, you can use save_png.

If you want to save the ImageTexture or the AtlasTextures or the SpriteFrames, you can use ResourceSaver.save. I suggest to use path in user://, see Data Paths.

To load them again, you can use load or ResourceLoader.load.

\$\endgroup\$
1
  • \$\begingroup\$ What a thorough answer! I haven't been able to try it out yet, but I expect it to work. Thank you! \$\endgroup\$ Oct 1 at 20:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.