Development/Unity Engine

[Retro유니티] Shooter 구현 - IK를 사용한 손/발 위치와 총 위치 <-> Animation 대응

사이바 미도리 2024. 12. 19. 13:01

PlayerShooter 스크립트를 만들자.

1. 입력에 따라 총을 쏘거나 재장전한다.

2. 손은 항상 총의 손잡이에 위치하게 한다. -> IK를 사용해야 한다.

 

IK란?

먼저 FK를 알아야 한다.

FK란?

Forward Kinematics 의 줄임말이다.

부모 joint에서 자식 joint 순서로 움직임을 적용한다.

FK로 물건을 집으면

1. 어깨를 움직인다

2. 팔을 움직인다

3. 손을 움직인다. 순으로 동작한다.

FK로 물건을 집는다면, 손의 위치는 누적계산된 최종결과이기 때문에, 손의 위치를 먼저 정하고 거기에 맞춰서 애니메이션을 변형할 수 없기 때문에, 손의 위치로 물건을 순간이동해야 한다.

 

반대로, IK는 자식 joint 에서 부모 joint 순서로 움직임을 적용하는 것이다. Inverse Kinematics

1. 손이 물건 위치로 이동한다.

2. 팔이 손의 위치에 맞게 움직인다

3. 어깨가 맞게 움직인다.

 

IK를 사용하려면, Animation Controller 레이어에서, IK Pass 설정이 켜져있어야 한다.

 

따라서 캐릭터 상체에 재생되는 Animation에는 IK가 이미 적용되어있다.

 

 

Animator Component가 IK정보를 갱신할 때마다, 자동적으로 OnAnimatorIK 메시지가 발생한다.

스크립트에서 OnAnimatorIK 메서드를 구현하면, IK를 어떻게 사용할지 코드로 작성할 수 있다.

우리는 캐릭터의 손이 항상 총의 손잡이에 위치하도록 OnAnimatorIK()를 사용할 것이다.

 

PlayerShooter.cs 구현

    private void Start() {
        // 사용할 컴포넌트들을 가져오기
        playerInput = GetComponent<PlayerInput>();
        playerAnimator = GetComponent<Animator>();
    }

OnEnable은 PlayerShooter 컴포넌트 활성화시 자동실행된다.

슈터 활성화시, 총도 함께 활성화되어야 하므로, Gun을 SetActive 한다.

    private void OnEnable() {
        // 슈터가 활성화될 때 총도 함께 활성화
        gun.gameObject.SetActive(true);
    }
    
	private void OnDisable() {
        // 슈터가 비활성화될 때 총도 함께 비활성화
        gun.gameObject.SetActive(false);
    }

 

Update 메서드에서, 매 frame마다 입력을 감지하고 총을 발사/재장전한다.

 

    private void Update() {
        // 입력을 감지하고 총 발사하거나 재장전
        if(playerInput != null) {  
            if(playerInput.fire) {
                gun.Fire();
            }
            else if(playerInput.reload) {
                if(gun.Reload()) {
                    playerAnimator.SetTrigger("Reload");
                }
            }
        }
        UpdateUI(); // 남은 탄약 UI 갱신
    }

재장전 시도결과를 if문으로 검사한 뒤, 성공했을 경우에만 SetTrigger("Reload")를 통해 Reload 애니메이션을 발동한다.

UpdateUI를 통해 매 frame마다 잔탄 UI를 갱신한다.

 

    // 남은 탄약 UI 갱신
    private void UpdateUI() {
        if (gun != null && UIManager.instance != null)
        {
            // UI 매니저의 탄약 텍스트에 탄창의 탄약과 남은 전체 탄약을 표시
            UIManager.instance.UpdateAmmoText(gun.magAmmo, gun.ammoRemain);
        }
    }

 

UIManager의 UpdateAmmotext는 탄알UI에 즉시 접근하여 현재 탄을 UI로 표시해준다.

UIManager는 싱글턴 객체로서, 각종 게임 UI에 즉시 접근할 수 있는 통로를 제공한다.

조건문에 UIManager 싱글턴이 존재하는 경우에만 실행하게 하였으므로, 컴파일 오류는 나지 않는다.(오 이건 신기한데?)

 

OnAnimatorIK() 메서드는 2가지 일을 해야 한다.

1. 총을 상체와 함께 흔들기

2. 캐릭터의 양손을 총의 각 파츠에 배치시키기

    // 애니메이터의 IK 갱신
    private void OnAnimatorIK(int layerIndex) {
        gunPivot.position = playerAnimator.GetIKHintPosition(AvatarIKHint.RightElbow); // 총의 기준점을 오른쪽 팔꿈치 위치로

        playerAnimator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1.0f); // 왼손 위치를 지정
        playerAnimator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1.0f); // 왼손 회전을 지정
        playerAnimator.SetIKPosition(AvatarIKGoal.LeftHand, leftHandMount.position); // 왼손의 위치를 왼손잡이의 위치로
        playerAnimator.SetIKRotation(AvatarIKGoal.LeftHand, leftHandMount.rotation); // 왼손의 회전을 왼손잡이의 회전으로

        playerAnimator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1.0f); // 오른손 위치를 지정
        playerAnimator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1.0f); // 오른손 회전을 지정
        playerAnimator.SetIKPosition(AvatarIKGoal.RightHand, rightHandMount.position); // 오른손의 위치를 오른손잡이의 위치로
        playerAnimator.SetIKRotation(AvatarIKGoal.RightHand, rightHandMount.rotation); // 오른손의 회전을 오른손잡이의 회전으로

    }

 

먼저 GetIKHintPosition을 통해 AvatarIKHint 타입을 가져온다.

부위를 표현하는 타입인 AvatarIKHint 의 각 멤버변수는 아래와 같이 존재한다.

  • LeftElbow
  • RightElbow
  • LeftKnee
  • RightKnee

 

AvatarIKGoal 타입을 사용하자. 각 부위를 표현하는 타입은 아래와 같다.

  • LeftHand
  • RightHand
  • LeftFoot
  • RightFoot

IK대상의 가중치를 아래 함수로 설정할 수 있다.

SetIKPositionWeight

SetIKRotationWeight

가중치의 범위는 0~1이며, 해당 부위의 원 위치와 IK에 의한 목표 위치 사이에서 실제로 적용할 중간 값을 결정한다.

예를 들어, IK 가중치가 0.5라면 원래 위치와 IK 목표 위치가 절반씩 섞여 적용된다.

 

SetIKPosition과 SetIKRotation은 3D모델의 위치를 그대로 IK Position/Rotation으로 가져올 수 있게 한다.

 

 

완성된 PlayerShooter.cs

using UnityEngine;

// 주어진 Gun 오브젝트를 쏘거나 재장전
// 알맞은 애니메이션을 재생하고 IK를 사용해 캐릭터 양손이 총에 위치하도록 조정
public class PlayerShooter : MonoBehaviour {
    public Gun gun; // 사용할 총
    public Transform gunPivot; // 총 배치의 기준점
    public Transform leftHandMount; // 총의 왼쪽 손잡이, 왼손이 위치할 지점
    public Transform rightHandMount; // 총의 오른쪽 손잡이, 오른손이 위치할 지점

    private PlayerInput playerInput; // 플레이어의 입력
    private Animator playerAnimator; // 애니메이터 컴포넌트

    private void Start() {
        // 사용할 컴포넌트들을 가져오기
        playerInput = GetComponent<PlayerInput>();
        playerAnimator = GetComponent<Animator>();
    }

    private void OnEnable() {
        // 슈터가 활성화될 때 총도 함께 활성화
        gun.gameObject.SetActive(true);
    }
    
    private void OnDisable() {
        // 슈터가 비활성화될 때 총도 함께 비활성화
        gun.gameObject.SetActive(false);
    }

    private void Update() {
        // 입력을 감지하고 총 발사하거나 재장전
        if(playerInput != null) {  
            if(playerInput.fire) {
                gun.Fire();
            }
            else if(playerInput.reload) {
                if(gun.Reload()) {
                    playerAnimator.SetTrigger("Reload");
                }
            }
        }
        UpdateUI(); // 남은 탄약 UI 갱신
    }

    // 남은 탄약 UI 갱신
    private void UpdateUI() {
        if (gun != null && UIManager.instance != null)
        {
            UIManager.instance.UpdateAmmoText(gun.magAmmo, gun.ammoRemain); // UI 매니저의 탄약 텍스트에 탄창의 탄약과 남은 전체 탄약을 표시
        }
    }

    // 애니메이터의 IK 갱신
    private void OnAnimatorIK(int layerIndex) {
        gunPivot.position = playerAnimator.GetIKHintPosition(AvatarIKHint.RightElbow); // 총의 기준점을 오른쪽 팔꿈치 위치로

        playerAnimator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1.0f); // 왼손 위치를 지정
        playerAnimator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1.0f); // 왼손 회전을 지정
        playerAnimator.SetIKPosition(AvatarIKGoal.LeftHand, leftHandMount.position); // 왼손의 위치를 왼손잡이의 위치로
        playerAnimator.SetIKRotation(AvatarIKGoal.LeftHand, leftHandMount.rotation); // 왼손의 회전을 왼손잡이의 회전으로

        playerAnimator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1.0f); // 오른손 위치를 지정
        playerAnimator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1.0f); // 오른손 회전을 지정
        playerAnimator.SetIKPosition(AvatarIKGoal.RightHand, rightHandMount.position); // 오른손의 위치를 오른손잡이의 위치로
        playerAnimator.SetIKRotation(AvatarIKGoal.RightHand, rightHandMount.rotation); // 오른손의 회전을 오른손잡이의 회전으로

    }
}

 

Guns와 마찬가지로,  Component 설정시키기

그냥 드래그

R클릭시 장전, 왼클릭시 총을 사격하는 것을 확인할수있다.

 

여태까지 Gun과 총을 쏘는 Shooter를 구현했다.

이제, 체력과 좀비의 인공지능을 구현하자.

 

하나의 Animation만 있음에도 불구하고,

IK를 활용해서, 손과 발의 위치를 먼저 정하고, 거기에 맞춰서 손잡이의 위치가 다른 여러 종류의 무기에 대응할 수 있다.