유니티 부트캠프 8기/Ch05. Unity 게임 개발 숙련

벽 타기, 매달리기

Imperor 2025. 3. 11. 11:07

기존의 Vector2 좌표 체계를 바꾸지 않고 이를 Vector3 좌표계로 변환하는 연습을 했다

 

    void CheckWall()
    {
        // Raycast를 사용하여 벽 감지
        RaycastHit hit;
        isNearWall = Helper.Raycast(transform.position, transform.forward, out hit, raycastDistance, wallLayer);
        
        if (isNearWall && hit.collider.CompareTag("Rock")) // 벽에 접근
        {
            // 벽이 감지되었을 때 추가 로직 처리
            rockNormal = hit.normal;    // Raycast로 얻은 법선 벡터 사용

            // 벽에 접근하면 벽 타기 상태로 전환
            if (Input.GetKey(KeyCode.W))
            {
                isClimbing = true;
                playerAnimator.SetBool("Climbing", true);
                _rigidbody.useGravity = false;
            }
        }
        else if (isClimbing) // 벽타기 중
        {
            // 벽 타기 중일 때의 물리적 처리
            _rigidbody.useGravity = false;

            /// 벽 쪽으로 밀어주는 힘 추가 (벽에서 떨어지지 않도록)
            _rigidbody.AddForce(-rockNormal * 10f, ForceMode.Force);
        }
        else // 벽에서 멀어질때, 벽타다가 내렸다
        {
            isNearWall = false;

            // 벽에서 멀어지면 벽 타기 종료
            if (isClimbing)
            {
                isClimbing = false;
                playerAnimator.SetBool("Climbing", false);
                _rigidbody.useGravity = true;
            }
        }
    }

 

이렇게 하면 되는줄 알았지만....

벽을 넘어서 허공을 타고 올라간다...

 

아래처럼 고치고 나서 해결됐다

    void CheckWall()
    {
        // Raycast를 사용하여 벽 감지
        RaycastHit hit;
        isNearWall = Helper.Raycast(transform.position, transform.forward, out hit, raycastDistance, wallLayer);
        
        if (isNearWall && hit.collider.CompareTag("Rock"))
        {
            // 벽이 감지되었을 때 추가 로직 처리
            rockNormal = hit.normal;    // Raycast로 얻은 법선 벡터 사용

            // 벽에 접근하면 벽 타기 상태로 전환
            if (Input.GetKey(KeyCode.W))
            {
                isClimbing = true;
                playerAnimator.SetBool("Climbing", true);
                _rigidbody.useGravity = false;
            }
        }
        else // 벽에서 멀어질때
        {
            isNearWall = false;

            // 벽에서 멀어지면 벽 타기 종료
            if (isClimbing)
            {
                isClimbing = false;
                playerAnimator.SetBool("Climbing", false);
                _rigidbody.useGravity = true;
            }
        }
        //
        if (isClimbing) // 벽 타기 중
        {
            // 벽 타기 중일 때의 물리적 처리
            _rigidbody.useGravity = false;

            /// 벽 쪽으로 밀어주는 힘 추가 (벽에서 떨어지지 않도록)
            _rigidbody.AddForce(-rockNormal * 10f, ForceMode.Force);
        }
    }

 

 

실제 이동은 다음처럼

    // 실제 이동
    void Move()
    {
        if (isClimbing)
        {
            // 벽의 오른쪽 방향 (표면 평행)
            Vector3 wallRight = Vector3.Cross(rockNormal, Vector3.up).normalized;
            // 벽의 위쪽 방향 (표면 평행)
            Vector3 wallUp = Vector3.Cross(wallRight, rockNormal).normalized;
            // 입력 조합 (A/D = 좌우, W/S = 상하)
            Vector3 moveDirection = (wallRight * curMovementInput.x) + (wallUp * curMovementInput.y);
            moveDirection *= moveSpeed;


            _rigidbody.velocity = moveDirection;  // 클라이밍 중 이동 적용
        }
        else // 일반 이동
        {
            Vector3 dir = transform.forward * curMovementInput.y + transform.right * curMovementInput.x;
            dir *= moveSpeed;
            dir.y = _rigidbody.velocity.y; // 점프를 했을 때에만 위아래로 움직여야 하므로 y방향 속도 초기화

            _rigidbody.velocity = dir;
        }
    }

이동로직 만드는게 정말 어려웠다

Vector2를 Vector3로 바꾸는 작업이 있어야 했다

 

 

 

원리를 설명한다

            // 벽의 오른쪽 방향 (표면 평행)
            Vector3 wallRight = Vector3.Cross(rockNormal, Vector3.up).normalized;
            // 벽의 위쪽 방향 (표면 평행)
            Vector3 wallUp = Vector3.Cross(wallRight, rockNormal).normalized;
            // 입력 조합 (A/D = 좌우, W/S = 상하)
            Vector3 moveDirection = (wallRight * curMovementInput.x) + (wallUp * curMovementInput.y);
            moveDirection *= moveSpeed;

이 부분을 이해하기 위해서는 벡터의 외적연산을 공부해야한다

Vector3.Cross는 벡터곱이며

현재 플레이어와 충돌한 Rock의 Mesh Collider의 일부(삼각형)에 수직인 Normal벡터와 플레이어의 up벡터(0, 1, 0)과 외적이다

 

현재 Vector2는

W는 (0,1), S는 (0,-1)

A는 (-1,0), D는 (1,0) 으로 설정되어있다

A와 D는 좌우이동이므로 영향이 없다

 

문제는 W와 A이다

지금 현재 W를 방향은 Rock을 뚫고 들어가는 방향이다

Vector2의 y가 변하니까 Vector3의 z가 변한다는 사실을 발견했다.

 

Vector2의 y는 Vector3의 z에 대응된다

벽은 플레이어와 거의 수직하므로 면에 수직인 normal벡터는 z방향에 거의 평행할 것이다

따라서 이 두 벡터를 가지고 외적하면 벽에 올라갈 수 없다!

 

잘 보면 플레이어의 ray가 벽에 닿을떄는 

Vector3.up 과 외적하라고했는데 이건 Vector3의 y축이다

        if (isClimbing)
        {
            // 벽의 오른쪽 방향 (표면 평행)
            Vector3 wallRight = Vector3.Cross(rockNormal, Vector3.up).normalized;
            // 벽의 위쪽 방향 (표면 평행)
            Vector3 wallUp = Vector3.Cross(wallRight, rockNormal).normalized;
            // 입력 조합 (A/D = 좌우, W/S = 상하)
            Vector3 moveDirection = (wallRight * curMovementInput.x) + (wallUp * curMovementInput.y);
            moveDirection *= moveSpeed;

 

 

 

벽의 노멀벡터(z방향에 근접)와 Vector3.up벡터가 외적하면 x방향이 나온다

그 x방향과 벽의 노멀벡터(z방향)을 다시 외적하면 y방향이 나온다

그러면 그 y방향으로 벽을 타고 올라가는 것이다

 

외적연산을 이용한 이유는

벽의 표면이 바뀌면, 노멀벡터가 바뀌기 때문에 그에 맞게 이동할 수 있기 때문이다

 

 

FixedUpdate에서 매 프레임 호출

    // 물리연산은 FixedUpdate에서 호출
    void FixedUpdate()
    {
        CheckWall();
        Move();

        // 벽에 붙은 채로 입력이 없는 경우 벽 타기 애니메이션 일시 정지
        if (isClimbing && curMovementInput.magnitude == 0)
        {
            playerAnimator.speed = 0; // 애니메이션 일시 정지
        }
        else
        {
            playerAnimator.speed = 1; // 애니메이션 재개
        }
    }