Mobile Game Base
March 17, 2026 ยท View on GitHub
๐ Hi there, I'm HoangVanThu. This repository helps you build mobile games quickly.
- This is a sample gamebase for mobile games using the singleton design pattern. It lets you get up to speed quickly and ship a complete game fast. Custom packages provide powerful inspector, tween, and debug tools that accelerate development.
- Note: The singleton design pattern is generally considered an anti-pattern for large projects. Use this template for small-to-mid-sized mobile games.
Table of Contents
- Features
- Installation
- Folder Structure
- Architecture Overview
- Data System
- Popup / UI System
- Sound System
- Level System
- Observer / Event System
- Shop & Item System
- Daily Reward System
- Extension Packages
- Debug Tools
- Third Party
- Support
Features
-
Resources Systemโ flexible UI with smooth animations -
Gameplayโ drag-and-drop puzzle with win/lose detection -
Settingโ music, sound FX, and vibration toggles -
Shopโ buy/unlock skins with in-game currency -
Daily Rewardโ calendar-based daily reward system -
Lucky Spinโ daily spin feature -
Debugโ quick debug popup for level/skin shortcuts -
Data Storageโ encrypted JSON save system -
Custom Inspectorโ configure which variables show in the Inspector -
Custom Hierarchyโ richer hierarchy panel -
Find Referenceโ find all references to any asset -
Build Reportโ build statistics report -
Custom Tweenโ lightweight tweening library (alternative to DOTween) -
PlayerPref Editorโ view/edit PlayerPrefs in the Editor -
Debug Consoleโ advanced in-game debugging console -
Level Editorโ design levels with a custom editor tool -
Laputa Thirdpartyโ Firebase, Ads integration (planned) -
Localizationโ multi-language support (planned) -
Rankโ online leaderboard (planned) -
Push Notificationโ mobile notifications (planned)
Installation
| Requirement | Version |
|---|---|
| Unity | 6000.2.10f1 |
| Platform | Android or iOS |
- Clone or download this repository.
- Open the project in Unity 6000.2.10f1 (or a compatible version).
- Switch the build platform to Android or iOS via File โ Build Settings.
- Open
Assets/_Project/Scenes/LoadingScene.unityand press Play.
Folder Structure
Assets/
โโโ _Project/
โ โโโ Animations/ # Animation clips
โ โโโ Audio/ # Music and SFX files
โ โโโ Config/ # All ScriptableObject assets (.asset)
โ โ โโโ GameConfig.asset
โ โ โโโ SoundConfig.asset
โ โ โโโ PopupConfig.asset
โ โ โโโ LevelConfig.asset
โ โ โโโ DailyRewardConfig.asset
โ โ โโโ VibrationConfig.asset
โ โ โโโ ItemConfig.asset
โ โ โโโ InternetConfig.asset
โ โ โโโ VisualEffectConfig.asset
โ โโโ Fonts/
โ โโโ Materials/
โ โโโ Models/
โ โโโ Prefabs/
โ โ โโโ Controller/ # Singleton manager prefabs
โ โโโ Resources/
โ โ โโโ Levels/ # Level prefabs (Level 1.prefab โฆ Level N.prefab)
โ โโโ Scenes/
โ โ โโโ LoadingScene.unity # Entry point
โ โ โโโ GameplayScene.unity # Main game loop
โ โโโ Scripts/
โ โ โโโ Common/ # Shared animation helpers (GoMove, GoBounce, etc.)
โ โ โโโ Gameplay/
โ โ โ โโโ Level/ # Level.cs, Pill.cs, Hole.cs
โ โ โโโ System/
โ โ โโโ GameManager.cs
โ โ โโโ Config/ # ScriptableObject class definitions
โ โ โโโ Controller/ # All singleton controllers (12 total)
โ โ โโโ Data/ # Player data + encryption
โ โ โโโ Observer/ # Event system (partial classes)
โ โ โโโ Pattern/ # Singleton / SingletonDontDestroy base classes
โ โ โโโ UI/ # All popup scripts + PopupCreator tool
โ โ โโโ Components/ # Popup base class, UIEffect
โ โ โโโ GUI/ # CustomButton, CustomSwitchButton, BackgroundScroller
โ โ โโโ Resources/ # Resource loading helpers
โ โ โโโ Vibration/ # Haptic feedback
โ โ โโโ Common/ # Utility, SafeArea, CanvasScaleHandler
โ โโโ Textures/ & Sprites/
โ โโโ ~ExtensionPackages/ # Reusable custom editor packages
โ โ โโโ CustomInspector/
โ โ โโโ CustomTween/
โ โ โโโ CustomHierarchy/
โ โ โโโ CustomFindReference/
โ โ โโโ CustomBuildReport/
โ โ โโโ CustomPlayerPref/
โ โโโ ~LevelEditor/ # Level design editor (project-specific)
โโโ Plugins/ # Android / iOS native plugins
โโโ Spine/ # Spine animation runtime
โโโ ThirdParty/ # TextMesh Pro, Lean packages
Architecture Overview
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ UI Layer (PopupController) โ
โ PopupHome, PopupShop, PopupSetting โฆ โ
โ Registry pattern โ Show<T>() / Hide<T>() โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโดโโโโโโโโโโโโ
โผ โผ
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโ
โ GameManager โ โ Observer (Events) โ
โ (GameState) โโโโโโโบโ static Actions โ
โโโโโโโโฌโโโโโโโโ โโโโโโโโโโโโฌโโโโโโโโโโ
โ โ
โโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโค
โผ โผ
Controllers (12) PlayerData
LevelController (partial classes)
SoundController Data.SaveData()
ItemController Data.LoadData()
PlayerDataController EncryptionHelper
โฆ
Scenes:
| Scene | Role |
|---|---|
LoadingScene | Bootstraps all singleton controllers, loads player data, then loads GameplayScene |
GameplayScene | Main game loop; levels are spawned dynamically at runtime |
All controllers inherit from SingletonDontDestroy<T> and survive scene transitions.
Data System
ScriptableObject Configs
All game configuration lives in Assets/_Project/Config/ as ScriptableObject assets. They are never modified at runtime โ only read.
| Asset | Class | Purpose |
|---|---|---|
GameConfig.asset | GameConfig | isTesting flag to enable debug tools |
SoundConfig.asset | SoundConfig | Maps SoundName enum values to AudioClip lists |
PopupConfig.asset | PopupConfig | Holds prefab references for every popup |
LevelConfig.asset | LevelConfig | maxLevel, levelLoopType, startLoopLevel |
DailyRewardConfig.asset | DailyRewardConfig | Per-day reward definitions |
VibrationConfig.asset | VibrationConfig | Delay time between haptic pulses |
ItemConfig.asset | ItemConfig | All shop items / skins |
Creating a new ScriptableObject config:
- Create a C# class that inherits from
ScriptableObjectwith a[CreateAssetMenu]attribute:
[CreateAssetMenu(fileName = "MyConfig", menuName = "ScriptableObject/MyConfig")]
public class MyConfig : ScriptableObject
{
public int someValue;
public string someText;
}
- In the Unity Editor, right-click inside
Assets/_Project/Config/โ Create โ ScriptableObject โ MyConfig. - Assign the asset as a serialized field on the controller or MonoBehaviour that needs it.
Player Data
Player data is stored as an encrypted JSON file at:
Application.persistentDataPath/player_data.json
The data class is split into partial files for clarity:
| File | Contents |
|---|---|
PlayerData.cs | IsFirstPlaying, CurrentLevelIndex, CurrentEnergy, CurrentGold, CurrentDiamond, SavingReward |
PlayerData.Setting.cs | MusicVolume, SoundVolume, VibrationState |
PlayerData.Shop.cs | CurrentSkin, OwnedSkins |
PlayerData.DailyReward.cs | CurrentDailyReward, LastDailyRewardClaimed |
Accessing player data from anywhere:
// Read
int gold = Data.PlayerData.CurrentGold;
// Write (automatically fires Observer events)
Data.PlayerData.CurrentGold += 100;
// Save manually (also auto-saves on app pause/quit)
Data.SaveData();
// Load (called automatically at startup)
Data.LoadData();
// Delete save file
Data.ClearData();
Property setters on PlayerData automatically invoke the relevant Observer events, so listeners update immediately (e.g., HUD counters refresh when gold changes).
How to Add a New Data Field
- Open (or create) the appropriate partial file in
Assets/_Project/Scripts/System/Data/. - Add a
[SerializeField]backing field and a public property. Fire anObserverevent in the setter when other systems need to react:
// PlayerData.cs (or a new partial file)
public partial class PlayerData
{
[SerializeField] private int currentStars;
public int CurrentStars
{
get => currentStars;
set
{
Observer.StarsChanged?.Invoke(value - currentStars);
currentStars = value;
}
}
}
- Declare the new event in
Observer.cs(see Observer / Event System). - The field is saved/loaded automatically because
Data.SaveData()serializes the wholePlayerDataobject.
Popup / UI System
All popups inherit from the Popup base class and are managed by PopupController (a singleton).
PopupController holds a Dictionary<Type, Popup> so every popup can be retrieved, shown, or hidden by its C# type.
How to Create a New Popup
Option A โ Using the built-in PopupCreator tool (recommended)
- In the Hierarchy, select the
PopupCreatorGameObject (found inGameplayScene). - In the Inspector, fill in:
- Popup Prefab โ the template prefab to copy from (e.g., an existing popup).
- Popup Saving Directory โ where the new prefab will be saved (e.g.,
Assets/_Project/Prefabs/UI/). - Script Saving Directory โ where the new C# script will be saved (e.g.,
Assets/_Project/Scripts/System/UI/).
- Click the Create New Popup button. A dialog will appear asking for the popup name (e.g.,
PopupAchievement). - Click OK. The tool will:
- Generate a new C# script (
PopupAchievement.cs) that inherits fromPopup. - Duplicate the template prefab and attach the new script.
- Save both files at the configured paths.
- Generate a new C# script (
- Register the new popup โ open
Assets/_Project/Config/PopupConfig.assetin the Inspector and add the new prefab to thePopupslist.
Option B โ Manually
- Create a new C# script that inherits from
Popup:
public class PopupAchievement : Popup
{
// Override lifecycle hooks as needed
protected override void BeforeShow()
{
// Called before the popup becomes visible
}
protected override void AfterShown()
{
// Called after the show animation completes
}
protected override void BeforeHide()
{
base.BeforeHide();
// Called before the hide animation starts
}
protected override void AfterHidden()
{
base.AfterHidden();
// Called after the popup is fully hidden
}
}
- Create a prefab in
Assets/_Project/Prefabs/UI/and attach the script to the root GameObject.
The root must also have a Canvas component and a CanvasGroup component. - Add the prefab to PopupConfig โ
Popupslist.
Show / Hide a Popup from Code
// Show with no animation
PopupController.Instance.Show<PopupAchievement>();
// Show with scale + fade animation
PopupController.Instance.Show<PopupAchievement>(PopupAnimation.ScaleFade);
// Hide
PopupController.Instance.Hide<PopupAchievement>();
PopupController.Instance.Hide<PopupAchievement>(PopupAnimation.FadeOnly);
// Hide all open popups at once
PopupController.Instance.HideAll();
// Get a reference to a popup instance
if (PopupController.Instance.Get<PopupAchievement>() is PopupAchievement popup)
{
popup.SetData(someData);
}
Available animations (PopupAnimation enum):
| Value | Effect |
|---|---|
None | Instant show/hide |
ScaleFade | Scale from small โ normal + fade in |
ScaleFade2 | Scale from large โ normal + fade in |
FadeOnly | Fade in/out only |
Animation parameters (duration, easing, scale range) are configurable per popup in the Inspector on the Popup component.
Popup Lifecycle Hooks
Override these virtual methods in your popup subclass to react at each stage:
OnInstantiate() โ called once when the popup is first created
BeforeShow() โ called every time before the popup appears
AfterShown() โ called after the show animation finishes
BeforeHide() โ called before the hide animation starts
AfterHidden() โ called after the popup is fully hidden / deactivated
You can also assign one-time callbacks without subclassing:
var popup = PopupController.Instance.Get<PopupWin>() as PopupWin;
popup.AfterHiddenAction = () => Debug.Log("Win popup closed!");
popup.Show(PopupAnimation.ScaleFade);
Sound System
How to Add a New Sound
- Add a new entry to the
SoundNameenum inSoundConfig.cs:
public enum SoundName
{
HomeBackgroundMusic,
InGameBackgroundMusic,
ClickButton,
PurchaseCompleted,
LevelComplete, // โ new entry
}
- Open
Assets/_Project/Config/SoundConfig.assetin the Inspector. - Click the Update Sound Data button โ this auto-adds a new row for
LevelComplete. - Expand the new row and drag your
AudioClip(s)into the Clips list.
Multiple clips are picked randomly each time the sound plays. - Set Delay Time if you want to debounce rapid repeated plays (e.g.,
0.1seconds).
Playing Sounds from Code
// Play a one-shot sound effect
SoundController.Instance.PlayFX(SoundName.LevelComplete);
// Play/switch background music
SoundController.Instance.PlayBackground(SoundName.HomeBackgroundMusic);
Volumes are driven by Data.PlayerData.MusicVolume and Data.PlayerData.SoundVolume (both 0โ1).
Setting those properties automatically fires Observer.MusicChanged / Observer.SoundChanged, which SoundController listens to and applies immediately.
Level System
Levels are stored as prefabs at Assets/_Project/Resources/Levels/ and loaded at runtime via Resources.Load.
How to Add a New Level
- Create your level GameObject in the scene, add a
Levelscript to the root. - Name the prefab exactly
Level {N}where N is the level number (e.g.,Level 11.prefab). - Save it to
Assets/_Project/Resources/Levels/. - Open
Assets/_Project/Config/LevelConfig.assetand update Max Level to include the new level.
Level Loop Configuration
Once the player finishes all designed levels, the system loops based on LevelConfig:
LevelLoopType | Behavior |
|---|---|
Recycle | Replays levels from startLoopLevel โ maxLevel in order |
Random | Picks a random level between 1 and maxLevel |
Observer / Event System
Observer is a static partial class containing C# Action delegates. It acts as a lightweight pub/sub event bus โ any script can publish or subscribe without holding a direct reference.
Subscribing to an event:
void OnEnable()
{
Observer.GoldChanged += OnGoldChanged;
Observer.WinLevel += OnWinLevel;
}
void OnDisable()
{
Observer.GoldChanged -= OnGoldChanged;
Observer.WinLevel -= OnWinLevel;
}
void OnGoldChanged(int delta) { /* update HUD */ }
void OnWinLevel(Level level) { /* show confetti */ }
Publishing an event:
Observer.GoldChanged?.Invoke(50); // notify all listeners
Available events (selected):
| Event | Signature | When fired |
|---|---|---|
StartLevel | Action<Level> | Level starts |
WinLevel | Action<Level> | Player wins |
LoseLevel | Action<Level> | Player loses |
ReplayLevel | Action<Level> | Level replayed |
SkipLevel | Action<Level> | Level skipped |
GoldChanged | Action<int> | Gold amount changes |
DiamondChanged | Action<int> | Diamond amount changes |
EnergyChanged | Action<int> | Energy amount changes |
MusicChanged | Action | Music volume changes |
SoundChanged | Action | SFX volume changes |
VibrationChanged | Action | Vibration toggle changes |
EquipPlayerSkin | Action<string> | Skin equipped |
Notify | Action<string, Vector3> | Floating text notification |
How to Add a New Event
- Open (or create) an appropriate partial file inside
Assets/_Project/Scripts/System/Observer/(e.g.,Observer.Gameplay.cs). - Declare the event:
public static partial class Observer
{
public static Action<int> StarsChanged;
}
- Fire it from the property setter or game logic:
Observer.StarsChanged?.Invoke(delta);
- Subscribe/unsubscribe in any MonoBehaviour that needs to react.
Shop & Item System
Items (skins, weapon skins, etc.) are defined in Assets/_Project/Config/ItemConfig.asset.
Each ItemData entry has:
| Field | Type | Description |
|---|---|---|
identity | string | Unique ID (e.g., "Skin_01") |
itemType | ItemType | PlayerSkin or WeaponSkin |
buyType | BuyType | How the item is obtained |
skinPrefab | GameObject | The 3-D / 2-D skin prefab |
shopIcon | Sprite | Thumbnail shown in the shop |
price | int | Cost in gold (shown only when buyType == Money) |
BuyType values:
| Value | Meaning |
|---|---|
Default | Free โ unlocked automatically at startup |
Money | Purchase with gold |
DailyReward | Obtained via the daily reward calendar |
WatchAds | Obtained by watching a rewarded ad |
Event | Obtained during limited-time events |
How to Add a New Skin / Item
- Open
Assets/_Project/Config/ItemConfig.asset. - Click + on the
Item Datalist and fill in the fields described above. - Set a unique
identitystring โ this is the key stored inData.PlayerData.OwnedSkins. - If
buyTypeisDefault, the skin will be unlocked automatically on first launch viaItemConfig.UnlockDefaultSkins().
Checking / granting ownership from code:
// Check if the player owns a skin
bool owns = Data.PlayerData.IsOwnedSkin("Skin_01");
// Grant a skin
Data.PlayerData.OwnedSkins.Add("Skin_01");
// Equip a skin
Data.PlayerData.CurrentSkin = "Skin_01";
Observer.EquipPlayerSkin?.Invoke("Skin_01");
Daily Reward System
Configured in Assets/_Project/Config/DailyRewardConfig.asset.
dailyRewardDataโ the fixed calendar (e.g., days 1โ7).loopDailyRewardDataโ rewards that cycle after the fixed list ends.
Each DailyRewardData entry:
| Field | Description |
|---|---|
dailyRewardType | Money or Skin |
icon | Display sprite |
value | Gold amount (Money type only) |
skinID | Skin identity string (Skin type only) |
The DailyRewardController retrieves the correct reward using:
DailyRewardData reward = DailyRewardController.Instance.GetDailyRewardData(dayIndex);
Claim logic lives in PopupDailyReward.cs and updates Data.PlayerData.CurrentDailyReward and Data.PlayerData.LastDailyRewardClaimed.
Extension Packages
All packages live in Assets/_Project/~ExtensionPackages/ and are designed to work in any Unity project.
CustomTween
A lightweight tweening library โ use instead of DOTween for smaller build sizes.
// Scale an object
Tween.Scale(transform, Vector3.zero, Vector3.one, duration: 0.5f, Ease.OutBack);
// Fade a CanvasGroup
Tween.Alpha(canvasGroup, from: 0f, to: 1f, duration: 0.3f);
// Chain animations in a Sequence
Sequence.Create()
.ChainDelay(1f)
.Chain(Tween.Scale(transform, Vector3.zero, Vector3.one, 0.5f, Ease.OutBack))
.ChainCallback(() => Debug.Log("Done!"))
.OnComplete(() => gameObject.SetActive(false));
All tweens support useUnscaledTime: true so they work correctly when the game is paused (Time.timeScale = 0).
CustomInspector
Provides attribute-based Inspector customization:
[ReadOnly] public int score; // Shows field but prevents editing
[ShowIf("isEnabled", true)] public float speed; // Conditional visibility
[Button]
public void ResetScore() { score = 0; } // Adds a clickable button in the Inspector
[TableList] public List<ItemData> items; // Renders a list as a compact table
CustomHierarchy
Enhances the Hierarchy panel with color labels, icons, and separators. Configure via the GameBase menu in the Unity Editor.
CustomPlayerPref
Opens an editor window (Window โ Custom Player Pref) that lets you read, edit, and delete all PlayerPrefs keys without writing code.
CustomFindReference
Right-click any asset in the Project window โ Find References to see every scene, prefab, and asset that references it.
CustomBuildReport
After a build, open Window โ Build Report to see a breakdown of asset sizes, textures, audio, and scripts.
Debug Tools
PopupDebug (in-game)
Accessible from the Home screen when GameConfig.isTesting = true. Lets you:
- Skip to any level.
- Unlock all skins instantly.
- Clear all save data.
PopupDebugConsole
A floating, draggable console overlay (enabled when isTesting = true) with:
- Real-time FPS counter.
- Unity log output (info / warning / error).
- Custom command panel โ register commands via
Observer.DebugConsoleevents. - Profiler panel for memory stats.
CustomPlayerPref Editor
Access via Window โ Custom Player Pref to view all saved PlayerPref keys directly in the Editor.
Third Party
| Library | Purpose |
|---|---|
| LeanTouch | Multi-touch input and drag-and-drop |
| LeanPool | Object pooling for UI notifications and VFX |
| TextMesh Pro | High-quality text rendering |
| Spine | Skeletal animation runtime |
Support
- If you like this project, please give it a โญ on GitHub!
- I would greatly appreciate it if you could support me with a cup of coffee: