본문 바로가기
Hello, World!/Unity

유니티 3D 좀비 서바이벌 게임 만들자 (14)

by 27일 낮 2022. 5. 30.
728x90

총을 쏘는 슈터 역할을 하는 PlayerShooter 스크립트를 만들자!

 

어떤 애니메이션을 사용하든 상관없이 캐릭터 손의 위치가 항상 손잡이에 위치하기 위해 애니메이터의 IK를 사용하자

  • IK?? 이해를 위해선 FK 먼저 알아야 함.
    캐릭터 애니메이션은 기본적으로 FK로 동작한다. 부모 조인트 -> 자식 조인트 순서로 움직임 적용함
    • FK(Forward Kinematics, 전진 운동학) : 대분류에서 소분류까지 순차적으로 힘이 연결되는 것
    • IK(Inverse Kinematics, 역운동학) : 자식 조인트의 위치를 먼저 결정하고 부모 조인트가 거기에 맞춰서 변형

그래서 IK를 사용!

사용하려면 IK Pass 가 체크돼있어야 함

Animator에서 확인

 

 

 

 

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.fire) {
            // 발사 입력 감지 시 총 발사
            gun.Fire();
        }
        else if (playerInput.reload) {
            // 재장전 입력 감지 시 재장전
            if (gun.Reload()) {
                // 재장전 성공 시에만 재장전 애니메이션 재생
                playerAnimator.SetTrigger("Reload");
            }
        }

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

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

    // 애니메이터의 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);
    }
}
  • OnEnable() 메서드 : PlayerShooter 컴포넌트가 활성화될 때 자동으로 실행. 슈터가 활성화되면 총도 함께 활성화되어야 함!
  • OnDisable() 메서드 : PlayerShooter 컴포넌트가 비활성화될 때 자동으로 실행. 슈터가 비활성화되면 총도 함께 비활성화되어야 함!

 

  • IK 적용 : OnAnimatorIK() 메서드로 구현
    • 가중치 : SetIKPositionWeight()와 SetIKRotationWeight()으로 결정
    • 대상의 위치와 회전 : SetIKPosition()과 SetIKRotation()으로 결정
    • 대상 : AvatarIKGoal 타입

 

 

 

 

Player Character에 PlayerShooter.cs 컴포넌트를 추가해주고, 할당해주자

 

 

 

 


  • 인터페이스 : 어떤 메서드를 반드시 구현한다는 계약, 상속한 클래스는 반드시 메서드를 public으로 구현
  • 스트립터블 오브젝트 : 데이터를 유니티 에셋 형태로 저장
  • 코루틴 : 대기 시간
  • 레이캐스트 : 광선을 쏴서 충돌하는 콜라이더가 있는지 검사하는 방법
  • IK : 하위 조인트를 먼저 결정하고, 그것에 상위 조인트를 맞추는 애니메이팅 방식

생명체의 기반이 되는 LivingEntity 클래스를 만들자 (부모 클래스)

플레이어 캐릭터의 체력과 좀비 AI 체력을 구성할 것

 

 

  • 다형성 (Polymorphism) : '여러 형태', 객체지향의 특징. C#에서 다형성은 자식 클래스 타입을 부모 클래스 타입으로 다룰 수 있게 함
  • 상속 : 부모 클래스를 기반으로 자식 클래스를 만드는 방법

 

 

  • FindObjectsOfType() 메서드 : 씬에서 명시한 타입의 모든 오브젝트를 찾아 배열로 반환

 

 

  • 메서드에서도 다형성을 적용하여 같은 이름의 메서드가 서로 다른 방식으로 동작하게 할 수 있다!
    그러한 방법 중 하나가 오버라이드(Override)

    : 부모 클래스에서 작성한 메서드를 자식 클래스에서 재정의
         ※ 오버로딩 : 이름이 같은 메서드들인데, 매개변수의 유형과 개수를 다르게 하는 것
public class Monster : MonoBehavior {
	public virtual void Attack() {
    	Debug.Log("공격!!");
    }
}

public class Orc : Monster {
	public override void Attack() {
    	base.Attack();
        Debug.Log("가자!!!!");
    }
}
  • virtual 키워드로 지정된 메서드는 가상 메서드가 됨!
    가상 메서드는 자식 클래스가 오버라이드할 수 있도록 허용된 메서드
  • 자식 클래스는 override 키워드를 사용해 부모 클래스의 가상 메서드를 재정의할 수 있음
  • base : 해당 메서드를 가지고 있는 윗 단계 부모 클래스를 지칭.
    오버라이드되기 전의 원형 메서드로 접근할 수 있음

 

 


 

LivingEntity.cs 작성

using System;
using UnityEngine;

// 생명체로 동작할 게임 오브젝트들을 위한 뼈대를 제공
// 체력, 대미지 받아들이기, 사망 기능, 사망 이벤트를 제공
public class LivingEntity : MonoBehaviour, IDamageable
{
    public float startingHealth = 100f; // 시작 체력
    public float health { get; protected set; } // 현재 체력 (자동구현 프로퍼티)
    public bool dead { get; protected set; } // 사망 상태
    public event Action onDeath; // 사망 시 발동할 이벤트


    // 생명체가 활성화될 때 상태를 리셋
    protected virtual void OnEnable() {
        // 사망하지 않은 상태로 시작
        dead = false;
        // 체력을 시작 체력으로 초기화
        health = startingHealth;
    }

    // 대미지를 입는 기능
    public virtual void OnDamage(float damage, Vector3 hitPoint, Vector3 hitNormal) {
        // 대미지만큼 체력 감소
        health -= damage;

        // 체력이 0 이하 && 아직 죽지 않았다면 사망 처리 실행
        if (health <= 0 && !dead) {
            Die();
        }
    }

    // 체력을 회복하는 기능
    public virtual void RestoreHealth(float newHealth) {
        if (dead) {
            // 이미 사망한 경우 체력을 회복할 수 없음
            return;
        }

        // 체력 추가
        health += newHealth;
    }

    // 사망 처리
    public virtual void Die() {
        // OnDeath 이벤트에 등록된 메서드가 있다면 실행
        if (onDeath != null) {
            onDeath();
        }

        // 사망 상태를 참으로 변경
        dead = true;
    }
}
  •  public float health { get; protected set; } , public bool dead { get; protected set; }
    • 자동구현 프로퍼티!
    • protected 접근 한정자 : 값의 변화는 나와 나를 상속받는 자식만 가능
  •  public event Action onDeath;
    • event : 구독!!!
      • 이벤트는 연쇄 동작을 이끌어내는 사건임
      • 이벤트 자체는 어떤 일을 실행하지 않지만 이벤트가 발생하면 이벤트를 구독하는 처리들이 연쇄적으로 실행됨
      • 이벤트를 사용하면 어떤 클래스에서 특정 사건이 일어났을 때 다른 클래스에서 그것을 감지하고 관련된 처리를 실행할 수 있음
      • 이벤트를 구현할 때는 이벤트와 이벤트에 관심이 있는 이벤트 리스너(Event Listener)로 오브젝트를 구분함
    • Action 타입 : 입력과 출력이 없는 메서드를 가리킬 수 있는 델리게이트(Delegete)
           ※ 델리게이트 : 대리자. 메서드를 값으로 할당받을 수 있는 타입
      • Action 타입의 변수에는 입력과 출력이 없는 메서드를 등록할 수 있음 (void)
      • += 을 사용해 메서드를 등록할 수  있음. 등록할 메서드 끝에는 괄호없이 이름만 명시!
        괄호를 붙이면 메서드를 등록하는 것이 아니라 실행하고 그 반환값을 할당하는 것이 됨
    • 델리게이트 타입의 변수는 event 키워드를 붙여 선언.
      어떤 델리게이트 변수를 event로 선언하면 클래스 외부에서는 해당 델리게이트를 실행할 수 없게 됨

 

 


체력 UI를 구현해보자

 

Hierarchy 뷰에서 UI - Slider 추가

 

 

 

  • Canvas
    • Canvas - Render Mode
      • Screen Space - Overlay : 게임화면과 UI화면이 별개라서 따로 작업
      • Screen Space - Camera : 카메라가 기준이 되는 카메라 시야에 맞춰서 게임과 캔버스가 배치돼서 작업됨
      • World Space : 해당 UI를 인게임 요소로 만듦
        • 일반적으로 VR 개발하면 UI를 World Space로 해줘야 함 (양안 처리를 위해서임)
    • Canvas Scaler
      • 단위당 레퍼런스 픽셀(Reference Pixels Per Unit)
        • UI 스프라이트의 픽셀 크기와 게임 월드의 유닛 크기가 대응되는 비율을 결정
        • 이 값은 UI의 스프라이트 화질에 영향을 줌

 

 

 

Canvas를 Player Character 자식으로 넣어 줌

 

 

 

Canvas의 Rect Transform 변경

 

 

Alt를 누르고 Canvas 클릭하면 자식 오브젝트까지 다 열림

Handle Slide Area(슬라이더의 손잡이임) 삭제해 줌

 

 

 

Canvas 안에 있는 거 전부 클릭해서 Anchor Presets

alt + shift 눌러서 full stretch

 

 

 

Slider 이름을 Health Slider로 변경 

Interactable : 반응형!

 

 

 

색상을 변경해주자

 

 

 

 

댓글