카테고리 없음

유니티 오브젝트 객체 컴포넌트, 코루틴

akama 2024. 9. 23. 16:45

유니티는 각 오브젝트에 Transform, Sprite Renderer, RigidBody, Collider 등 다양한 컴포넌트들을 갖고 있습니다. 이런 컴포넌트에는 오브젝트에 영향을 주는 값들이 있고 inspector뷰에서 이 값을 조정해서 오브젝트가 움직이거나 속성이 바뀌는 이벤트가 발생합니다. 직접 값을 변경하지만 실제 플레이에서는 이 값을 직접 바꿀 수 없기 때문에 스크립트 컴포넌트를 활용해서 데이터 값을 가져와서 사용합니다. 이번에는 TrashObject 스크립트에서 GameController에 있는 함수를 가져와서 사용하는 방법을 해보겠습니다. 먼저 TrashObject를 더블클릭해서 스크립트를 열어줍니다.

public class TrashObject : MonoBehaviour
{
    private GameController gameManager;
   
    private void Start()
    {
        gameManager = GameObject.Find("GameManager").GetComponent<GameController>();     
    }    

    private void OnTriggerEnter2D(Collider2D collision)
    {
          if (collision.CompareTag("Player"))
          {
                gameObject.SetActive(false);
          }
    }
}

굵은 글씨로 추가시킨 명령어입니다. 잘 보면 public class는 Monobehaviour에 속한 클래스로써 다른 스크립트에서 클래스 변수로 사용할 수 있습니다. 이 스크립트에서는 게임 매니저인 GameController를 가져와서 변수명을 gameManager로 붙여주었습니다. 변수를 만들었다면 변수에 컴포넌트를 넣어주어야 해당 함수를 부를 때 참조가 가능합니다. 따라서 Start()함수에서 게임이 시작될 때 GameManager라는 오브젝트 이름을 찾고 나서 그 오브젝트에 있는 GameController 컴포넌트를 가져와서 gameManager에 지정해 주고 앞으로 사용하겠다는 뜻입니다. 이제부터 이 스크립트에서는 변수 gameManager를 사용하면 GameController의 public 변수나 public 함수를 가져와서 사용할 수 있게 되었습니다. 아직 게임 매니저 스크립트에는 점수를 올려주는 함수가 없기 때문에 GameController 스크립트를 열어서 만들어 줘야 합니다.

using UnityEngine.UI;

public class GameController : MonoBehaviour
{
    public Text leftObjects;
    public Text gameScoreText;
    public Text gameScorePopupText;

    int leftCount;
    int gameScore;
    public GameObject SuccessBoard;

    void Update()
    {
        UpdateScore();
    }

    public int GetScore (int score)
    {
        gameScore += score;
        return gameScore;
        gameScoreText.text = gameScore.ToString()
    }

    void UpdateScore()
    {
        if(!isCleared)
        {
            if (leftCount > 0)
            {
                leftCount = GameObject.FindGameObjectsWithTag("Trash").Count();
                leftObjects.text = "Left : " + leftCount.ToString();
            }

            if ( leftCount <= 0)
            {
                leftCount = GameObject.FindGameObjectsWithTag("Trash").Count();
                leftCount = 0;
                leftObjects.text = "Left : " + leftCount.ToString();
                winBoard();
                isCleared = true;
            }
        }
    }

    void winBoard()
    {
        SuccessBoard.SetActive(true);
        gameScorePopupText.text = gameScore.ToString()
    }
}

 GameController에서 얻은 점수를 화면에 UI로 보여줄 Text 변수와 추가되는 점수를 담을 int 변수를 추가해 줍니다. 그리고 이 변수를 사용할 GetScore 함수를 public으로 만들어서 다른 스크립트에서 이 스크립트로 접근해서 가져다 사용할 수 있도록 합니다. 함수는 score 인자를 받아서 gameScore에 더해주고 결과 값을 int 타입으로 반환하고 그 결과값을 Text로 보여주도록 합니다. 결과 점수는 게임 화면에서 gameScoreText로 보여주고 미션 성공 시 팝업창에서 gameScorePopupText에서 한 번 더 보여주도록 합니다. GameManager 오브젝트를 클릭해서 inspector뷰를 보면 public으로 선언된 두 개의 Text 변수가 none으로 지정되어 있지 않습니다. Hierarchy뷰를 보면 이전에 만들었던 Canvas - SuccessBoard - CleanScoreData가 있는데 이것을 드래그해서 gameScorePopupText에 드롭해서 지정해 줍니다. 그리고 UI를 Text로 한 개 더 생성해서 이름을 Score로 변경하고 이 Score를 드래그 해서 gameScoreText에 드롭해 줍니다.

TrashObject 스크립트에서 몇 가지 추가해 줍니다.

public class TrashObject : MonoBehaviour
{
    public int score;
    private GameController gameManager;
   
    private void Start()
    {
          gameManager = GameObject.Find("GameManager").GetComponent<GameController>();     
    }    

    private void OnTriggerEnter2D(Collider2D collision)
    {
          if (collision.CompareTag("Player"))
          {
                gameManager.GetScore(score);
                gameObject.SetActive(false);
          }
    }
}

이제 TrashObject에서 GameController에 있는 함수 GetScore를 가져와서 사용할 수 있습니다. pulic으로 선언된 score를 사용해서 점수를 추가하게 됩니다. public으로 선언된 변수는 inpector뷰에서 점수를 지정할 수 있기 때문에 TrashObject 스크립트가 있는 오브젝트를 클릭해서 점수 값을 정해 줍니다. 플레이 해서 테스트를 해보면 목표 오브젝트를 회수할 때마다 정해진 점수가 올라가는 것을 확인할 수 있습니다. 목표 오브젝트가 모두 회수되면 해당 지역이 깨끗해 지고 곧 미션 성공 팝업창이 뜹니다. 성공 팝업창에는 획득한 점수가 보여지고 다음 스테이지로 넘어 갈수 있는 버튼이 있습니다. 이 버튼은 클릭만 될 뿐 아무 반응이 없습니다. public으로 선언된 NextGame()를 만들고 SceneManager를 이용하여 두번째 스테이지인 두번째 씬을 불러오도록 합니다. 게임에서 버튼을 해당 버튼을 누르게 되면 두번째 씬을 로드하게 됩니다. 다만 현재까지는 스테이지를 복사해서 같아보이기 때문에 테스트 전에 Stage2 Scene을 더블클릭해서 로드합니다. 두번째 스테이지이니 타일맵을 조정해서 스테이지에 변화를 주고 저장합니다. 다시 Stage1 Scene으로 돌아와서 플레이 해서 Next 버튼을 누르면 Stage2로 이동하게 됩니다. 이제 미션 실패 부분을  다루겠습니다. GameController 스크립트에 다음 명령어들이  추가되어야 합니다. GameController 스크립트 내에 있는 기존 함수들은 생략하고 추가되는 부분만 쓰도록 하겠습니다.

using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class GameController : MonoBehaviour
{
    public GameObject FailBoard;

    public void NextGame()
    {
        SceneManager.LoadScene(2);
    }

    public void RetryGame()
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    }

    public void loseBoard()
    {
        FailBoard.SetActive(true);
    }
}

이 스크립트에서 씬 매니저를 통해서 씬을 불러오는데 GetActiveScene().name을 통해서 현재 활성화된 씬을 다시 불러오는 명령어 입니다. 다음은 미션 실패 시 나오는 팝업창을 만들어야 합니다. Hierarchy - Canvas에서 SuccessBoard를 클릭한 채로 Ctlr + D로 복사해서 이름을 FailBoard로 바꾸고 타이틀의 이미지도 Fail 관련 이미지로 변경해 줍니다. 버튼 텍스트는 Retry로 바꾸고 버튼을 클릭해서 Button 컴포넌트의 on Click ()에 GameManager 오브젝트를 드래그 앤 드롭하고 public 선언된 RetryGame을 지정해 줍니다.

현재까지는 캐릭터가 벽에 부딪히면 애니메이션만 발생하고 미션 실패창이 나오지 않았습니다. GameManager 오브젝트를 클릭하고 Canvas에서 만든 FailBoard 오브젝트를 드래그 해서 FailBoard에 드롭해서 지정해 줍니다. 지정한 FailBoard를 비활성화해서 화면에서 보이지 않게 하고 PlayerMovement 스크립트를 열어 줍니다.

public class PlayerMovement : MonoBehaviour
{
    Animator animator;
   GameController gameManager;

    void Start()
    {
         animator = GameObject.FindGameObjectWithTag("Player").GetComponent<Animator>();
         gameManager = GameObject.Find("GameManager").GetComponent<GameController>();
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Wall"))
        {
            animator.SetBool("isCollision", true);
            gameManager.loseBoard();
        }
     }
}

gameManager 변수를 만들어서 GameManager 오브젝트를 찾고 GameController 컴포넌트를 가져 옵니다. GameController 내에 있는 public 선언된 loseBoard 함수를 사용하도록 합니다. 게임을 플레이 해서 캐릭터가 벽에 부딪히면 이제 미션 실패 팝업창오게 됩니다. 근데 여기서 미션 성공이나 실패 시에 팝업창이 바로 즉각적으로 나오는 것이 약간 게임이 부자연스러워 보입니다. 미션 성공 또는 실패에 따른 캐릭터 애니메이션이 약간 진행된 후에 팝업창이 발생하도록 팝업창 발생 딜레이를 주고 싶을 때 코루틴을 사용합니다. 코루틴은 특정 명령을 잠시 중단하고 다시 시작할 수 있도록 하는 기능입니다.

GameController 스크립트를 열고 winBoard와 loseBoard 함수 부분만 변경합니다.

using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class GameController : MonoBehaviour
{
    void winBoard()
    {
        StartCoroutine(DelayWinBoard());
        gameScorePopupText.text = gameScore.ToString()
    }

    IEnumerator DelayWinBoard ()
    {
        yield return new WaitForSeconds(1.7f);
        SuccessBoard.SetActive(true);
    }

    public void loseBoard()
    {
        StartCoroutine(DelayFailBoard());
    }

    IEnumerator DelayFailBoard ()
    {
        yield return new WaitForSeconds(1.7f);
        FailBoard.SetActive(true);
    }
}

코루틴 함수들은 다른 함수와 달리 void가 아닌 IEnumerator를 취하고 있습니다. IEnumerator 내에서는 반드시 yield return이 있어야 합니다. yield return new WaitForSeconds(1.7f); 명령어는 1.7초 동안 기다렸다가 차례대로 실행한다는 뜻입니다. 참고로 컴퓨터가 c# 스크립트의 명령을 수행할 때는 위에서 아래로 차례대로 처리하기 때문에 처리 순서가 중요한 기능 또는 시스템은 위아래 순서를 잘 정해야 합니다. 위에서 보면 코루틴을 실행하고 1.7초 기다렸다가 각 보드판을 활성화 합니다. 미션 성공이나 실패에 따른 보드가 잘 작동하지만 미션 성공 시 Next 버튼을 누르게 되면 항상 Stage2를 로드하게 됩니다. 미션 성공 시 고정된 씬 로드가 아닌 의도하고자 하는 다음 스테이지가 로드 되도록 하기 위해서는 싱글톤과 List<> 배열이 필요합니다. 다음 포스팅에는 이 부분을 다루어 보겠습니다.