본문 바로가기

Unity/▶ Game Development: Zombie Game

[Zombie Game] #3. 슈터: PlayerShooter 스크립트

728x90
반응형

지난 포스팅에서 총을 완성했으니, 이번 포스팅에서는 총을 쏘는 '슈터' 역할을 하는 PlayerShooter 스크립트를 작성해 보도록 한다.

PlayerShooter 스크립트는 아래와 같은 기능을 가지도록 작성한다.

 

1. 플레이어의 입력에 따라 총을 쏘거나 재장전한다.

2. 플레이어 캐릭터의 손이 항상 총의 손잡이에 위치하도록 한다.

 

이때 어떤 애니메이션을 사용하든 상관없이 캐릭터의 손의 위치가 항상 총의 손잡이에 위하게 하려면 애니메이터의 IK를 사용해야 한다.

IK

대부분의 애니메이션은 스켈레톤의 조인트 각도를 미리 정해진 값으로 회전하여 만든다. 자식 조인트의 포지션은 부모의 회전에 따라 변하므로 조인트 체인의 끝 점은 체인에 포함된 각 조인트의 각도와 상대 위치에 따라 결정될 수 있다. 이런 스켈레톤 포즈 메서드를 순운동학(FK)이라고 한다.

 

FK 부모 조인트에서 자식 조인트 순서로 움직임을 조정하며 움직이고자 하는 물체의 최종 위치는 상위에서 하위 조인트의 누적 움직임으로 결정된다.
IK 하위 조인트의 최종 위치를 먼저 결정할 수 있으며, 거기에 맞춰 원하는 (상위)가동 오브젝트의 위치를 결정

FK(Forwrad Kinematics)

캐릭터의 애니메이션은 기본적으로 진진 운동학 즉 , FK로 작동한다.

위의 언급에서 나왔듯 FK는 부모 조인트에서 자식 조인트 순서로 움직임을 조정하며 큰 단위의 관절에서 세부적인 관절 순서로 움직임을 조절한다. 따라서 움직이고자 하는 물체의 최종 위치는 상위에서 하위 조인트의 누적 움직임으로 결정된다.

 

따라서 FK는 최종 위치 계산 결과를 따르므로 원하는 오브젝트의 위치를 '먼저' 정하고 '거기에 맞춰' 애니메이션을 변형할 수 없다.

물건에 위치에 맞춰 원하는 오브젝트의 위치를 변형할 수 없으므로 이를 해결하기 위해서는 위치를 순간이동할 수밖에 없다.

IK(Inverse Kinematics)

FK와 반대로 조인트 포즈 작업을 반대 시각에서 바라보는 것이 유용한 경우도 있는데, 공간에서 선택된 포지션에 따라서는 역으로 작업하여 적합한 조인트 방향을 찾아 해당 포지션에 조인트 끝점이 오도록 하는 방법이 유용할 수 있다.

 

이 방법은 사용자가 선택한 포인트의 오브젝트를 캐릭터가 건드리게 하거나, 울퉁불퉁한 표면 위에 캐릭터의 두 발이 자연스럽게 밀착해 있도록 하려는 경우에 유용하다. 이 접근법을 역운동학(IK)이라고 한다.

 

IK는 하위 조인트의 최종 위치를 먼저 결정할 수 있으며, 거기에 맞춰 원하는 (상위) 가동 오브젝트의 위치를 결정할 수 있다. 따라서 물건이 어디에 있든 자연스럽게 집을 수 있는 모션이 가능해진다.

 

애니메이터 컴포넌트가 IK 정보를 갱신할 때마다 OnAnimatorIK 메시지가 발생하며 스크립트에서 IK 정보가 갱신될 때마다 자동 실행되는 OnAnimatorIK() 구현하면 IK를 어떻게 사용할지 코드를 작성할 수 있다.

PlayerShooter 스크립트

스크립트의 필드

public Gun gun; // 사용할 총
public Transform gunPivot; // 총 배치의 기준점
public Transform leftHandMount; // 총의 왼쪽 손잡이, 왼손이 위치할 지점
public Transform rightHandMount; // 총의 오른쪽 손잡이, 오른손이 위치할 지점

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

IK 이용 트랜스폼(Transform) 변수

gunPivot IK를 사용해 위치가 언제나 팔의 오른쪽 팔꿈치가 되도록 설정하여 캐릭터의 상체가 움직일 시에 총도 같이 움직이도록 설정함.
leftHandMount 왼쪽 게임 오브젝트에 할당 IK를 이용해 해당 위치에 맞춰지도록 조정
rightHandMount 오른쪽 게임 오브젝트에 할당

Start() / OnEnable() / OnDisalbe() 함수

아래 [더 보기] 란의 코드를 참조한다.

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

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

private void OnDisable() {
    // 슈터가 비활성화될 때 총도 함께 비활성화
    gun.gameObject.SetActive(false);
}
OnEnable() PlayerShooter 컴포넌트가 활성화 될 때 자동으로 실행되며 총 오브젝트를 활성화한다.
OnDisable()  PlayerShooter 컴포넌트가 비활성화될때 자동으로 실행되며 총 오브젝트를 비활성화한다.

Update() 메서드

update() 메서드에서는 매 프레임마다 플레이어 입력을 감지하고 총을 발사하거나 재장전한다.

아래 [더 보기] 란의 코드를 참조한다.

더보기
private void Update() {
    // 입력을 감지하고 총 발사하거나 재장전
    if (playerInput.fire)
    {
        // 발사 입력 감지시 총 발사
        gun.Fire();
    }
    else if (playerInput.reload)
    {
        // 재장전 입력 감지시 재장전
        if (gun.Reload())
        {
            // 재장전 성공시에만 재장전 애니메이션 재생
            playerAnimator.SetTrigger("Reload");
        }
    }

    // 남은 탄약 UI를 갱신
    UpdateUI();
}

UpdateUI() 함수

updateUI() 함수에서는 남은 탄알 UI를 갱신한다. 아래 [더 보기] 란의 코드를 참조한다.

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

해당 게임의 탄알은 추후에 다룰 오브젝트와 UIManager 스크립트를 이용해 추가되고 관리된다. 이때 UIManager는 싱글턴으로 각종 게임 UI에 즉시 접근할 수 있는 통로를 제공하는 스크립트다. 아직 다루지 않았으므로 게임에 오류가 나지 않도록 필요한 부분만을 제작한다.

 

이때 싱글턴에 대한 내용은 아래 포스팅을 재참고한다.

 

[Unity] 🌟2D 게임 핵심 메서드 및 응용🌟

캐릭터 생성 및 편집 캐릭터 스프라이트 설정 준비된 소스 파일에는 각각의 스프라이트가 존재한다. 스프라이트 시트는 여러 이미지를 하나의 이미지 파일로 합친 것이다. 캐릭터가 연속적으로

udangtangtang-cording-oldcast1e.tistory.com

24.01.14 추가

OnAnimatorIK() 메서드

OnAnimatorIK() 메서드의 역할은 다음과 같다.

 

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

2. 캐릭터의 양손을 총의 양쪽 손잡이에 위치시키기

 

위의 사항을 구현하지 않을 경우 상체가 동작하는 동안 총은 제자리에서 떠다니는 것처럼 보여 게임 상황과 맞지 않으므로 Gun 오브젝트의 부모 오브젝트인 Gun Pivot오브젝트를 항상 캐릭터의 오른쪽 팔꿈치 위치에 배치하도록 구현한다.

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

    // IK를 사용하여 왼손의 위치와 회전을 총의 오른쪽 손잡이에 맞춘다
    playerAnimator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1.0f);
    playerAnimator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1.0f);

    playerAnimator.SetIKPosition(AvatarIKGoal.LeftHand,
        leftHandMount.position);
    playerAnimator.SetIKRotation(AvatarIKGoal.LeftHand,
        leftHandMount.rotation);

    // IK를 사용하여 오른손의 위치와 회전을 총의 오른쪽 손잡이에 맞춘다
    playerAnimator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1.0f);
    playerAnimator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1.0f);

    playerAnimator.SetIKPosition(AvatarIKGoal.RightHand,
        rightHandMount.position);
    playerAnimator.SetIKRotation(AvatarIKGoal.RightHand,
        rightHandMount.rotation);
}

코드 설명

[1] GetIKHintPosition() 메서드 : AvatarIKHint 타입으로 현재 위치를 가져옴

 

캐릭터의 오른쪽 팔꿈치 위치를 찾아 gunPivot의 위치로 사용한다.

 

gunPivot.position = playerAnimator.GetIKHintPosition(AvatarIKHint.RightElbow);

애니메이터 컴포넌트의 GetIKHintPosition() 메서드는 AvatarIKHint 타입으로 부위를 입력받아 해당 부위의 현재 위치를 가져온다.

여기서 사용한 AvatarIKHint타입은 IK 대상 중 다음 부위를 표현하는 타입이다.

 

· AvatarIKHint.LeftElbow : 왼쪽 팔꿈치

· AvatarIKHint.RightElbow: 오른쪽 팔꿈치

· AvatarIKHint.LeftKnee: 왼쪽 무릎

· AvatarIKHint.RightKnee: 오른쪽 무릎

 

[2] SetIKPositionWeight() / SetIKRotationWeight() 메서드: IK 대상의 회전 가중치 변경

 

캐릭터 왼손의 위치와 회전을 leftHandMount의 위치와 회전으로 변경한다. 그러기 위해 먼저 왼손 IK에 대한 위치와 회정 가중치를 1.0으로 변경한다. (100%)

playerAnimator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1.0f);
playerAnimator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1.0f);

 

IK 대상의 가중치를 설정할 때의 메서드는 아래와 같다.

위치 SetIKPositionWeight()
회전 SetIKRotationWeight()

 

위 두 메서드는 가중치를 변경할 IK 대상과 적용할 가중치를 입력받는다. 이때 사용한 AvatarIKGoal 타입은 IK 대상 중 아래의 부위를 표현하는 타입이다.

 

· AvatarIKGoal.LeftHand : 왼손

· AvatarIKGoal.RightHand: 오른손

· AvatarIKGoal.LeftFoot: 왼발

· AvatarIKGoal.RightFoot: 오른발

 

IK 가중치의 범위는 0에서 1까지이며 IK 가중치는 해당 부위의 원래 위치와 IK에 의한 목표 위치 사이에서 실제로 적용할 중간값을 결정한다. 즉, IK의 가중치가 0.5(50%)인 경우 원래 위치와 IK 목표 위치가 절반씩 섞여 적용된다.

 

[2] SetIKPosition() / SetIKRotation() 메서드: IK 대상이 사용할 목표의 위치/ 회전 설정

 

왼손 IK의 목표와 목표 회전을 leftHandMount의 위치와 회전으로 지정한다. 애니메이터의 아래 메서드는 IK 대상이 사용할 목표 위치와 목표 회전을 설정한다.

 

playerAnimator.SetIKPosition(AvatarIKGoal.LeftHand,leftHandMount.position);
playerAnimator.SetIKRotation(AvatarIKGoal.LeftHand,leftHandMount.rotation);

 

적용할 IK 대상은 AvatarIKGoal.LeftHand, 목표 위치는 총의 왼손 손잡이의 위치인 leftHandMount.position, 목표 회전은  왼손 손잡이인 AvatarIKGoal.leftHandMount, 목표 회전은 leftHandMount.rotation으로 설정한다.

 

오른손의 IK 설정은 아래와 같다.

playerAnimator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1.0f);
playerAnimator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1.0f);

playerAnimator.SetIKPosition(AvatarIKGoal.RightHand,rightHandMount.position);
playerAnimator.SetIKRotation(AvatarIKGoal.RightHand,rightHandMount.rotation);
728x90
반응형
댓글