EasyCastleUNITY

길건너 친구들 모작 개발일지6 (Ray를 통한 장애물 검출 및 이동금지) 본문

3D 프로젝트 개발 일지(Crossy Road, 한글명: 길건너 친구들)

길건너 친구들 모작 개발일지6 (Ray를 통한 장애물 검출 및 이동금지)

EasyCastleT 2023. 10. 4. 17:17

추석 연휴 동안 푹 쉬고 다시 모작 개발을 시작했습니다. 

여러분은 푹 쉬셨나요? 

푹 쉬셨기를 바라며, 

이제 다시 모작 개발 시작해보겠습니다. 

 

이동제한

여태까지는 미봉책으로 Clamp를 사용하여, 플레이어의 움직임 범위를 제한했습니다. 

하지만, 실제 길건너 친구들에서는, 특정 범위를 넘어가면, 죽는 기믹이 있습니다. 

뗏목을 타고 범위 밖으로 이동하여 죽는 모습

따라서, 이동 위치를 직접적으로 제한하는 Clamp는 사용하지 않는 것이 맞습니다. 

그래서 자세히 살펴본 결과, 게임 내에서는 위에 나온 강 말고는, 모두 여러가지 장애물을 통해

이동을 막아둔 것을 확인 할 수 있었습니다. 

 

장애물의 특징은, 플레이어 앞에 장애물이 있을 경우, 그 위치로 이동하지 못한다는 점이 있습니다. 

장애물에 막혀 이동하지 못하는 모습

그래서 캐릭터가 본인 앞에 장애물이 있다는 것을 인식하고 있어야 합니다. 

여러 방법을 고려해보고, 캐릭터 정면을 향해, Ray를 발사하여, 검출하는 방법으로 만들어 보기로 하였습니다. 

 

먼저 플레이어의 앞으로 레이를 발사할 위치를 빈 오브젝트로 만들고, 플레이어의 자식으로 만들어 주었습니다.

그리고 이 위치에서 레이를 발사하고, Obstacle이라는 Layer를 가지고 있는 오브젝트만을 검출하도록 만들어주었습니다. 

Obstacle Layer를 가지고 있는 프리팹들
1만큼의 길이를 가진 레이를 발사

그리고, isFrontObstacle이라는 bool 변수를 선언하고, 앞에 장애물이 있다면 true 없다면 false로 되도록 만들었습니다. 

기존의 만들어둔 이동부분의 조건으로서 isFrontObstalce을 추가하여 

false인 경우에만 움직이도록, 즉 앞에 장애물이 없을때만 움직이도록 했습니다. 

실제 테스트 해본 결과

위에 결과처럼, 잘 작동하는 것을 볼 수 있습니다. 

사용한 스크립트 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class Player : MonoBehaviour
{
    #region Delegate
    private System.Action<Vector3> onCanceled;
    public System.Action onCoin;
    public System.Action onPoint;
    public System.Action onDie;
    #endregion
    private Vector3 moveDir;
    private Animator anim;
    #region PlayerMove
    private PlayerInput playerInput;
    private InputActionMap mainActionMap;
    private InputAction moveAction;
    #endregion
    #region State
    private bool isDie = false;
    private bool isMoveAble = false;

    public bool IsMoveAble { get => isMoveAble; set => isMoveAble = value; }
    public bool IsOut { get => isOut; set => isOut = value; }
    #endregion
    [SerializeField] private float turnSpeed = 3.0f;

    [SerializeField] private Transform raySpot;
    [SerializeField] private bool isFrontObstacle = false;
    private bool isOnRaft = false;
    private bool isOut = false;

    // Start is called before the first frame update
    void Start()
    {
        this.anim = GetComponent<Animator>();
        this.PlayerMoveInit();  
        this.PlayerMove();
    }

    // Update is called once per frame
    void Update()
    {
        this.PlayerRotation();
        this.RayHitObstacle();

        if (this.isOut) this.isDie = true;
    }

    private void PlayerMoveInit()
    {
        this.playerInput = this.GetComponent<PlayerInput>();
        this.mainActionMap = this.playerInput.actions.FindActionMap("Player");
        this.moveAction = this.mainActionMap.FindAction("Move");
    }
    private void PlayerMove()
    {
        this.moveAction.performed += (context) =>
        {
            if (this.isMoveAble && this.isDie == false)
            {
                Vector2 dir = context.ReadValue<Vector2>();
                this.moveDir = new Vector3(dir.x, 0, dir.y);
                //애니메이션 실행
                this.anim.SetTrigger("Input");
            }
        };
        this.moveAction.canceled += (context) =>
        {
            if (this.isMoveAble && this.isDie == false)
            {
                this.anim.SetTrigger("Idle");
                this.onCanceled(moveDir);
            }
        };
        this.onCanceled = (moveDir) =>
        {
            if (this.isFrontObstacle == false)
            {
                this.anim.SetTrigger("Jump");
                // StartCoroutine(CoMove(this.transform.position,moveDir));
                this.transform.position = this.transform.position + (moveDir.normalized);
            }
        };
    }
    private void PlayerRotation()
    {
        if (this.moveDir != Vector3.zero && this.isDie == false)
        {
            Quaternion rot = Quaternion.LookRotation(this.moveDir);
            this.transform.rotation = Quaternion.Slerp(this.transform.rotation, rot,
                Time.deltaTime * this.turnSpeed);
        }
    }

    private void RayHitObstacle()
    {
        Ray ray = new Ray(this.raySpot.position, this.raySpot.forward);
        Debug.DrawRay(ray.origin, ray.direction * 1f, Color.red);
        int layerMask = 1 << LayerMask.NameToLayer("Obstacle");
        RaycastHit hit;
        if (Physics.Raycast(ray, out hit, 1f, layerMask))
        {
            this.isFrontObstacle = true;
        }
        else
        {
            this.isFrontObstacle = false;
        }
    }
    private Vector3 ClampPosition(Vector3 position)
    {
        return new Vector3(Mathf.Clamp(position.x, -4.0f, 6f),
            position.y, position.z);
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Ground"))
        {
            if (this.moveDir == Vector3.forward && other.gameObject.GetComponent<GroundPlayerCheck>().IsCheck==false)
            {
                this.onPoint();
            }
        }

        else if (other.CompareTag("Coin"))
        {
            this.onCoin();
        }
        else if (other.CompareTag("Vehicle"))
        {
            this.onDie();
            this.isDie = true;
            this.anim.SetTrigger("DieByCar");
        }

        else if (other.CompareTag("Raft"))
        {
            this.isOnRaft=true;
            this.transform.SetParent(other.transform);

            if (this.moveDir == Vector3.forward && other.gameObject.GetComponent<RaftAnimControl>().IsCheck==false)
            {
                this.onPoint();
            }
        }

        if (other.CompareTag("Water") && this.isOnRaft==false)
        {
            this.onDie();
            this.isDie = true;
            this.anim.SetTrigger("DieByWater");
        }
    }


    private void OnTriggerExit(Collider other)
    {
        if (other.CompareTag("Raft"))
        {
            this.isOnRaft = false;
            this.transform.SetParent(null);
            this.transform.position = new Vector3((int)this.transform.position.x,
                this.transform.position.y, this.transform.position.z);
        }
    }

}

이렇게 장애물을 통해, 플레이어의 이동을 제한하는 방법 구현을 완료했습니다.