Ways to think about sprites for character customization

Started by deadsuperhero, April 03, 2019, 01:00:42 AM

Previous topic - Next topic
One thing that I've been musing about for a while involves different methods of rendering a custom player character. I'm not overtly familiar with Lua yet, so I'll try instead to talk instead in loose conceptual terms.

Problem: Let's say that you're making an RPG where the main character can be customized - this can include articles of clothing, but also skin tone, hair, gender, faces. Maybe you can even swap out different outfits throughout the game as well, for either cosmetic or plot-specific reasons.

How do you approach this? I have a few thoughts.

Solution 1: Paint a lot of different versions of the same character
If you have the time and energy, this is probably fine. But it's labor-intensive, and a headache if you have to incorporate potentially many animations. It's really jarring for the player to try to pick up a rock, only to have your blue cape turn into a red one, because you're using a fallback animation.

Solution 2: split body parts into components
This is probably less crazy than option 1, in the sense that you're just splitting up pieces and making the game engine re-assemble them on-the-fly. Suddenly your green-girl-haired elf head can be placed on top of a suit of armor, without the need to explicitly build out every permutation of the player's sprite. This still creates a lot of components to graft together, and it could still lead to janky-looking animations, but it could ultimately reduce some work with creating art assets. The tradeoff is that you'll also have to track the custom body pieces that the player set, and keep them.

The other weird thing is that a "hero" would basically be an object consisting of several sub-objects held together a certain way. This would take some trial-and-error to get to render correctly.

Solution 3: use hardware acceleration to color parts of sprites for you
I'm not sure how proven this idea is, but maybe we can reduce the amount of unique body components sprites you have to draw by using the GPU to render color values as parts of sprites. Maybe you actually just have transparent hair styles, for example, and various hair-shaped colored backgrounds could be dynamically rendered behind them. If you do it right, it could look fairly convincing, and allow you to set a bunch of custom color values on a limited set of generic body pieces, assuming you combine this with Solution #2


Anyway, this is where my train of thought is right now. I realize that I'm not actually providing any scripts here, but I'm curious as to whether other creators have come up against this challenge yet, and what their thoughts might be on how to approach the issue. Maybe by kicking around a few ideas, we can come up with some actual scripts.
Sent from your dad's iPhone

I'd say that #2 is the best approach for this. For this you'd use hero:create_sprite(). As for rendering correctly, it would probably be easiest to make all the pieces the same size (e.g. 16x16 if that's the size of your hero) with transparent pixels where not used (and the sprites would all have the same origin). That way you wouldn't have the alignment issues.

Solution #1 could work, but as you said, all the possible permutations quickly become complicated. I'd only recommend using this method if you were only swapping out the outfit in its entirety and not dealing with subcomponents.

This is especially complicated for the hero. Let's say you had 5 possible subcomponents (hat, face, shirt, pants, boots) with 3 variants for each. That makes 15 complete sprite sets you'd have to create, and you'd need to account for all the poses as well, including the use of various items. The hero has a lot of them, so you'd have a ton of sprites to make in the end.

Solution #3 is possible with shaders that were just introduced in Solarus v1.6. Seem like more effort than it's worth to me. I suppose if you were strictly dealing with palette swaps, then it might not be too bad.

Quote from: llamazing on April 03, 2019, 02:12:18 AM
Solution #3 is possible with shaders that were just introduced in Solarus v1.6. Seem like more effort than it's worth to me. I suppose if you were strictly dealing with palette swaps, then it might not be too bad.

I think it's also possible by drawing a surface of a particular color and using a blend mode.
RIP Aaron Swartz

Quote from: alexgleason on April 03, 2019, 02:47:13 AM
I think it's also possible by drawing a surface of a particular color and using a blend mode.

Eeesh, while it is technically possible using a surface with a blend mode, you'd be much better off doing it with a shader.

So with the add blend mode you could change purple (127, 0, 255) to magenta (255, 0, 255) by adding 128 to the red component. But you cannot change purple to red using the add blend mode because it would require subtracting blue. The multiply blend mode allows you to reduce color values, i.e. multiply the blue component by 0 to result in 0. But now you're doing 2 different blend mode operations, and you'd have to figure out the correct colors to add and multiply on a pixel by pixel basis. So really it would just be as much work as creating individual sprites, but really twice as much effort because you'd need to create an "add" and "multiply" overlay unique to each sprite.

On top of that, you can't draw a surface onto a sprite directly; you'd have to draw it on the screen. So you could use transparent pixels on the blend mode surfaces anywhere where the hero sprite is not present, but if another entity were obscuring the hero then you'd have to account for it, which would not be pretty. Not to mention there would be further complications if the hero sprite used any semi-transparent pixels.

A shader is definitely the way to go here, where you could fairly easily change all pixels of any one particular color to any other specific color, and the same transformation would be applicable to all sprites in the set. Plus it has the advantage of being able to apply a shader to an individual sprite rather than to the screen. The hardest part would probably be setting up the data tables to define all the possible palette swaps.

I've been in the initial phases of thinking about this exact problem, for a project I'm thinking about taking on. I also think we'd need a general approach to this in order for Solarus to successfully branch out to more traditional RPGs.

I agree that #2 seems like the best option, possibly combined with #3 for palette swaps. That option is actually similar to the philosophy behind the Universal LPC Character Generator Project (http://gaurav.munjal.us/Universal-LPC-Spritesheet-Character-Generator). This uses a base character sprite and then simply layers each of the armor/weapon components on top at a set location (based on the location of the armor/weapon sprite sheet relative to the character sprite sheet, so transparent pixels like llamazing said).

If you're able to combine #2 and #3, the number of sprite sheets required wouldn't be too bad. You'd need a base sprite sheet for each "race" appearance (like the LPC project has Orcs, Skeletons, and the rest are human-like) which you could color-swap with shaders. Then you'd need a sprite sheet for each armor and weapon type, but those could be applied to any of the base sprite sheets for a large number of combinations. These could also be color swaps with shaders if needed.

Quote from: wrightmat on April 03, 2019, 04:11:16 PM
You'd need a base sprite sheet for each "race" appearance (like the LPC project has Orcs, Skeletons, and the rest are human-like)...

Bringing different races into the mix that share the same clothes sprites and weapon sprites opens up a whole new can of worms (unless all the races have the same basic shape). For example, if one race has a figure that is taller than the others, then now the sub sprites need to have a vertical offset added. Not impossible by any means, but certainly more hurdles to clear. In that case, however, you'd probably be better off with making a set of sub sprites unique to each race.

Agreed. LPC gets around it by keeping the same basic shape, as you mentioned.

This is crazy that I find this thread because I was just thinking about this problem a little while back and something like solution #2 is what I figured would work best.

I'm trying to work on an ARPG mix of Dark Souls and ALttP and I had planned on having interchangeable weapons and armour. I don't know for certain that this would work on a large scale, but my thought was that you would layer the armour/weapon sprites on top of/underneath the hero sprite and synchronise them. It is a bit of work to make all the sprite sheets but it's not all that terrible. As a proof of concept, I used a custom enemy sprite and overlaid the weapon (a broken straight sword) in his hand.

I copied the soldier.lua from the ALttp_1.6 resource pack and changed a few things and named it hollow.lua. This function:

function enemy:order_sprites(main_sprite,sword_sprite)
    local direction4 = enemy:get_direction4_to(enemy:get_facing_position())
print(direction4)
      if direction4 == 1 or direction4 == 2 then
        enemy:bring_sprite_to_back(sword_sprite)
        enemy:bring_sprite_to_front(main_sprite)
        print("sword in back")
      else
        enemy:bring_sprite_to_front(sword_sprite)
        enemy:bring_sprite_to_back(main_sprite)
        print("sword in front")
      end
    return true
  end


is what I used to change the sprite overlay depending on the direction of the entity. It is called in the hollow.lua here:

function enemy:check_hero()

    local _, _, layer = enemy:get_position()
    local _, _, hero_layer = hero:get_position()
    local near_hero = layer == hero_layer
        and enemy:get_distance(hero) < 500
        and enemy:is_in_same_region(hero)

    if near_hero and not going_hero then
      if properties.play_hero_seen_sound then
        sol.audio.play_sound("hero_seen")
      end
      enemy:go_hero()
    elseif not near_hero and going_hero then
      enemy:go_random()
    end
    sol.timer.stop_all(self)
    sol.timer.start(enemy, 50, function() enemy:order_sprites(main_sprite, sword_sprite) end)   --timer to ensure weapon sprite is in front/
                                                                                                --behind the enemy when necessary
    sol.timer.start(self, 100, function() enemy:check_hero() end)
  end


I call it every 50ms because it doesn't perform well above that (weapon sprites pop through after the hero has changed direction). I also haven't tested this with any other circumstance aside from this single enemy on the Kakariko Village map from the resource pack, so I have no clue if it would work with 10 or more enemies on the same map. I had planned to use this for the hero too...no clue if that would work too, but that is for a little bit later on.

Comments and feedback are appreciated.
Build a man a fire, he will be warm for the night. Set a man on fire, he will be warm for the rest of his life.
Other Handles: Kamigousu, Xejk, Mr Sheik