iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0
Vibe Coding

從課堂半成品到完整發布:獨立遊戲的30天重生記系列 第 23 篇

【DAY23】Unity 2D 敘事遊戲開發:成就系統核心架構與資料模型(Achievement System Part 1 - Data & ScriptableObject)

  • 分享至 

  • xImage
  •  

一、前言
在前幾天陸續搞定主選單、場景轉換、Bad End 條件判定之後,我們的敘事遊戲已經具備了完整的核心循環與生死機制。但一款耐玩的遊戲,絕對少不了讓玩家在探索、抉擇、甚至是推進劇情時獲得額外的「成就感回饋」。今天我們就要正式動工,打造成就系統的第一步——利用 ScriptableObject 建立擴充性極高的成就資料模型與全域管理器!

二、 成就系統架構與設計挑戰
在設計一款包含多種成就且需要跨場景持久化的成就系統時,程式架構必須克服以下痛點:

  • 資料與邏輯分離(ScriptableObject 模式): 不能把成就要解鎖的文字、ID、圖標寫死在程式碼裡。我們需要用 ScriptableObject 把每一個成就獨立成一份資產檔(Asset),方便隨時調整與新增。
  • 全域狀態追蹤與單例保護(AchievementManager): 必須有一個常駐的管理器負責記錄哪些成就已經解鎖、哪些尚未達成,並在遊戲啟動時載入儲存狀態。
  • 資料持久化防呆: 利用 PlayerPrefs 或存檔機制,確保玩家關閉遊戲後,辛苦解鎖的成就進度不會歸零。

三、 核心程式實作:Achievement 數據結構與管理器

  1. 首先,我們建立一個用來定義單一成就資料的資料結構腳本 AchievementData.cs:
using UnityEngine;
[CreateAssetMenu(fileName = "NewAchievement", menuName = "Achievement System/Achievement Data")]
public class AchievementData : ScriptableObject {
    [Header("成就基本資訊")]
    public string achievementID;     // 唯一識別碼 (例如: "ITEM_CAPYBARA")
    public string achievementName;   // 成就顯示名稱 (例如: "卡皮巴拉的溫暖")
    
    [TextArea(3, 5)]
    public string description;       // 成就解鎖條件描述
    public Sprite iconSprite;        // 成就圖標 (彩色版)
    [Header("狀態設定")]
    public bool isUnlocked = false;  // 當前是否已解鎖
}
  1. 接著,我們建立負責全域追蹤、條件判定與存檔的 AchievementManager.cs:
using UnityEngine;
using System.Collections.Generic;
public class AchievementManager : MonoBehaviour {
    public static AchievementManager Instance { get; private set; }

    [Header("所有成就清單")]
    [Tooltip("把專案中建立好的所有 AchievementData 拖進這裡")]
    public List<AchievementData> allAchievements = new List<AchievementData>();
    private void Awake() {
        if (Instance == null) {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            LoadAchievements();
        }
        else {
            Destroy(gameObject);
        }
    }
    /// 解鎖指定 ID 的成就
    public void UnlockAchievement(string id)  {
        AchievementData target = allAchievements.Find(a => a.achievementID == id);
        if (target != null && !target.isUnlocked) {
            target.isUnlocked = true;
            SaveAchievements();
            Debug.Log($"[Achievement] 恭喜解鎖成就:{target.achievementName}!");
        }
    }
    /// 儲存所有成就的解鎖狀態
    private void SaveAchievements() {
        foreach (var achievement in allAchievements) {
            PlayerPrefs.SetInt("ACH_" + achievement.achievementID, achievement.isUnlocked ? 1 : 0);
        }
        PlayerPrefs.Save();
    }
    /// 讀取成就解鎖狀態
    private void LoadAchievements()  {
        foreach (var achievement in allAchievements) {
            if (PlayerPrefs.HasKey("ACH_" + achievement.achievementID))  {
                int status = PlayerPrefs.GetInt("ACH_" + achievement.achievementID, 0);
                achievement.isUnlocked = (status == 1);
            }
            else {
                achievement.isUnlocked = false;
            }
        }
    }
}

四、 Unity Editor 實作步驟

  1. 建立資料藍圖(ScriptableObject 腳本): 在專案中建立名為 AchievementData.cs 的腳本,並貼上對應屬性。
  2. 建立 ScriptableObject 資產卡片: 在 Project 視窗空白處點擊右鍵,透過 Create -> Achievement System -> Achievement Data 建立成就資料資產(Asset),並在 Inspector 填入 ID、名稱與敘述。
  3. 建立全域管理器(Manager): 建立 AchievementManager.cs,並將其掛載到全域的常駐物件或管理器群組(如 PersistentManagers)底下,將剛剛做好的成就資產拖入 All Achievements 清單中。
    https://ithelp.ithome.com.tw/upload/images/20260929/20184073X0tUmIAH4v.png
    圖一、分配並整理好成就類表

https://ithelp.ithome.com.tw/upload/images/20260929/20184073NO6SgXaIPH.png
圖二、腳本掛載與成就代入(如圖一內容所示)

五、 開發實戰經驗:踩坑與架構反思

  • Tips1: ScriptableObject 狀態在 Editor 裡的殘留
    • 當你在編輯器中測試解鎖成就後,停止執行(Stop)時,SO 檔案內的 isUnlocked 狀態會暫存在記憶體中。記得在遊戲初始化時一定要確實執行 LoadAchievements() 覆蓋狀態,避免測試失真。
  • Tips2: 全域常駐管理器的擺放位置
    • 像AchievementManager 這種帶有 DontDestroyOnLoad 的單例,絕對不要分散掛載在各個普通場景物件上,應統一收納在最初始的常駐管理器物件中,避免重複生成與資料錯亂。

六、 今日成果與小結

  • 成就資料模型底層建立: 成功運用 ScriptableObject 打造出模組化的成就資料結構。
  • 管理器架構與排錯避坑: 釐清了腳本命名規則與全域常駐的正確配置方式。

DAY 23 的內容主要是開始打造全新的 成就系統(Achievement System),包含了透過 ScriptableObject 建立資料模型(AchievementData)以及跨場景持久化的全域管理器(AchievementManager)。因為這是一個全新獨立的功能模組,它不屬於前面的存讀檔、常駐 UI 或 Bad End 分支,所以為它開立一個專屬的新分支--feature/achievement-system:

1.檢查目前修改狀態
git status

2.建立並切換至新的獨立分支:
git checkout -b feature/achievement-system

3.將今天的程式碼與修改加入暫存區並提交 Commit:
git add .
git commit -m "feat: 實作 AchievementData ScriptableObject 與 AchievementManager,建立成就系統底層與持久化管理"

4.將新分支推送到遠端倉庫:
git push origin feature/achievement-system


上一篇
【DAY22】Unity 2D 敘事遊戲開發:Bad End 條件判定與決策流攔截機制(GameConditionManager )
下一篇
【DAY24】Unity 2D 敘事遊戲開發:主選單成就清單與灰階視覺化(Achievement System Part 2 - Main Menu UI & GrayScale)
系列文
從課堂半成品到完整發布:獨立遊戲的30天重生記 共 24 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言