Home Artists Posts Import Register

Content

Author: Dei

What follows are some idle thoughts, which I couldn’t quite fit into the post proper. If you have more time on your hands than you know what to do with, feel free to give them a read.

During my talks with Munisix, he told me that if there’s one lesson he’s learned, it’s this—people who say things like it’s just a visual novel are the ones who won’t deliver. This is a remark that has stuck with me. Being reasonably familiar with game genres, I’m well aware of how visual novels are perceived by most people. They’re “just sprites on a background” and “pretty easy to make.” Fundamentally, this is true. When you’re just putting 2D sprites on top of a 2D background and overlaying it with a text window, an engine can more or less handle all the heavy lifting for you. It’s arguably easy enough that you don’t even need a programmer. To make a decent visual novel, all you have to do is have your assets ready and script them into your scenarios.

But what if you wanted to make a good visual novel? Or even a great one? As it turns out, there seems some sort of immutable and highly inconvenient truth to this world—nothing good is ever easy to make.

Some of the technical problems I ran into will doubtlessly seem trivial to experienced game developers, but consider how the common perception of visual novels being an easy medium to work with affects the dynamic. It seems entirely possible for many people to underestimate the difficulty of making a good visual novel and end up over-promising or underdelivering. I certainly did not find the process of developing fault - StP- LIGHTKRAVTE easy. In fact, it felt like all sorts of impossible, and I’m still in disbelief that the game managed to release without any major game-breaking bugs (which were definitely still present a week before release). The level of presentational polish required for a linear visual novel is, as I discovered, exceedingly high, because the presentation is all it has. A single graphics glitch, a stuttered animation, or a weird fade, could take the audience out of the story, ruining the experience. Without gameplay to fall back on, once the illusion is broken, there is nothing left. As a result, perfectionism was practically a prerequisite for developing games at the standard of AiD. This is not the kind of responsibility a first-time programmer should ever sign up for.

In fact, here’s a fun little exercise we can enjoy together. I’ll list some examples of issues I ran into. See if you can figure out what caused them and how to solve them before I give you the solutions I implemented. If you’re not a programmer or game developer by trade, you should probably be on level footing with me, making this a fairly accurate representation of what it might feel like for an amateur to code a visual novel. If you’re a veteran game developer, then you can have a good chuckle at the thought of a clueless coder being flummoxed by the following issues.

Issue #1

The ‘j’ for the font we were using has the little curvy tail at the bottom jutting a little too far out to the left, so when a left-aligned line began with ‘j’, the text reveal mask would fail to cover the little tail. You’d see a tiny little white tail on the screen before the text was supposed to be revealed.

Issue #2

The Chinese localization used extremely long rubies (the tiny text on top of regular text) that were much longer than the base text and poked far outside the text box of the Backlog window, causing them to be cut off.

Issue #3

I blurred the in-game scene when the menu is active for artistic effect. However, this caused the thumbnails to be taken for saves to be blurred as well. With tons of things to do and very little time to do them in, what could be a simple way to get around this issue?

Issue #4

While designing a scene, I added some fog using Unity’s particle system by having it generate slow-moving particles using fog-shaped sprites. The background was a couple of sprites—some big, some small—with semi-transparent fog floating around. It looked great on PC, but we had plans to release the game on Nintendo Switch, so I tried it on a dev kit. Upon loading the foggy background, it tanked the fps to something like 40. Why was the fog so detrimental to the frame rate?

Issue #5

While implementing Spine animations, I was delighted to find that Naninovel had an extension for Spine assets, allowing me to drop them in directly through the engine’s implementation, and even fade them in and out without any transparency issues. It worked beautifully on PC. When I tried it on Switch, fps fell to something like 20 with a bunch of Spine assets on screen. What was the issue with the Spine assets?

Issue #6

After getting around Issue #5, I realized fading out the Spine assets was a problem, because the entire asset fades together, causing transparency issues at the joints. Imagine a paper puppet, where the arm overlaps the shoulder. If both the arm and shoulder become semi-transparent, the area where the arm is overlapping the shoulder will look weird due to the shoulder being slightly visible under the arm. This is a well-known issue with 2D assets that animate through a skeleton. How can we get around it?

Issue #7

As the release date was nearing and we were doing build testing, we realized that shadow layers on Spine assets were sometimes showing through. That is, imagine a character whose hair is brown. Under the hair is another hair-shaped layer of a darker brown. Only a tiny bit of the darker brown should be showing to give the art some depth. However, there were times when the darker brown layer ended up on top of the regular hair, and it was not reliably reproducible. It never happened in the editor, and only in the build.

Issue #8

A week and half before release, diced sprites suddenly stopped dicing properly. We are using Naniovel’s diced sprites implementation, which had been working fine the whole time until one day, without touching anything, they suddenly borked. A character suddenly lost her expression, leaving her face blank until the script called the diced sprite again, and it reset.

Issue #9

Seamless transitions to videos required us to have the videos in the scene as actors. Controlling playback of the videos turned out to be more of a headache than I expected, as Naninovel did not have any inherent functions to do this. One problem that took far longer to solve than it should (due to a hole in my logic) was that despite setting the video player to not loop, the videos would restart playback after reaching the end. Checking in the inspector showed that Loop was definitely false. Nevertheless, the video restarted from the beginning after reaching the end. What kind of logic mistake could cause this?

Issue #10

Upon loading the game in a scene that uses particle generators (the water fountain in particular), the particles would leave a huge trail on the screen as they snapped into the right position. The effect resembled something like the water fountain being spawned at position (0,0,0) and then sliding very quickly into position with the water leaving a trail across the screen despite the movement occurring in a single frame.

What do you think? Any ideas? Some of these seem pretty bewildering, don’t they? Well, here’s how I got around them. Note that any reasoning I provide might very well be wrong. I’m only guessing at the cause.

Issue #1 Solution

I manually added line breaks to the instances where this occurred so the line doesn’t begin with ‘j’. Yes, you heard that right.

I did find a decent way around this by reducing the scale of the Textmesh Pro Font asset from 1 to about 0.98, which managed to keep the size of the dialogue box and text reveal mask the same, but shrink the text itself. However, by this time, the Japanese script had already been manually line broken for aesthetic purposes based on the original scale, and since the Japanese and English scripts shared the same font asset, changing the scale would mess up some of the Japanese line breaks. I could have made a new font asset just for the English script and assigned it to all the English text printers, but this was where I took the pragmatic approach and just fixed the English text.

Issue #2 Solution

I disabled overflow clipping, allowing the rubies to jut out as far as they want. However, the soft mask I was using for the Backlog window to produce that nice fade at the top and bottom was still cutting off the rubies, so I made the actual Backlog window much bigger, and pushed the text elements to the right just enough to give the longest ruby enough space on the left to fit within the mask. Easy peasy, though I was particularly happy to have figured this one out in under thirty minutes, because we were less than a week away from release.

Issue #3 Solution

I had the game take the thumbnail when the main menu was first opened and before the blur was applied. Since the save/load menu could only be accessed from the main menu, the save could use the thumbnail taken when the main menu was first opened. This created an interesting dynamic where the player could open the main menu while the camera was still moving, and the thumbnail would reflect the moment when the player opened the menu instead of when the save was initiated, but I figured it was a decent tradeoff and moved on to solve other problems with my limited time.

Issue #4 Solution

Semi-transparency draw calls are very expensive in video game rendering, because the renderer needs to draw every single semi-transparent layer to figure out the final color. Because we used a particle generator, it was spitting out a bunch of fog particles, which all overlaid on top of each other, creating massive transparency sandwiches that exponentially increased the render cost. We sort of got around this issue by using much much bigger fog particles that didn’t overlap as much.

Issue #5 Solution

In order to fade animated sprites without transparency issues at the joints, Naninovel was rendering all the Spine assets to separate render textures, then fading the textures. I’m still not entirely sure, but I think the process of drawing all the Spine assets to render textures is computationally heavy enough to overload the Switch’s capabilities. I had to drop the Spine assets directly into the scene without using Naninovel’s implementation (though this happened much earlier when I was even more clueless than I am now, so I might be wrong on the cause).

Issue #6 Solution

Since the Spine assets were not going through any sort of special treatment, fading them would cause transparency issues at the joints. I hacked a solution around this by turning the sprite black in the process of fading them, which masked the issue just enough to get by. That is, if you’re not looking carefully, you probably won’t notice.

...Which makes this explanation counter-productive. Don’t look too carefully at the fading sprites, okay?

Issue #7 Solution

I believe this was actually a depth value precision issue caused by the camera’s clipping planes. I used the default clipping planes of 0.3 and 1000, but because I was working on fairly small scales, there wasn’t enough precision in the depth buffer and z-fighting occurred. I don’t know why it wasn’t happening in the editor and only in the build, but I figure the way the editor interacts with the hardware might be slightly different than the build. By dropping the far clipping plane down, the issue was resolved.

Issue #8 Solution

The diced sprites issue is still a mystery. Deleting the Library folder and rebuilding the project seems to solve the issue, so it might be some sort of cache issue. I don’t know what causes it to happen, and I have no way of reliably reproducing it to show Naninovel’s support staff. However, the problem doesn’t seem to make it into the build, which is my first experience of a game being broken in the editor but fine in a build. Never even knew that was possible.

Issue #9 Solution

I was saving the video’s intended state as either Pause or Play, and constantly checking the Video Player’s state against the saved state. If the Player was paused or stopped, and the intended state was Play, I told it to start Playing. If the Player was playing, and the intended state was Pause, I told it to Pause (Yes I know this is a stupid solution, but the Video Player component wasn’t guaranteed to be generated on the same frame as the game object itself, and they just weren’t behaving, so I gave up trying to implement step-wise logic and threw a constant check into the Update loop).

Here’s the problem. After the video finished playing, the Player would now be stopped. However, I didn’t have any logic to change the intended state, which was still Play. So the next frame, the game would check the intended state against the Player, and tell it to Play again, restarting it from the beginning.

Once proper logic was implemented, as far as I could tell, the issue was solved.

Issue #10 Solution

The particles were being simulated in Local Space instead of World Space, so they moved with the parent object. What bewildered me was the fact that despite moving in a single frame, they drew the entire path. I guess because particles are simulations, they mathematically calculate the trail regardless of whether or not they’re rendered in the frame.

And there you go. Those are the answers that I, to the best of my ability, managed to come up with. Did you figure any of them out beforehand? Do they even make any sense to you?

Regardless, those are the types of problems you can run into when developing a visual novel in Unity. With the limited human resources of indie teams, the programmer is often the only person who can solve such problems, which is certainly no small amount of stress. For anyone considering dipping their foot into indie game development, even for something as “simple” as a visual novel, I’d advise plenty of caution and probably a good deal of practice before trying to make a game for real.

And for the love of all that is holy, do not sign onto an existing team with a track record of releasing highly polished products as a programmer if you don’t know what you’re doing. A series of highly fortunate circumstances granted me enough miracles to pull through, but there is no way such things can happen all the time. I mean, just look at some of those issues I listed. How the hell is someone learning Unity for the first time supposed to figure those out in a reliable fashion?

Comments

Dan Park

I have a hobby of analyzing video games for tool assisted speedruns/challenges and these issues would've been a field day, haha

Anonymous

After reading through all the issues (I only have a bit of experience writing simple automation scripts for python for work), I would definitely be as clueless as you! Just wanna say thank you for bringing Lightkravte to life, I very thoroughly enjoyed it!