Decided to write a couple things I feel can help a game in the long run when it comes to porting it to other platforms. It’s always inevitable we have to change elements in a game when targeting a new console/device we never considered, but some considerations, if done from start can save a lot of time, headaches, porting budget, and equally important, avoiding introduction of new bugs due to code changing drastically. These are based on observations I made on a couple dozen projects that crossed my desk, I don’t expect this to be news for someone who’s been in the field for a while, and might even be “too basic” for many, but hopefully it can help someone, since I’ve seen these many times.
Many of these are not specific to a certain game engine (unless noted), so they can be used anywhere, it’s just a collection of things I’ve encountered while porting games which can help.
Don’t hard-code strings in your code.
This is one of the most well know ones and it’s not specific to games either, any software, but there’s still a lot of games that have it. It’s not hard to happen either, a gamejam or prototype starts, even if just a couple some strings that we leave near a //todo:, but suddenly we’re months in development with strings all over the place. Now you need to add another language and it’s a problem. Write some get_localization(ID) function, read all text from a file as early as possible. Also never write them directly on images, even if it looks prettier, it’s going to be a problem trust me, you don’t want image variations of each language, I can tell you first hand that becomes big problem whenever small text changes are required.
Avoid string concatenation in localizable text as much as possible.
If we need to mix in dynamic content with text, very common but this examples poses an issue:
string label = localize(“Our score is “) + to_string(int_value) + localize(“, which is very good!”);
This is not very flexible when it comes to localization. It assumes 2 different strings being localized, and the phrase has to make sense in all languages in this order, and that’s not always the case, what if a certain language needs the value to be in a different place in the concat order? The person localising won’t have any way to do it.
So let’s look at this example:
string label = localize(“Our score is {0}, which is very good!”, int_value);
Many languages have string utils to allow this, the {0} will be replaced with our variable, which means that the localisation team can put the {0} wherever they want, your code only assumes there’s a {0} somewhere, doesn’t matter where.
Separate Accept/Back button bindings in menus from other gameplay actions.
Let’s say our game uses the A button on a controller to Fire a bullet, it’s also the most appropriate button for the “accept” action on a menu button. We might be tempted to only query for a “fire” action everywhere, but that’s not the best idea. First of all if you later need to change that “fire” to something else you break menus, and more, on some platforms you might need a different button, for example PS4 in Japan the X button might not be “Accept” but O instead, so you’d have to flip your mapping, having them separate prevents breaking the whole code. If you want to read more about the PS4 specific case here’s a good article about the subject https://www.theverge.com/2019/3/9/18255901/ps4-x-o-cross-circle-remap-firmware-6-50-dualshock-4
Leaving unused assets/textures on scenes/projects
This is not necessarily a porting issue on it’s own, but can hurt performance on lower spec devices and it’s something I encountered more on Unity. I get it, it’s still development phase, something gets removed, but we leave it on the scene disabled, that on it’s own might not be a problem, but let’s say the object happens to be an entire older level that we remade better, and wanna keep the old one around “just in case”. It will be imported to the final build, all the meshes, textures etc, the level might be loaded, just not used. A variety of times I’ve seen build sizes reduce a lot just be removing unused things, but this is a more complicated process after the game is done, because they might be hard to spot, my example was an obvious one, but dozens of tiny things won’t, someone looking might just think disabled stuff will be activate later in script somewhere.
Gigantic non power of 2 textures
This is a recurring issue I see, a couple texture asset packs etc, really big textures, works on PC, even uncompressed. Many textures that are non power of two textures, you target another lower spec device and now there’s a problem, most engines will allow NPOT textures on their pipeline, but for example Unity won’t even allow compression, because it will only happen on POT textures, this is because, textures like 256×256, 512×512, 1024×1024, to keep it short and a bit more simplistic, is a form of optimisation for images feed in to the GPU pipeline. Many years ago most devices wouldn’t even allow something like 125×125, you’d need to find the next POT value, 128×128. A game with big textures in weird sizes will use immensely more disk space than if we use POT and some compression.
Particles
They look pretty, it’s easy to get distracted, don’t start by high particle count and go down until it looks good. Start by 1 and go up until it looks good. I’ve seen particle systems with 100 particles look nearly the same as a 5000 one, multiple that with a couple explosions on screen and you got yourself a problem.
Naming controller buttons in text
“Press A to jump”
But on PlayStation it might be “Press X to jump”
Inject a way in your code so that strings where you use buttons are parsed in runtime, and you can query the platform for the button itself. If it’s just a button sprite on a menu, it’s relatively easier, you just need a sprite and flip it accordingly. For written text like a tutorial or a message somewhere there are several ways to accomplish that, and one way might be to put the icons directly on a font.
For example
“Press A to jump” becomes “Press [JUMP_BUTTON] to jump”, and then in code you can search and replace [JUMP_BUTTON] with a glyph in your font that corresponds to the right controller button. That way localization team can write it in any order, and the code simply replaces it. Another advantage of this is if instead of actual glyphs you want to write it, you can also localize it.
Imagine “Move Left Thumbstick to walk”, in another language we might want to change the part “Left Thumbstick” to something else, probably by console certification requirements, so having “Move [Left_Thumbstick] to walk” can help here with the string find replace, you get the idea.
Avoid constant file saving
Disk access even if faster these days is a big bottleneck, that’s why loading something takes, well… time. A few times I saw cases where games would save something every time a certain action happened, and I’m not talking about game progress checkpoints, which is fine, but for example a game wants to save “steps taken” for some stats, and it would auto-save a certain file every time. Needless to say, while it might not be a problem on PC (SSDs etc) even if wrong, but on certain consoles that will not only be a problem, as well as a possible certification failure. So bulk up actions to save, and save them once in a while, the ones that don’t require the player to press “Save”, you can also enforce saving on application quit.
Framerate consideration
If you coded the game to consider 60FPS only, not considering that it might have to be locked at 30FPS due to lower hardware, using what we can call fixed delta (time since last update) this will be a big problem. If your character movement isn’t taking into account different framerate, for example Unity has FixedUpdate() and Update(), you’ll have problems later if you can’t reach the 60FPS for some reason, everything in-game will run much slower as we only get half the updates. And this is much harder to change later in development, most of the game feel & calculations might be tied into those values. This is an article explaining it from a Unity perspective but you’ll get the idea. https://gamedevplanet.com/delta-time-vs-fixed-delta-time-differences-and-purposes/
That’s it for now
I’ll probably update it if I think of something else, there are many other things specific for each engine but I just tried to focus mostly on engine-agnostic stuff. If all these are covered this already helps a lot most of the port work. Let me know if you find errors or have any other suggestions.