지우너

[Unity 3D Run] 장애물 충돌 설정 본문

Project

[Unity 3D Run] 장애물 충돌 설정

지옹 2023. 3. 13. 19:13

03.13

장애물 설치하기

장애물에 닿으면 게임오버 → OnCollisionEnter

게임 오버 함수 -캐릭터 움직임 중지, 카메라 움직임 중지, 게임오버 UI

현재 c# 스크립트 파일이 아래와 같이 있다.

 

  • Player.cs

Player 스크립트는 모든 입력이 들어오는 스크립트이고, Player 스크립트는 이 입력을 PlayerController 스크립트로 보낸다.

  • PlayerController.cs

PlayerController 스크립트는 실제 플레이어 컨트롤을 담당.

  • CameraController.cs

카메라의 움직임을 관리하는 스크립트. 플레이어가 살아있다면(=게임이 진행 중이라면) 플레이어를 따라가도록 설정했다.

  • Obstacle.cs

플레이어가 부딪히면 죽도록 설계되어야 함.

 

https://github.com/SebLague/Create-a-Game-Source/tree/master/Episode%2025

위 사이트에서 Die를 구현할 때 썼던 코드에 나온 문장인데 왜 쓴 건지 모르겠어서 검색해봤다.

[C#] virtual 함수 의미 및 활용

System.Action ? ( 델리게이트 )

// 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 float moveSpeed = 3.0f;
    protected bool dead;
    public event System.Action OnDeath;

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

    void Start()
    {

    }

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

    void Update()
    {
        // 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 Die()
    {
        // https://github.com/SebLague/Create-a-Game-Source/tree/master/Episode%2025
        // livingEntity, player.cs에 있음
        dead = true;
        if (OnDeath != null)
        {
            OnDeath();
        }
        GameObject.Destroy(gameObject);
    }

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

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

    // Start is called before the first frame update
    void Start()
    {
        target = GameObject.FindGameObjectWithTag("Player");
        player = GetComponent<Player>();
    }

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

    }

    void OnCollisionEnter(Collision collision)
    {

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

player = GetComponent<Player>(); 이렇게 변수에 저장했기 때문에 player.Die(); 부분에서 NullReferenceException이 났다. player.target.GetComponent<Player>();이라고 변경해주어야 한다.

 

유니티-충돌 처리 하기(2) 게임 중지시키기 - Study@withCoffee

Time.timeScale=0으로 하면 게임 씬이 정지하게 된다.

Time.timeScale=1이면 정상적인 속도로 시간이 움직이고, Time.timeScale=2라면 2배속으로 빠르게 재생된다.

 

더보기
//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 float moveSpeed = 3.0f;
    protected bool dead;

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

    void Start()
    {

    }

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

    void Update()
    {
        if (!dead)
        {
            // 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 Die()
    {
        dead = true;
        Time.timeScale = 0;
    }

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

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

    // Start is called before the first frame update
    void Start()
    {
        target = GameObject.FindGameObjectWithTag("Player");
        player = target.GetComponent<Player>();
    }

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

    }

    void OnCollisionEnter(Collision collision)
    {

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

 

Time.timeScale=0으로 하는 게 뭔가 이렇게 하면 안 될 것 같다는 생각이 들어서 원래 코드처럼 player 오브젝트를 파괴하는 방식으로 코드를 수정했다. 

더보기
// 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 float moveSpeed = 3.0f;
    protected bool dead;

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

    void Start()
    {

    }

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

    void Update()
    {
        if (!dead)
        {
            // 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 Die()
    {
        dead = true;
        GameObject.Destroy(gameObject);
    }

}

그랬더니 아래와 같이 에러가 났다.

target = GameObject.FindGameObjectWithTag("Player").transform; target이 없어졌기 때문에 카메라가 이동할 새로운 위치를 받을 수 없는 것이다.

따라서 카메라 코드도 이에 맞춰 조건을 추가해줘야 한다.

// CameraController.cs
using UnityEngine;

public class CameraController : MonoBehaviour
{
    private Transform target;
    private Vector3 offset;

    float lerpTime = 0.6f;
    float currentTime = 0;

    void Start()
    {
        target = GameObject.FindGameObjectWithTag("Player").transform;
        offset = transform.position - target.position;
    }

    void LateUpdate()
    {
        // player가 obstacle에 맞아서 파괴가 되면 target은 null이 된다.
        if (target != null)
        {
            // currentTime은 시간 흐름에 따라 증가함
            currentTime += Time.deltaTime;
            // 이 코드를 넣어주면 0부터 증가하다가 최대 0.5(lerpTime)까지만 증가함
            if (currentTime >= lerpTime) currentTime = lerpTime;
            float t = currentTime / lerpTime;

            // 플레이어 이동에 따라 변하는 카메라 위치
            Vector3 newPosition = new Vector3(transform.position.x, transform.position.y, offset.z + target.position.z);
            transform.position = Vector3.Lerp(transform.position, newPosition, t);
        }
    }

}

[베이스씬에서 만들어야 할 것]

(*베이스씬: 스테이지를 만들 때 가장 기본이 될 씬인데, 뭐라고 불러야 할지 모르겠어서 일단 베이스씬이라고 부르고 있다.)1. 게임오버 UI2. esc를 눌렀을 때 게임이 일시정시 되면서, <게임 이어하기>, <그만하기> 이런 버튼이 나오도록+) 만들면 좋은 거: 플레이어가 죽을 때 애니메이션(지금은 너무 갑자기 슉 하고 없어지니까)

 

내일은 게임오버UI를 만들어야 할 것 같다. 원래 계획대로라면 어제 베이스씬을 다 만들었어야 했는데, 생각보다 늦어졌다...