在昨天的文章中我們看到遊戲畫面中玩家的血條UI並沒有同步運作,這裡我們可以先檢查一下UI的code有沒有問題,之前適用GameManager
來修改UI。詳細的code可以去看D14的文章,我們可以發現我們有在GameManager
寫player
的資料結構,但我們後來又把player
在PlayerController
中生成了一次,並且我們的技能是依據PlayerController
來寫的。
這裡我們可以問一下ChatGPT要怎麼修改,我這裡是想用單例模式(Singleton)來實作角色,然後我的想法是可能可以把單例模式直接寫在Character
的class裡,但我不確定這是不是可行的。
我想在其他code也存取到這個角色的數值,但不希望重複實例化。
那我要在這個code的player做單例模式,還是直接在Character的變數裡加入單例模式讓角色不會被重複註冊
要解決這個問題,可以使用單例模式來確保 Character
只會被實例化一次,並且可以在其他腳本中存取相同的角色數據。你有兩種選擇:
PlayerController
中實現單例模式,讓 PlayerController
中的 player
成為全局的唯一實例。Character
類中實現單例模式,確保不會重複創建 Character
實例,並且能夠從任何地方存取。我會為這兩種方式各自提供解決方案,並討論哪種方法更合適。
PlayerController
中實現單例模式你可以將 PlayerController
設置為單例,這樣其他代碼可以直接通過 PlayerController.Instance.player
來存取玩家的數據。
PlayerController
為單例模式:using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
// 單例
public static PlayerController Instance { get; private set; }
// 定義一個角色,這裡可以是玩家角色
public Character player;
// 定義技能
private ISkill flameImpact;
private ISkill healingLight;
void Awake()
{
// 檢查是否已有實例存在,若有,則銷毀重複的實例
if (Instance != null && Instance != this)
{
Destroy(gameObject);
}
else
{
Instance = this;
DontDestroyOnLoad(gameObject); // 遊戲場景切換時保持單例存在
}
}
void Start()
{
// 初始化玩家
player = new Character("Player", 100, 50);
// 初始化技能,這裡引用之前的技能實現
flameImpact = new FlameImpact();
healingLight = new HealingLight();
}
void Update()
{
}
public void PlayerUseHealingLight()
{
healingLight.UseSkill(player);
Debug.Log("玩家魔力: " + player.Mana);
}
public void PlayerUseFlameImpact()
{
flameImpact.UseSkill(player);
}
}
PlayerController
單例來存取玩家數據:public class GameManager : MonoBehaviour
{
void Start()
{
// 直接通過PlayerController的單例來存取玩家數據
PlayerController.Instance.PlayerUseHealingLight(); // 讓玩家使用治癒技能
Debug.Log("玩家生命值: " + PlayerController.Instance.player.Health);
}
}
這樣,PlayerController
成為一個全局唯一的實例,可以在其他任何腳本中通過 PlayerController.Instance
存取玩家數據,而不需要重複實例化。
Character
類中實現單例模式如果你希望 Character
本身只存在一個實例,而不依賴於 PlayerController
,你可以在 Character
類中實現單例模式。
Character
類為單例模式:public class Character
{
// 單例
private static Character _instance;
public static Character Instance
{
get
{
if (_instance == null)
{
_instance = new Character("Player", 100, 50);
}
return _instance;
}
}
public string Name { get; private set; }
public float Health { get; private set; }
public float Mana { get; private set; }
// 私有構造函數,防止外部直接實例化
private Character(string name, float health, float mana)
{
Name = name;
Health = health;
Mana = mana;
}
public void TakeDamage(float damage)
{
Health -= damage;
if (Health < 0) Health = 0;
}
public void Heal(float healAmount)
{
Health += healAmount;
if (Health > 100) Health = 100;
}
public void UseMana(float manaCost)
{
Mana -= manaCost;
if (Mana < 0) Mana = 0;
}
}
PlayerController
和其他腳本中使用單例 Character
:在 PlayerController
中,不再需要實例化 Character
,可以直接使用 Character.Instance
:
public class PlayerController : MonoBehaviour
{
// 定義技能
private ISkill flameImpact;
private ISkill healingLight;
void Start()
{
// 初始化技能
flameImpact = new FlameImpact();
healingLight = new HealingLight();
}
void Update()
{
}
public void PlayerUseHealingLight()
{
healingLight.UseSkill(Character.Instance);
Debug.Log("玩家魔力: " + Character.Instance.Mana);
}
public void PlayerUseFlameImpact()
{
flameImpact.UseSkill(Character.Instance);
}
}
在其他腳本中,你可以像這樣使用 Character.Instance
來存取玩家數據:
public class GameManager : MonoBehaviour
{
void Start()
{
// 通過Character的單例來存取玩家數據
Debug.Log("玩家初始生命值: " + Character.Instance.Health);
// 讓玩家使用治癒技能
PlayerController.Instance.PlayerUseHealingLight();
}
}
方式 1:在 PlayerController
中實現單例模式:
PlayerController
可以不僅控制 Character
,還可以控制技能、行為邏輯等其他組件。方式 2:在 Character
類中實現單例模式:
PlayerController
成為單例。Character
本身成為單例會更直接。根據你的需求選擇合適的方式來實現單例模式。
根據ChatGPT的回答,比較適合我們的方法應該是方法1,所以我們將PlayerController
的code更新成ChatGPT寫的,還需要修改一下GameManger
的code。
GameManger
的codeusing UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour
{
// 玩家和AI的角色實例
//public Skill.Character player;
//public Skill.Character ai;
// 連接到UI元件的引用
public Slider playerHealthSlider;
public Slider playerManaSlider;
public Slider aiHealthSlider;
public Slider aiManaSlider;
// 初始化玩家和AI的生命值與法力值
void Start()
{
//// 初始化玩家和AI角色
//player = new Skill.Character("Player", 100, 50);
//ai = new Skill.Character("AI", 100, 50);
UpdateUI(); // 初始化UI
}
void Update()
{
UpdateUI(); // 實時更新UI
//if(Input.GetKeyUp(KeyCode.Space))
//{
// Debug.Log("玩家生命"+player.Health);
// Debug.Log("玩家魔力" + player.Mana);
//}
}
// 更新UI顯示玩家和AI的生命值、法力值
void UpdateUI()
{
playerHealthSlider.value = PlayerController.Instance.player.Health / 100;
playerManaSlider.value = PlayerController.Instance.player.Mana / 100;
//aiHealthSlider.value = ai.Health / 100;
//aiManaSlider.value = ai.Mana / 100;
}
}
這裡我先修改了player
的Health跟Mana的更新,我們再到Unity裡執行看看。
player
沒有正確實例化UI是有正常運作了,但出現了一個錯誤訊息。這裡的錯誤大概是指我們沒有把player
先實例化出來,雖然看起來不影響遊戲的進行,我們還是可以試著修一下,我們直接把錯誤訊息丟給ChatGPT看看。
NullReferenceException: Object reference not set to an instance of an object
GameManager.UpdateUI () (at Assets/Script/GameManger.cs:42)
GameManager.Start () (at Assets/Script/GameManger.cs:25)
NullReferenceException
表示你在程式中嘗試存取一個尚未初始化的物件。根據你的錯誤信息,錯誤發生在 GameManager.UpdateUI()
的第42行和 GameManager.Start()
的第25行。這通常是因為你嘗試使用了一個尚未設置的變數或物件。
具體的原因可能有幾個,常見的原因是你的 Slider
元件尚未在 Inspector 中正確連接。讓我們來檢查解決這個問題的步驟。
檢查 Slider
是否正確連接到 Inspector 中的變數:
GameManager
腳本的 GameObject(應該是場景中的某個物件)。playerHealthSlider
、playerManaSlider
、aiHealthSlider
和 aiManaSlider
這些變數的字段。UI Slider
元件拖到這些字段中。這些 Slider 應該是你場景中的 UI 元素(例如,顯示生命值和法力值的條形圖)。確認 UI 元件是否存在於場景中:
檢查 UpdateUI
函數:
UpdateUI
函數中是否正確存取了 Slider 的屬性。例如,應該確保 playerHealthSlider.value
這樣的表達式在 Slider 存在的前提下執行。GameManager.cs
的簡化版本using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour
{
// 玩家和AI的角色實例
public Character player;
public Character ai;
// UI 元件的引用
public Slider playerHealthSlider;
public Slider playerManaSlider;
public Slider aiHealthSlider;
public Slider aiManaSlider;
void Start()
{
// 確保 UI 元件已正確連接
if (playerHealthSlider == null || playerManaSlider == null || aiHealthSlider == null || aiManaSlider == null)
{
Debug.LogError("UI Slider 元件未正確設置,請檢查 Inspector 中的設置。");
return;
}
// 初始化玩家和 AI
player = new Character("Player", 100, 50);
ai = new Character("AI", 100, 50);
// 更新 UI
UpdateUI();
}
void UpdateUI()
{
// 確保 Slider 元件存在後再操作
if (playerHealthSlider != null && playerManaSlider != null && aiHealthSlider != null && aiManaSlider != null)
{
playerHealthSlider.value = player.Health / 100;
playerManaSlider.value = player.Mana / 100;
aiHealthSlider.value = ai.Health / 100;
aiManaSlider.value = ai.Mana / 100;
}
}
}
Slider
元件是否正確連接到 GameManager
的對應字段。Slider
,確保你所連接的 Slider
是對應的那一個。Start()
或 UpdateUI()
執行之前,所有的 Slider
變數都已經被初始化,否則會拋出 NullReferenceException
。Slider
是在運行時動態創建的,則需要在創建後設置它們的引用。如果它們是靜態 UI 元件,則確保已經在 Inspector 中設置正確。通過這些步驟應該能夠解決 NullReferenceException
錯誤。
我們可以看到ChatGPT是直接修改GameManager
的code,但我們其實已經在PlayerController
中的 void Start()
實例化player
。所以正確方式應該是再提早player
的實例化,我們把player
的實例化移到void Awake()
測試看看。
看起來是沒問題了,我再使用技能看看。
這樣看起來遊戲有正常運作了!