TIL(2024,06,19)팀프로젝트 자유주제 (unRailed)만들어보기

김보근·2024년 6월 19일

Unity

목록 보기
20/113

오늘은 unRailed의 캐릭터의 상호작용을 만들어볼예정이다.

캐릭터

일단 캐릭터 이동부분은 간단하게 만들었다.

using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
    public float speed = 6.0f;
    public float jumpSpeed = 8.0f;
    public float gravity = 20.0f;

    private Vector3 moveDirection = Vector3.zero;
    private CharacterController controller;

    void Start()
    {
        controller = GetComponent<CharacterController>();
    }

    void Update()
    {
        if (controller.isGrounded)
        {
            // 플레이어가 땅에 있을 때 이동 방향 설정
            float moveHorizontal = Input.GetAxis("Horizontal");
            float moveVertical = Input.GetAxis("Vertical");

            moveDirection = new Vector3(moveHorizontal, 0.0f, moveVertical);
            moveDirection = transform.TransformDirection(moveDirection);
            moveDirection *= speed;

            
        }

        // 중력 적용
        moveDirection.y -= gravity * Time.deltaTime;

        // 이동 적용
        controller.Move(moveDirection * Time.deltaTime);
    }
}

이렇게 설정하고 나서

캐릭터 컨트롤러라는 컴포넌트를 이용해서 움직인다.

스페이스바를 이용한 물건 들기

스크립트

using UnityEngine;

public class PlayerInteraction : MonoBehaviour
{
    public float interactionRange = 2.0f;     // 상호작용 범위 (전방)
    public float closeRange = 1.0f;           // 가까운 범위 탐지 구 반지름
    public Transform holdPoint;               // 들기 위한 지점(Transform)
    public LayerMask interactableLayer;       // 상호작용 가능한 레이어 마스크
    public Material highlightMaterial;        // 하이라이트할 때 사용할 재질

    private IInteractable heldObject = null;      // 현재 들고 있는 객체
    private IHighlightable highlightedObject = null; // 현재 하이라이트된 객체

    void Update()
    {
        HighlightInteractable();    // 상호작용 가능한 객체 하이라이트 처리
        HandleInput();              // 입력 처리 (Space 키)

    }

    private void HandleInput()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            if (heldObject == null)
            {
                TryPickup();    // 들기 시도
            }
            else
            {
                TryPlaceOrDrop();   // 놓기 또는 드롭 시도
            }
        }
    }

    private void TryPickup()
    {
        Collider[] hitColliders = Physics.OverlapSphere(transform.position, closeRange, interactableLayer); // 발밑의 가까운 범위에 있는 Collider 배열 가져오기
        foreach (Collider hitCollider in hitColliders)
        {
            IInteractable interactable = hitCollider.GetComponent<IInteractable>(); // 상호작용 가능한 객체 가져오기
            if (interactable != null && IsFacing(hitCollider.gameObject)) // 방향이 맞고 상호작용 가능한 객체인 경우
            {
                heldObject = interactable;  // 객체를 들고
                heldObject.Pickup(holdPoint);   // 들기 처리
                return;
            }
        }
        Debug.Log("No interactable object in range."); // 범위 내에 상호작용 가능한 객체가 없는 경우 로그 출력
    }

    private bool IsWithinView(GameObject target)
    {
        Vector3 directionToTarget = (target.transform.position - transform.position).normalized; // 캐릭터에서 타겟까지의 방향 벡터
        Vector3 forward = transform.forward; // 캐릭터의 전방 벡터

        // y축을 무시한 방향 벡터 계산
        forward.y = 0;
        directionToTarget.y = 0;

        // 벡터를 정규화하여 다시 계산
        forward.Normalize();
        directionToTarget.Normalize();

        float angle = Vector3.Angle(forward, directionToTarget); // 전방과 타겟 사이의 각도 계산
        return angle <= 30f; // 180도의 절반인 90도 이내인지 확인하여 반환
    }

    private void HighlightInteractable()
    {
        if (heldObject != null) return; // 들고 있는 객체가 있으면 반환

        Collider[] hitColliders = Physics.OverlapSphere(transform.position, closeRange, interactableLayer); // 발밑의 가까운 범위에 있는 Collider 배열 가져오기
        foreach (Collider hitCollider in hitColliders)
        {
            IHighlightable highlightable = hitCollider.GetComponent<IHighlightable>(); // 하이라이트 가능한 객체 가져오기
            if (highlightable != null && IsWithinView(hitCollider.gameObject)) // 객체가 존재하고 캐릭터가 보고 있는 방향에 있는 경우
            {
                if (highlightedObject != null && highlightedObject != highlightable)
                {
                    highlightedObject.ResetHighlight(); // 기존에 하이라이트된 객체가 있으면 리셋
                }
                highlightedObject = highlightable; // 현재 하이라이트된 객체 설정
                highlightedObject.Highlight(); // 하이라이트 처리
                return;
            }
        }

        if (highlightedObject != null)
        {
            highlightedObject.ResetHighlight(); // 하이라이트된 객체가 있으면 리셋
            highlightedObject = null;
        }
    }

    private bool IsFacing(GameObject target)
    {
        Vector3 directionToTarget = (target.transform.position - transform.position).normalized; // 캐릭터에서 타겟까지의 방향 벡터
        return Vector3.Dot(transform.forward, directionToTarget) > 0.5f; // 캐릭터의 전방 벡터와 타겟까지의 방향 벡터의 내적값이 0.5보다 큰지 반환
    }

    private void TryPlaceOrDrop()
    {
        if (heldObject.TryPlace())
        {
            heldObject = null; // 놓기 성공하면 들고 있는 객체 비우기
        }
        else
        {
            heldObject.Drop(); // 놓기 실패하면 객체 드롭
            heldObject = null;
        }
    }

    private void OnDrawGizmos()
    {
        // 가까운 범위 탐지 구를 그립니다.
        Gizmos.color = Color.blue;
        Gizmos.DrawWireSphere(transform.position, closeRange);

        // 전방 상호작용 범위를 그립니다.
        Gizmos.color = Color.red;
        Vector3 forward = transform.forward * interactionRange;
        Gizmos.DrawLine(transform.position, transform.position + forward);

        // 상호작용 각도를 그립니다.
        Gizmos.color = Color.green;
        // y축을 0으로 설정하여 평면에서만 각도 계산
        Vector3 forwardFlat = new Vector3(transform.forward.x, 0, transform.forward.z).normalized;

        // 30도 각도로 변경
        float angle = 30f;
        Vector3 rightBoundary = Quaternion.Euler(0, angle, 0) * forwardFlat * interactionRange;
        Vector3 leftBoundary = Quaternion.Euler(0, -angle, 0) * forwardFlat * interactionRange;

        Gizmos.DrawLine(transform.position, transform.position + rightBoundary);
        Gizmos.DrawLine(transform.position, transform.position + leftBoundary);

        // 상호작용 각도 안의 객체를 시각적으로 표시
        Collider[] hitColliders = Physics.OverlapSphere(transform.position, closeRange, interactableLayer);
        foreach (Collider hitCollider in hitColliders)
        {
            Vector3 directionToTarget = (hitCollider.transform.position - transform.position).normalized;
            directionToTarget.y = 0; // y축을 무시한 방향 벡터

            float targetAngle = Vector3.Angle(forwardFlat, directionToTarget);
            if (targetAngle <= angle) // 설정된 각도 내에 있는지 확인
            {
                Gizmos.color = Color.yellow;
                Gizmos.DrawLine(transform.position, hitCollider.transform.position);
            }
        }
    }
}

레이캐스트를 이용해 앞의 물체를 감지하고 스페이스바를 이용해 지정해둔 트랜스폼으로 오브젝트가 들어오게 된다.

using UnityEngine;

public class Pickupable : MonoBehaviour, IInteractable
{
    private Transform originalParent;

    public void Pickup(Transform holdPoint)
    {
        originalParent = transform.parent;
        transform.parent = holdPoint;
        transform.localPosition = Vector3.zero;
        transform.localRotation = Quaternion.identity;
    }

    public void Drop()
    {
        transform.parent = originalParent;
    }

}

Pickup 메서드:

Pickup(Transform holdPoint): 객체를 집을 때 호출됩니다. 인자로 받은 holdPoint에 해당 객체를 부모로 설정하여 집은 위치로 이동시킵니다. 이 때 객체의 로컬 위치와 회전을 초기화하여 정확히 holdPoint 위치에 배치됩니다.
Drop 메서드:

Drop(): 객체를 놓을 때 호출됩니다. originalParent를 통해 이전에 객체가 속했던 부모를 찾아 그 아래로 다시 배치합니다.

using UnityEngine;

public class Highlightable : MonoBehaviour, IHighlightable
{
    private Renderer RenDerer;
    private Material originalMaterial;
    public Material highlightMaterial;

    void Start()
    {
        RenDerer = GetComponent<Renderer>();
        originalMaterial = RenDerer.material;
    }

    public void Highlight()
    {
        RenDerer.material = highlightMaterial;
    }

    public void ResetHighlight()
    {
        RenDerer.material = originalMaterial;
    }
}

Start 메서드:

Start(): 객체가 활성화될 때 호출되며, Renderer 컴포넌트를 가져와 originalMaterial에 원래의 재질을 저장합니다.

Highlight 메서드:

Highlight(): 객체를 강조 표시할 때 호출됩니다. highlightMaterial을 객체의 Renderer에 적용하여 객체를 강조 표시합니다.
ResetHighlight 메서드:

ResetHighlight(): 객체의 강조 표시를 원래 상태로 되돌릴 때 호출됩니다. originalMaterial을 객체의 Renderer에 적용하여 객체의 재질을 원래의 상태로 복구합니다.

이렇게 설정하고 밑의 두개의 스크립트는 상호작용할 물건에 넣의면되고 PlayerInteraction은 플레이어에 넣게 되면 상호작용이 가능하다.

profile
게임개발자꿈나무

0개의 댓글