การให้คะแนนต่อเนื่องไปใน Scene Game 3D ถัดไปและสามารถเล่นเกมต่อได้


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

แนวคิดหลัก

  1. GameObject ที่เก็บคะแนนไม่ถูกทำลาย: สร้าง GameObject ใหม่ (หรือใช้ Player Object เดิมก็ได้) ที่จะเก็บ PlayerScore Script นี้ และทำให้มันไม่ถูกทำลายเมื่อโหลด Scene ใหม่
  2. Scene Management ที่ซับซ้อนขึ้น:
    • ชนะ: เมื่อชนะ จะเปลี่ยนไป Scene ใหม่สำหรับ Level ถัดไป แทนที่จะเป็นแค่ “WinScene”
    • แพ้: กลับไป Scene เริ่มต้น โดยที่คะแนนอาจจะถูกรีเซ็ตหรือไม่รีเซ็ตก็ได้ (ในที่นี้จะแนะนำให้รีเซ็ตเมื่อแพ้)
  3. เก็บข้อมูลใน 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);: นี่คือส่วนสำคัญที่ทำให้ GameObject GameManager นี้ไม่ถูกทำลายเมื่อคุณเปลี่ยน Scene
  • AddScore(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
  • 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 ถ้าแยกต่างหาก)

สรุปขั้นตอนการทำงาน

  1. เริ่มเกม: GameManager ถูกสร้างขึ้นและไม่ถูกทำลายเมื่อเปลี่ยน Scene
  2. ผู้เล่นชนวัตถุ: PlayerMovement (หรือ Script การชนของ Player) จะเรียก GameManager.instance.AddScore()
  3. GameManager อัปเดตคะแนนและ UI: currentScore เพิ่ม/ลด และ scoreText ถูกอัปเดต
  4. ตรวจสอบสถานะ:CheckGameStatus() ถูกเรียก
    • ชนะ Level: ถ้า currentScore ถึง scoreToWinLevel จะโหลด Scene ถัดไป ใน Build Settings โดยที่ currentScore ยังคงอยู่
    • แพ้: ถ้า currentScore ถึง scoreToLose จะเรียก ResetGame() เพื่อรีเซ็ต currentScore เป็น 0 และโหลด startSceneName (Scene เริ่มต้น)
  5. Scene ใหม่โหลด: OnSceneLoaded ใน GameManager จะถูกเรียก เพื่อหา UI Text ใน Scene ใหม่ และอัปเดตคะแนนให้ถูกต้อง

เราใช้คุกกี้เพื่อพัฒนาประสิทธิภาพ และประสบการณ์ที่ดีในการใช้เว็บไซต์ของคุณ คุณสามารถศึกษารายละเอียดได้ที่ นโยบายความเป็นส่วนตัว และสามารถจัดการความเป็นส่วนตัวเองได้ของคุณได้เองโดยคลิกที่ ตั้งค่า

ตั้งค่าความเป็นส่วนตัว

คุณสามารถเลือกการตั้งค่าคุกกี้โดยเปิด/ปิด คุกกี้ในแต่ละประเภทได้ตามความต้องการ ยกเว้น คุกกี้ที่จำเป็น

ยอมรับทั้งหมด
จัดการความเป็นส่วนตัว
  • เปิดใช้งานตลอด

บันทึกการตั้งค่า