지우너

[Unity 3D Run] 게임오버/클리어 UI (3) 본문

Project

[Unity 3D Run] 게임오버/클리어 UI (3)

지옹 2023. 3. 25. 20:39

03.23-03.24

월요일 취업 상담이 끝나고 카페에서 4시쯤까지 공부한 이후로, 유니티를 거의 만지지 않았다…

스크립트를 만드는 게 생각보다 어렵다고 생각이 들어서 자꾸 회피하게 됐는데, 그래도 오늘은 공부를 해야 할 것 같아서 잠깐이나마 할 수 있는 것들을 손봤다.

 

Goal 오브젝트 만들기

큐브 오브젝트 생성 > is Trigger 체크 > Goal Material 생성&적용

Goal Material은 살짝 투명한 녹색으로 만들어 닿으면 GameClear 판정

투명도 값을 조정해줘도 투명하게 설정되지 않았다.

유니티 오브젝트 투명도 조절 - UniCoti

Shader가 Standard로 설정되어 있는데 이를 클릭하여

Legacy Shaders→Transparent→Diffuse 선택

Goal 스크립트 - OnTriggerEnter(Collider col)

게임 클리어를 판단할 오브젝트도 생성했으니, 여기에 접촉했을 때 클리어 판정이 되도록 스크립트를 만들면 된다.

Obstacle은 장애물이라서 Trigger 체크를 안 하고(부딪혀서 없어지는 느낌이니까), OnCollisionEnter 함수를 사용했다.

Goal은 결승지점을 통과하는 느낌이라서, 부딪히면 안 되기 때문에 Trigger로 설정했다. 따라서 함수도 OnCollisionEnter가 아니라. OnTriggerEnter함수를 사용했다.

 

💡 유니티 2D 게임 제작 p118

OnTriggerEnter2D(Collider2D collision) OnTriggerEnter2D메서드는 Collider에 무엇인가 닿으면 호출되는 메서드이다. 인수의 collision이 접촉한 Collider 컴포넌트이며, collision이 가진 gameobject 변수가 ‘Collider컴포넌트가 어태치된 게임 오브젝트’이다.

OnTriggerEnter2D / OnTriggerStay2D / OnTriggerExit2D 와 같은 충돌 판정 메서드가 있는데, 이들은 Is Trigger를 체크해둔 콜라이더가 다른 콜라이더와 접촉했거나, 접촉 중이거나, 접촉이 끝났을 때 호출되는 메서드이다. 인수의 collision 변수는 접촉한 콜라이더 컴포넌트이다.

 

RESTART / QUIT 버튼

RESTART 버튼을 Canvas에 연결된 GameUI 스크립트의 OnClick_Restart 함수와 연결

QUIT 버튼을 Canvas에 연결된 GameUI 스크립트의 OnClick_Quit 함수와 연결

 

 

더보기
// Player.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// GetComponent<PlayerController>()는 PlayerController와 Player 스크립트가 같은 오브젝트에 붙어 있는걸 전제로 한다.
// [RequireComponent(typeof(PlayerController))] 코드를 적어줘서 이 부분이 에러를 내지 않게 됨.
[RequireComponent(typeof(PlayerController))]
public class Player : MonoBehaviour
{
    public enum State { playing, gameclear, gameover };
    State currentState;
    public float moveSpeed = 3.0f;
    protected bool dead;

    // moveVelocity를 다른 스크립트인 PlayerController로 전달해서 물리적인 부분들을 처리할 수 있도록 할 것
    // 그래서 PlayerController에 대한 레퍼런스를 가져와야합니다.
    PlayerController controller;
    public GameManager manager;

    void Start()
    {
        currentState = State.playing;
    }

    void Awake()
    {
        controller = GetComponent<PlayerController>();
    }

    void Update()
    {
        if (currentState==State.playing)
        {
            // Movement input 플레이어의 움직임을 입력 받는 것
            // 수평(Horizontal) 과 수직(Vertical) 방향에 대한 입력을 받고 싶으니
            // Input. 을 적고 GetAxis()를 호출해서 "Horizontal"을 넣습니다.
            // y값은 내버려 둬도 되며(0), 일정한 속도로 앞으로 가도록 moveSpeed를 넣어줬다.
            Vector3 moveInput = new Vector3(Input.GetAxisRaw("Horizontal") * moveSpeed, 0, moveSpeed);
            // 입력 받은 값을 방향으로 변환하고 움직임 속도를 곱할 겁니다.
            // 입력의 방향을 얻기 위해 moveInput를 정규화(nomalized)하여 가져옵니다 (nomarlized는 방향을 가리키는 단위벡터로 만드는 연산)
            // 거기에 moveSpeed를 곱해줍니다
            Vector3 moveVelocity = moveInput.normalized * moveSpeed;
            // controller에게 velocity값을 넘겨줌
            controller.Move(moveVelocity);
        }
    }

    public void Clear()
    {
        currentState = State.gameclear;
        GameObject.Destroy(gameObject);
    }

    public void Die()
    {
        // 죽었으니까 dead를 true로 변경
        dead = true;
        currentState = State.gameover;
        // 게임 매니저의 GameOver함수 호출
        manager.GameOver();
        // 플레이어 오브젝트를 삭제
        GameObject.Destroy(gameObject);
        
    }

}
// GameManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;


public class GameManager : MonoBehaviour
{
    // +++ 변수 선언 +++
    public GameObject gameEndPanel;
    public Text gameStateText;
	public GameObject restartButton;
	public GameObject nextButton;

	// 플레이어를 스크립트로 가져오기 
	public Player player;
	

	// RESTART 버튼이 눌리면 
	public void GameStart()
	{
		//gameEnd UI가 안 보이게 설정
		gameEndPanel.SetActive(false);
		// 현재 씬(=현재 스테이지) 다시 시작

	}

	public void GameOver()
    {
		// 텍스트가 기본적으로 GameOver로 설정되어 있다.
		// gameStateText.text = "GameOver!";

		// next 버튼 비활성화 Restart 버튼 활성
		nextButton.SetActive(false);
		restartButton.SetActive(true);

		gameEndPanel.SetActive(true);
	}

	public void GameClear()
	{
		gameStateText.text = "GameClear!";

		// next 버튼 활성화 Restart 버튼 비활성화
		restartButton.SetActive(false);
		nextButton.SetActive(true);

		gameEndPanel.SetActive(true);
		
	}

}
// GameUI.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; // UI를 사용할 때 필요
using UnityEngine.SceneManagement;

public class GameUI : MonoBehaviour
{
    public GameObject GameEnd;
    public GameObject GameState;// 게임 상태를 표시하는 오브젝트
    private Text text; // GameOver!/GameClear!를 표시
    public GameObject RestartButton;
    public GameObject QuitButton;

    // 현재 씬 이름 (RESTRAT를 위/)
    public string sceneName;
    public string nextScene;

    // Start is called before the first frame update
    void Start()
    {
        text = GameState.GetComponent<Text>();
        GameEnd.SetActive(false); // 패널숨기기

    }

    // Update is called once per frame
    void Update()
    {

    }

    // UI Input
    // RESTART 버튼을 눌렀을 때 현재 씬 다시 시작
    public void OnClick_Restart()
    {
        SceneManager.LoadScene(sceneName);
    }

    // QUIT버튼을 누르면 스테이지 선택창으로 돌아가도록 만들기
    public void OnClick_Quit()
    {
        // 스테이지 선택 씬을 아직 만들지 않았으므로 주석처리
        // SceneManager.LoadScene("Menu");
    }

    // 게임을 클리어하면 Restart 버튼이 비활성화 되고
    // 다음 스테이지로 갈 수 있는 Next 버튼이 활성화 된다.
    public void OnClick_Next()
    {
        SceneManager.LoadScene(nextScene);
    }

}
// Obstacle.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Obstacle : MonoBehaviour
{
    GameObject target;
    private Player player;

    void Start()
    {
        target = GameObject.FindGameObjectWithTag("Player");
        player = target.GetComponent<Player>();
    }

    void Update()
    {

    }

    void OnCollisionEnter(Collision collision)
    {

        if (collision.gameObject.tag == "Player")
        {
            // 플레이어가 접촉했다면 플레이어Die(GameOver, Camera Stop)
            player.Die();
        }
    }
}
// Goal.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Goal : MonoBehaviour
{
    GameObject target;
    private Player player;
    public GameManager manager;

    void Start()
    {
        target = GameObject.FindGameObjectWithTag("Player");
        player = target.GetComponent<Player>();
    }

    void Update()
    {

    }

    void OnTriggerEnter(Collider collision)
    {

        if (collision.gameObject.tag == "Player")
        {
            // 플레이어 오브젝트가 Goal 오브젝트에 닿으면 게임매니저의 GameClear 호출 
            manager.GameClear();
            player.Clear();
        }
    }
}

그리고 스크립트를 어태치한 각 오브젝트 인스펙터의  변수에 해당하는 오브젝트들을 끌어다 넣었더니, 제대로 작동했다!

player 오브젝트에 어태치된 스크립트
GameManager 오브젝트에 어태치된 스크립트
Canvas에 어태치된 스크립트
Canvas의 버튼들 설정
Goal 오브젝트에 어태치된 스크립트

그리고 플레이어가 서 있는 땅을 Plane에서 Quad로 변경해주었다.

유니티 상식) Plane과 Quad의 차이

이건 오브젝트 투명도 조절을 보다가 해당 블로그의 최신글이라 보게 된 글이다.

Quad가 폴리곤을 더 적게 사용하여 성능적인 부하가 적다고 한다. 단, Quad는 2D 기반이라 3D에서 사용할 때는 x축 회전이 필요하다.

http://answers.unity3d.com/questions/139900/what-is-the-difference-between-a-quad-and-a-plane.html

he built-in plane has 121 vertices and 200 triangles, and a quad has 4 vertices and 2 triangles. As for the shader,

 

현재까지 진행한 부분의 동영상이다. 아래 베이스를 바탕으로 여러 개의 스테이지를 구성할 것이다.

(아직 여러 스테이지를 만들지 않았고, 베이스라서 일단 NEXT를 누르면 재시작 되게 만들었다)

 

앞으로 해야 할 것

캐릭터의 점프 구현

타이틀 화면(게임시작, 옵션, 게임 종료 버튼)

스테이지 선택 화면

사운드

 

있으면 좋겠다 싶은 기능

캐릭터 모양 선택(캡슐, 큐브, 구 등)

사망 애니메이션(죽으면 부서지는 것 같은 느낌으로)