👋 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.
- 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
-
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)
| 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.
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 ┌─────────────────────────────────────────────┐ │ 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.
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 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).
- 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.
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.
- 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.
- 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 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.
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);- 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).
// 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.
Levels are stored as prefabs at Assets/_Project/Resources/Levels/ and loaded at runtime via Resources.Load.
- 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.
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 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 listenersAvailable 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 |
- 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.
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 |
- 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");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.
All packages live in Assets/_Project/~ExtensionPackages/ and are designed to work in any Unity project.
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).
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 tableEnhances the Hierarchy panel with color labels, icons, and separators. Configure via the GameBase menu in the Unity Editor.
Opens an editor window (Window → Custom Player Pref) that lets you read, edit, and delete all PlayerPrefs keys without writing code.
Right-click any asset in the Project window → Find References to see every scene, prefab, and asset that references it.
After a build, open Window → Build Report to see a breakdown of asset sizes, textures, audio, and scripts.
Accessible from the Home screen when GameConfig.isTesting = true. Lets you:
- Skip to any level.
- Unlock all skins instantly.
- Clear all save data.
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.
Access via Window → Custom Player Pref to view all saved PlayerPref keys directly in the Editor.
| 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 |
- 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:

