Character customization | Blog | Little Brats!

Character customization | Blog | Little Brats! Character customization | Blog | Little Brats!
  • Steam
  • Itch.io
  • Google Play
The little brats and the teacher

Blog

Character customization

2024-10-09

For this video game, I attached quite a lot of importance to character customization, i.e. the little brat you control… as well as the bots, of course. This is also what you can find in the “Litte Brats!” avatar generator.

Here's a look at how it works technically with the Godot game engine.

The creation of a typical little brat

The game is seen in faux-3D isometric with a 2D cartoon style. I chose to represent the characters in eight directions: top, right, bottom, left, as well as the four intermediate directions top-right, bottom-right, bottom-left, top-left.

Screenshot

For each of these eight directions, there are also two possible animations: idle or in motion. This gives us 16 different animations in all. By taking advantage of symmetry, we can reduce this number to 2 times 5 (the top-left, left and bottom-left positions are obtained by simply mirroring the top-right, right and bottom-right positions horizontally).

In terms of drawing, we need 12 sprites for each character: 2 feet, 2 legs, 1 pelvis, 1 torso, 2 arms, a face, hair, mouth and eyes. In some positions, not all sprites are visible, and some sprites are reused from one position to the next (for example, the head, torso and pelvis sprites are the same in resting and moving positions). Other variations include different moods for eyes and mouths, and movements such as slapping, etc.

Sprites

In practice, we also have other animations when the character uses an object (skipping rope, yo-yo, swing, etc.), but they reuse a lot of existing sprites.

That's all very well, but now how do you customize this character?

Colors

As you've probably noticed, my character is either an albino with a taste for white clothes… or he's simply lacking in color.

Obviously, I've colored all the sprites white on purpose, because it allows me to use a great Godot feature: the self_modulate property.

This is a property of the CanvasItem object (from which Sprite2D inherits) that allows you to… well, modulate the object's color. It's similar to modulate, except that modulate applies to this node and to all child nodes, whereas self_modulate only applies to the node in question: the arms, for example, are children of the torso node, but we clearly don't want the t-shirt color to be applied to the arms.

By setting this property, we can give any hue we like to each of our character's sprites:

Colors

Obviously, in practice, colors are restricted to a pre-determined subset, to avoid ending up with characters with alien skin, for example…

In the character creation menu, sliders allow you to select the color of each element: skin, hair, T-shirt, pants and shoes. Each slider is connected to a color ramp with, once again, predefined colors. To save the color of each character, simply save the position of each of these sliders!

Sprite variants

Changing colors is cool, but we'd also like to be able to change hairstyle, face shape, glasses, T-shirt and so on.

To do this, we start by organizing our sprite sheet: each element is on a separate line. Then, when we want to add a new variant, we simply create a new line, and by shifting the position of the chosen rectangle on the display, we can simply switch to a new sprite line.

It's even possible to use several sprite sheets to avoid ending up with a single huge image. For example, here are the four sheets used in v1 of the game:

Spritesheets

In the script, we create a function that applies the line change to all the sprites selected, giving us:


func set_y_for_all(sprites: Array, y: int) -> void:
    for sprite: Sprite2D in sprites:
        sprite.region_rect.position.y = y

As I want to be able to add variants later, sometimes from other sprite sheets, I have a config file in which I can indicate which lines to use for which part of the body:


const BRAT_BASE: Texture2D = preload("res://assets/characters/brat_base.png")
const BRAT_VARIANT: Texture2D = preload("res://assets/characters/brat_variant.png")
const BRAT_VARIANT2: Texture2D = preload("res://assets/characters/brat_variant2.png")
const BRAT_VARIANT3: Texture2D = preload("res://assets/characters/brat_variant3.png")

const POSSIBLE_HAIR_SKINS: Array = [
    [BRAT_BASE, 2, PRNG.Proba.COMMON], # Straight
    [BRAT_VARIANT, 2, PRNG.Proba.COMMON], # Curly
    [BRAT_VARIANT2, 4, PRNG.Proba.COMMON], # Ponytail
    [BRAT_VARIANT3, 8, PRNG.Proba.COMMON], # Curly ponytail
    [BRAT_VARIANT, 10, PRNG.Proba.COMMON], # Long
    [BRAT_VARIANT2, 10, PRNG.Proba.COMMON], # Thick
    [BRAT_VARIANT3, 10, PRNG.Proba.COMMON], # Wavy
    [BRAT_VARIANT, 8, PRNG.Proba.COMMON], # Nerdy
    [BRAT_VARIANT2, 0, PRNG.Proba.COMMON], # Short
    [BRAT_VARIANT2, 2, PRNG.Proba.COMMON], # Messy
    [BRAT_VARIANT3, 12, PRNG.Proba.COMMON], # Bushy
    [BRAT_VARIANT, 4, PRNG.Proba.RARE], # Pigtails
    [BRAT_VARIANT2, 12, PRNG.Proba.RARE], # Dreadlocks
    [BRAT_VARIANT, 6, PRNG.Proba.RARE], # Punk
    [BRAT_VARIANT2, 6, PRNG.Proba.RARE], # Palmtree
    [BRAT_VARIANT2, 8, PRNG.Proba.RARE], # Mohawk
    [BRAT_BASE, 0, PRNG.Proba.VERY_RARE], # Bald
    ]

const POSSIBLE_HEAD_SKINS: Array = [
    [BRAT_BASE, 4, PRNG.Proba.COMMON],
    [BRAT_VARIANT, 0, PRNG.Proba.COMMON],
    [BRAT_VARIANT3, 0, PRNG.Proba.RARE],
    [BRAT_VARIANT3, 2, PRNG.Proba.RARE],
    [BRAT_VARIANT3, 4, PRNG.Proba.RARE],
    [BRAT_VARIANT3, 6, PRNG.Proba.RARE],
    ]

const POSSIBLE_EYES_SKINS: Array = [
    [BRAT_BASE, 7, PRNG.Proba.COMMON], # No glasses
    [BRAT_VARIANT, 12, PRNG.Proba.RARE], # Round glasses
    [BRAT_VARIANT, 13, PRNG.Proba.RARE], # Square glasses
    [BRAT_VARIANT2, 14, PRNG.Proba.VERY_RARE], # Sun glasses
    [BRAT_VARIANT2, 15, PRNG.Proba.VERY_RARE], # Sun glasses
    [BRAT_VARIANT2, 16, PRNG.Proba.VERY_RARE], # Sun glasses
    ]

const POSSIBLE_TSHIRT_SKINS: Array = [
    [BRAT_BASE, 11],
    [BRAT_VARIANT, 14],
    [BRAT_VARIANT, 15],
    [BRAT_VARIANT, 16],
    [BRAT_VARIANT, 17],
    [BRAT_VARIANT, 18],
    [BRAT_VARIANT3, 14],
    [BRAT_VARIANT3, 15],
    [BRAT_VARIANT3, 16],
    ]

const POSSIBLE_PANTS_SKINS: Array = [
    [BRAT_BASE, 12],
    [BRAT_VARIANT2, 17],
    [BRAT_VARIANT2, 18],
    ]

On each line, we have the file in which to find the sprite line, and below it the line index. As you can guess from the code, there are also notions of rarity for certain skins, to avoid ending up with 10 kids with punk or bald haircuts, which would be rather unrealistic…

With this method, I can easily add skins later on, arrange them in different spritesheets and so on.

In v1 of the game, there are 17 hairstyles, 6 face shapes, 6 types of glasses, 9 T-shirts and 3 pairs of pants — a total of 16524 possible combinations! And that's without counting the color variations. In short, it's a great way to bring diversity and ensure there are no “clones” in the playground!

Conclusion

By using two simple Godot properties (color modulation and sprite rectangle position), you can customize the character, generate bots with various random skins... while preserving the animations and interactions developed for all characters. Skin saving is a simple list of integers corresponding to the index of each sprite and, for colors, to the position on the color ramp.

Of course, other skins will be added to the existing list in future game updates, so stay tuned!

Game based on the free software engine Godot.

CC-By-Sa 2021-2024 Simon « Gee » Giraudot
Site kindly hosted by Framasoft.

Logo
i