

Platform :
Mobile
Team size :
5 Developers
3 Artists
Time span :
10 days
This game play around creatures named "Allies", they are mysterious beast that hide in the world, you can collect them through your phone camera by scanning QR Code and once it's done you can either fight it or catch it so he can help you later.
One of my first priorities on this project was to establish a clean and understandable project hierarchy, essential when working in a collaborative team environment. A well-structured hierarchy directly improves workflow, readability, and long-term maintainability.
For the Artists, most of whom were discovering Unity for the first time, it was crucial to provide a clear structure. Without a proper framework, they had no reference for organizing their assets, materials, and prefabs. Creating an intuitive and easy-to-use folder hierarchy ensured that they could focus on content creation rather than searching for the right directories.
For the Developers, we were a team of five programmers frequently interacting with each other’s scripts. A consistent and well-organized structure enabled smooth collaboration, reduced merge conflicts, and kept the overall workflow faster, cleaner, and more scalable throughout the project.

I worked on a scene inspired by a Pokédex, designed to display all the creatures encountered by the player across their progression. The objective was to build a flexible and data-driven progression system capable of persisting information across multiple scenes.
All runtime information is centralized within a singleton-based GameData manager, which stores player data, captured creatures, and Pokédex entries. This ensures consistent synchronization between all systems without duplication or desynchronization.
The entire dataset is serialized into JSON files, allowing fast and reliable save/load operations while keeping the structure human-readable and easy to debug. Each creature uses a custom serializable class storing stats, rarity, shiny state, and its origin zone, making the architecture naturally scalable.
The UI relies on a ScrollView-based dynamic layout, which adapts to data size and maintains smooth performance even with large datasets. This approach offers clean architecture, flexibility, and maintainability suitable for progression-tracking systems.
private class SavePokedex
{
public List savePokedex = new List();
}
[System.Serializable]
public class AllieIndividual : Allie
{
public bool isShiny;
public Stats stats;
public int exp;
public AllieIndividual(Allie allie)
{
id = allie.id;
rarity = allie.rarity;
sprite = allie.sprite;
spriteShiny = allie.spriteShiny;
name = allie.name;
canEvolve = allie.canEvolve;
idEvolved = allie.idEvolved;
}
public AllieIndividual() {;}
}
[System.Serializable]
public class PokedexInfo
{
public bool isPokemonCatched;
public bool isShinyObtained;
public int captureLevel;
public float weight;
public float height;
public CaptureManager.ZoneName zoneName;
}
The differents classes used to save the datas
public void SaveInfoToJson()
{
if (!File.Exists(Application.persistentDataPath + "/InfoSaveFile.json"))
{
Directory.CreateDirectory(Path.GetDirectoryName(Application.persistentDataPath + "/InfoSaveFile.json"));
}
Debug.Log("Save");
SaveInfoClass temp = new SaveInfoClass() { infoplayer = GameData.Instance.infoplayer };
string json = JsonUtility.ToJson(temp, true);
Debug.Log(json);
System.IO.File.WriteAllText(Application.persistentDataPath + "/InfoSaveFile.json", json);
}
public void LoadInfoToJson()
{
if (File.Exists(Application.persistentDataPath + "/InfoSaveFile.json"))
{
Debug.Log("Load");
string json = System.IO.File.ReadAllText(Application.persistentDataPath + "/InfoSaveFile.json");
Debug.Log(json);
GameData.Instance.infoplayer = JsonUtility.FromJson(json).infoplayer;
Debug.Log(GameData.Instance.infoplayer);
}
}
public void SavePokedexIntoFile()
{
if (!File.Exists(Application.persistentDataPath + "/PokedexSaveFile.json"))
{
Directory.CreateDirectory(Path.GetDirectoryName(Application.persistentDataPath + "/PokedexSaveFile.json"));
}
SavePokedex temp = new SavePokedex() { savePokedex = GameData.Instance.Pokedex };
string json = JsonUtility.ToJson(temp, true);
Debug.Log(json);
System.IO.File.WriteAllText(Application.persistentDataPath + "/PokedexSaveFile.json", json);
}
public void LoadPokedexFromFile()
{
if (File.Exists(Application.persistentDataPath + "/PokedexSaveFile.json"))
{
string json = System.IO.File.ReadAllText(Application.persistentDataPath + "/PokedexSaveFile.json");
Debug.Log(json);
GameData.Instance.Pokedex = JsonUtility.FromJson(json).savePokedex;
Debug.Log(GameData.Instance.PlayerBox);
}
}
Save functions that serialize data into JSON files
To manage the entire audio layer of the game, I developed a
custom SoundManager in Unity.
It centralizes all music and sound-effect logic, using
AudioMixers to dynamically adjust master,
music, and FX volumes with persistent
PlayerPrefs saving.
The system supports both one-shot sounds
(UI clicks, attacks…) and
looped background tracks, and relies on a
singleton pattern to ensure consistent audio
behavior across all scenes.
By leveraging Unity’s
AudioMixerGroups, I was able to fine-tune
the mix in real time and optimize audio performance, especially for mobile
platforms.
Additionally, I added an editor-only feature
that automatically updates and displays all sound categories in the Inspector
through OnEnable, making audio organization
much more efficient during development.
public enum SoundType
{
BUTTON_PRESSED,
ALLIE_HIT,
ALLIE_ATTACK,
CLICK_BAR,
QTE_CIRCLE,
SHINY_POP,
CATCH,
VICTORY,
BATTLE_MUSIC,
MAIN_MENU_MUSIC,
TOTAL_SOUNDS_TYPE
}
public enum MusicType
{
MAIN,
BATTLE,
}
public class SoundManager : MonoBehaviour
{
public enum SoundInfo
{
MASTER,
SOUNDS,
FX,
TOTAL_SOUNDS_MIXER
}
private class SaveSound
{
public List<float> saveSounds = new List<float>();
}
[SerializeField] private SoundList[] soundList;
private static SoundManager instance;
private AudioSource audioSource;
private AudioSource[] audioSources;
[SerializeField] private AudioSource FXSource;
[SerializeField] private AudioMixerGroup[] audioMixers;
public List<float> volumesPrefs;
private void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject);
DontDestroyOnLoad(gameObject.GetComponent<AudioResource>());
}
else
{
Destroy(gameObject);
}
}
private void Start()
{
audioSource = GetComponent<AudioSource>();
volumesPrefs = new List<float>(audioMixers.Length);
audioSources = new AudioSource[(int)SoundType.TOTAL_SOUNDS_TYPE];
if (PlayerPrefs.HasKey("MasterVolume"))
{
instance.audioMixers[0].audioMixer.SetFloat("MasterVolume", PlayerPrefs.GetFloat("MasterVolume"));
instance.audioMixers[1].audioMixer.SetFloat("MusicVolume", PlayerPrefs.GetFloat("MusicVolume"));
instance.audioMixers[2].audioMixer.SetFloat("FXVolume", PlayerPrefs.GetFloat("FXVolume"));
}
FXSource.volume = 0.2f;
audioSource.volume = 0.2f;
}
public static void PlaySound(SoundType sound, float volume = 0.2f)
{
instance.audioSource.PlayOneShot(instance.soundList[(int)sound].Sounds, volume);
instance.audioSource.clip = null;
}
public static void PlayMusic(SoundType music, float volume = 0.2f)
{
instance.FXSource.clip = instance.soundList[(int)music].Sounds;
instance.FXSource.Play();
}
public static void PauseAllSound()
{
instance.audioSource.Pause();
}
public static void UnPauseAllSound()
{
instance.audioSource.UnPause();
}
public static void PlaySoundSpecificTime(float timeWanted)
{
instance.audioSource.time = timeWanted;
instance.audioSource.Play();
}
public static void UnPauseSoundSpecificTime(float timeWanted)
{
instance.audioSource.time = timeWanted;
instance.audioSource.UnPause();
}
public static void SetSoundVolume(int _i, float _volume)
{
if (_i == (int)SoundInfo.MASTER)
{
instance.audioMixers[_i].audioMixer.SetFloat("MasterVolume", _volume);
PlayerPrefs.SetFloat("MasterVolume", _volume);
}
else if (_i == (int)SoundInfo.SOUNDS)
{
instance.audioMixers[_i].audioMixer.SetFloat("MusicVolume", _volume);
PlayerPrefs.SetFloat("MusicVolume", _volume);
}
else if (_i == (int)SoundInfo.FX)
{
instance.audioMixers[_i].audioMixer.SetFloat("FXVolume", _volume);
PlayerPrefs.SetFloat("FXVolume", _volume);
}
PlayerPrefs.Save();
}
#if UNITY_EDITOR
private void OnEnable()
{
string[] names = Enum.GetNames(typeof(SoundType));
Array.Resize(ref soundList, names.Length);
for (int i = 0; i < soundList.Length; i++)
{
soundList[i].name = names[i];
}
}
#endif
}
[Serializable]
public struct SoundList
{
public string name;
public AudioClip Sounds { get => sounds; }
public AudioMixerGroup mixer;
[SerializeField] private AudioClip sounds;
}
The differents classes used to save the datas


public void SaveInfoToJson()
{
if (!File.Exists(Application.persistentDataPath + "/InfoSaveFile.json"))
{
Directory.CreateDirectory(Path.GetDirectoryName(Application.persistentDataPath + "/InfoSaveFile.json"));
}
Debug.Log("Save");
SaveInfoClass temp = new SaveInfoClass() { infoplayer = GameData.Instance.infoplayer };
string json = JsonUtility.ToJson(temp, true);
Debug.Log(json);
System.IO.File.WriteAllText(Application.persistentDataPath + "/InfoSaveFile.json", json);
}
public void LoadInfoToJson()
{
if (File.Exists(Application.persistentDataPath + "/InfoSaveFile.json"))
{
Debug.Log("Load");
string json = System.IO.File.ReadAllText(Application.persistentDataPath + "/InfoSaveFile.json");
Debug.Log(json);
GameData.Instance.infoplayer = JsonUtility.FromJson(json).infoplayer;
Debug.Log(GameData.Instance.infoplayer);
}
}
public void SavePokedexIntoFile()
{
if (!File.Exists(Application.persistentDataPath + "/PokedexSaveFile.json"))
{
Directory.CreateDirectory(Path.GetDirectoryName(Application.persistentDataPath + "/PokedexSaveFile.json"));
}
SavePokedex temp = new SavePokedex() { savePokedex = GameData.Instance.Pokedex };
string json = JsonUtility.ToJson(temp, true);
Debug.Log(json);
System.IO.File.WriteAllText(Application.persistentDataPath + "/PokedexSaveFile.json", json);
}
public void LoadPokedexFromFile()
{
if (File.Exists(Application.persistentDataPath + "/PokedexSaveFile.json"))
{
string json = System.IO.File.ReadAllText(Application.persistentDataPath + "/PokedexSaveFile.json");
Debug.Log(json);
GameData.Instance.Pokedex = JsonUtility.FromJson(json).savePokedex;
Debug.Log(GameData.Instance.PlayerBox);
}
}
Save functions that serialize data into JSON files
Since this game is the first I did on Unity I learned a lot through it. The fact that it was a mobile game was a great opportunity to do something different than what I was used to.
It helped me on my team-leading skills and how to sometimes , think more about the groups than the individual and how managing problem could be easier with teamworks.