Although the game ran fine on PlayStation 4 and modern PCs -- with a smooth 60 FPS framerate throughout and virtually instant loading times-- the PS Vita version needed a lot of love in both of those areas.
It was a surprise to me that this was an issue, because the game is a fairly simple 2D game on a technical level, and because the previous engines the game was being built in ran it fine: C#/PlayStation Mobile initially, and then the C++-based PhyreEngine.
In the end, optimizing the game for the Vita alone ended up taking a fair chunk of the game's overall development time. While the game in its final state still has a few hiccups, it's at least very playable, when initially the load times and framerate would have been unbearable.
Prior to optimization, levels took a long time to load. Larger levels took 20+ seconds to load, and each time your character died, you'd have to wait the entire time again.
My first solution to this was to implement a "partial reload" system.
The way this works is that when a level is restarted, only the game objects are reloaded. The level's geometry, i.e., all of the tileset tiles, are kept from the last time the level loaded. This was a somewhat simple solution that easily cut the restart time in over half.
But there was still the initial load time issue. Nobody wants to wait 20 seconds for a level to load, even if the restart takes less than half that.
After a few experiments to find out what was causing levels to load so slowly, I discovered that a major bottleneck was just the amount of time it took the system to read the text files that the levels were stored in.
These levels were created with a map editor called Tiled, and stored in the game as JSON files. By default, Tiled exports a lot of data about the objects and tiles in each level -- most of which Emerald Shores doesn't use. For example, the "rotation" property is completely unused, because none of the enemies/coins/items in Emerald Shores have a default rotation that can vary between instances. A quick experiment in manually removing all of those unused properties from a level concluded that it would decrease loading times by a noticeable amount.
My initial attempt to add this into my workflow was to write a custom exporter for Tiled, similar to the JSON exporter, but which would neglect to export the unused data. I ran into a few technical hurdles there when attempting to do this while keeping Tiled itself updated to its latest version, so I came up with another solution instead.
However, there was still room for even more improvement.
Tiled exports data for every tile in the level -- even when a tile was empty. In other words, if a level was 30 tiles wide and 40 tiles high, there were 1,200 entries for tile data in the level file, even if the top half of the level is blank and only ground tiles exist. That made the level file significantly bigger, and, in turn, made it take longer for the game to load the level data from the file.
Every tile exists in the file in order, so the game knows that the first tile will be the top-left-most corner, and then the next tile will be immediately to the right of that, etc.
My first experiment here was to remove all empty tiles from the level data, and then add "x" and "y" coordinates for the tiles that did exist. This actually ended up making most level files larger, so I scrapped the idea. Oh well, it was worth a try. But the problem still needed a solution.
It occurred to me that, in most levels, most of the empty tiles were in the top-left corner of level. What if I could get rid of those, and tell the level-loader that the first tile it sees in the level file isn't actually at position 0,0 (the top-left corner), but at something like 300,20 -- or whatever the first non-empty tile in that particular level ends up being.
So, that was what I did. I added onto my level-compression program a feature to remove all of the first tiles it comes across, until it finds one that's not empty. And then it would make a note of the coordinates of the non-empty tile, so that the level loader knew the coordinates of that first tile, since it could no longer assume that the first tile would be at position 0,0.
After I finished updating the level-loading code to be able to handle this newly-modified level format, loading times were reduced even further. At this point, most levels took fewer than 5 seconds to initially load, with the longest I timed being about 8 seconds. Much better than the 20-30 seconds. And with the "partial reload" system still in place on top of that, it only takes a couple of seconds to restart a level once you're already in it. Phew. Countless hours paid off.
It was mostly the bomb particle effect that caused issues.
The solution here was pretty simple: I removed the explosion particle effect, and replaced it with a "Bomb!" action-comic-like image reminiscent of the bombs in Super Mario Bros. 2. If you're playing the game on PS Vita or PlayStation TV, you'll see that image when a bomb explodes; otherwise, you'll see the nicer-looking explosion particle effects.
Large backgrounds seemed to take a toll on the framerate for whatever reason. I speculated that it was due to the game swapping textures in and out of memory, but GameMaker's debugging tools didn't help me much to confirm or deny that, and experimenting with the texture swap settings didn't help, either.
So I was going to need to just simplify the background images. Through trial-and-error, I discovered that if background images took up more than about half the screen space, the framerate would usually drop. In the first level of the game, this meant having clouds and mountains would cause a framerate drop, so I disabled the mountains. It's probably not something you'd notice unless you're actively looking for it, so it wasn't a huge deal; the mountains just add a little more immersion to the scene, but they aren't critical.
In some cases, such as level 1-2: Forest of Secrets, I was able to keep the backgrounds by simplifying them.
On the PS4 and PC versions of the game, the forest background is made up of three parallax images. In the PS Vita version, I've combined these into one smaller image that takes up half of the screen instead of the full screen. The parallax effect is gone, but otherwise the scene doesn't look terribly different.
Optimizing a game for older platforms is tough work, even for games that don't appear to be technically demanding. All of the optimization efforts I put into the game were for the Vita specifically; the game always ran smoothly on other platforms. But the Vita launch was always a priority, having started as a PlayStation Mobile project years ago, so I was determined to make sure it was playable.
My current project, Emerald Shores, was heavily inspired by platformers from the Super Nintendo and Sega Genesis era.
And something that I’ve always enjoyed about those platformers is the incredible breadth of variety many of them implemented, frequently changing up the gameplay mechanics throughout the course of a game.
While running-and-jumping has always been the cornerstone of a classic platformer, most of the memorable ones from that era had a tendency to take an occasional detour from the regular mechanics and present the player with a level that could very well have been its own simple game.
Disney’s platformers are some of the best examples of this. Take Aladdin - as soon as you start getting bored of jumping around and throwing apples, the game presents you with a magic carpet-flying level.
The Lion King is great as well. Not only does your character transform halfway through, changing the core gameplay mechanics for the rest of the game’s duration, but it also has various minigames, like Timon and Pumba collecting bugs, and another one of these “detour” levels where the core mechanics are abandoned for a few minutes.
Toy Story did the same, replacing one of its traditional platforming levels with a racecar-driving level.
Keeping the gameplay interesting by switching up the mechanics here and there provides a breath of fresh air that’s needed sometimes.
Most AAA games today seem to apply this strategy as well - not just platformers. Open-world games like Grand Theft Auto have always capitalized on having a wide variety of side content to explore when the “main” content starts to get repetitive, but even action games like Spider-Man now offer players a wide variety of side missions and minigames to take on whenever the main story starts to feel repetitive.
That’s something I’ve tried to achieve with Emerald Shores: keeping things interesting by mixing things up after every few “regular” levels, and providing a variety of side content for players to tackle whenever they need a break.
The game has a couple of minigames thrown in as mandatory levels, it has a couple of optional, hidden minigames that offer valuable rewards, it has a huge, optional, ultra-powerful boss, and there’s even a key-collecting sidequest that unlocks a large castle with its own valuable reward hidden inside.