การเปลี่ยนฉากใหม่ ใน Game Code Unity สำหรับการชนวัตถุเพื่อเปลี่ยนฉากและส่งค่าคะแนนไปยังฉากถัดไป

แน่นอนครับ นี่คือโค้ด Unity สำหรับการชนวัตถุ (เช่น ประตู) เพื่อเปลี่ยนฉากและส่งค่าคะแนนไปยังฉากถัดไป


สคริปต์สำหรับประตู (DoorTrigger.cs)

สร้างสคริปต์ C# ใหม่ชื่อ DoorTrigger.cs แล้วคัดลอกโค้ดด้านล่างนี้ไปวาง

C#

using UnityEngine;
using UnityEngine.SceneManagement; // ต้องมี namespace นี้สำหรับ SceneManager

public class DoorTrigger : MonoBehaviour
{
    public string nextSceneName; // ชื่อของฉากถัดไปที่จะโหลด
    
    // ตั้งค่าตัวแปร static เพื่อเก็บคะแนนชั่วคราวระหว่างฉาก
    // static ทำให้เข้าถึงได้จากที่ไหนก็ได้และข้อมูลยังอยู่แม้ฉากจะถูกทำลายไปแล้ว
    public static int scoreFromPreviousScene = 0; 

    // ฟังก์ชันนี้จะถูกเรียกเมื่อมี Collider อื่นเข้ามาชนกับ Collider ของวัตถุนี้
    // วัตถุที่มาชนจะต้องมี Rigidbody และ isTrigger ของ Collider จะต้องถูกติ๊ก
    void OnTriggerEnter(Collider other)
    {
        // ตรวจสอบว่าวัตถุที่เข้ามาชนคือ "ผู้เล่น" ของเราหรือไม่
        // คุณสามารถเปลี่ยน Tag นี้ให้เป็น Tag ของผู้เล่นของคุณได้ (เช่น "Player")
        if (other.CompareTag("Player")) 
        {
            // ดึงสคริปต์ที่เก็บคะแนนของผู้เล่นมา
            // สมมติว่าสคริปต์คะแนนชื่อ PlayerScore และอยู่บน GameObject ผู้เล่น
            PlayerScore playerScores = other.GetComponent<PlayerScore>();

            if (playerScores != null)
            {
                // เก็บค่าคะแนนของผู้เล่นลงในตัวแปร static
                scoreFromPreviousScene = playerScores.currentScore;
                Debug.Log("คะแนนที่ส่งไปฉากหน้า: " + scoreFromPreviousScene);
            }
            else
            {
                Debug.LogWarning("ไม่พบสคริปต์ PlayerScore บนผู้เล่น! จะเปลี่ยนฉากโดยไม่มีคะแนน");
            }

            // โหลดฉากถัดไปตามชื่อที่กำหนด
            SceneManager.LoadScene(nextSceneName);
        }
    }
}

สคริปต์สำหรับคะแนนผู้เล่น (PlayerScore.cs)

สร้างสคริปต์ C# ใหม่ชื่อ PlayerScore.cs แล้วคัดลอกโค้ดด้านล่างนี้ไปวาง สคริปต์นี้จะอยู่บน GameObject ของผู้เล่นของคุณ

C#

using UnityEngine;
using TMPro; // ถ้าใช้ TextMeshPro สำหรับแสดงคะแนน (ต้อง Import TextMeshPro Assets ก่อน)
// หรือใช้ UnityEngine.UI; ถ้าใช้ UI.Text ปกติ

public class PlayerScore : MonoBehaviour
{
    public int currentScore = 0; // คะแนนปัจจุบันของผู้เล่น
    public TextMeshProUGUI scoreText; // อ้างอิงถึง UI Text ที่จะแสดงคะแนน (ถ้าใช้ TextMeshPro)
    // public Text scoreText; // ถ้าใช้ UI.Text ปกติ

    void Start()
    {
        // เมื่อฉากใหม่โหลดขึ้นมา ให้ตรวจสอบว่ามีคะแนนส่งมาจากฉากก่อนหน้าหรือไม่
        // และเพิ่มคะแนนนั้นเข้ากับคะแนนเริ่มต้นในฉากปัจจุบัน
        if (DoorTrigger.scoreFromPreviousScene > 0)
        {
            currentScore += DoorTrigger.scoreFromPreviousScene;
            Debug.Log("ได้รับคะแนนจากฉากก่อนหน้า: " + DoorTrigger.scoreFromPreviousScene);
            // หลังจากใช้งานแล้ว ให้รีเซ็ตค่า static เพื่อป้องกันการนำไปใช้ซ้ำโดยไม่ตั้งใจ
            DoorTrigger.scoreFromPreviousScene = 0; 
        }

        UpdateScoreUI(); // อัปเดต UI แสดงคะแนนเมื่อเริ่มเกม
    }

    // ฟังก์ชันสำหรับเพิ่มคะแนน
    public void AddScore(int amount)
    {
        currentScore += amount;
        UpdateScoreUI();
    }

    // ฟังก์ชันสำหรับอัปเดต UI แสดงคะแนน
    void UpdateScoreUI()
    {
        if (scoreText != null)
        {
            scoreText.text = "คะแนน: " + currentScore;
        }
    }

    // ตัวอย่างการเพิ่มคะแนน (คุณสามารถเรียกใช้จากที่อื่นได้ เช่น เมื่อเก็บไอเทม)
    void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Coin")) // สมมติว่ามีเหรียญ Tag "Coin"
        {
            AddScore(10); // เพิ่ม 10 คะแนนเมื่อชนเหรียญ
            Destroy(other.gameObject); // ทำลายเหรียญที่เก็บไป
        }
    }
}

ขั้นตอนการตั้งค่าใน Unity

  1. สร้างฉาก (Scenes):
    • สร้างฉากของคุณ เช่น “Level1” และ “Level2” (หรือ “NextLevel” ตามที่คุณต้องการ)
    • สำคัญมาก: ไปที่ File > Build Settings… แล้วลากฉาก “Level1” และ “Level2” เข้าไปใน “Scenes In Build” เพื่อให้ Unity รู้จักฉากเหล่านี้
  2. ตั้งค่า GameObject ของประตู:
    • สร้าง GameObject ที่เป็นตัวแทนของประตูของคุณ (เช่น Cube, 3D Model)
    • เพิ่ม Component Box Collider (หรือ Collider อื่นๆ ที่เหมาะสมกับรูปร่างประตู)
    • ติ๊กช่อง “Is Trigger” ใน Box Collider component
    • เพิ่ม Component Rigidbody ให้กับประตู (สามารถติ๊ก “Is Kinematic” ได้ถ้าไม่ต้องการให้ประตูถูกแรงภายนอกกระทบ)
    • ลากสคริปต์ DoorTrigger.cs ไปใส่ใน GameObject ประตู
    • ใน Inspector ของประตู จะมีช่อง “Next Scene Name” ให้คุณใส่ชื่อฉากถัดไปที่คุณต้องการให้โหลด (เช่น “Level2”)
  3. ตั้งค่า GameObject ของผู้เล่น:
    • ตรวจสอบให้แน่ใจว่าผู้เล่นของคุณมี Collider (เช่น Capsule Collider) และ Rigidbody
    • ตั้งค่า Tag ของผู้เล่น: เลือก GameObject ผู้เล่นของคุณ ใน Inspector ตรงส่วนบนสุดจะมีช่อง “Tag” ให้เลือก “Add Tag…” แล้วสร้าง Tag ใหม่ชื่อ “Player” จากนั้นกลับมาที่ผู้เล่นแล้วเลือก Tag “Player” ให้กับมัน (สคริปต์ DoorTrigger จะใช้ Tag นี้เพื่อตรวจสอบว่าใครมาชน)
    • ลากสคริปต์ PlayerScore.cs ไปใส่ใน GameObject ผู้เล่น
    • ตั้งค่า UI แสดงคะแนน (ถ้ามี):
      • สร้าง Text UI ใน Canvas ของคุณ (GameObject > UI > Text – TextMeshPro หรือ Text)
      • ใน Inspector ของ PlayerScore บนผู้เล่น จะมีช่อง “Score Text” ให้คุณลาก Text UI ที่สร้างไว้มาใส่
  4. ทดสอบ:
    • ลองเล่นเกมของคุณในฉาก “Level1”
    • เพิ่มคะแนนให้ผู้เล่น (อาจจะใช้สคริปต์ PlayerScore เพิ่มคะแนนเมื่อเก็บไอเทม หรือแค่เพิ่มค่า currentScore ใน Inspector ตอนเล่น)
    • เดินผู้เล่นไปชนประตู
    • เกมควรจะเปลี่ยนไปที่ฉาก “Level2” และในฉาก “Level2” คะแนนของผู้เล่นควรจะแสดงผลรวมจากฉากก่อนหน้าด้วย

คำอธิบายเพิ่มเติม:

  • OnTriggerEnter(Collider other): เป็นฟังก์ชันของ Unity ที่จะถูกเรียกโดยอัตโนมัติเมื่อ Collider ของวัตถุนี้ (ประตู) ชนกับ Collider ของวัตถุอื่น (other) โดยที่ Collider ของประตูจะต้อง Is Trigger และวัตถุที่มาชนจะต้องมี Rigidbody
  • other.CompareTag("Player"): เป็นวิธีที่ปลอดภัยและมีประสิทธิภาพในการตรวจสอบว่าวัตถุที่เข้ามาชนมี Tag ที่เรากำหนดไว้หรือไม่ การใช้ CompareTag ดีกว่าการใช้ other.tag == "Player" ตรงที่มันจะหลีกเลี่ยงการสร้าง Allocation ในหน่วยความจำ
  • SceneManager.LoadScene(nextSceneName): นี่คือคำสั่งสำหรับโหลดฉากใหม่ตามชื่อที่ระบุ
  • public static int scoreFromPreviousScene: นี่คือหัวใจของการส่งข้อมูลข้ามฉาก.
    • public: ทำให้เข้าถึงได้จากสคริปต์อื่น
    • static: นี่คือส่วนสำคัญที่สุด ทำให้ scoreFromPreviousScene เป็นตัวแปรที่ “เป็นของคลาส” (DoorTrigger) ไม่ใช่ “เป็นของ Instance” (วัตถุประตูแต่ละบาน) และข้อมูลนี้จะยังคงอยู่ในหน่วยความจำแม้ว่าฉากปัจจุบันจะถูกทำลายไปแล้วเมื่อมีการเปลี่ยนฉาก ทำให้ฉากถัดไปสามารถเข้าถึงค่านี้ได้
    • เมื่อฉากใหม่โหลดขึ้นมา สคริปต์ PlayerScore ในฉากใหม่จะเข้าไปอ่านค่าจาก DoorTrigger.scoreFromPreviousScene เพื่อดึงคะแนนที่ส่งมา
    • ควรตั้งค่า DoorTrigger.scoreFromPreviousScene = 0; หลังใช้งาน: เพื่อป้องกันไม่ให้คะแนนเดิมถูกนำไปใช้ซ้ำหากมีการเปลี่ยนฉากอีกครั้งในอนาคตโดยไม่ได้ตั้งใจ

หวังว่าโค้ดและคำอธิบายนี้จะเป็นประโยชน์ในการพัฒนาเกม Unity ของคุณนะครับ! หากมีคำถามเพิ่มเติม ถามได้เลยครับ

วิธีแก้ไข ScoreManager.currentScore’ is inaccessible due to its protection level

ข้อความ “ScoreManager.currentScore’ is inaccessible due to its protection level” หมายความว่าคุณกำลังพยายามเข้าถึงตัวแปร currentScore ในคลาส ScoreManager แต่ตัวแปรนั้นไม่ได้ถูกตั้งค่าให้สามารถเข้าถึงได้จากภายนอกคลาสครับ

ปัญหานี้มักเกิดจากตัวแปรนั้นเป็น private หรือไม่ได้ระบุ public หรือ [SerializeField] ไว้

นี่คือวิธีแก้ไขครับ:


1. ทำให้ currentScore เป็น public

วิธีที่ง่ายที่สุดคือการทำให้ตัวแปร currentScore ในคลาส ScoreManager เป็น public ครับ

ในไฟล์ ScoreManager.cs ของคุณ:

C#

public class ScoreManager : MonoBehaviour
{
    // แก้ไขตรงนี้: เพิ่ม 'public' เข้าไป
    public int currentScore = 0; 

    // ... ส่วนอื่นๆ ของโค้ด ScoreManager
}

เมื่อคุณประกาศตัวแปรเป็น public สคริปต์อื่น ๆ จะสามารถเข้าถึงและแก้ไขค่า currentScore ได้โดยตรง เช่น ScoreManager.currentScore = 100; หรือ int score = ScoreManager.currentScore;


2. ใช้ Property (แนะนำสำหรับโครงสร้างที่ดีขึ้น)

ถ้าคุณอยากให้โค้ดมีความเป็นระเบียบและควบคุมการเข้าถึงได้ดีขึ้น คุณสามารถใช้ Property ได้ครับ

ในไฟล์ ScoreManager.cs ของคุณ:

C#

public class ScoreManager : MonoBehaviour
{
    private int _currentScore = 0; // เปลี่ยนเป็น private และมี underscore นำหน้า (ตามหลักปฏิบัติที่ดี)

    // สร้าง Public Property สำหรับเข้าถึงคะแนน
    public int CurrentScore 
    {
        get { return _currentScore; } // อนุญาตให้อ่านค่า
        set { _currentScore = value; } // อนุญาตให้กำหนดค่า
    }

    // หรือถ้าอยากให้แค่ "อ่าน" ได้อย่างเดียวจากภายนอก แต่ "กำหนดค่า" ได้เฉพาะในคลาสนี้เท่านั้น
    // public int CurrentScore { get; private set; } // สำหรับ C# 6.0 ขึ้นไป

    // ... ส่วนอื่นๆ ของโค้ด ScoreManager
}

วิธีการเรียกใช้จากสคริปต์อื่น:

C#

// สมมติว่าคุณมี reference ไปยัง ScoreManager หรือเป็น Singleton
ScoreManager.instance.CurrentScore = 100; // กำหนดค่า
int score = ScoreManager.instance.CurrentScore; // อ่านค่า

การใช้ Property ช่วยให้คุณสามารถเพิ่ม logic เข้าไปในการอ่านหรือเขียนค่าได้ เช่น การอัปเดต UI ทุกครั้งที่คะแนนเปลี่ยน หรือการตรวจสอบค่าคะแนนก่อนที่จะตั้งค่า


3. ใช้เมธอด public สำหรับการเข้าถึง/แก้ไข (ถ้าต้องการควบคุมที่ละเอียดกว่า)

หากคุณต้องการควบคุมการเข้าถึง currentScore อย่างละเอียด และไม่อนุญาตให้สคริปต์อื่นเข้าถึงตัวแปรโดยตรง แต่ให้ผ่านฟังก์ชันเท่านั้น คุณสามารถสร้างเมธอด public สำหรับการเพิ่ม/ลด หรือรับค่าคะแนนได้ครับ

ในไฟล์ ScoreManager.cs ของคุณ:

C#

public class ScoreManager : MonoBehaviour
{
    private int currentScore = 0; // เก็บเป็น private

    // เมธอดสำหรับเพิ่มคะแนน
    public void AddScore(int amount)
    {
        currentScore += amount;
        // อาจจะเรียกฟังก์ชันอัปเดต UI ตรงนี้ด้วย
    }

    // เมธอดสำหรับลดคะแนน
    public void DeductScore(int amount)
    {
        currentScore -= amount;
        // อาจจะเรียกฟังก์ชันอัปเดต UI ตรงนี้ด้วย
    }

    // เมธอดสำหรับดึงค่าคะแนนปัจจุบัน
    public int GetCurrentScore()
    {
        return currentScore;
    }

    // ... ส่วนอื่นๆ ของโค้ด ScoreManager
}

วิธีการเรียกใช้จากสคริปต์อื่น:

C#

// สมมติว่าคุณมี reference ไปยัง ScoreManager หรือเป็น Singleton
ScoreManager.instance.AddScore(50); // เพิ่มคะแนน
int score = ScoreManager.instance.GetCurrentScore(); // อ่านคะแนน

สรุป:

  • ถ้าคุณต้องการเข้าถึง currentScore โดยตรงจากที่อื่น: ทำให้เป็น public int currentScore;
  • ถ้าคุณต้องการโครงสร้างที่ดีขึ้น และอาจมี logic เพิ่มเติมในการเข้าถึง: ใช้ public int CurrentScore { get; set; } (Property)
  • ถ้าคุณต้องการควบคุมการเปลี่ยนแปลงคะแนนผ่านฟังก์ชันเท่านั้น: ใช้เมธอด public void AddScore(int amount) และ public int GetCurrentScore()

เลือกวิธีที่เหมาะสมกับความต้องการและโครงสร้างโปรเจกต์ของคุณได้เลยครับ วิธีที่ 1 คือวิธีแก้ปัญหาที่ตรงไปตรงมาที่สุด ส่วนวิธีที่ 2 และ 3 จะช่วยให้โค้ดของคุณเป็นระเบียบและจัดการได้ง่ายขึ้นในระยะยาวครับ

แต่ถ้าตัวแปร currentScore ถูกประกาศเป็น public อยู่แล้ว (public int currentScore = 0;) ซึ่งหมายความว่ามันสามารถเข้าถึงได้จากภายนอกคลาสครับ

ดังนั้น ข้อความแจ้งเตือน “‘ScoreManager.currentScore’ is inaccessible due to its protection level” ไม่ได้เกิดจากโค้ด ScoreManager แต่เกิดจาก โค้ดในสคริปต์อื่น (เช่น DoorTrigger.cs หรือสคริปต์ที่เคยเป็น PlayerScore.cs ก่อนหน้านี้) ที่พยายามจะเข้าถึง currentScore ผิดวิธี ครับ

เนื่องจากได้นำ Singleton Pattern มาใช้กับ ScoreManager แล้ว (public static ScoreManager Instance { get; private set; }) คุณจะต้องเข้าถึง currentScore ผ่าน Instance ของ ScoreManager เสมอครับ


วิธีแก้ไขคือ:

คุณต้องไปแก้ไขโค้ดในสคริปต์ที่พยายามจะเข้าถึงคะแนน (เช่น DoorTrigger.cs) ให้เรียกใช้ผ่าน ScoreManager.Instance ครับ

ตัวอย่างการแก้ไขในสคริปต์ DoorTrigger.cs (หรือสคริปต์อื่นที่เกี่ยวข้องกับการส่งคะแนน):

C#

using UnityEngine;
using UnityEngine.SceneManagement;

public class DoorTrigger : MonoBehaviour
{
    public string nextSceneName; // ชื่อของฉากถัดไปที่จะโหลด
    
    // ตั้งค่าตัวแปร static เพื่อเก็บคะแนนชั่วคราวระหว่างฉาก
    // static ทำให้เข้าถึงได้จากที่ไหนก็ได้และข้อมูลยังอยู่แม้ฉากจะถูกทำลายไปแล้ว
    public static int scoreFromPreviousScene = 0; 

    void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Player")) 
        {
            // *** แก้ไขตรงนี้: เข้าถึงคะแนนผ่าน ScoreManager.Instance ***
            // ตรวจสอบว่า ScoreManager.Instance ไม่เป็น null ก่อนใช้งาน
            if (ScoreManager.Instance != null)
            {
                scoreFromPreviousScene = ScoreManager.Instance.currentScore; // ดึงคะแนนจาก ScoreManager
                Debug.Log("คะแนนที่ส่งไปฉากหน้า: " + scoreFromPreviousScene);
            }
            else
            {
                Debug.LogWarning("ไม่พบ ScoreManager.Instance! จะเปลี่ยนฉากโดยไม่มีคะแนน");
            }

            // โหลดฉากถัดไปตามชื่อที่กำหนด
            SceneManager.LoadScene(nextSceneName);
        }
    }
}

คำอธิบายการเปลี่ยนแปลง:

  • จากเดิมที่อาจจะพยายามเข้าถึง PlayerScore.currentScore (ถ้ายังมีสคริปต์ PlayerScore อยู่) หรือ ScoreManager.currentScore โดยตรง (ซึ่งจะใช้ไม่ได้เพราะ currentScore ไม่ใช่ static ใน ScoreManager แต่เป็นตัวแปรของ Instance)
  • เราเปลี่ยนมาใช้ ScoreManager.Instance.currentScore แทน เพื่อเข้าถึงตัวแปร currentScore ผ่าน Instance ของ ScoreManager ที่ถูกสร้างขึ้นมาเพียงครั้งเดียวในเกม

สรุปขั้นตอนการตรวจสอบและแก้ไข:

  1. ตรวจสอบ ScoreManager.cs: โค้ดที่คุณให้มาถูกต้องแล้วสำหรับ ScoreManager ที่เป็น Singleton และมี currentScore เป็น public
  2. แก้ไขสคริปต์ DoorTrigger.cs: อัปเดตตามโค้ดด้านบน เพื่อให้ดึงคะแนนจาก ScoreManager.Instance.currentScore
  3. ตรวจสอบสคริปต์อื่นๆ (ถ้ามี): หากคุณมีสคริปต์อื่นที่เคยเข้าถึงคะแนนของผู้เล่นโดยตรง (เช่น สคริปต์ที่เคยเป็น PlayerScore.cs หรือสคริปต์ที่เพิ่ม/ลดคะแนน) ให้เปลี่ยนมาเรียกใช้เมธอด ChangeScore ของ ScoreManager หรือเข้าถึง ScoreManager.Instance.currentScore โดยตรง เช่น:C#// ตัวอย่างการเพิ่มคะแนนจากสคริปต์อื่น if (ScoreManager.Instance != null) { ScoreManager.Instance.ChangeScore(10); // เพิ่ม 10 คะแนน }

หลังจากแก้ไขแล้ว ข้อผิดพลาด “inaccessible due to its protection level” ควรจะหายไปครับ

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

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

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

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

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