TapBrain Template

Chương 3: Kiến trúc tổng quan

🏛️ Triết lý thiết kế của TapBrain Template

Kiến trúc template được thiết kế xoay quanh một mục tiêu cốt lõi: Độ độc lập tuyệt đối giữa hạ tầng (Core) và sản phẩm (Game). Khi bạn làm game mới, bạn chỉ viết code vào thư mục Game/ và cấu hình CoreConfig.asset, giữ cho tầng Core/ nguyên vẹn như một package thư viện.

1. Hai tầng kiến trúc: Core Layer vs Game Layer

Hệ thống được chia thành 2 phân tầng tách biệt với ranh giới rõ ràng:

flowchart TD
    subgraph GameLayer["TẦNG GAME (Assembly-CSharp) - Namespace: TapBrain.Game"]
        direction TB
        GEntry["GameEntry (Composition Root phía Game)"]
        GApp["GameApp Facade (Wallet, Progress)"]
        GMeta["Meta: GameWallet · LevelProgress"]
        GPlay["Gameplay: GameplayController · LevelSession"]
        GUI["UI: HomeScreen · MainScreen · ResultDialog"]
    end

    subgraph FacadeLayer["GIAO DIỆN TRUY CẬP (Stateless Facades)"]
        direction LR
        AppF["App.Ads · App.Iap · App.Analytics · App.Dialogs · App.Scenes..."]
        SLoc["ServiceLocator (Register / Get / TryGet)"]
    end

    subgraph CoreLayer["TẦNG CORE (TapBrain.Core.asmdef) - Namespace: TapBrain.Core"]
        direction TB
        Boot["Bootstrapper (BeforeSceneLoad) + InitPipeline"]
        Services["Services: Ads · IAP · Analytics · Firebase · RTDB · BI · Platform"]
        Infra["Infra: SceneLoader · DialogService · UIOverlay · Audio · Storage"]
    end

    subgraph SDKLayer["SDKs & HỆ ĐIỀU HÀNH BÊN THỨ BA"]
        direction LR
        SDKs["AppLovin MAX · Unity IAP 5 · Firebase · GameAnalytics · AppMetrica · iOS/Android"]
    end

    GameLayer -->|"chỉ gọi qua"| AppF
    AppF --> SLoc
    SLoc --> CoreLayer
    CoreLayer --> SDKLayer

    style CoreLayer fill:#1e293b,stroke:#3b82f6,stroke-width:2px,color:#fff
    style GameLayer fill:#0f172a,stroke:#10b981,stroke-width:2px,color:#fff
    style FacadeLayer fill:#334155,stroke:#f59e0b,stroke-width:1px,color:#fff
    style SDKLayer fill:#1e1e2e,stroke:#94a3b8,stroke-width:1px,color:#fff
          

Nguyên tắc vàng: R1 & R2

  • Quy tắc R1: Toàn bộ code gameplay, logic game, coin, level, inventory và UI của game chỉ được nằm trong thư mục Assets/_Project/Game/. Không bao giờ đưa class game vào Core/.
  • Quy tắc R2: Core không bao giờ được biết Game. Ranh giới này được đảm bảo bằng file TapBrain.Core.asmdef. Vì assembly của Core không tham chiếu tới Assembly-CSharp, nếu bạn vô tình gõ bất kỳ class nào của game trong Core, compiler sẽ báo lỗi lập tức!
⚠️ Core giao tiếp với Game bằng cách nào?

Nếu Core không biết Game, làm sao Core báo cho Game biết khi user mua hàng IAP thành công hoặc khi app pause?
Câu trả lời: Core giao tiếp thông qua Interface, Delegate và Event:
- Với IAP: Core cung cấp delegate public delegate bool PurchaseGrantHandler(string productId). Phía Game (trong GameEntry) đăng ký hàm cấp quà: App.Iap.SetGrantHandler(wallet.GrantProduct).
- Với Notification: Core phát event NotificationService.ScheduleRequested khi app pause để Game tự lên lịch nhắc nhở.

2. Bản đồ cấu trúc thư mục (Repository Map)

Dưới đây là cấu trúc chi tiết toàn bộ project trong Assets/_Project/:

Assets/_Project/
├── Core/                               # TapBrain.Core.asmdef (Namespace: TapBrain.Core)
│   ├── Bootstrap/
│   │   ├── Bootstrapper.cs             # Composition root của Core, tự kích hoạt BeforeSceneLoad
│   │   ├── InitPipeline.cs             # Quản lý 10 bước khởi động có timeout
│   │   └── LoadingScreen.cs            # Màn hình thanh tiến trình trong scene Loading
│   ├── Config/
│   │   └── CoreConfig.cs               # ScriptableObject chứa mọi key và cấu hình module
│   ├── Infra/
│   │   ├── App.cs                      # Facade tĩnh gọi nhanh dịch vụ hàng ngày
│   │   ├── ServiceLocator.cs           # Bảng đăng ký và truy xuất service
│   │   ├── CoreHost.cs                 # MonoBehaviour vĩnh viễn chạy coroutine cho class C# thuần
│   │   ├── CoreLog.cs                  # Ghi log chuẩn hóa có tag [Boot], [Ads], [IAP]...
│   │   ├── LocalStorage.cs             # Bọc PlayerPrefs: hỗ trợ long, double, json, prefix
│   │   ├── SceneLoader.cs              # Chuyển scene an toàn theo SceneId kèm fade màn hình
│   │   ├── UserIdentity.cs             # Sinh và lưu giữ UserId dùng chung cho mọi SDK
│   │   ├── AudioService.cs             # Quản lý nhạc nền (loop) và SFX (one-shot), tự mute khi xem ads
│   │   └── UI/
│   │       ├── DialogBase.cs           # Lớp cơ sở cho mọi popup (hiệu ứng scale/fade, nút Back)
│   │       ├── DialogService.cs        # Quản lý ngăn xếp popup (stack) trên canvas DontDestroyOnLoad
│   │       └── UIOverlay.cs            # Canvas trên cùng: fade chuyển scene, blocker, toast
│   ├── Services/
│   │   ├── Ads/                        # IAdsService, MaxAdsService, AdsPolicy (chống spam)
│   │   ├── Analytics/                  # AnalyticsService (hub) + 3 sink (Firebase, GA, AppMetrica)
│   │   ├── Firebase/                   # FirebaseService, RemoteConfigService, RealtimeDbService
│   │   ├── IAP/                        # IIapService, IapService, PendingOrderStore (crash-safe)
│   │   ├── BI/                         # BIService (đẩy user profile telemetry)
│   │   └── Platform/                   # AttService, SearchAdsService, NotificationService,
│   │                                   # StoreServices (AppUpdate, Review), HapticService
│   ├── Plugins/iOS/                    # Code native iOS (.m): ATT, SearchAds, Haptics, Review
│   └── Editor/                         # TapBrain.Core.Editor.asmdef: CoreConfigTools, Build Guard
│
├── Game/                               # KHÔNG asmdef (biên dịch vào Assembly-CSharp mặc định)
│   ├── GameEntry.cs                    # Composition root phía game (tự chạy AfterSceneLoad)
│   ├── GameApp.cs                      # Lối tắt dịch vụ game: GameApp.Wallet, GameApp.Progress
│   ├── GameKeys.cs                     # Tập trung toàn bộ key: storage, Remote Config, ads, IAP
│   ├── Meta/
│   │   ├── GameWallet.cs               # Dịch vụ quản lý soft currency (Coin), lưu trữ tức thì
│   │   └── LevelProgress.cs            # Dịch vụ quản lý số thứ tự màn chơi hiện tại
│   ├── Gameplay/
│   │   ├── GameplayController.cs       # ĐIỂM BẮT ĐẦU GAMEPLAY tại scene Main
│   │   └── LevelSession.cs             # Luật 1 lượt chơi: thưởng coin, đếm giờ, gửi analytics
│   └── UI/
│       ├── HomeScreen.cs               # Giao diện màn Home (nút Play, cài đặt)
│       ├── MainScreen.cs               # HUD trong màn chơi (hiển thị coin, level, sự kiện nút)
│       ├── ResultDialog.cs             # Popup kết quả thắng/thua kèm nút xem ads x2 coin
│       ├── SettingsDialog.cs           # Popup cài đặt âm thanh, nhạc nền, rung
│       └── DebugServicePanel.cs        # Bảng test nhanh F1 (test ads, IAP, event ngay trong Editor)
│
├── Scenes/
│   ├── Loading.unity                   # Scene index 0 (chạy boot pipeline)
│   ├── Home.unity                      # Scene index 1 (sảnh chính)
│   └── Main.unity                      # Scene index 2 (scene chơi game)
│
└── Resources/
    ├── CoreConfig.asset                # File cấu hình duy nhất của toàn bộ project
    └── Dialogs/
        ├── ResultDialog.prefab         # Prefab popup kết quả
        └── SettingsDialog.prefab       # Prefab popup cài đặt

3. Sơ đồ luồng gọi dịch vụ (Call Path Diagram)

Khi bạn viết một dòng code gameplay trong GameplayController, luồng thực thi đi qua các tầng như thế nào? Hãy quan sát sơ đồ dưới đây:

sequenceDiagram
    autonumber
    actor Player as Người chơi
    participant GC as GameplayController
    participant AppFacade as App Facade (Static)
    participant Loc as ServiceLocator
    participant MaxSvc as MaxAdsService (IAdsService)
    participant Policy as AdsPolicy
    participant SDK as AppLovin MAX SDK

    Player->>GC: Bấm nút "x2 Coins"
    GC->>AppFacade: App.Ads.ShowRewarded("double_reward", callback)
    AppFacade->>Loc: ServiceLocator.Get()
    Loc-->>AppFacade: Trả về instance MaxAdsService
    AppFacade->>MaxSvc: ShowRewarded("double_reward", callback)
    MaxSvc->>Policy: Kiểm tra tính khả dụng & trạng thái
    MaxSvc->>SDK: MaxSdk.ShowRewardedAd(adUnitId, placement)
    Note over SDK,Player: Người chơi xem video quảng cáo
    SDK-->>MaxSvc: OnRewardedAdReceivedRewardEvent
    MaxSvc-->>GC: callback(RewardedResult.Rewarded)
    GC->>GC: GameApp.Wallet.Add(bonusCoins)
          

Các mắt xích trong chuỗi gọi:

  1. Code Game (Caller): GameplayController muốn hiện quảng cáo nhận thưởng, gọi App.Ads.ShowRewarded(...).
  2. Facade (App.Ads): Lớp tĩnh cực mỏng, chỉ thực hiện lệnh chuyển tiếp: ServiceLocator.Get<IAdsService>(). Nó không chứa biến, không chứa state.
  3. ServiceLocator: Tra cứu trong từ điển Dictionary<Type, object> đã được đăng ký từ lúc boot, trả về thể hiện đang chạy là MaxAdsService.
  4. Triển khai dịch vụ (Service Implementation): MaxAdsService kiểm tra chính sách, tắt âm thanh game, rồi chuyển lệnh sang SDK gốc.
  5. SDK bên thứ 3: AppLovin MAX hiển thị quảng cáo lên màn hình thiết bị và gửi callback trả về khi hoàn tất.
🎯 Bài tập hiểu kiến trúc

Giả sử studio quyết định chuyển toàn bộ quảng cáo từ AppLovin MAX sang Google AdMob Mediation:
- Bạn có cần phải sửa bất kỳ dòng code nào trong GameplayController.cs hay MainScreen.cs không?
- Bạn cần tạo class mới nào và đăng ký nó ở đâu trong thư mục Core/?