Skip to content

Macro Gameplay Loops

In the last chapter, we ripped the entire game apart and rebuilt it with only the essential elements of a 1v1 brawler. The general feedback to the idea has been positive, although pretty much everyone finds the game to be too difficult. Difficulty aside, they genuinely seem to enjoy the experience, attempting the same battle over and over in an attempt to overcome the AI.

The biggest issue I have with the game right now isn't the ability tuning or even the difficulty of the AI - it's the lack of options and understanding. Whenever I let someone play the game for the first time, they can quickly identify what to do - select a target, and start casting abilities. The matching blue backgrounds of the abilities and the health bars on the right side seem to be enough to indicate which "team" the player controls. It's not the goal or controls that are confusing, rather the abilities themselves. The most common question I get from new players is "what do these buttons do?".

Some of the buttons are more obvious than others. "Scorch" is obviously some form of harmful attack, while "Restore" is a heal of some sort. "Infuse" and "Siphon" aren't as clear, and honestly "Siphon" doesn't even make sense to me! In any case, these abilities wouldn't be so alien to players if they were the ones choosing which abilities they had - they could gain that understanding before being thrown into a battle.

Even the Pokémon series has done a poor job with this: whenever a monster learns a new move, the player must first choose which existing move to forget, without even having the change to try it out first! The first generation of Pokémon games felt very mysterious as a child, and sometimes you would learn a new move only to be disappointed by it. Later generations added the ability to compare the relative power of moves before permanently forgetting one that you actually liked better.

The biggest redemption for Pokémon's ability system is its turn-based combat, which allows the player to use the new ability in isolation. This isolation, in turn, allows the player to observe exactly what that new ability did, so that they can decide how valuable the ability is for possible future use in a relatively safe environment. When combined with the mostly-intuitive type system, players are able to easily evaluate the likely effectiveness of a given ability against a specific opponent.

Games in the MOBA genre, such as League of Legends or DotA 2, are often considered to have a very high barrier to entry due to the sheer amount of knowledge the player must have before being able to play the game somewhat competitively. When compared to RPGs, which give the player room to learn each new ability at their own pace, MOBAs can be very stressful. A new player's first experience in a MOBA is often spent reading a bunch of ability tooltips while waiting for their character to respawn because they died again.

The Super Smash Bros. games manage to structure each character's abilities into very similar categories, so even if you've never played a character before, you at least know that pressing the B button while holding "up" is very likely to be an ability that helps you recover from falling off the stage (though there are exceptions to those conventions).

I guess I'd just like to determine how the player learns about their abilities. Should they be able to choose them freely? Should they "unlock" them? How do they go about selecting them? What happens outside of the battles?

More Emotional Manipulation

I stated before that I want the player to feel emotions ranging from anxiety to relief during the battles. If the player is capable of making choices and performing actions outside of those battles, then I can't help but wonder: how should the player feel during those parts of the game?

That simple question is pretty hard to answer when I don't have any idea what the player will be doing. Let's try to break down what we know so far.

  • The player needs to be able to choose which abilities they would like to use in battles.
  • There should be a way to convey the function and power of all the available abilities.

That's all I got. Not very helpful.

If anything, those requirements just invoke further questions:

  • Does the player have access to all abilities? If not, how do they obtain new ones?
  • Should the functionality of an ability be conveyed numerically? With a textual description? Through experimentation? A combination of the three?

From a completely uneducated perspective, I think I'm more interested in recreating the feelings invoked by the early Pokémon games than I am any other games. Namely, I would like the player to feel curious, proud, and social. I don't just want the players to compete, I want them to collect - I want them to be proud of the the party members they've earned and share them with their friends. No matter how I go about this, the game's scope will increase dramatically. However, I think it's important for the game to have a sense of progression.

Crafting?

Assuming the game has no "world" for players to explore, how are they supposed to acquire new entities at all? The only idea that I can muster up is some sort of crafting system. The player collects resources from the opponents they defeat, which they can then use to construct a new entity. This type of system could theoretically enable the player to customize the entity's stats (currently each entity only has health points), in addition to the ability it grants. This idea reminds me a lot of Dark Cloud's weapon upgrade system.

This concept has my brain coming up with all sorts of ideas. I don't want to forget anything, so I guess I'll just write them down.

  • Elemental types, each with their own strengths and weaknesses. The word "core" comes to mind for some reason.
  • Entity durability - if you let one of your party members die, then it breaks down into a subset of the resources it took to construct it.
  • Difficulty levels - lower difficulties have slower pacing, but are otherwise identical to the higher difficulties. Rewards for all difficulties are the same, but players are able to collect resources faster if they can defeat higher difficulty encounters (because the speed of the battle is quicker).
  • Possible micro-transactions. I'm not a fan of pay-to-win concepts, but perhaps there's a way to let the player pay for non-advantageous crafting resources. Whether or not I use this idea, I need to keep monetization strategies in mind.

While the concept is interesting, I find myself very skeptical. With a sufficient number of resource types, players will either undergo a long discovery phase in which they learn for themselves what the outcome of each combination is, or they will simply look up the strongest combinations on the internet and spend all of their time working toward those requirements. Even my 8-year-old self knew enough about Pokémon to target specific monsters (either through word-of-mouth or from a strategy guide). The genius of those games was in their pacing, requiring the player to progress through the world, and only exposing them to very limited options within each new area.

How is it possible to achieve good pacing with a crafting system at the game's core? The obvious answer is to limit the player's ability to collect resources, but that's a very vague idea.

I can't tell if I'm just not that excited about the idea, or if I've just been overwhelmed with other responsibilities lately, but I haven't made any progress on this for an entire week, and that worries me. Remember way back in the introduction, when I mentioned that I disliked "content creation"? Well, this is the type of thing that I consider to be in that category - now that the main prototype is playable, it's hard for me to stay focused.

I've been trying for days to come up with alternatives. I think the root of my problem is that the entities in my game lack unique identity - they lack soul. It's hard to imagine a game like Pokémon that doesn't have hundreds of unique monsters. If the game instead involved running around collecting generic "wisps", I don't think it would have had such a massive appeal.

Maybe I just need another spin on the idea. Perhaps the entities aren't "monsters" or "wisps", or anything living at all - maybe they are robots, which the player must build and control! In that context, a crafting system makes perfect sense. Inside of battles, the player simply controls when to use each robot's ability; outside of battles, the player spends their time crafting or upgrading robots. Maybe, just maybe, I can make the robots modular such that their "parts" are recognizable, but the combination of different parts is what makes each player's team unique. Furthermore, I don't even have to make hundreds of parts to make hundreds of possibilities - if each robot consists of 3 "tiers", and there are 5 possible parts for each tier, then that's already 125 possible combinations!

Three Weeks Later...

It's been a few weeks since I wrote anything here, and about a month since I wrote any code. Work has been very busy, but I also took a week off to spend some time with my wife on the beach for our 5 year anniversary. The vacation was much needed, and it was the first time I've taken off of work for anything other than family emergencies in over a year.

I've been pushing myself really hard this year. I desperately want to release a finished product that I can call my own, but I think I've been burning myself out. Taking time to relax has been incredibly refreshing, but I'm a little bummed out that we are back to the daily grind. The coast is beautiful, the open sky is beautiful, my wife is beautiful.

Even during my time away from the game, I expend a lot of mental energy thinking about it. I spent some time sketching some concepts for building robots before deciding that I suck at drawing robots, and switched to drawing towers instead. I played around with designing towers using voxel art using an app called Voxel Max before coming full-circle and deciding that pixel art is probably a more realistic goal for myself. I spent even more time trying to create pixel art for modular towers before deciding that this is all stupid and a waste of time and getting super discouraged.

I eventually started looking around for pre-made assets that might work for our game. I didn't find anything that was exactly on-point, but seeing so many quality assets available was somewhat inspiring. I'm not an artist, but there are a lot of artists out there that might be able to help, when I decide I'm ready to ask for it.

That's one of my problems though: how come I'm not ready to reach out to artists? The short answer is that I don't feel like the game is complete enough to justify that next step yet. However, if I don't feel like the game is at that point, then why am I wasting time trying to create any artwork? Well, simply because it makes the game feel more complete by having it! Therein lies my conundrum. Hypothetically, if I were to reach out to an artist, what would I even ask for?

So here I am, weeks passing by without any progress, once again contemplating the "theme" of my game.

17.1 Exploring the Theme

I don't know why this is so hard for me. People come up with half-baked video game themes all the time - usually with no consideration for the actual mechanics or how "fun" the games might be. I've been a lot of effort into finding the essence of what I consider to be a fun core mechanic, but without a theme assigned to it, it's ultimately just a game about health bars.

One concept that's been floating around my mind is to get rid of the separation between healing and damaging abilities. Each ability could simply be assigned an elemental type, and could either heal or damage a target depending on its type. The type of each ability and unit would need to be made clear enough to the player so that they can plan appropriately.

Theoretically, each unit would be of the same elemental type as the ability that it can use, so it could always heal itself, if needed. However, there could be a case in which the only two remaining opponents are of the same type and cannot damage one another. I keep finding myself coming up with ideas that are likely to result in stalemates.

I sat in front of the screen for hours, and eventually just went to bed. It's morning now, and I'm so annoyed with myself that I'm going to try something somewhat extreme.

First, I'm going to add an energy bar for each player. Each ability will have an energy cost, and energy will not be a renewable resource (at least, not within the context of a single battle). Next, I'm going to make the health of all units drain over time. I can just say that the player is summoning these wisps from some other plane, and they cannot exist outside of their plane forever. The player can mitigate this by casting healing spells using the player's own energy, but once that energy is depleted, the wisps will eventually fizzle into nothingness.

Before I can implement those ideas, I first need to convert my game from using dedicated healing and damaging abilities into something more elemental. I don't want to go into too much detail about the code, but you are free to look at the commit if you're interested in the changes.

Unfortunately, I hate the result. The units' health bars are now colored by their type, as are the ability buttons, so you'd think it would be super intuitive. However, with so many health bars on the screen at the same time, choosing the correct ability on any given target is tedious! First you have to identify which target you want to prioritize, then you need to determine its type by its color, then you need to choose an appropriate ability (also by color). The entire process, on repeat, in real-time, is quite disorienting. The confusion is compounded by the fact that you would choose a different ability color when targeting your own units (since you want to heal them instead of damage them). If I can barely keep it straight in my head, how can I possibly expect any random player to do so? You can see what I mean by playing it here.

17.2 Hard Pivot

I've been feeling completely discouraged.

I made a lot of progress when I got excited about the concept of a PvP battler, but after whipping up a functional prototype, I lost all momentum very quickly. I didn't anticipate the sheer amount of design work involved in fleshing out a battler, including designing unique characters, abilities, and player progression - as well as all the visual aspects of those features.

So this weekend, I'm going to try something completely different, but (hopefully) more achievable: I'm going to build an infinite runner.

I hate the genre, though it is undeniably popular. Obviously I'll be introducing a unique twist in the form of healing mechanics. The idea in my head involves a space ship flying through an asteroid field - colliding with an asteroid damages your ship's "shields", which you must regenerate while continuing to avoid obstacles. The catch is that you only get points by destroying the asteroids, so you must take damage to beat your high score!

Building the Prototype

I'll be honest, I completely neglected to write about the game as I was building it. It only took me a couple of days, and I wasn't particularly focused on it, abiding by my typical work schedule along with other obligations. The game didn't require any intensive changes to the engine, nor did it require much modification of the existing systems. That's not to say it was entirely trivial, so I'll go over some of the snags I ran into along the way.

Abilities with No Target

The Action class's execute() method requires a target Entity parameter. I wanted to add a collectable item that heals all of the player's shields. Instead of doing any major refactoring of the Action interface, I just implemented a MassHeal action that ignores the target completely, and queries the EntityManager for all of the party members. When the player collects the item (shown as a green box for the time-being), I just execute the action with the player Entity.

Missing Health Bars

When testing the game on iOS, the health bars were completely missing, but they worked just fine on macOS and in the web browser.

I spent entirely too much of my Saturday digging into the issue. I verified that the health bars were getting drawn by the ProgressFeatureRenderer using the UI camera, and noticed that they were drawn just fine using the main camera instead. This lead me down a rabbit hold of verifying that the depth buffer wasn't preventing the bars from being shown.

I eventually broke down and opened up the project in Xcode. Xcode allows you to take a "capture" of Metal commands submitted to the GPU while debugging - an incredibly powerful feature that I have been missing out on by opting to use CLion instead. It was in this capture that I discovered that the viewProjectionMatrix being submitted to the uniform buffer contained NaN values ("not-a-number", usually representative of infinity or division-by-zero). After quite a bit more debugging and reverse-engineering, I finally discovered my error.

When I started building the prototype, I decided that I wanted the game to be played in portrait mode rather than the landscape mode that the previous prototype used. However, I wanted to maintain the same "pixel" dimensions of the canvas for the UI: 240 virtual pixels wide, using the aspect ratio of the screen to handle the height. Since our camera requires a height, I calculated the height using the aspect ratio within the scene's constructor.

Apparently, on iOS, the renderer does not learn about the viewport's dimensions (and thus its aspect ratio) until after the engine has been constructed. With the aspect ratio defaulting to zero, I was providing an invalid height to the camera.

I moved the height calculation logic for the UI camera into the CameraSystem. Assuming the system will eventually be reused for another game, it's really not a good place to put that logic, but it works for now!

Text Transparency

I noticed that, when the asteroids fly behind the score counter, the transparent parts of the text were just using the background color instead of allowing the asteroid color to pop through. This only occurred in Alfredo and Scampi, so it was evidently an issue with the Metal implementation of the TextFeatureRenderer.

I resolved it simply by using metal::discard_fragment(); when the alpha channel is zero. That's probably not the "right" way to do it, but it did fix the issue.

Control Schemes

I struggled to settle on a control scheme that I liked. At first I modified the PlayerControllerSystem to keep the player entity in one of three "lanes", similar to Subway Surfers. The new SpawnSystem, responsible for spawning the asteroids in random positions, didn't care about the lanes, so it would spawn things in awkward positions, requiring the player to slide at just the right moment to intercept the entity. It was fun but distracted from the healing-oriented gameplay.

Next I implemented a mechanism which allowed the player to move along the X-axis by dragging their finger from side-to-side. This option was obviously worse, encouraging the player to hold down their finger in order to quickly move around, meanwhile completely ignoring the healing mechanic.

I tried a click-to-move system that simply moved the player toward a selected screen position (by utilizing the nifty glm::unProject() method). It was here that I discovered that the depth buffer was not being cleared properly in the OpenGL implementation of the SelectableFeatureRenderer. Luckily that problem was easy to detect and resolve. Unfortunately that wasn't the only issue present in Pesto: it turns out that the mouse movement detection logic had been broken in the WebInputManager this entire time! I had assumed mouse buttons were 1-indexed, such that mouse button 1 == 1. Alas, that is not the case. Instead, they are 0-indexed, so mouse button 1 == 0, mouse button 2 == 1, etc.

After fixing those issues, the actual click-to-move interface felt clumsy because the asteroids are already moving toward the player. Often you'd tap on an asteroid to move toward its position, just for it to have moved out of the way by the time the player entity reached its destination.

I eventually settled on the lane-based approach, though I updated the SpawnSystem to only spawn asteroids and power-ups within a random lane. It still feels frustrating when you have to move across two lanes in a short amount of time, so I might constrain the spawns to prevent that - but I'll revisit that later.

Swipe Gestures

I added support to the InputManager for detecting swipe gestures from the platform, rather than implementing that detection within the GestureRecognitionSystem.

Obviously the new isSwipeDetected() method just returns false in Alfredo and Pesto, but Scampi is able to return a value using the UISwipeGestureRecognizer, as explained in Apple's documentation.

The PlayerControllerSystem uses A and D key events as well as swipe gestures to control the player, so that it works on both desktop and mobile platforms. User input is still very broken on a mobile web browser, but I don't want to spend any time on that right now.

The Result

The game is simple. The player controls a little space ship that can fly within three lanes. The ship has 5 shields, each with their own health bar, which may be healed by tapping on the health bar. The player's ability to heal is limited by a short "recharging" bar.

Colliding with an asteroid grants you points based on its size, but also damages a random shield. Allowing an asteroid to pass will instead damage the Earth, which cannot be healed.

The longer you stay alive, the faster it gets.

The game ends whenever the player or the Earth dies. Full disclosure: the game actually crashes when you lose.

Power-Ups

Right now there is a single power-up: a green box that heals all of your shields for a moderate amount.

I've had a few ideas for other power-ups:

  • More shields
  • Speed boost
  • Score multipliers
  • Different healing abilities
  • Something that helps with intercepting the asteroids more effectively

Obstacles

I also want to add obstacles that you want to avoid, but I'm not really sure what that might be yet.

The Earth

The role of the Earth in an orbital defense system is obvious: it is the entity which must be protected! However, I realized that the game tends to end with the Earth's destruction much more often than when the player's shields are depleted. When I let some friends try the game, none of them seemed to even realize they needed to protect the Earth until they lost because of it.

I had a silly idea that would re-contextualize the entire concept: rather than saving the earth from a barrage of asteroids, let's just say you're a space-fairing construction worker who gets paid to clear out the asteroid belt to make room for space-houses. Astro Dozer!

In that context, it's no big deal if the player misses an asteroid - they just have to play longer to make up for the lost currency.

Checkpoint

My wife complemented the game's aesthetic, which I found interesting, considering it contains no artwork of any kind (unless you count the font, I suppose). During our vacation, we spent a day at NASA, and I got her a hoodie colored with navy blue, mustard yellow, and light gray - a retro space color scheme. That hoodie was particularly inspiring.

I searched for a space color palette on coolors.co, and the only result was exactly what I was looking for. I built the game with that palette in mind.

Infinite Runner Prototype

You can view all the changes that were required to build the prototype at this commit - it's remarkably simple considering the entire game was changed. You can also play the game here.

17.3 Iteration

I went ahead and knocked out a few things that I mentioned previously. I removed the Earth entirely, and I added red bombs that the player must avoid, or else all of their shields take massive damage. I also modified the SpawnSystem so that the distance between the asteroids is fixed, rather than being based on time. Overall the game feels better with these changes.

I was considering the types of power-ups that I might want to include in the game, and it occurred to me that the player should be able to purchase permanent power-ups, which would assist in the process of asteroid collection, allowing the player to generate more points, which can be used to purchase even more power-ups.

This economic feedback loop is not a novel concept. Cookie Clicker is perhaps the epitome of that gameplay loop - but that doesn't mean we can't go in that direction as well! Let's see what we can whip up.

Scene Transitions

I've come a long way without supporting the concept of multiple scenes. Realistically, I could just keep creating and destroying entities within the current scene, but there's something appealing about throwing all the garbage away and starting fresh.

I created a new SceneManager interface with a single load() method that just takes in a std::unique_ptr<Scene>. The idea is that a system in the current scene can construct a new scene and call several functions on it over a potentially indeterminate amount of time, and only switch to the new scene when it's ready. This should allow for scenes to pass data to one another prior to the destruction of the source scene's EntityManager.

I also created new methods within the ServiceLocator to return a reference to the SceneManager so that it is accessible within the scenes and systems. Rather than implementing the interface in a new class, I decided to just have the Engine class implement it. It's not much more responsibility than it already has, and I can always break it out later.

I updated the LivenessSystem to detect when all the shields were no longer Alive, and instead of crashing, load up the good old TestScene. My first attempt had the Engine just updating its _currentScene variable immediately, but doing so broke all sorts of things, since the old scene was cleaned up before it was even done iterating over all the systems in the current tick(). I resolved that by adding a _pendingScene variable - load() will only update the pending scene, and at the end of the current frame, the current scene will be replaced if necessary.

At this point, the scene transitions technically worked. The entities in the old scene's EntityManager would get destroyed, while the new scene would create its own new EntityManager with its own version of the world. However, I noticed that some visual elements from the previous scene would remain - most noticeably: the stars in the background.

Even though the scenes do not share EntityManagers, they still share the rest of the Engine, and therefore they share the Renderer. The Renderer relies on the scenes to properly create and destroy their visual resources, so something was obviously amiss.

The first thing I noticed was that Cameras are never actually destroyed - so objects in the new scene were getting rendered multiple times, by any camera with the correct layer. I did some cleanup in the Renderer and Camera classes to allow cameras to be destroyed properly, and updated the scenes to clean up their camera resources. Unfortunately, the issue was still occurring.

I eventually realized that the "removal listeners" for entities never actually get called whenever the EntityManager gets destroyed - only when the component is removed from the entity in some other way. Since the entities don't go through a formal destruction process in the ArchetypeEntityManager's destructor, nothing ever invokes the removal listeners. I updated the destructor to just iterate over all entities and call any removal listeners - there is no need to actually migrate the entities all the way up to the root archetype, so I didn't bother with any formal entity destruction logic.

Eventually, I'd like to construct a cleaner way for components to clean up resources like these. For now, I think I'm just happy to finally have some scene management in the engine.

The Shop

Using the new scene transition mechanism, I created a new ShopScene, and updated the LivenessSystem to load the new scene rather than the TestScene. I added a single UI camera, and configured the clear color to be blue. While the scene transition worked just fine, the app was crashing somewhat unexpectedly shortly after the new scene was displayed.

Due to the style of gameplay, I am constantly tapping and swiping on the screen in erratic fashion as my shields are rapidly being depleted. Because of this, I was tapping on where my health bars would have been had the scene not changed. I was surprised to see the crash coming from the GestureRecognitionSystem, but come to find out, the renderer-based entity selection logic was still sampling entity IDs from the old scene. When attempting to get components from an entity with an invalid ID, the game would crash.

Obviously I just need to clear the hidden selection framebuffer when the scene changes. Looking in my SelectionFeatureRenderers, they both contain a block of code in their draw() methods that resemble something like this:

if (camera.getId() == 0) {
  // clear framebuffer
}

This code makes one major (now incorrect) assumption: the first camera in the scene has an ID of 0. Due to the way we've organized the scene transition system, a new scene will be created using newly-allocated cameras, with new unused camera IDs, and the cameras from the old scene will get destroyed once the scene itself gets destroyed.

I decided to add a new onFrameBegin() method to our feature renderers, so that I can do this sort of per-frame/pre-draw logic without relying on "magic" numbers. I updated the Metal and OpenGL renderers to iterate over the feature renderers and call the new methods prior to iterating over the cameras to draw their renderables.

While I'm playing around with the rendering code, I wanted to improve a couple of things. First, I want to be able to define my camera's height or width. I just added a new Measurement enum, containing Height and Width values, to the CameraFixture component. I added a type field to contain the type of measurement that should be used, and I changed the height field to size instead. I then updated the CameraSystem to adjust the camera's projectionMatrix based on the type, and removed the temporary hard-coded solution for the UI camera. With this new solution, I updated the InfiniteRunnerScene's world camera to use a width of 12.0f instead of a height of 20.0f, which allowed Scampi to render more consistently with Alfredo and Pesto, since my phone's aspect ratio is much longer than the others.

The other issue I'd like to fix is that the CircleRenderers depth buffer sometimes prevents background circles (the stars) from rendering over the quad of the foreground circles (the asteroids and bombs), even in the corners that should be fully transparent. There are various solutions to this problem, but I'm just going to sort the renderables within the CircleRenderers by their distance from the camera, and render them back-to-front.

renderers/metal/src/features/CircleFeatureRenderer.cpp snippet

auto cameraDepth = camera.viewMatrix[3][2];

auto filteredRenderables = std::list<Renderable*>();

for (const auto& renderable : getRenderables()) {
  if (renderable.second->getLayer() == camera.layer && renderable.second->isEnabled()) {
    auto feature = renderable.second->getFeature<CircleFeature>();
    auto distance = glm::abs(feature.modelMatrix[3][2] - cameraDepth);

    filteredRenderables.insert(std::lower_bound(filteredRenderables.begin(), filteredRenderables.end(), distance, [cameraDepth](const Renderable* i, float value) {
      auto feature = i->getFeature<CircleFeature>();
      auto distance = glm::abs(feature.modelMatrix[3][2] - cameraDepth);
      return value < distance;
    }), renderable.second);
  }
}

With those issues out of the way, I can focus on building the actual shop. Unfortunately, another week has passed without much progress on the game, though I have been playing it quite a bit. Even without the economic feedback loop, the game is pretty enjoyable to play. Perhaps more importantly, it's given me a vague idea of how many points a player is expected to accrue over time. Assuming the a ship with 5 shields, each with 1000 HP, it's pretty typical to accrue about 150-180 points per run, with each run averaging about 2 minutes. My personal best is somewhere between 500-600 points, which lasted about 5 minutes. While not hugely impactful, the player is able to collect more points as they get deeper into the level due to the increase in speed.

The game somewhat beckons me to add various upgrades from different categories:

  • Defensive upgrades could allow the player to be more careless during their run, ultimately leading to a more "idle" style of gameplay.
  • More shield generators - the player currently starts with 5, but we should probably reduce that to 1.
  • Stronger shields - should each shield be individually upgradable?
  • Shield priority - if each shield can be upgraded individually, it might be cool for the player to have more control over which shield takes the most damage.
  • Speed upgrades would obviously allow the player to accumulate points quicker, allowing them to purchase even more upgrades.
  • Base speed increase - the game currently starts off very slow.
  • Higher acceleration rate - it currently takes 5 minutes for the ship to accelerate from its base speed to its maximum speed. Players comfortable with the maximum speed might want to just cut to the chase.
  • Maximum speed increase - the ship is currently limited to 20 units per second in order to prevent the fixed time step from "missing" any collisions, however I could always reduce the fix time step if the player really wants to go faster.
  • Power-up upgrades could help players who feel that they are unable to keep up with the pace of the game on their own.
  • Types - the AoE heal is really handy, but is just an example of what could be achieved using collectible power-ups.
  • Frequency - power-ups currently spawn every 15 seconds.
  • Variance - the spawn rate is currently pretty rigid, but adding variance could provide for a more random experience, and allowing the player to reduce the variance could allow for a more deterministic outcome.
  • Active abilities could provide the player with more tools to handle certain situations, beyond simple movement.
  • The current single-target heal could become a purchasable upgrade, which can be further upgraded to recover more HP. There would be a point in which healing a shield for more than the maximum HP would be a waste, but then the player could spend points to upgrade the shield's capacity instead.
  • Another obvious candidate is a more controllable AoE heal, rather than relying on the power-up to spawn.
  • Temporary speed boosts.
  • Visual upgrades are technically useless, but could provide an expensive goal for players to work toward.

While the ideas for different types of upgrades flow pretty easily, it's more difficult for me to imagine a UI that can sufficiently describe each of the upgrades so that the player can make an educated decision on which upgrade to purchase. I suppose I just need to whip something up and iterate on it.

My first attempt continues to use the same space-themed color palette, but it inadvertently turned out looking more like the old Visa logo.

Shop Mockup 1

I showed it to a few people without mentioning the resemblance, just to see if they had the same reaction. Interestingly, none of them did. When I brought it up after the fact, they brushed it off, stating the font and shade of blue were sufficiently different as to break any sort of associations they may have made. I find it interesting that I made that association so quickly - but then again, I make all sorts of weird associations that my family finds amusing. I guess we'll just leave it as it is!

UI Layouts

I spent a while thinking about UI layouts, and creating a robust layout system that could properly organize multiple items into a scrollable list. I decided that was stupid, because at this point I should just focus on building out a complete gameplay experience.

Instead, I decided to whip up an UpgradeDatabase, very similar to the existing SpellDatabase. The new Upgrade struct contains a name, description, number of ranks, and cost per rank. I'm not sure if it will stay like that, but I'm just trying to move quickly. A new UpgradeSystem is responsible for creating the UI elements for each Upgrade and laying them out relative to one another. If the list happens to extend beyond the screen, then I'll just have to handle that problem later. For now, the list is designed to display up to 4 upgrade panels on the screen at a time.

I added a new PurchaseButton component and whipped up some logic to change each button's color when the button is Pressed. This type of thing should be moved into a Button component and a ButtonSystem, but I'm still resisting the temptation to build out a more fully-featured UI system. Similarly, I created a new PlayButton component, and added a "GO" button at the bottom of the screen that transitions the game to the InfiniteRunnerScene.

To my surprise, the game was crashing when transitioning to the InfiniteRunnerScene. After a bit of debugging, it turns out that the Renderer's createCamera() method was still relying on the new camera ID being the index of the std::vector containing all the cameras. While this is technically still the case when the game first starts, after a scene transition, the camera IDs are no longer in sync with the indices (since the cameras from the previous scene are destroyed and removed from the vector). Rather than relying on indexing into the vector, I will simply return the last entry, which was recently inserted.

While the scene transitions were working in Alfredo and Scampi, for some reason, transitioning back to the InfiniteRunnerScene in Pesto resulted in game rendering everything as giant blocks in the middle of the screen (as if the viewProjectionMatrix was the identity matrix), and then the game would lock up indefinitely. My suspicion was that the GestureRecognitionSystem of the new scene was attempting to sample the selectable framebuffer, which contained entity IDs from the old scene. I wouldn't expect this to result in a loop or anything, but if I render an empty frame after switching the _currentScene, then everything works fine. Rather than spending more time on it, I'll just leave it like this and carry on.

With the scene transitions working, I need to add a way for the points gathered within the InfiniteRunnerScene to be added to a "wallet", so that the player can spend those points in the ShopScene. I added a new entity to the ShopScene to display the number of points, and created a new WalletSystem that simply updates that text based on the point value contained within a new Wallet component. I updated the ShopScene's constructor to receive the point value that the player earned.

Week After Week

I haven't been working on the game lately. Part of the reason has been due to an increased workload at work, but I've mostly just chosen not to work on the game. I'm not even sure what I've been doing instead - I don't generally have much time to work on it anyway. I suppose I've just been going to bed at a reasonable hour (though not necessarily getting any sleep).

Honestly, even during the infrequent sessions of working on the game, I have been neglecting writing about the process. Most of the previous section was written weeks after-the-fact, and it's possible that I missed some details. However I do not think it matters very much, since the few people that read these posts are more interested in the pictures anyway.

Purchasing Upgrades

I stayed up late wiring up all the code necessary to keep track of points over multiple runs, and allow the player to spend those points in the shop for actual upgrades. The most important thing that enabled this was the addition of a SaveManager, which is an engine-level class that is retrievable via the ServiceProvider interface. The new class keeps track of the player's points via addPoints(amount), removePoints(amount), and getPoints() methods. It also keeps track of the player's current upgrades using increaseRank(upgradeId) and getRank(upgradeId) methods.

Most of the code utilizing the new SaveManager lives in the UpgradeSystem, which dynamically colors the rank progress indicators by finding entities with a new RankIndicator component, dynamically enables/disables purchase buttons based on rank costs using new Enabled and Disabled components, and dynamically updates the cost text on those buttons using a new CostLabel component.

The LivenessSystem used in the InfiniteRunnerScene now adds points through the SaveManager prior to transitioning to the ShopScene, rather than passing the point value of the run through the ShopScene's constructor. The InfiniteRunnerScene's constructor is responsible for setting up the scene based on the current rank of all the upgrades - including the number of shields, the health of each shield, the player's base speed, and the player's acceleration. Lastly, I did a quick tuning pass to the cost of each of the upgrades.

You can view the exact changes here if you're interested.

Unfortunately, the bug still exists in which the game hangs for a while upon transitioning to the InfiniteRunnerScene. I'm not sure what causes it to occur, but it definitely happens on my phone, usually after the third or fourth time I leave the ShopScene.

As I debugged further, I noticed that the logs for Scampi (via the Console app) were complaining about re-using a "drawable", and thus prevented further execution of any Metal commands. My "fix" for Pesto actually broke Scampi. Since Scampi relies on the event-driven platform to "tick" the engine, the Renderer's draw() method was re-using the MTK::View's currentDrawable(), causing Metal to throw a fit.

I still need to prevent the Renderer from sampling invalid entity IDs from the selectable framebuffer, so I'll have to handle this in a different way. During my delve into the rendering code, I also noticed some major GPU memory leaks! Each of our FeatureRenderers contains a a number of MTL::Buffers, created dynamically for each Camera. When a Camera is destroyed, we never actually clean up the MTL::Buffers, so GPU memory keeps growing every time we create new Cameras during every scene transition!

I decided to solve both of these issues using a new virtual reset() method on the Renderer class. I'll define a simple boolean _isFirstFrame to determine whether the selectable framebuffer should be sampled, and simply set it to true on reset() and false at the end of each frame. I'll also use the reset() method to iterate over all the FeatureRenderers and clean up those orphaned MTL::Buffers.

Since the OpenGL implementation doesn't explicitly manage its buffer memory, the reset() method simply sets the _isFirstFrame boolean to true.

With those changes, all the platforms appear to work again. I notice that there's still that single frame at the beginning of each new scene (immediately after the transition) in which the viewProjectionMatrix is still the identity matrix, which doesn't make sense to me. In any case, it's no big deal so I'm just going to ignore it for now.

Slow and Steady

Once again, I've put off doing any work on the game for another week. Perhaps worse, it's been two full months since the last time I pushed any updates to the web site. With the game in a somewhat playable state, I suppose now is as good a time as any to conclude this chapter and move onto the next thing.

The latest source code can be found here, and you can play the game here.

Happy Thanksgiving.

Scene Transitions