Unity/John Lemon in Nightmareland

Unity Piscine Module06 - 5. FPS / TPS 시점 적용

surkim 2024. 8. 25. 14:50

TPS와 FPS 시점을 모두 구현해야 했는데, 처음에는 간단할 줄 알았다. 하지만 실제로 구현해보니 생각보다 많은 문제들이 발생했다. 이번 포스트에서는 이를 해결하기 위해 겪었던 과정과 배운 점들을 기록해본다.

TPS 시점

0

TPS 시점에서는 Unity의 Cinemachine을 적극 활용했다. 특히 FreeLook Camera를 사용하여 플레이어를 따라다니도록 설정했는데, 이 과정에서 중요하게 고려해야 했던 부분이 있었다.

문제 1: 카메라 축 고정

카메라가 플레이어를 따라가도록 설정하면, 이동할 때 카메라와 플레이어의 방향이 일치하지 않아서 조작이 불편해질 수 있다. 이를 해결하기 위해 카메라의 축을 고정하여 항상 플레이어가 이동하는 방향을 기준으로 시점이 유지되도록 설정했다.

문제 2: 플레이어 가리기

카메라와 플레이어 사이에 벽이나 장애물이 있으면 플레이어가 보이지 않게 되는 문제가 발생했다. 이 문제를 해결하기 위해 카메라와 플레이어 사이에 있는 오브젝트들을 감지한 후, 해당 오브젝트들을 투명하게 만들어 플레이어를 가리지 않도록 처리했다.

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

public class CameraObstacleHandler : MonoBehaviour
{
    [SerializeField] private Transform playerTransform;
    [SerializeField] private Transform cameraTransform;
    [SerializeField] private LayerMask obstacleLayer;
    private bool isFPS = false;

    private List<Renderer> previousObstacles = new List<Renderer>();

    void LateUpdate()
    {
        if (isFPS)
            return;
        Vector3 direction = playerTransform.position - cameraTransform.position;
        RaycastHit[] hits = Physics.RaycastAll(cameraTransform.position, direction.normalized, direction.magnitude, obstacleLayer);

        List<Renderer> currentObstacles = new List<Renderer>();

        foreach (RaycastHit hit in hits)
        {
            Renderer renderer = hit.collider.GetComponent<Renderer>();
            if (renderer != null)
            {
                currentObstacles.Add(renderer);
                SetTransparency(renderer, 0.3f);
            }
        }

        foreach (Renderer renderer in previousObstacles)
        {
            if (!currentObstacles.Contains(renderer))
            {
                SetTransparency(renderer, 1f);
            }
        }

        previousObstacles = currentObstacles;
    }

     void SetTransparency(Renderer renderer, float alpha)
    {
        foreach (Material material in renderer.materials)
        {
            Color color = material.color;
            color.a = alpha;
            material.color = color;

            if (alpha < 1f)
            {
                material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
                material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
                material.SetInt("_ZWrite", 0);
                material.DisableKeyword("_ALPHATEST_ON");
                material.EnableKeyword("_ALPHABLEND_ON");
                material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
                material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent;
            }
            else
            {
                material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
                material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
                material.SetInt("_ZWrite", 1);
                material.DisableKeyword("_ALPHABLEND_ON");
                material.renderQueue = -1;
            }
        }
    }

    public void SwitchToFPS()
    {
        isFPS = true;
        foreach (Renderer renderer in previousObstacles)
        {
            SetTransparency(renderer, 1f);
        }
    }

    public void SwitchToTPS()
    {
        isFPS = false;
    }


}

 

코드 설명:

  1. 카메라와 플레이어 간의 벡터를 계산해 그 경로상의 장애물들을 감지한다.
  2. 감지된 오브젝트들을 투명하게 만들어 플레이어가 보이도록 한다.
  3. FPS 모드로 전환되면 이러한 처리를 비활성화하여 성능을 최적화한다.

FPS 시점

0

FPS 시점에서는 Cinemachine Virtual Camera를 사용했다. 여기서 몇 가지 예상치 못한 문제들을 만나게 되었다.

문제 1: 부자연스러운 움직임과 에임 문제

처음에는 플레이어의 회전과 카메라 회전을 동기화시키려 했으나, 이는 결과적으로 부자연스러운 움직임을 초래했다. 결국 카메라의 에임을 POV로 설정하고, 카메라의 방향에 플레이어를 맞추는 방식으로 변경했다.

    void HandleMovementFPS()
    {
        Vector3 cameraForward = fpsCamera.transform.forward;
        cameraForward.y = 0f;
        cameraForward.Normalize();

        if (cameraForward.magnitude > 0.1f)
        {
            rotation = Quaternion.LookRotation(cameraForward);
            rb.MoveRotation(rotation);
        }

        if (Input.GetKey(KeyCode.Z))
        {
            animator.SetBool("IsWalking", true);
            rb.velocity = cameraForward * moveSpeed;
        }
        else
        {
            animator.SetBool("IsWalking", false);
            rb.velocity = Vector3.zero;
            rb.angularVelocity = Vector3.zero;
        }
    }

 

문제 2: 카메라가 플레이어 머리 내부를 보게 되는 문제

FPS 시점에서는 카메라가 플레이어의 머리 내부를 볼 수 있는 문제가 발생했다. 이를 해결하기 위해 카메라의 Near Clip Plane 값을 조정해봤지만, 벽과 가까워지면 벽을 뚫어보는 현상이 발생했다. 최종적으로는 플레이어의 콜라이더를 살짝 넓히고, FPS 모드에서는 플레이어 레이어를 렌더링하지 않도록 설정했다.

 

결론

이번 시점 전환 구현을 통해 Cinemachine의 강력한 기능을 충분히 활용할 수 있었다. TPS와 FPS 모두 자연스럽게 전환되도록 하면서, 다양한 문제를 해결해 나가면서 많이 배웠다. 특히 시점 전환에서 중요한 점은, 플레이어의 조작감을 해치지 않으면서도 시야가 명확하게 유지되도록 하는 것이라는 걸 깨달았다.

 

이제 맵구현 해야지