• hainv hainv

    • Popup Service là hệ thống quản lý Popup trong MS 2.0
    • Popup Service sẽ sử dụng queue để quản lý thứ tự hiển thị các popup. Chỉ có 1 popup xuất hiện trên màn hình tại 1 thời điểm.
      - Cấu trúc interface IPopupService
      5672ac60-8b29-428a-9931-6d5c49a3d76f-image.png
      - Cấu trúc Popup Data
      3ebbb88d-e273-4587-9af3-7f54c238b218-image.png

    Cách sử dụng

    • Setup một Popup
      • Tạo Popup Controller
        bdbc7d04-daed-475f-8855-2e17dfd120fd-image.png
        • Class của popup controller phải kế thừa lại PopupBase
        • Có thể override lại method DoIntroDoOutro để thực hiện Animation khi mở và đóng popup nếu cần
      • Tạo Popup Prefab
        de99331f-b9b9-44f2-95c7-c281015b0425-image.png
        • Kéo Scripts Popup Controller vào ngang hàng với Canvas của Popup
        • Gọi Hàm Close để đóng popup
      • Kéo Prefab vào Group Popup trong Addressable. Tên của popup trong addressable phải trùng với tên của class Popup Controller
        dcded0a2-a5c8-46c3-ad66-f9c9df247d76-image.png
    • Push Popup lên màn hình
     var popupService = SupportSystem.Instance[SupportSystem.ServiceIDs.PopupService] as IPopupService;
    PopupData popupData = new PopupData()
    {
          //Type của Popup Controller
          popupType = typeof(DemoPopup),
          // Data truyền vào trong Popup. Data này sẽ lấy được khi Popup chạy vào hàm Start.
          data = new Dictionary<string, object>()  
          {
               {"Demo", "Value" }
          }, 
         //Callback khi Popup hiện lên trên màn hình
          OnPopupPushed = (obj) =>   
          {
               DebugMonkey.Log(() => $"Popup Pushed: {obj.GetType().Name}");
          },
          //Callback khi đóng Popup
          OnPopupClosed = () =>
          {
               DebugMonkey.Log(() => "Popup Closed");
          },
          //Override lại sorting của canvas. nếu ko set giá trị thì canvas sẽ giữ nguyên sorting của prefab
          order = 100
    }
    popupService.PushPopup(popupData);
    

    - Editor
    b5a0b4da-b0a7-4d29-a42e-5df0a666c7dd-image.png
    trong Inspector của PopupService, có thể xem được Popup hiện tại trên màn hình vào danh sách Popup trong Queue.Popup sẽ hiển thị lần lượt theo danh sách trong Popups In Queue

    - Các lỗi có thể gặp phải

    • PopupData cant be null => PopupData không thể null
    • PopupDemo is not a Popup => Class PopupDemo không được kế thừa PopupBase
    • Load prefab popup from Addressable failed => Không load được Popup từ Addressables
    • [ERROR] Get Data with Key: {key}. the data type is wrong. cant cast {value.GetType().Name} to {typeof(T).Name} => Key tồn tại trong data nhưng sai value sai định dạng
    • [ERROR] Get Data with Key: {key}. Key not found => Key trong data không tồn tại

    posted in Monkey Stories 2.0 read more
  • hainv hainv

    • Assetbundle Pipeline được quản lý tại git repo:
      https://github.com/eduhub123/MonkeyXAssetBunldeBuilder

    • Script build: CreateAssetBundles.cs

      • Cấu trúc:
        9e972452-2727-4efa-9582-95123cf00916-image.png
    • Khi build Assetbundle, Unity sẽ tự động setting thông số cho từng Assets theo config được đặt sẵn trong code.

    • Assetbundle sẽ được build cho 3 platform:

      • Android
      • Ios
      • Standalone (PC Win)
    • Load file Assetbundles trong MJ5: Scripts: AssetBundleManager.cs

      • Cấu trúc:
        b5b198d6-c3d7-412f-a911-f97608d085f7-image.png

      - Hàm load Assetbundle

      • AssetBundleManager.Instance.GetAssetAward<T>(record);
      • AssetBundleManager.Instance.GetAssetNormal<T>(RecordBundleInfo record)
      • AssetBundleManager.Instance.GetAssetWord<T>(RecordBundleInfo record)
      • AssetBundleManager.Instance.GetAssetStory<T>(RecordBundleInfo record)

      - Assetbundle sẽ tự động download nếu chưa tồn tại

    posted in Chung read more
  • hainv hainv

    Theo dõi chỉ số Crash free:
    https://firebase.google.com/
    Kiểm tra chỉ số free crash trên Firebase Crashlytics.

    861c6c08-6388-4c15-81d5-4b3c16a7fcc9-image.png

    Chỉ số cần quan tâm:
    Crash free users: tỷ lệ user không bị crash.
    Crash free session: tỷ lệ session không bị crash

    Cách tăng chỉ sổ Crash free:
    Kiểm tra mục Issues của crashlytics
    c6ac6c61-c040-4e74-a55f-e0068a5099ca-image.png

    • Nhận biết các lỗi crash từ Unity:
      Các lỗi crash từ Unity thì sẽ có thông tin Unity version
      f26e5232-c586-483d-9531-b2ea229ccfa2-image.png

    - Các lỗi thường gặp phải:

    • Out of memory
    • Lỗi I/O
    • Lỗi do Vulkan trên các máy cũ

    Kiểm tra thêm chi tiết các lỗi crash trên Crashes and ARN của playstore
    15cbcc28-baca-418b-b058-e06033f7b27d-image.png

    posted in Chung read more
  • hainv hainv

    Project build: https://github.com/eduhub123/MonkeyStory2GameBuilder
    Script Auto Build: build_game.bat
    Trong script build cần config các thông tin:
    UNITY_PATH=<Path Unity Editor>
    ADDRESSABLE_PROJECT_PATH=<Path Build Project>
    MAIN_PROJECT_PATH=<Path Monkey Story 2.0 Project>

    Script Build c#: GameBuild.cs

    Cách download game trong MS 2.0
    Cấu trúc

    public interface IGameService
        {
            public void DownloadGameBundle(int gameId, Action<bool> callback);
            public void LoadGameCatalogs(int gameId, Action<bool> callback);
        }
    

    Download Game:

    IGameService gameService = SupportSystem.Instance[SupportSystem.ServiceIDs.GameService] as IGameService;
    gameService.DownloadGameBundle(gameConfigData.GameID, IsSuccess =>
    {
         //Callback sau khi download xong
    });
    

    Load Catalogs

    IGameService gameService = SupportSystem.Instance[SupportSystem.ServiceIDs.GameService] as IGameService;
    gameService.LoadGameCatalogs(gameConfigData.GameID, IsSuccess =>
    {
           if (IsSuccess)
                 //Load Game
           else
           {
                Debug.LogError("Load Game Catalogs Failed");
           }
    });
    

    Sau khi load catalogs thành công thì có thể load game từ Addressables như bình thường

    posted in Monkey Stories 2.0 read more
  • hainv hainv

    Các bước build MJ5 PC:

    • main branch: dev_intergration/pc_build/main

    Bước 1: Github

    • tạo 1 branch mới từ branch main.
    • Merge code mới nhất từ develop_intergration vào branch vừa tạo

    Bước 2: Nâng version

    • Nâng version trong Project Setttings

    Bước 3: Build Standalone

    • Build Addressable: từ menu của unity editor chọn Build/Addressable_Android
      a44cd5b9-3751-4ef1-adba-1bee62949a7d-image.png
    • Build Standalone: Build standalone từ Build Setting như bình thường.

    Bước 4: Đóng gói Installer
    Sử dụng phần mềm Advanced Installer phiên bản 21.3.1

    • Mở file copy_ms.back(19.1) trong project proj.setup
    • Thay thế toàn bộ files trong Application folder bằng files vừa mới build
      09295927-b88f-42fe-8cd9-a5a44c338915-image.png
    • Nâng version trong Project details
      c6beb1ce-e52f-4984-a947-0ff3c47e9446-image.png
    • Bấm build

    posted in Base read more
  • hainv hainv

    • Hệ thống Addressable cho MJ5 sử dụng để build Assets trong Unity (Images, Audio, Prefabs, Scenes)
    • Addressables Version 1.21.19

    - Cấu trúc Groups:

    • Remote Groups: Groups sẽ được đẩy lên cdn và download trong quá trình mở Unity
    • Local Groups: Groups sẽ được build cùng với bản build.

    - Remote Groups

    • Path lưu trữ: {UnityEngine.Application.persistentDataPath}/Addressables
    • Cấu trúc đặt tên group: bắt đầu với GROUPNAME_
    • Các groups có cùng prefix sẽ zip lại cùng với nhau.

    Lưu ý: remote groups chỉ được dependence với các groups có cùng prefix hoặc local groups.

    - Script quản lý Addressable:

    namespace Monkey.Support.Addressable
    {
        public interface IAddressableService
        {
            /// <summary>
            /// Download Addressable Group
            /// </summary>
            /// <param name="groupName">Group Name</param>
            /// <param name="callback">Callback when Download finished</param>
            /// <param name="progress">Callback the progress</param>
            /// <param name="total_retry">No need to change the value</param>
            /// <returns></returns>
            IEnumerator DownloadGroup(string groupName, Action<bool> callback, Action<float> progress = null, int total_retry = 0);
            bool IsNeedDownload(string groupName);
            void LoadAssets<T>(string asset_name, Action<AsyncOperationHandle<T>> callback) where T : UnityEngine.Object;
            bool HasKey(string key);
            /// <summary>
            /// Check when Addressable service ready
            /// </summary>
            /// <returns></returns>
            bool IsServiceActive();
        }
    }
    

    - Cách download group:

    • Kiểm tra xem group có cần download hay không
    IAddressableService addressable = SupportSystem.Instance[SupportSystem.ServiceIDs.AddressableService] as IAddressableService;
    bool isNeedDownload = addressable.IsNeedDownload("GROUP NAME");
    
    • Download Group
    IAddressableService addressable = SupportSystem.Instance[SupportSystem.ServiceIDs.AddressableService] as IAddressableService;
    CoroutineRunner.instance.RunCoroutine(addressable.DownloadGroup("GROUP NAME", isDone => {
      //Callback khi download thành công
      }, progress => {
      //Callback download progress
      }));
    

    - Build Addressables
    Script quản lý build Addressables: RNBuild.cs
    Method: static void BuildAddressable(string buildPlatform)
    Hàm build addressables thực hiện 3 bước:

    • Clean up
    • Build Addressable
    • Zip and upload to cdn

    - Các lỗi thường xảy ra
    Lỗi không load được dependencies

    OperationException : GroupOperation failed because one of its dependencies failed
    RemoteProviderException : Invalid path in AssetBundleProvider: 
    

    cách kiểm tra:

    • Kiểm tra xem group đã được download hay chưa.
    • Kiểm tra xem có assets nào depend đến remote group khác không.
      mở file buildlayout.txt trong folder: MonkeyX\Library\com.unity.addressables
      Kiểm tra Bundle Dependencies và Expanded Bundle Dependencies
      8623aeaf-ec84-456f-bfe0-ce097311ee1f-image.png

    posted in Base read more
  • hainv hainv

    9-Slice Trimmer là package để optimize Sprite hỗ trợ 9-Slice
    Để mở cửa sổ 9-Slice Trimmer. Chọn KH-Tools/Nine-Slice Trimmer trên thanh Menu
    4af61b63-eb82-44c5-b026-bed8f5c58831-image.png

    Cửa sổ của tool như sau:
    9aaa5257-011a-4a07-9b42-d4380e7205e9-image.png

    Hướng dẫn sử dụng:
    Bước 1: Chọn ảnh cần cắt và config vị trí cần slice
    80661fa4-2a51-4c26-9382-54c35934e2d1-image.png

    Bước 2: Bật Read/Write option và Apply
    cd3aa856-27ce-48ac-b3a1-bdfde826622d-image.png

    Bước 3: Kéo Sprite vào Sprite Texture trong tool
    95d4f722-13d5-4354-8416-e76bcf322a1f-image.png

    Bước 4: Bấm Preview để kiểm tra ảnh sau khi cắt đã đúng chưa
    5c63aa72-7688-45c5-ad9c-2f8f20f1e926-image.png

    Bước 5: Bấm Trim & Save để cắt Sprite
    2cda11e2-f615-419c-844f-f3acf2f17e17-image.png

    Bước 6: Tắt Read/Write trong ảnh vừa tạo
    Ảnh mới nhận được
    59092513-ac67-40aa-b92a-d28c41a3d013-image.png

    Tài liệu tham khảo thêm: https://github.com/KingHipUnity/Unity-NineSliceTrimmer/blob/main/README.md

    posted in Monkey Stories 2.0 read more
  • hainv hainv

    Viết thêm Cheat vào file CheatMethodHandle.cs
    01060da7-9512-4f97-8e94-29cf76a25a9f-image.png
    Quy tắc đặt tên Cheat mới

    _<Section> _<Tên Cheat>
    trong đó các Cheat có cùng Section sẽ được gộp vào chung 1 mục

    Cheat hỗ trợ 2 loại hàm void và biến static bool
    ví dụ

     public void _MS2_ShowLog()
     {
         Debug.Log("MS2 Show Log Clicked");
     }
    
     public static bool _IsDemoBool
     {
         get {
             return PlayerPrefs.GetInt("DebugKey_IsDemoBool", 0) == 1;
         }
         set {
             PlayerPrefs.SetInt("DebugKey_IsDemoBool", value ? 1 : 0);
         }
     }
    

    Để sử dụng Cheat, bấm vào button đầu khỉ ở ngoài màn hình
    611a6701-faf7-401d-af18-a2b49e07dae8-image.png

    aaaeecb2-2e3c-4dea-80b7-914643ed9619-image.png

    Click vào Section để mở ra các Cheat trong cùng 1 section
    Click vào từng Cheat để thực hiện hàm cheat

    posted in Monkey Stories 2.0 read more
  • hainv hainv

    Script Build được viết trong File MKBuild.cs

    • Đối với build bằng Unity Editor. Chọn Build trên thanh menu và chọn mục cần build
      da5e8199-4b6f-4646-bfc8-c469a67f32a3-image.png
      Sau khi build xong output file sẽ ở folder Build ( ngang hàng với Folder Assets )
      c25e56eb-7a91-4b75-8d0c-ab61fa0675b3-image.png
      Định dạng file APK: Android_yyyyMd_Hm.apk

    • Đối với sử dụng trong pipeline CI/CD
      Sử dụng Command:
      <Path To Unity Editor> -projectPath <Path To Project> -executeMethod Monkey.BuildTool.Editor.MKBuild.Build -outputPath <Path Output> -buildPlatform <Platform> -isProd true -batchmode -quit
      Platform: bao gồm: Android, iOS
      isProd: true hoặc false

    Ví dụ

    /Applications/Unity/Hub/Editor/2022.3.57f1/Unity.app/Contents/MacOS/Unity -projectPath /Users/monkey/Workspace/monkeyx_ci/monkey_story_2 -executeMethod Monkey.BuildTool.Editor.MKBuild.Build -buildPlatform iOS -isProd true -outputPath /Users/monkey/Workspace/monkeyx_ci/build/ios -batchmode -quit
    

    posted in Monkey Stories 2.0 read more
  • hainv hainv

    Các bước thực hiện để có thể test game bằng Game Tool
    Bước 1: Xoá các thành phần không cần thiết trong game nếu có
    3eec8679-69a8-4824-a028-6addaef20bbf-image.png
    128dd669-a838-4334-986a-3427f9ec9bf0-image.png
    EventSystem, SoundManager, AudioListener đã được load trong Game Tool nên không cần để trong scene game.

    Bước 2: Gán Addressable cho Scene game
    2.1: Mở cửa sổ Addressables Groups bằng cách sau:
    91e26b1b-6e99-419d-8a04-c0a5df57fdda-image.png
    2.2: Kéo Scene game vào group Games trong Addressable. Nếu game ở local thì kéo vào group Games_Local thay vì group Games
    8e5bdf46-8966-4b1d-b855-7b674d884baa-image.png
    2.3: Chuột phải vào Addressable vừa kéo chọn Simplify Addressable Name
    b612b11b-a203-46d1-8968-8f9b6232ba60-image.png

    Bước 3: Thêm Game Config trong File GameConfigSO trong đường dẫn: Assets/5.Manager/GameTool/Data
    a7201cc9-1cb8-4c0e-a245-7f6d7f5282a9-image.png

    • ở mục Asset Key: Điền tên của scene game
    • ở mục Asset Path: Điền đường dẫn đến scene đó
    • ở mục status lưu ý chọn đúng tiến độ của game để hiển thị màu button cho chính xác với quy định chung được diễn giải ở scene
    • IsLocalBuild = true nếu muốn build game ở local

    Bước 4: Chạy Scene GameTool và test lại.
    4f80f3ff-aaf3-499d-bfdb-ce7ee0c7bfba-image.png

    posted in Monkey Stories 2.0 read more
  • hainv hainv

    Hướng Dẫn Quy Tắc Sử Dụng Git Trong Unity Project

    1. Quy tắc đặt tên Branch

    Việc đặt tên branch có hệ thống giúp quản lý dễ dàng hơn và tránh nhầm lẫn trong quá trình phát triển. Dưới đây là quy tắc đặt tên branch áp dụng cho dự án Unity:

    1.1. Các loại branch chính

    • main: Chứa phiên bản ổn định của dự án, chỉ merge code đã kiểm thử kỹ lưỡng.
    • develop: Nhánh chính để phát triển, nơi tập hợp các tính năng trước khi merge vào main.
    • [user]/feature/: Dành cho các tính năng mới.
    • [user]/bugfix/: Dành cho các sửa lỗi.
    • [user]/hotfix/: Sửa lỗi khẩn cấp trên main.

    1.2. Cách đặt tên branch

    Khi dự án có nhiều thành viên, mỗi người nên có namespace riêng để dễ dàng xác định ai đang làm việc trên nhánh nào.

    Cấu trúc chung:

    [tên-thành-viên]/[loại-branch]/[mô-tả-ngắn-gọn]
    

    Ví dụ:

    • hainv/feature/weapon-system
    • hainv/bugfix/fix-ui-button
    • hainv/hotfix/crash-loading-screen

    Việc sử dụng tên thành viên giúp dễ dàng theo dõi ai chịu trách nhiệm cho từng nhánh và tránh trùng lặp tên branch.

    2. Quy tắc Commit

    Commit nên rõ ràng, ngắn gọn và có ý nghĩa để dễ theo dõi lịch sử thay đổi.

    Chỉ commit những file mà mình thay đổi

    2.1. Cấu trúc commit message

    [Loại commit]: [Mô tả ngắn gọn]
    
    [Giải thích chi tiết (nếu cần)]
    

    Loại commit phổ biến:

    • feat: Thêm tính năng mới
    • fix: Sửa lỗi
    • refactor: Cải tiến code không làm thay đổi tính năng
    • docs: Cập nhật tài liệu
    • style: Thay đổi không ảnh hưởng logic (format, tab, dấu cách)

    Ví dụ:

    feat: Thêm hệ thống quản lý vũ khí
    
    - Thêm cơ chế trang bị vũ khí
    - Thêm animation thay đổi vũ khí
    
    fix: Sửa lỗi nhân vật không thể nhảy khi đang chạy
    
    - Cập nhật input system
    - Điều chỉnh logic kiểm tra trạng thái nhân vật
    

    3. Merge Flow

    Việc merge code cần tuân theo một quy trình nhất định để tránh lỗi và đảm bảo tính ổn định.

    3.1. Quy trình Merge

    1. Tạo branch mới từ develop hoặc main trong trường hợp hotfix.
    2. Commit & Push: Sau khi hoàn thành công việc, commit code và push lên remote repository.
    3. Tạo Pull Request (PR): Yêu cầu merge vào develop hoặc main.
    4. Code Review: Thành viên nhóm kiểm tra code và đưa ra phản hồi.
    5. Fix review (nếu có): Chỉnh sửa theo góp ý.
    6. Merge: Gộp commit nếu cần trước khi merge vào nhánh chính.
    7. Xóa branch (sau 3 bản release, branch của thành viên nào thì thành viên đó sẽ chịu trách nhiệm xoá branch của mình).

    3.2. Các quy tắc khi Merge

    • Chỉ merge vào main từ develop hoặc hotfix/*.
    • Các tính năng mới nên merge vào develop trước khi lên main.
    • Không merge trực tiếp vào main.
    • Luôn đảm bảo code đã được test kỹ trước khi merge.

    4. Lưu ý Khi Dùng Git Trong Unity

    • Sử dụng .gitignore: Yêu cầu file .gitignore có các mục như Library/, Temp/, Logs/, *.csproj, *.unitypackage, *.sln, *.userprefs để tránh đẩy file không cần thiết lên repository.
    • Tránh merge conflict: Luôn pull bản mới nhất trước khi làm việc để tránh xung đột.
    • Commit thường xuyên: Giúp theo dõi tiến trình dễ dàng và giảm thiểu mất mát dữ liệu.

    Tài liệu này giúp chuẩn hóa quy trình làm việc với Git trong dự án Unity, giúp nhóm làm việc hiệu quả và giảm thiểu lỗi khi quản lý mã nguồn.

    posted in Chung read more
  • hainv hainv

    Coding Convention cho Unity - C#

    1. Quy tắc đặt tên (Naming Conventions)

    1.1 Biến và hằng số

    • Biến cục bộ (Local variables): sử dụng camelCase, bắt buộc có access modifier.
      private int playerScore;
      protected float moveSpeed;
      
    • Biến thành viên (Fields):
      • Luôn có access modifier, không dùng _ trước biến private.
      private int health;
      
      • Nếu là public, protected dùng PascalCase và bắt buộc quy định { get; set; }.
      public int MaxHealth { get; set; }
      [SerializeField] private float moveSpeed;
      
    • Hằng số: Dùng toàn bộ chữ hoa với _ giữa các từ.
      public const float GRAVITY = 9.81f;
      private readonly int MAX_LIVES = 3;
      

    1.2 Hàm và phương thức (Methods)

    • Dùng PascalCase.
      void MovePlayer();
      int CalculateScore();
      

    1.3 Class, Struct và Interface

    • Class & Struct: PascalCase.
      public class PlayerController {}
      public struct PlayerStats {}
      
    • Interface: Bắt đầu với chữ I, dùng PascalCase.
      public interface IDamageable {}
      

    1.4 Namespace

    • Dùng PascalCase, nên có cấu trúc theo thư mục.
      namespace Game.Core {}
      namespace Game.UI {}
      

    1.5 Enum

    • Dùng PascalCase và gán giá trị mặc định.
      public enum GameState { Idle = 0, Playing = 1, Paused = 2, GameOver = 3 }
      

    2. Quy tắc định dạng mã (Formatting)

    2.1 Dấu ngoặc nhọn {}

    • Mở dấu { trên cùng dòng với khai báo.
      if (isRunning) {
        Run();
      }
      
    • Với khối code một dòng, luôn sử dụng {}.
      if (isJumping) {
        Jump();
      }
      

    2.2 Khoảng trắng

    • Dùng 2 dấu cách (không dùng tab).
    • Giữa các toán tử, thêm khoảng trắng.
      int sum = a + b;
      
    • Không có khoảng trắng trước dấu ( trong phương thức.
      void Attack(int damage) {}
      

    3. Quy tắc tổ chức mã (Code Organization)

    3.1 Thứ tự trong Class

    • Fields
    • Properties
    • Unity Methods (Start, Update, FixedUpdate, LateUpdate, ...)
    • Methods (Public → Private)
    • Event Handlers

    Ví dụ:

    public class Player : MonoBehaviour {
      // Fields
      private int health;
    
      // Properties
      public int Health { get; set; }
    
      // Unity Methods
      private void Start() {
        Debug.Log("Game Started");
      }
    
      private void Update() {
        Move();
      }
    
      // Methods
      public void TakeDamage(int amount) {
        health -= amount;
      }
    }
    

    3.2 Quy tắc sử dụng MonoBehaviour

    • Tránh sử dụng Find(), FindObjectOfType() trong Update() vì hiệu suất kém.
    • Dùng SerializeField thay vì public cho biến cần hiển thị trên Inspector.

    4. Bình luận và Tài liệu (Comments & Documentation)

    • Sử dụng /// để thêm mô tả.
    /// <summary>
    /// Di chuyển người chơi theo hướng chỉ định.
    /// </summary>
    /// <param name="direction">Hướng di chuyển.</param>
    public void Move(Vector3 direction) {}
    
    • Chỉ bình luận những đoạn code khó hiểu hoặc cần giải thích.
    // Giảm máu của người chơi khi bị tấn công
    health -= damage;
    

    5. Xử lý sự kiện và delegate

    • Tên event dùng PascalCase, có hậu tố Event.
    public event Action OnPlayerDeathEvent;
    
    • Khi gọi event, kiểm tra null.
    OnPlayerDeathEvent?.Invoke();
    

    6. Sử dụng ScriptableObject

    • Dùng ScriptableObject để lưu dữ liệu chung thay vì Singleton.
    [CreateAssetMenu(fileName = "GameSettings", menuName = "Game/GameSettings")]
    public class GameSettings : ScriptableObject {
      public float volume;
      public int maxEnemies;
    }
    

    7. Best Practices

    • Sử dụng readonly hoặc const cho các biến không thay đổi.
    • Tránh sử dụng static trừ khi cần thiết.
    • Tránh sử dụng string so sánh trực tiếp (==), thay vào đó dùng enum hoặc nameof.
    • Sử dụng Linq cẩn thận, tránh dùng trong Update().
    • Luôn kiểm tra null trước khi truy cập đối tượng.
    • Sử dụng LogMe thay vì Debug

    8. Hướng dẫn import Convention Rule cho Visual Studio 2019 và Rider

    Visual Studio 2019

    1. Mở Visual Studio 2019.
    2. Vào ToolsOptions.
    3. Chọn Text EditorC#Code Style.
    4. Chỉnh sửa theo quy tắc đặt tên và format đã đề cập.
    5. Lưu lại và áp dụng.

    Rider

    1. Mở Rider.
    2. Vào FileSettings.
    3. Chọn EditorCode StyleC#.
    4. Cấu hình quy tắc đặt tên, format theo convention.
    5. Lưu lại và áp dụng.

    Tài liệu này giúp duy trì mã nguồn thống nhất, dễ bảo trì và tối ưu hiệu suất trong Unity C#. Việc tuân theo coding convention sẽ giúp dự án của bạn dễ đọc, dễ mở rộng và chuyên nghiệp hơn.

    posted in Chung read more
  • hainv hainv

    Support Base for Monkey Junior 5.0

    Core Structure:

    Support System: Management of all the services
    We implemented multiple Services to Support Monkey Junior 5.0

    How to use the Support System:
    SupportSystem class is a Singleton class and DontDestroyOnScene Object.
    Support System must attached to the GameObject and Execute before any class.
    eb1e52b5-22a7-4246-979b-e62e08b9fd5f-image.png

    The services will automatically initialize.
    To use the Service, follow the code

     IDownloadService DownloadService = SupportSystem.Instance[SupportSystem.ServiceIDs.DownloadService] as IDownloadService;
    

    Explanation:
    IDownloadSerivce: Interface of Service
    SupportSystem.ServiceIDs.DownloadService: Service type name


    Services:

    1. UpdateService: Manage the update method of the MonoBehaviour class
    Based on the best practice: Update in every class is worse on the performance.
    https://blog.unity.com/engine-platform/10000-update-calls
    So Update Service is the solution for that:

    1.1. Sample:

    using Monkey.Support;
    using UnityEngine;
    
    public class TestUpdateServiceScript : MonoBehaviour, IUpdate
    {
        public void OnDisable()
        {
            this.RemoveMe();
        }
    
        public void OnEnable()
        {
            this.AddMe();
        }
    
        public void UpdateMe()
        {
            Debug.Log("Update Me: " + Time.realtimeSinceStartup);
        }
    }
    
    

    1.2. Explanation:

    • The class that uses UpdateService must inherit IUpdate interface
    • The UpdateMe method will be called every frame.
    • UpdateService will work for both the MonoBehaviour class and the normal class.
    • Manage list classes active in Update Service:
      77f5d749-a64a-4c6d-b513-cb9404bfc2e7-image.png

    2. UserDataService: Manage the User Data, Profile Data, and Profile Setting of Users. To use UserDataService, call from Interface IUserDataService.

    2.1. Interface:

    public interface IUserDataService
        {
            /// <summary>
            /// Get Profile Data
            /// </summary>
            /// <returns>UserProfileModel</returns>
            public UserProfileModel GetProfile();
    
            /// <summary>
            /// Get User Data
            /// </summary>
            /// <returns>UserModel</returns>
            public UserModel GetUser();
    
            /// <summary>
            /// Get All Profile Settings
            /// </summary>
            /// <returns>List of ProfileSettingModel</returns>
            public List<ProfileSettingModel> GetProfileSettings();
    
            /// <summary>
            /// Get Specific Profile Setting
            /// </summary>
            /// <param name="setting">Setting Name</param>
            /// <returns>ProfileSettingModel</returns>
            public ProfileSettingModel GetProfileSetting(string setting);
    
        }
    

    2.2. Sample:

    var userService = SupportSystem.Instance[SupportSystem.ServiceIDs.UserDataService] as IUserDataService;
    var user = userService.GetUser();
    

    3. DataSyncService: Manage the Course Data, and Award Data (Pet Items, Sticker, Coin). To use UserDataService, call from Interface IDataSyncService.
    3.1. Interface:

    public interface IDataSyncService
        {
            #region Data Course
            /// <summary>
            /// Get Lesson Data
            /// </summary>
            /// <param name="course">Course ID</param>
            /// <param name="lessonId">Lesson ID</param>
            /// <returns></returns>
            LessonReportDataSync GetLessonData(TYPE_COURSE course, string lessonId);
            /// <summary>
            /// Get Course Data
            /// </summary>
            /// <param name="course">Course ID</param>
            /// <returns></returns>
            DataCourseSyncModel GetCourseData(TYPE_COURSE course);
            /// <summary>
            /// Update Lesson Data
            /// </summary>
            /// <param name="course">Course ID</param>
            /// <param name="lesson">New Lesson Data</param>
            void UpdateLessonData(TYPE_COURSE course, LessonReportDataSync lesson);
            #endregion
    
            #region Pet&Award
            /// <summary>
            /// Get Current Coin
            /// </summary>
            /// <returns></returns>
            int GetCoin();
            /// <summary>
            /// Set Coin Data
            /// </summary>
            /// <param name="coinChanged">Coin Changed</param>
            void SetCoin(int coinChanged);
            /// <summary>
            /// Get Theme Data
            /// </summary>
            /// <param name="themeId">Theme ID</param>
            /// <returns></returns>
            DataThemesModel GetTheme(string themeId);
            /// <summary>
            /// Add new Sticker
            /// </summary>
            /// <param name="themeId">Theme ID</param>
            /// <param name="stickerId">Sticker ID</param>
            void AddSticker(string themeId, int stickerId);
            /// <summary>
            /// Get All Pet Items
            /// </summary>
            /// <returns></returns>
            List<PetItem> GetPetItems();
            /// <summary>
            /// Get List Used Pet Item
            /// </summary>
            /// <returns></returns>
            List<int> GetUsedItems();
            /// <summary>
            /// Check Pet Item is Used
            /// </summary>
            /// <param name="itemId">Item ID</param>
            /// <returns></returns>
            bool IsUsedItem(int itemId);
            /// <summary>
            /// Get List New Pet Items
            /// </summary>
            /// <returns></returns>
            List<int> GetNewItems();
            /// <summary>
            /// Check Pet Item is New
            /// </summary>
            /// <param name="itemId"></param>
            /// <returns></returns>
            bool IsNewItem(int itemId);
            /// <summary>
            /// Add a Pet Item
            /// </summary>
            /// <param name="ItemId">Item ID</param>
            /// <param name="isNew">Is tag New Item, for the default IsNew = false</param>
            void AddItem(int ItemId, bool isNew = false);
            /// <summary>
            /// Change Pet Item State: Used, Unuse
            /// </summary>
            /// <param name="ItemId">Item ID</param>
            /// <param name="isUsed">Is Item Used</param>
            void ChangeItemState(int ItemId, bool isUsed);
            #endregion
        }
    

    3.2. Sample:

    var syncData = SupportSystem.Instance[SupportSystem.ServiceIDs.DataSyncService] as IDataSyncService;
    syncData.GetCourseData(TYPE_COURSE.MJ5);
    

    4. EventTrackingService: Send tracking, events, data to React Native. To use UserDataService, call from Interface IEventTrackingService.

    4.1. Interface

     public interface IEventTrackingService
        {
            /// <summary>
            /// Push Event to React Native to Tracking, Sync Data
            /// </summary>
            /// <param name="nameEvent">Event name</param>
            /// <param name="typeEvent">Event Type</param>
            /// <param name="properties">Properties</param>
            /// <param name="typeToPushEvent">Event Type: AWS, CLEVERTAP, AWS_AND_CLEVERTAP, AIRBRIDGE</param>
            void PushEvent(string nameEvent, string typeEvent, Dictionary<string, object> properties, TYPE_EVENT typeToPushEvent = TYPE_EVENT.AWS);
        }
    

    4.2. Sample

    var trackingService = SupportSystem.Instance[SupportSystem.ServiceIDs.EventTrackingService] as IEventTrackingService;
    Dictionary<string, object> properties = new Dictionary<string, object>() {
                { "completed", complete},
                { "time_on_screeen", endTimeLesson - startTimeLesson},
                { "age", UserManager.instance.currentProfile.Age}
            };
    trackingService.PushEvent("mx_learn_AI_lesson", EnvironmentConfig.awsLearnLesson, properties, TYPE_EVENT.CLEVERTAP);
    

    5. AddressableService: Download Game assets and course assets. To use UserDataService, call from Interface IAddressableService.

    5.1. Interface

    public interface IAddressableService
        {
            /// <summary>
            /// Download Addressable Group
            /// </summary>
            /// <param name="groupName">Group Name</param>
            /// <param name="callback">Callback when Download finished</param>
            /// <param name="progress">Callback the progress</param>
            /// <param name="total_retry">No need to change the value</param>
            /// <returns></returns>
            IEnumerator DownloadGroup(string groupName, Action<bool> callback, Action<float> progress = null, int total_retry = 0);
    
            void LoadAssets<T>(string asset_name, Action<AsyncOperationHandle<T>> callback) where T : UnityEngine.Object;
    
            /// <summary>
            /// Check when Addressable service ready
            /// </summary>
            /// <returns></returns>
            bool IsServiceActive();
        }
    

    5.2. Sample

    var AddressableService = SupportSystem.Instance[SupportSystem.ServiceIDs.AddressableService] as IAddressableService;
    yield return new WaitUntil(() => AddressableService.IsServiceActive());
    yield return AddressableService.DownloadGroup("lrc", (success)=> {
          Debug.Log("Download Success? " + success);
    }, (progress)=> {
          Debug.Log("Progress: " + progress);
    });
    

    6. DownloadService: Manage the Download process for Assetbundle, words, and lesson data. To use DownloadService, call from Interface IDownloadService.

    6.1. Interface

    public interface IDownloadService
        {   
            /// <summary>
            /// Download a file in the background.
            /// </summary>
            /// <param name="info"> Info item download </param>
            public void DownLoadFileInBackGround(ItemDownload info);
            /// <summary>
            ///  Download list files in the background.
            /// </summary>
            /// <param name="list_info"> List info item download </param>
            /// <param name="sucessCallBack"> Call back when successful download </param>
            /// <param name="progressCallBack"> Call back while downloading, this call back return percent </param>
            /// <param name="errorCallBack"> Call back when download fail, this call back return a message error </param>
            public void DownLoadListFileInBackGround(List<ItemDownload> list_info, Action<object> sucessCallBack, Action<object> progressCallBack, Action<object> errorCallBack);
            /// <summary>
            /// Download a file in the background by priority.
            /// </summary>
            /// <param name="info">Info item priority download</param>
            public void DownLoadFilePriority(ItemDownload info);
            /// <summary>
            /// Download list files in the background by priority.
            /// </summary>
            /// <param name="list_info"> List info item priority download </param>
            /// <param name="sucessCallBack"> Call back when successful download </param>
            /// <param name="progressCallBack"> Call back while downloading, this call back return percent </param>
            /// <param name="errorCallBack"> Call back when download fail, this call back return a message error </param>
            public void DownLoadListFilePriority(List<ItemDownload> list_info, Action<object> sucessCallBack, Action<object> progressCallBack, Action<object> errorCallBack);
            /// <summary>
            /// Download all files bundle words in Lesson.
            /// </summary>
            /// <param name="idLesson"> Id of lesson </param>
            /// <param name="sucessCallBack"> Call back when successful download </param>
            /// <param name="progressCallBack"> Call back while downloading, this call back return percent </param>
            /// <param name="errorCallBack"> Call back when download fail, this call back return a message error </param>
            public void DownLoadWordsInLesson(int idLesson, Action<object> sucessCallBack, Action<object> progressCallBack, Action<object> errorCallBack);
            /// <summary>
            ///  DownLoad file addressable by name addressable
            /// </summary>
            /// <param name="nameAddressable"> Name of addressable </param>
            /// <param name="sucessCallBack"> Call back when successful download </param>
            /// <param name="progressCallBack"> Call back while downloading, this call back return percent </param>
            /// <param name="errorCallBack"> Call back when download fail, this call back return a message error </param>
            public void DownLoadAddressable(string nameAddressable, Action<object> sucessCallBack, Action<object> progressCallBack, Action<object> errorCallBack);
            /// <summary>
            /// Get link host download file bundle word in course.
            /// </summary>
            /// <returns> Return link host link host download file bundle word </returns>
            public string GetLinkDownloadWord();
            /// <summary>
            /// Get link host download file zip activity in course.
            /// </summary>
            /// <returns> Return link host link host download file zip activity </returns>
            public string GetLinkDownloadAct();
        }
    

    6.2. Sample

    var downloadService = SupportSystem.Instance[SupportSystem.ServiceIDs.DownloadService] as IDownloadService;
    downloadService.GetLinkDownloadAct();
    

    7. Popup Service: Manage the Popups in the game, Push and Pop the Popup on scene. To use PopupService, call from Interface IPopupService.

    7.1. Interface

    public interface IPopupService
        {
            /// <summary>
            /// Show popup by name class
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="data"></param>
            /// <param name="showType"></param>
            /// <param name="actionShow"></param>
            /// <returns> return popup </returns>
            public T Show<T>(object data = null, ShowType showType = ShowType.DissmissCurrent, Action actionShow = null) where T : Panel;
            /// <summary>
            /// Close popup by name class
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="data"></param>
            /// <param name="actHide"></param>
            public void Hide<T>(object data = null, Action actHide = null) where T : Panel;
            /// <summary>
            /// Close all popups
            /// </summary>
            /// <param name="actHideAll"></param>
            public void HideAll(Action actHideAll = null);
        }
    

    7.2. Sample

    public class PopupTestService : Panel
    {
        public override void Hide(object data = null, Action actionHide = null)
        {
            base.Hide(data, actionHide);
        }
    
        public override void Show(object data = null, bool duplicated = false, Action actionShow = null)
        {
            base.Show(data, duplicated, actionShow);
        }
    
        public void OnClickBtnClose()
        {
            Hide(null, () => { Debug.LogError("OnClickBtnClose"); });
        }
    
        public void OnClickBtnOpenPopup2()
        {
            var popup = SupportSystem.Instance[SupportSystem.ServiceIDs.PopupService] as IPopupService;
            popup.Show<PopupTestService2>(null, PopupService.ShowType.NotHide);
        }
    }
    

    8. Assetbundles Service: Manager the Asset Bundle and get data game. To use AssetbundlesService, call interface IAssetbundlesService..

    8.1. Interface

    public interface IAssetbundlesService
        {
            /// <summary>
            /// Get Data Game Primitive
            /// </summary>
            /// <param name="dictWordInforDetail"> Dictionary info word detail with key is link down load word and value is WordInforDetail </param>
            /// <param name="listWord"> List word model in file word.json </param>
            /// <returns> Dictionary<int, DataGamePrimitive> </returns>
            public Task<Dictionary<int, DataGamePrimitive>> GetDataGamePrimitive(Dictionary<string, WordInforDetail> dictWordInforDetail, List<ListWordModel> listWord);
            /// <summary>
            /// Get asset in bundle by type T
            /// </summary>
            /// <typeparam name="T"> Type asset </typeparam>
            /// <param name="bundle_name"> Name bundle </param>
            /// <param name="asset_name"> Name asset </param>
            /// <param name="local_path_file"> Path bundle in storage device </param>
            /// <param name="on_done"> Action invoke when get asset complete </param>
            /// <param name="on_error"> Action invoke when get asset fail </param>
            /// <returns> Asset with type T </returns>
            public Task<T> ResourceGetAssetInBundle<T>(string bundle_name, string asset_name, string local_path_file, string link_download, Action<object> on_done = null, Action<object> on_error = null) where T : UnityEngine.Object;
        }
    

    8.2. Sample

    
     var assetbundleService = SupportSystem.Instance[SupportSystem.ServiceIDs.AssetbundleService] as IAssetbundlesService;
    
            string linkBundle = "https://monkeymedia.vcdn.com.vn/App/uploads/course_install/bundle/hdr/ios/";//course.d.w_b;
            string linkWord = "https://vnmedia2.monkeyuni.net/App/zip/hdr/word_bundle/ios/";//course.d.w_b;
            string bundle_name = "course_install_ai_speaking_v2_78_1716429352.bundle";
            string asset_name = "course_installation.json";
            string local_path = $"{Ultis.persistentDataPath}/zips/{bundle_name}";
            var json = await assetbundleService.ResourceGetAssetInBundle<TextAsset>(bundle_name, asset_name, local_path, linkBundle + bundle_name, 
                (object d) => { Debug.LogError("Complete" + d?.ToString());}, (object d) => { Debug.LogError("Fail" + d?.ToString()); });
    
    

    posted in Base read more