top of page
Portrait 3200px.png

Ariel Albano Soares

Game designer, game programmer & pixel artist
mail.png
twitter.png

    All the games I've worked on were made with Unity, which is where most of my expertise lies. By working with Unity since 2020, I've developed deep familiarity and understanding of the engine.

    As a programmer, I have a Unity project template which is constantly being maintained and expanded. This template contains modules for various functionalities which are relevant to most game projects, so that when starting a new project I just have to duplicate the template, instantly giving me access to a lot of work already done.

    The mindset I have when creating either the modules or some other system to a specific game is, unless it's for a prototype, prioritizing scalability of the project. From my past projects, I have personally felt how having hastily made code without any planning and architecture becomes an obstacle to iteration. So I've taken to carefully creating architecture that makes working with it later as frictionless as possible. It is also important to implement the system in a way that makes it possible to be expanded later on in case additional features are necessary. And on a personal level, the code architecture itself might be my favorite thing in programming, creating a robust system that makes applying it to the game a seamless task feels great.

    I also value data-driven architectures, hard-coding values are a no-go for me. The way I like to approach this is by making ample use of Scriptable Objects, maybe to a fault sometimes hahaha. They are practical, powerful and clean to use. I use them for things such as individual items, weapons, button styles, sound effects, for game settings stuff, I use them as a way to access the save system, I use them to define colors to be used throughout the game, to store localization data, to store tweens and text animations, and so many other uses. And working in this way makes iterating a simple and fast task, while also contributing to have an organized project folder structure. It even helps when searching for assets of a specific type, you just have to search for "t:MyScriptableObject" to see all assets you're looking for.

    Now, that was a lot of self-boasting, so let's actually take a deeper look on the architecture I've been talking about. Next is some of the modules/systems I have in the project template.

   You can also checkout the entire template in here, and the modules here.

How I see myself as a programmer

Save System

    The save system is built around a SaveData class, which should contain all the information relevant to save for the specific game. What is in the class exactly is irelevant to the save system, it is agnostic in regards to that, so when it saves or loads, it only needs to do so for one object.

    Having a SaveData, we now use a Scriptable Object to hold a SaveData, and also perform all the tasks necessary for saving and loading. The system uses binary file format, and also employs backup files in case of file corruption.

 

    You can see the presence of a UIDAssetReferenceResolver, this is something used to make it possible to have Scriptable Objects inside the SaveData, otherwise it wouldn't work. Both the save system itself and the UIDAssetReferenceResolver specifically make use of the Odin Serializer, which is a popular Unity asset.

    Having multiple SaveDataContainers allows us to have multiple save files, as many as we want. To make things easier we use a CurrentSave Scriptable Object that just points to the current in use save file. What this accomplishes is that in game logic, when trying to access the SaveData, is agnostic to which save is currently in use, it just needs to reference the CurrentSave. And in the game menu, when loading a file, it is only necessary to reassign the SaveDataContainer in the CurrentSave, and call the Load() method.

    To add some more quality of life, there is a custom editor which exposes some of the SaveDataContainer methods.

screenshot1.png

    This architecture makes it so that we have a serialized SaveData class which contains all the data in one place, and it is possible to manually edit any field and press the SAVE button to start playing from a custom save, making it fast to test specific scenarios in the game.

    Talking about only the sound effects aspect, the Audio System is, again, based around Scriptable Objects, instead of having GameObjects with an AudioSource component referencing an audio file, we have a ClipData Scriptable Object that references the audio file and some other parameters to define how the sound should be played. What this accomplishes is it allows us to alter the audio file for a specific use in the game, or alter how it should be played, by only having to edit a single asset, instead of having to go through all assets and scene objects that reference the specific audio, which wouldn't be a scalable architecture.

Audio System

    To make use of the ClipData, there is an AudioManager class, which exposes the Play() method, with the addition of a few extra parameters.

    The manager instantiates new audio sources in real time on demand. Having the AudioManager and Parameters class makes it easy to expand the system with new functionalities, since all the logic is processed through the manager, and adding fields to the Parameters class doesn't break any external parts of the codebase, which wouldn't necessarily be the case if we were to add the fields as parameters to the function.

    There is also a ClipDataEditor to make the ClipData more readable, and to give us the powerful tool of previewing the sound without having to enter play mode.

screenshot2.png

Localization System

    This system uses the LocalizedString class, which contains a string for each defined language in the game. It automatically uses the current language set in the game settings, so that other classes don't have to consider which language is currently in use.

    To use the LocalizedString, we can have any object define its own instance of it, and there is also the LocalizedStringData, which holds an instance of a LocalizedString

    Notice that there is an ILocalizer interface in the LocalizedStringData. This interface is simple, it only exposes a method that returns a string. But the real potential of this system, is that, when paired with the StringManager class present in the template, it becomes possible to reference any ILocalizer asset we have, be it a LocalizedStringData or some other class, directly into any text we have.

    If for example we have a WeaponData that implements ILocalizer, and it returns the "Iron Sword" name, we could have a string such as "Quest reward: <loc=Iron Sword Data>", which gets parsed by the StringManager to replace the correct name of the sword in the string. If we were to later have to alter the name of the sword, it is only necessary to change it in the WeaponData of the Iron Sword to alter it in the entire project.

    Here is an example of the system being used in the game Rogamol. The top image is how the string is written, and below is how it looks after getting parsed.

 

    The LocalizedStringData asset called Current is used to create a standard in how current values are shown across the game.

    You can also notice the "{scaling}" in the string. This is not a part of the Localization System, but is rather another functionality provided by the StringReplacer. The "+1%" is also parsed dynamically into the string, so when we alter the value on the item, it automatically reflects on the description, making it unnecessary to update it manually.

screenshot3.png
screenshot4.png
screenshot5.png

    Something worth noting about this system is that it lacks a crucial functionality, the translations for each language are all contained in Unity objects, so the people translating the game would have to do it inside the engine, while looking everywhere in the project to not miss anything. While this is a problem, this brings us back to a previous point, while the system is not feature complete, it is open to being expanded. Since all the translations are contained inside the LocalizedString class, we only have to modify it to, for example, add an integration between this class and a Google Sheets column, thus bridging the gap between programmer and translator.

Finishing thoughts

    I wanted to highlight a few of the systems present in the project template because I believe they speak to the scalable codebase architecture, editor tools, and workflow I bring to the projects I work on. I have not only developed multiple systems as a programmer, but also used them as a designer to make games, so that gives me the point of view from both sides to learn how to improve the systems in both functionality, and usage.

 

    There is also a lot that goes on top of the template when using it on a game, the game-specific code has its own problems and solutions, while making use of the generic template systems.

    If you have any questions about anything I mentioned here, or anything that piqued your interest in the project template, feel free to reach out to me!

bottom of page