การให้คะแนนต่อเนื่องไปใน Scene Game 3D ถัดไปและสามารถเล่นเกมต่อได้
การให้คะแนนต่อเนื่องไปใน Scene ถัดไปและสามารถเล่นเกมต่อได้ (ชนะหรือแพ้แล้วกลับมาเริ่มใหม่) เราจะต้องใช้เทคนิคที่เรียกว่า “Don’t Destroy On Load” ซึ่งจะทำให้ GameObject ที่เราต้องการเก็บไว้ (ในที่นี้คือ Player หรือ GameObject ที่เก็บ Score Script) ไม่ถูกทำลายเมื่อเปลี่ยน Scene

แนวคิดหลัก
- GameObject ที่เก็บคะแนนไม่ถูกทำลาย: สร้าง GameObject ใหม่ (หรือใช้ Player Object เดิมก็ได้) ที่จะเก็บ
PlayerScore
Script นี้ และทำให้มันไม่ถูกทำลายเมื่อโหลด Scene ใหม่ - Scene Management ที่ซับซ้อนขึ้น:
- ชนะ: เมื่อชนะ จะเปลี่ยนไป Scene ใหม่สำหรับ Level ถัดไป แทนที่จะเป็นแค่ “WinScene”
- แพ้: กลับไป Scene เริ่มต้น โดยที่คะแนนอาจจะถูกรีเซ็ตหรือไม่รีเซ็ตก็ได้ (ในที่นี้จะแนะนำให้รีเซ็ตเมื่อแพ้)
- เก็บข้อมูลใน Script:
currentScore
จะถูกเก็บอยู่ใน Script นี้ตลอดไป
การปรับปรุงโค้ด PlayerScore.cs
เราจะสร้าง GameObject “GameManager” ขึ้นมาเพื่อดูแลเรื่องคะแนน และทำให้มันไม่ถูกทำลายเมื่อเปลี่ยน Scene ครับ
1. สร้าง GameManager GameObject
- สร้าง Empty GameObject ใน Scene ของคุณ ตั้งชื่อว่า “GameManager”
- ลบ
PlayerScore.cs
ออกจาก Player GameObject เดิม - สร้าง Script ใหม่ชื่อ
GameManager.cs
(หรือจะเปลี่ยนชื่อPlayerScore.cs
เดิมเป็นGameManager.cs
ก็ได้) แล้วลากไปใส่ที่ GameObject “GameManager” นี้
2. โค้ดสำหรับ GameManager.cs
C#
using UnityEngine;
using UnityEngine.SceneManagement;
using TMPro; // ถ้าใช้ TextMeshPro สำหรับ UI แสดงคะแนน
public class GameManager : MonoBehaviour
{
public static GameManager instance; // Singleton pattern เพื่อให้เข้าถึงได้ง่ายจากที่อื่น
[Header("คะแนนและเงื่อนไข")]
public int currentScore = 0; // คะแนนปัจจุบันของผู้เล่น
public int scoreToWinLevel = 5; // คะแนนที่ต้องการเพื่อให้ชนะ Level ปัจจุบัน
public int scoreToLose = -3; // คะแนนที่ผู้เล่นแพ้เมื่อถึงจุดนี้ (กลับไปเริ่มใหม่)
[Header("การจัดการ Scene")]
public string winSceneName = "WinScene"; // ถ้าต้องการ Scene เฉพาะเมื่อชนะ (เช่น Scene แสดงความยินดี)
// public string nextLevelSceneName = "Level2"; // ชื่อ Scene ของ Level ถัดไป
public string startSceneName = "StartScene"; // ชื่อ Scene เริ่มต้นของเกม
[Header("UI")]
public TextMeshProUGUI scoreText; // หรือ public Text scoreText; ถ้าใช้ Text ธรรมดา
// --- Singleton Pattern ---
void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject); // ทำให้ GameObject นี้ไม่ถูกทำลายเมื่อเปลี่ยน Scene
}
else
{
// ถ้ามี GameManager ตัวอื่นอยู่แล้วใน Scene ใหม่ ให้ทำลายตัวเองทิ้ง
Destroy(gameObject);
}
}
void Start()
{
// กำหนดชื่อ Scene เริ่มต้น หากยังไม่ได้กำหนด
if (string.IsNullOrEmpty(startSceneName))
{
startSceneName = SceneManager.GetActiveScene().name;
}
// อัปเดต UI เมื่อเริ่มต้น (โดยเฉพาะเมื่อโหลด Scene ใหม่)
UpdateScoreUI();
}
// --- ฟังก์ชันสำหรับเพิ่ม/ลดคะแนน ---
public void AddScore(int amount)
{
currentScore += amount;
Debug.Log("คะแนนเปลี่ยน: " + amount + " คะแนนปัจจุบัน: " + currentScore);
UpdateScoreUI();
CheckGameStatus();
}
// --- ฟังก์ชันสำหรับอัปเดต UI ---
void UpdateScoreUI()
{
if (scoreText != null)
{
scoreText.text = "คะแนน: " + currentScore;
}
}
// --- ตรวจสอบสถานะเกม (ชนะ/แพ้) ---
void CheckGameStatus()
{
if (currentScore >= scoreToWinLevel)
{
Debug.Log("คุณชนะ Level นี้!");
// ตรวจสอบว่ามี Level ถัดไปหรือไม่
int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
int nextSceneIndex = currentSceneIndex + 1;
// ตรวจสอบว่า Scene ถัดไปมีอยู่ใน Build Settings หรือไม่
if (nextSceneIndex < SceneManager.sceneCountInBuildSettings)
{
// โหลด Level ถัดไป
SceneManager.LoadScene(nextSceneIndex);
// อาจจะเพิ่ม scoreToWinLevel สำหรับ Level ถัดไป (ถ้าแต่ละ Level ต้องการคะแนนไม่เท่ากัน)
// scoreToWinLevel += 5; // ตัวอย่าง: Level ถัดไปต้องการคะแนนเพิ่มอีก 5
}
else
{
// ถ้าไม่มี Level ถัดไปแล้ว แสดงว่าเล่นจบเกม
Debug.Log("คุณเล่นจบทุก Level แล้ว!");
// อาจจะเปลี่ยนไป WinScene หรือ EndGameScene แทน
if (!string.IsNullOrEmpty(winSceneName))
{
SceneManager.LoadScene(winSceneName);
}
else
{
Debug.LogWarning("ไม่มี Win Scene กำหนดไว้!");
}
}
}
else if (currentScore <= scoreToLose)
{
Debug.Log("คุณแพ้!");
ResetGame(); // รีเซ็ตคะแนนและกลับไป Scene เริ่มต้น
}
}
// --- รีเซ็ตเกมเมื่อแพ้ ---
public void ResetGame()
{
currentScore = 0; // รีเซ็ตคะแนน
UpdateScoreUI(); // อัปเดต UI ให้เป็น 0
SceneManager.LoadScene(startSceneName); // กลับไป Scene เริ่มต้น
}
// ฟังก์ชันนี้จะถูกเรียกเมื่อ Scene โหลดเสร็จสิ้น
void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
void OnDisable()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
Debug.Log("Scene โหลดแล้ว: " + scene.name);
// อัปเดต UI อีกครั้งเมื่อ Scene โหลดเสร็จ เพื่อให้แน่ใจว่า UI ใน Scene ใหม่แสดงคะแนนถูกต้อง
// ต้องหา TextMeshProUGUI ใน Scene ใหม่ด้วย
// วิธีนี้ง่ายกว่า: ลาก TextMeshProUGUI จาก Scene แรกที่สร้าง GameManager ไปใส่ใน GameManager Inspector
// หรือถ้าอยากหา Text ในแต่ละ Scene ใหม่:
GameObject scoreTextObject = GameObject.Find("ScoreText"); // ตั้งชื่อ UI Text ของคุณเป็น "ScoreText"
if (scoreTextObject != null)
{
scoreText = scoreTextObject.GetComponent<TextMeshProUGUI>();
UpdateScoreUI();
}
}
}
คำอธิบายการเปลี่ยนแปลง:
public static GameManager instance;
และAwake()
: นี่คือการใช้ Singleton Pattern ทำให้มีGameManager
เพียงตัวเดียวในเกม และสามารถเข้าถึงได้จาก Script อื่นๆ ได้ง่ายๆ (เช่นGameManager.instance.AddScore(1);
)DontDestroyOnLoad(gameObject);
: นี่คือส่วนสำคัญที่ทำให้ GameObjectGameManager
นี้ไม่ถูกทำลายเมื่อคุณเปลี่ยน SceneAddScore(int amount)
: เปลี่ยนจากOnTriggerEnter
มาเป็นฟังก์ชันสาธารณะที่ Script อื่นจะเรียกใช้scoreToWinLevel
: เปลี่ยนชื่อให้ชัดเจนขึ้นว่าเป็นการชนะใน Level ปัจจุบันnextLevelSceneName
: เตรียมไว้สำหรับชื่อ Scene ของ Level ถัดไป- การจัดการ Scene ใน
CheckGameStatus()
:- เมื่อชนะ จะตรวจสอบ
SceneManager.GetActiveScene().buildIndex
เพื่อหาดัชนีของ Scene ปัจจุบัน และโหลด Scene ถัดไปโดยใช้nextSceneIndex = currentSceneIndex + 1;
- ถ้าไม่มี Scene ถัดไปแล้ว (หมายถึงเล่นจบเกม) ก็จะไป
winSceneName
หรือแสดงข้อความจบเกม
- เมื่อชนะ จะตรวจสอบ
ResetGame()
: ฟังก์ชันใหม่สำหรับรีเซ็ตคะแนนเป็น 0 และกลับไป Scene เริ่มต้นเมื่อผู้เล่นแพ้OnSceneLoaded(Scene scene, LoadSceneMode mode)
: ฟังก์ชันนี้จะถูกเรียกทุกครั้งที่ Scene ใหม่ถูกโหลด คุณสามารถใช้มันเพื่ออัปเดต UI ใน Scene ใหม่ให้แสดงคะแนนที่ถูกต้องได้ (โดยการหา Text component ใน Scene ใหม่)
การปรับปรุงโค้ด Player
และ Good/Bad Score Object
Player ไม่ต้องมี PlayerScore
Script แล้ว แต่จะเรียกใช้ GameManager
แทน
1. PlayerMovement.cs
(หรือ Script ของ Player ที่จัดการการชน)
C#
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
// เพิ่มความเร็วในการเคลื่อนที่ หรือส่วนอื่นๆ ของ Player ที่มีอยู่แล้ว
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("GoodScore"))
{
// เรียกใช้ GameManager เพื่อเพิ่มคะแนน
GameManager.instance.AddScore(1);
Destroy(other.gameObject); // ทำลายวัตถุที่ชน
}
else if (other.CompareTag("BadScore"))
{
// เรียกใช้ GameManager เพื่อลดคะแนน
GameManager.instance.AddScore(-1);
Destroy(other.gameObject); // ทำลายวัตถุที่ชน
}
}
}
2. ตั้งค่า GameObject ใน Unity Editor
- GameManager GameObject:
- ลาก Script
GameManager.cs
ไปใส่ - ตั้งค่า
Score To Win Level
,Score To Lose
,Next Level Scene Name
,Start Scene Name
และWin Scene Name
- ลาก UI Text ที่คุณสร้าง (เช่น “ScoreText”) ไปใส่ในช่อง
Score Text
ของGameManager
- ลาก Script
- Player GameObject:
- มี
Rigidbody
และCollider
(Is Trigger = true) เหมือนเดิม - มี Script การเคลื่อนที่ (เช่น
PlayerMovement.cs
ที่มีการแก้ไขตามข้างต้น)
- มี
- Good/Bad Score Objects:
- มี
Collider
(Is Trigger = true) เหมือนเดิม - มี Tag เป็น “GoodScore” และ “BadScore” ตามลำดับ
- มี
- UI Text GameObject:
- สร้าง UI Text (TextMeshPro) ใน ทุกๆ Scene ที่คุณต้องการให้แสดงคะแนน (เช่น ใน Level1, Level2)
- สำคัญ: ตั้งชื่อ GameObject ของ UI Text นี้ให้เหมือนกันในทุก Scene เช่น “ScoreText” เพื่อให้
GameManager
สามารถหาและอัปเดตได้ง่ายขึ้น (หรือคุณสามารถลากตัว UI Text ที่สร้างใน Scene แรกไปใส่ในscoreText
ของGameManager
โดยตรงใน Inspector)
การจัดการ Scene ใน Build Settings
- ไปที่ File -> Build Settings…
- ลาก Scene ทั้งหมดที่คุณต้องการให้เป็น Level ต่างๆ เข้าไปในช่อง “Scenes In Build” ตามลำดับที่คุณต้องการให้เกมดำเนินไป
- Scene 0: StartScene (หรือ Level1)
- Scene 1: Level2
- Scene 2: Level3
- …
- (อาจจะมี Scene สุดท้ายสำหรับ WinScene ถ้าแยกต่างหาก)
สรุปขั้นตอนการทำงาน
- เริ่มเกม:
GameManager
ถูกสร้างขึ้นและไม่ถูกทำลายเมื่อเปลี่ยน Scene - ผู้เล่นชนวัตถุ:
PlayerMovement
(หรือ Script การชนของ Player) จะเรียกGameManager.instance.AddScore()
- GameManager อัปเดตคะแนนและ UI:
currentScore
เพิ่ม/ลด และscoreText
ถูกอัปเดต - ตรวจสอบสถานะ:
CheckGameStatus()
ถูกเรียก- ชนะ Level: ถ้า
currentScore
ถึงscoreToWinLevel
จะโหลด Scene ถัดไป ใน Build Settings โดยที่currentScore
ยังคงอยู่ - แพ้: ถ้า
currentScore
ถึงscoreToLose
จะเรียกResetGame()
เพื่อรีเซ็ตcurrentScore
เป็น 0 และโหลดstartSceneName
(Scene เริ่มต้น)
- ชนะ Level: ถ้า
- Scene ใหม่โหลด:
OnSceneLoaded
ในGameManager
จะถูกเรียก เพื่อหา UI Text ใน Scene ใหม่ และอัปเดตคะแนนให้ถูกต้อง