Every visual novel relies heavily on the graphics, with no other asset being the most used than sprites. Visual novel sprites are the backbone of most visual novel graphics- they help us see the cast and how they react to situations. But depending on the project, the sprites need to be set up differently! Today we’re going to look at how we set up our visual novel sprites in 4 different projects.
Note: we use Ren’Py for our projects! There are also several different ways to go about making sprites- the code shown here isn’t the only way to do so.
The Witch in the Forest
For the original The Witch in the Forest we had the sprites as a single image- every expression was a separate, complete image.
Once we later added additional poses, the 2nd and 3rd poses became files 2 and 3. Some expressions only had one pose. Because each sprite is a separate image (with the starting tag of the character name), the image had to be shown in line each time.
show fidelia smile 2 at right show jastiline smile 2 at left with dissolve
This method of making sprites- where every expression is a complete image- is easiest but has several downsides:
- If you need to make any edits to the sprite’s body (such as editing the hair or clothing) then you have to make it to every image
- The file size for the project increases dramatically
The file size wasn’t a big problem for The Witch in the Forest due to how few characters are in the project, but for other projects this should be kept in mind.
However, for the Steam port of The Witch in the Forest we used the modern LayeredImage feature.
This new sprite set up allowed for more combinations and a smaller file size! The Steam version is right under 60 MB while the original is over 100 MB.
You may be wondering “what are LayeredImage sprites?”. We’ll take a look at them in just a bit. For now, let’s look at an older iteration of them- LiveComposite sprites.
That Which Binds Us
Our first commercial visual novel, That Which Binds Us, used an older method of sprite setup called LiveComposite.
LiveComposite (and LayeredImages) require the sprite to be in layers. The layers are up to you, depending on what you want to change, but the basic setup is having a base image, additional outfits as other images, and expressions as other images.
Evalise’s sprite had 9 base images- 3 different poses and 3 different states (normal, blushing, and creeped out). Her various outfits (depending on the pose) were overlayed on top of the base. Her mouth and eyebrows were on one image together, which was overlayed on top of that.
LiveComposite sprites are smaller to set up but more finicky. They use variables to switch the images shown.
image eva = LiveComposite( (540, 810), (0, 0), "eva [eva_base] [eva_pose].png", (0, 0), ConditionSwitch( "eva_outfit == '1'", "blankimg.png", "eva_outfit == '2'", "eva outfit2 [eva_pose].png", "eva_outfit == '3'", "eva outfit3 [eva_pose].png", "eva_outfit == '4'", "eva outfit4 [eva_pose].png", "eva_outfit == '5'", "eva outfit5 [eva_pose].png", "eva_outfit == '6'", "eva outfit6 [eva_pose].png", "eva_outfit == '7'", "eva outfit7 [eva_pose].png", "eva_outfit == '8'", "eva outfit8 [eva_pose].png" ), (0, 0), "e_[eva_expression].png" )
Because Eva’s first base was her normal outfit, we had to make the first outfit a transparent image.
$ idris_expression = "smirk" $ eva_expression = "unsuretalk" $ idris_pose = "1" $ eva_base = "blush" $ eva_pose = "3"
Whenever we wanted to change how they were posed or looked, we had to change the variables. This wasn’t very fun to script and caused the line count on our .rpy files to become very long. Thankfully, Ren’Py now has a much easier solution called LayeredImages…
Asterism‘s sprites were put together with the LayeredImage system. Here’s the layout for Kotachi’s sprite.
As you can see, we have Kotachi’s base as well as various things to lay on top of it such as his jackets and expressions. We separated his eyebrows from his mouths to give us even more combinations for his face.
layeredimage kotachi: always: "sprites/kotachi base.png" if gainedOrbital: "sprites/kotachi orbital.png" if jacket1: "sprites/kotachi jacket1.png" elif jacket2: "sprites/kotachi jacket2.png" if not jacket1 and not jacket2: "sprites/kotachi transparent.png" if vS: "sprites/kotachi vulpe shoulder.png" elif vST: "sprites/kotachi vulpe shoulder talk.png" group eyes: attribute wink: "sprites/kotachi wink.png" attribute asleep: "sprites/kotachi closed.png" group eyebrows: attribute up default: "sprites/kotachi eyebrow1.png" attribute down: "sprites/kotachi eyebrow3.png" attribute unsure: "sprites/kotachi eyebrow2.png" attribute sad: "sprites/kotachi eyebrow4.png" attribute confused: "sprites/kotachi eyebrow5.png" attribute confused2: "sprites/kotachi eyebrow6.png" group mouth: attribute smile default: "sprites/kotachi expression1.png" attribute talk: "sprites/kotachi expression2.png" attribute frown: "sprites/kotachi expression3.png" attribute frowntalk: "sprites/kotachi expression4.png" attribute grin: "sprites/kotachi expression5.png" attribute frown2: "sprites/kotachi expression6.png" attribute frowntalk2: "sprites/kotachi expression7.png" attribute frowntalk3: "sprites/kotachi expression8.png" group face: attribute blush: "sprites/kotachi blush.png" attribute deepblush: "sprites/kotachi blush2.png"
For some of the parts like his Orbital and jackets we used variables as those change less frequently. Once Kotachi gains the Orbital, he has it for the rest of the game, so it’s easier to set a variable for it than to remember to show it every time his sprite is shown after that.
There are other ways to go about setting up LayeredImage code, so please consult the Ren’Py documents if you’re considering setting one up yourself!
Using LayeredImages allows for us to change the expressions and such inline while a character is talking, like so.
k unsure frowntalk "It looks hurt!" show kotachi frown o unsure frowntalk "I... think that's just the color of its tail." show ophi frown k confused frowntalk "Have you ever seen a dog that had a red-tipped tail?" show kotachi frown
This is because the character speaking tags also have what image to associate with them.
define k = Character('Kotachi', color="#ff525b", image="kotachi") define o = Character('Ophi', color="#6dcff6", image="ophi")
Drops of Death
You’ve probably noticed that all of the sprites we’ve looked at so far have been halfbody sprites! These are much quicker to make but give developers less flexibility, namely in how you can position the sprites on screen. Halfbody sprites can only be zoomed in and out so much, but fullbody sprites can be placed almost anywhere.
For Drops of Death we went with fullbody sprites to allow for more positioning around the screen. The visual novel sprites in DoD still use the LayeredImage system in Ren’Py.
As you can see here, Natasha is further in the background than would normally be possible with halfbody sprites.
Every love interest has a winter coat, so they all have:
- Base sprite
- Mouth (expression)
- Eyebrows (brows)
In the future they’ll have additional eye positions as well, but this is what the sprite set up looks like for now. You might also notice how the naming scheme looks like the one used for Kotachi’s sprite. Using the same naming scheme makes it easier to remember. Typically, expression1 = smile, expression2 = talk, expression3 = frown, expression4 = frowntalk (neutral talk).
Visual Novel Sprites Summary
- Halfbody sprites are easier to make than fullbody sprites but give you less flexibility.
- Having your sprites be complete images for each expression is easier to get started but harder to edit if you have to make changes.
- Using a similar naming scheme for sprites will make everything easier!
We hope this overview of how we set up our sprites in Ren’Py was informative!