Search code examples
spritegame-developmentgodotgdscriptgodot4

How can I combine all child sprites into a single sprite?


I have a scene that consists of one parent and several children, which are all Sprite2Ds, like "Body", "Clothes", "Armor", etc. I want to be able to refer to the parent as if it was a single sprite that looks like the composition of all the children sprites. So, for instance, I would like to be able to set the flip_h on the parent and have that flip all of the children. I would like to be able to refer to the texture of the parent, and have that be the image that the scene looks like, i.e., all of the sprites layered on top of each other.

I was expecting this to work just by making the parent a "Sprite2D" and adding the child sprites, but then the properties just refer to the empty texture of the parent sprite. In other posts I have seen people recommending stitching the images together in GIMP or something, but that is not going to work for me--I need control over all the behavior of all the individual layers of the overall sprite, but I want to be able to refer to the resulting sprite as if it was a single image. I have also seen recommendations to add a camera/viewport and grab the displayed image that way, but I don't think this is a good idea in my case because I will have hundreds of these scenes and I want it to be as performant as possible.


Solution

  • I'd suggest to try using the SubViewport approach. Yes, it has a performance cost, but it might not be too bad depending on your target platform and how many you use.


    With that said, given that you want to be able to retrieve a Texture2D, but you do not want to use a SubViewport...

    I'd say not use multiple Node2Ds.

    You might create an ImageTexture.

    var custom_texture := ImageTexture.new()
    

    And create an Image via code:

    var image := Image.create(render_size.x, render_size.y, false, Image.FORMAT_RGBA8)
    

    Which you give to the ImageTexture using set_image:

    custom_texture.set_image(image)
    

    You can set your ImageTexture to the texture of the Sprite2D. The only Sprite2D:

    %sprite.texture = custom_texture
    

    You can create a new image and set it with set_image replacing the old one.

    In fact, (not necessary, but the usability might be worth it) you can create a class that extends ImageTexture and has all the drawing code internally, and calls set_image on itself.

    To draw on the image you can, for example use blit_rect. This is an example that draws icon.png:

    var source := preload("res://icon.png").get_data()
    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.

    I'm adapting the above code from a prior answer: How can I bake 2D sprites in Godot at runtime?.

    The trouble with this approach is that the API does not support drawing a rotated image. Thus if you need that, you would have to set the pixels one by one... Even if the API supported drawing a rotated image, it would be doing it CPU anyway. Thus, this approach won't be as performant as letting the GPU handle it.

    The situation in which using ImageTexture would have the most performance edge in its favor is when the image is not updated often.


    There are other ways to get what you want drawn on the screen which are more performant, including using _draw (see Custom drawing in 2D), and using the RenderingServer (see Optimization using Servers), but you would still have the problem of getting a Texture2D out of it.