– This post has been a hold-over for a few weeks, but I decided it’s now time to flush it to the screen! –
After coming to grips with my shortage of artistic inspiration, I’ve decided to go on full steam ahead with my game features and underlying systems. More menus, more screens- including the ubiquitous Pause screen- a continuous rebuild of the underlying system that controls them, and I even did some touch-ups with the visuals. Bubble Tower is slowly but surely becoming more polished in gameplay and presentation.
First I want to talk about the visuals, because it’s will just be a short overview. There are no graphics other than menu text, bubbles, and backgrounds, but I’m moving on to using more with 3D rendered graphics for a lot of the stuff. So I start with the most obvious, the bubbles. The original spritesheet contained 8 bubbles, one for each color. I decided to stick with this principle but generating the spritesheet at runtime by drawing the mesh to a render target with 8 different colors and viewports. I get to keep the original code that splits up the spritesheet and select the appropriate colored bubble to draw.
What’s nice about this is that the bubbles can be animated, or they can be replaced with another mesh. After working on a shader to get the highlights and shading looking just right on the bubbles, I messed around with some other geometric meshes, and found one that I got stuck on. I was inspired by the gem puzzle games and wanted to use more interesting shapes. Mine look more like some sort of paper origami balls than bubbles now, but I actually like the look. So after all it looks like I am getting somewhere with the visuals. Afterwards I just stuck in a wallpaper as a test background.
Improving the screen system
The more I try to make the screens do what I want, the more bugs I was finding, but I’m smoothing out the screen system as I keep progressing. The screens have the ability to transition in and out of view, with user-defined times (as far as user-defined animations I’m working on it). This added a lot of code bloat to the
GameScreen class, so I isolated all this transition stuff into its own Transition class. Now every screen just has a Transition that it can use internally.
I’ve also made a new type of screen, the Splash Screen. Splash Screens are non-interactive, stay on for a fixed amount of time on the center of the screen, and leave. They can have text or an image. In the essence of time, I initially reused a class to display menu text, but broke it down further because it wasn’t using the button click functions.
It’s also possible to load a series of splash screens, each following in succession, with just one function call. The trick is passing a string array with your text in it, and storing it in the splash screen. The first splash screen uses the first string of text, removes it from the array and when it’s going to exit, passes the shorter array to a new
SplashScreen constructor. If there is a transition time, you will see the text/images smoothly crossfade between each other. This feature is great for displaying several game logos in a row, or having a visual countdown with your own text. I would not use it for long still-frame cutscenes, though. The player doesn’t have control of the splash screens, so not being able to skip through them would be very annoying.
Rules and control flow
Adding new splash screens on top of interactive screens have made the control flow a tad unpredictable, because I would want some splash screens to take away some control from the player, but not some others. I initially decided to make a distinction between interactive screens and non-interactive screens, and loop through the interactive screens to read input events, but that just made the results more confusing.
Here is the current list of screen classes that are used in this game:
The menus may seem redundant in distinction, but I won’t need many menus anyways, and totally fine hard-coding the pathways in them instead of using external scripts. On the quest to make the screen manager more robust, here are some rules I’ve set to implement:
- If a new screen is added, start reading input (if it’s interactive)
- If the new screen is marked “exclusive”, stop updating/reading input for other screens
- If an exclusive screen is removed, read input from the other screens again
- Screens may start reading input as soon as they’re entering, or when they’ve completely entered
- Screens should stop reading input as soon as they’re leaving
Pretty straightforward stuff. Here’s where the rules become more involved:
All screens undergoing transitions should be allowed to complete the transition phase.
What this basically means is, if a screen is exiting or entering, the transition timer that counts down or up must not be stopped at any cost. Just keep it going. To accomplish this, the transition updates need to be decoupled from the user-defined update code.
The reasoning behind this is, sometimes, an exclusive screen may enter at the same time other screens are entering. The exclusive screen wants to stop the others from updating, but if the transitions are ran in the Update function, those other screens will never get to complete their transitions! Decoupling that from the Update function would be the way to go, so I moved all the transition logic to an UpdateState function, which will always get called no matter what.
Avoid adding or removing screens while other screens are being queued for updates/input reading.
The screen manager has two special lists, ScreensToUpdate and ScreensToReadInput. ScreensToUpdate is a mainstay from the XNA code sample for game state management- it simply tells the manager to call Update on the screens in it. ScreensToReadInput was added later, and this tells the manager to HandleInput from the screens on that list. This action is done before calling Update on all the screens.
Here is the order of functions that are called in the game’s main loop:
- Read input from screens
- Update screen events
- Draw each screen
There is no particular spot that says where AddScreen or RemoveScreen is called. They may be called one or multiple times where screens are updated or reading input. That can have undesirable effects and can lead to subtle bugs if we’re not being careful.
Suppose that in one screen inside the ScreensToReadInput list, a key press was detected which will tell the screen manager to remove this screen and then add another one. That would potentially modify the list while it’s still looping inside of it! This would throw an exception if done in a foreach loop, so you have to use a for loop to iterate through instead. Still, modifying a list of screens while screens are updating can lead to unexpected behavior.
Updating screen lists
To resolve this problem, screens should not use AddScreen or RemoveScreen at all. These two functions can be made private, and there are two new functions to supplement them: PushToAdd and PushToRemove. Those would be the functions that screens can call, to notify the screen manager that there are new screens waiting to be added or removed.
Both functions would add a screen to one of two new lists: ScreensToAdd and ScreensToRemove. They are a sort of waiting list for the screen manager to go through. The purpose of this to move AddScreen and RemoveScreen calls inside a separate function, away from updating and reading input. The new loop of functions would look like this:
- Add or remove screens
- Read input from screens
- Update screen events
- Draw each screen
The ScreensToAdd and ScreensToRemove lists are guaranteed to be cleared by the time the screen manager is done with the first step. Now we would know exactly where new screens are pushed or removed from all the lists.
Determining screen priority
Why not just have the topmost screen receive input, you might ask? Sometimes you’d want more than one screen at a time being interactive, like a group of menus for a strategy game. Or the aforementioned splash screens that would still allow player control of screens underneath, I’d have to give them a special flag or property that says they are not “exclusive”. I plan to use splash screens more in this way, such as displaying larger score numbers or messages for clearing certain groups of bubbles.
Once I have all these problems sorted out, though, I can go back to adding more game-centric features, and hopefully adding a two-player mode. Still don’t know how I’ll work that one out, as I am just using a keyboard and don’t even have an Xbox controller to hook up. I can always take the more daring route and start coding an AI to play against the player. That would certainly be another challenge, but most likely I will just start out making the “AI” fire bubbles at random times in random directions, then gradually mold its crazy mind into something more coherent. But for now, on to polishing more menus!