FPS서바이벌 디펜스

4.팔 구현

ruripanda 2025. 4. 14. 17:44

본 강좌는 케이디님의 유튜브강좌 영상을 보고 유니티6로 구현한 것 입니다 이렇게 멋진 강좌를 만드신 케이디님을 존경합니다

using UnityEngine;

public class Hand : MonoBehaviour
{
    public string handName;         //너클이나 맨손 구분
    public float range;             //공격범위
    public int damage;              //공격력
    public float workSpeed;         //작업속도
    public float attackDelay;       //공격 딜레이
    public float attackDelayA;      //공격 활성화 시점
    public float attackDelayB;      //공격 비활성화 시점

    public Animator anim;           //애니메이션
}
using System.Collections;
using UnityEngine;

public class HandController : MonoBehaviour
{
    //현재 장착된 Hand형 타입 무기
    [SerializeField]
    private Hand currentHand;

    //공격중??
    private bool isAttack = false;
    private bool isSwing = false;

    private RaycastHit hitInfo;

    void Update()
    {
        TryAttack();
    }

    private void TryAttack()
    {
        if (Input.GetButton("Fire1"))
        {
            if (!isAttack)
            {
                StartCoroutine(AttackCoroutine());
            }
        }
    }

    IEnumerator AttackCoroutine()
    {
        isAttack = true;
        currentHand.anim.SetTrigger("Attack");

        yield return new WaitForSeconds(currentHand.attackDelayA);
        isSwing = true;

        StartCoroutine(HitCoroutine());

        yield return new WaitForSeconds(currentHand.attackDelayB);
        isSwing = false;

        yield return new WaitForSeconds(currentHand.attackDelay - currentHand.attackDelayA - currentHand.attackDelayB);
        isAttack = false;
    }

    IEnumerator HitCoroutine()
    {
        while (isSwing)
        {
            if (CheckObject())
            {
                isSwing = false;
                Debug.Log(hitInfo.transform.name);
            }
            yield return null;
        }
    }

    private bool CheckObject()
    {
        if (Physics.Raycast(transform.position, transform.forward, out hitInfo, currentHand.range))
        {
            return true;
        }
        return false;
    }
}

 

이번 시간에는 케이디님이 FPS팔을 구현하셨다 같이 따라 만들자

 

구현되어 있는 팔 오브젝트

일단 lowPoly_arms_Basic 이라고 명명되어 있는 팔 오브젝트를 Player 오브젝트의 아래에 넣어준다

그뒤 Transform을 리셋시켜서 위치를 맞춰준다

이렇게 하면 쉽게 붙일 수 있다

 

그뒤  lowPoly_arms_Basic이라고 명명된 오브젝트의 카메라 오브젝트를 삭제해준다

블렌더로 만들어진 팔 오브젝트이기 때문에 카메라가 남아 있을 수 있다

 

 

그런데 팔 오브젝트를 달아보면 카메라 방향과 맞지가 않다

이럴때는 상위 오브젝트를 만들고 Transform의 Rotation을 위 스샷처럼 Y축을 90도 꺽어준다

부모객체가 방향이 꺽어졌기 때문에 하위 오브젝트도 영향을 받는다

 

그리고 위 스크린샷과 같이 상위로 만든 빈 오브젝트를 Hand Holder로 명명해주고

lowPoly_arms_Basic라고 명명된 오브젝트를 Hand로 이름을 바꿔준다

 

그리고 팔을 붙여서 게임화면을 보면 이렇게 팔의 위치가 맞지 않는다

 

이 부분은 아까 만들어둔 Hand Holder 오브젝트의 Position을 Y축은 -0.4, Z축을 -1로 설정해서 왼쪽의 스크린샷 처럼 바꿔준다

 

여기서 필자가 재미있게 Position을 기억하는 법을 가르쳐주겠다

손가락을 이 포즈로 만들어주고 X는 가로세로, Y는 높이, Z는 앞으로 뒤로라고 외워두면 의외로 외우기 쉽다

 

이제 가이드로 돌아와서...

팔 프리팹에는 케이디님이 애니메이션도 넣어주셨다

그런데 한개의 프래임에 여러개의 애니메이션을 끊김 없이 다 넣으셔서 우리는 애니메이션을 쪼개줘야 한다

 

위 스샷은 필자가 미리 케이디님의 가이드를 보고 쪼개놓은 스샷이다

 

 

애니메이션을 쪼개는 방법은 일단 왼쪽의 스크린샷의 + 버튼을 누르고 아래 밑줄친 부분의 이름을 지정해준다

그 뒤 Start 프래임을 숫자로 지정해주고 End 프래임으로 애니메이션의 끝 부분을 지정해준다

그리고 Loop Time를 체크박스로 체크해서 반복할지 말지 결정해줍니다

여기서 Loop Pose라는 체크박스가 있는데 이건 애니메이션이 바뀔때 부드럽게 해주는 기능입니다

Loop Time를 체크하면 선택할 수 있게 활성화 됩니다

 

 

스크린샷으로 애니메이션을 이름을 어떻게 명명했는지 몇 프레임부터 몇 프레임까지인지 Loop Time와 Loop Pose 체크박스를 체크했는지 다 찍어서 만들었다 확인하고 똑같이 나눠주자

 

이 작업을 다 해주면 아래 스크린샷 처럼 쪼개진 애니메이션이 Project창의 오브젝트의 하위에 추가가 된다

 

 

이제 애니메이션을 제어할 수 있게 Animator을 만들어주자

 

Animation 폴더를 만들어주고 그 하위에 Hand 폴더를 만들어 주자

그뒤 Hand_Animator 애니메이터를 만들어주자

그리고 Hand 오브젝트에 Animator 컴포넌트를 만들어주자

그뒤 우리가 위에 만들어둔 Hand_Animator을 Controller 슬롯에 넣어주자

 

필자가 실습하면서 만들어 놓은 완성된 Animator 설정

우리는 위처럼 에니메이션을 만들어 줘야한다

 

일단 Parameters(파라메터)를 만들어주자 아래 텍스트로 작성한 표를 참고해서 만들자

 

Walk / Bool

Run / Bool

Item_Pickup / Trigger

Weapon_Out /  Trigger

Attack /  Trigger

Work /  Trigger

 

스크린샷은 완성된 것 입니다 오해하지 말아주세요 ^^;;

이제 Animator에 우리가 쪼개서 만들어 놓은 애니메이션들을 전부 선택해서 넣어주자

 

자 이제 애니메이터를 설정해줄 시간이다 좀 지루할 수 있다

 

여기서 알고 있으면 편한 단축키를 하가 가르쳐주겠다

애니메이션 트리가 만약 중구난방으로 퍼저있어서 위 창의 보이는 부분 밖에 있으면 안보여서 못 찾을 수 있다

이럴때 Ctrl + A 버튼을 눌러주면 애니메이션 박스가 모든 창에 보이게 크기를 자동 조절 할 수 있다

 

이제 가이드로 돌아와서

Hand_Weapon_In을 맨 처음에 작동되게 한다

Entry가 애니메이션이 실행되면 맨 처음 실행되는 애니메이션 박스에 연결되어 있는 박스이다

위 스크린샷 처럼 오랜지색으로 맨 처음 실행되게 할려면

박스 위에서 마우스 오른쪽 클릭 Set as Layer Default State를 눌러주면 된다

 

여기서 아래를 보면 Any State라는 박스도 있는데 이 박스는 언재든지 실행이되는 애니메이션에 연결되는 박스이다 알아두자

 

Exit는 애니메이션이 끝나면 여기로 연결되게 해주면 된다

아직은 우리가 쓸 일이 없으니 알아만 두자

 

일단 애니메이션 박스에 화살표를 연결해준다

박스에서 Make Transition을 눌러주면 화살표가 마우스를 따라 움직이는데 이 화살표를 애니메이션 박스 아이콘에 눌러주면 애니메이션 트리가 연결된다

애니메이션 트리는 우리가 애니메이션을 실행할때 어느 애니메이션에서 실행되서 어느 애니메이션으로 연결되는지를 나타내주는 것이다

 

그리고 연결된 화살표를 클릭해주면 왼쪽 Inspector창에 속성이 나타난다

애니메이션 트리를 연결해주면 화살표 마다 전부 설정을 해 주어야 한다

인스팩터 창의 내용을 간단하게 설명이 필요해서 항목에 번호를 매겨놨다

1. Has Exit Time는 화살표가 출발하는 애니메이션 박스의 움직임이 모두 완료되면 다음 애니메이션이 실행되게 해주는 것이다

2.Exit Time는 끝나는 비율이다 Has Exit Time를 체크하면 나오는데 전의 애니메이션 박스가 어느 비율에서 다음 애니메이션 박스                    로 바뀌는지 설정하는 것이다

3.Transition Duration...은 애니메이션이 변경되는데 걸리는 시간이다

4.Conditions는 애니메이션이 실행되는 파라메터 조건이다

 

위 표의 번호와 Inspector의 번호를 참고해서 세팅을 다 해주자

Conditions를 잊으면 큰일난다 우리가 스크립트로 이걸 움직여서 애니메이션이 움직이기 때문에 중요하다!

 

위 작업이 다 끝났으면 C#스크립트를 만들자

 

이 스크립트는 플레이어 팔의 속성을 관리하는 커스텀 클레스이다 작성한뒤

우리가 위에 만들어준 Hand 오브젝트에 스크립트를 넣어주고 Animator컴포넌트를 Anim에 연결해준다

 

그 뒤 변수의 속성에 다 값을 넣어주자

여기서 중요한게 Attack Delay, ...A, ...B 이다

이건 공격을 한뒤 일정 텀이 지난 후에 공격을 다시 할 수 있게하는 기능이다

이걸 설정하지 않으면 마우스 버튼을 연타하면 그게 전부 공격으로 들어갈 것이다

Range는 사정거리이다

여기서 중요한게 유니티엔진에서는 거리 단위가 기본 1m 단위라고 간략하게 생각하면 되므로 거리를 잴때 기억하자

 

이제 HandController 이라고 하는 C# 스크립트를 만들자

 

스크립트의 첫번째 스크린샷이다

1번 줄에 코루틴을 쓰기 위해서 using System.Collections;를 선언해주자

그리고 7번 줄에 [SerializeField]를 선언해서

8번줄의 private Hand currentHand;를 선언해주자

이건 인스팩터에서 우리가 Hand스크립트를 작성할 것을 넣을고 속성을 불러올 변수이다

 

11번과 12번은 bool값으로 공격시도하고 휘두르는 시도를 구현해준 것이다

 

그리고 16번 줄의 void Update는 매프래임마다 실행되게하는 함수이다

18번에 TryAttack();를 작성해서 구현한 함수를 불러올 수 있게 하자

 

TryAttack()함수이다

23번줄에 "Fire1" 버튼을 누르는 조건으로 isAttack가 false 일때 AttackCoroutine()코루틴이 실행되게 하는 함수이다

 

여기서 Fire1 버튼은 무었일까?

해답은 InputManager에 있는 Fire1 항목이다

우리는 여기에 mouse 0 버튼이 Fire1 버튼에 대응하게 설정했으므로 스크립트에 Fire1 이라고 작성만해도 대응하게 된 것이다

기본 값에는 Fire1에 다른 버튼 값도 있는데 mouse 0만 남겨주고 다른 버튼은 지워주자

 

다음은 AttackCoroutime() 코루틴이다

34번 줄의 isAttack가 true일때

우리가 이번 시간에 만들어준 애니메이션에서 Attack 트리거가 움직이게 해주어서 애니메이션이 실행된다

그리고 37번줄에 attackDelayA의 속성 숫자 만큼의 초를 대기해주고

38번 줄에 isSwing를 true로 바꿔준다

40번 줄에 HitCoroutine() 코루틴을 실행시켜주고

attackDelayB의 속성 숫자 만큼 초를 대기해주고

isSwing = false로 바꿔준다

그뒤 45번 줄에 attackDelay에 attackDelayA - attackDelayB 한 초만큼 대기해주고

isAttack를 false로 바꿔준다

다음은 HitCoroutine()코루틴이다

while문으로 조건이 isSwing일때 반복문이 실행되게 해주고

53번 줄에 CheckObject()함수가 true일때

isSwing를 false해주고 Debug.Log로 hitInfo에 적중한 객체의 transform.name를 불러온다

그뒤 한 프래임 쉬어 준다

 

여기서 Transform이 광범위하게 많이 쓰이는걸 볼 수 있는데

 

오브젝트의 Transform

유니티엔진의 모든 오브젝트에는 Transform이 다 포함되어 있다

이걸 이용해서 유니티엔진은 오브젝트의 데이터를 주고 받을 수 있다

 

Debug.Log(hitInfo.transform.name);

이라고 작성해주면 hitInfo에 적중한 오브젝트의 transform의 name를 불러오는 것이라고 풀어 쓸 수 있다

 

이제 CheckObject()함수이다

64번 줄에 조건문으로 Physics.Raycast()라는 함수를 써서 가상의 레이져를 쏴준다

transform.position은 자기 자신이고 transform.forward는 자신의 앞쪽 방향으로 out hitInfo는 레이져의 끝나는 부분이고

currenthand.range는 레이져의 길이이다

그리고 여기에 닿는게 있다면 return true를 반환한다

그래서 이 함수는 private void CheckObject()가 아니라 private bool CheckObject로 작성되어 있다

만약 여기서 닿는게 없다면 reutrn false를 반환해 준다

 

Physics.Raycast(자기자신, 레이져방향, 레이져가 끝나는 부분, 레이져거리)라고 이 함수는 생각해주면 된다

 

이제 이 스크립트를

Player의 하위 오브젝트 Holder을 만들어서 여기에 붙여넣어주고

우리가 만들어준 Hand Holder을 이 하위에 위치하게 해주자

 

그리고 Hand 오브젝트를 끌어서 인스펙터의 Current Hand의 슬롯에 넣어주자

 

이렇게 하는 이유는 오브젝트의 방향과 관련이 있다

Hand Holder 오브젝트의 파란색 화살표가 캐릭터의 앞을 바라보지 않는다

우리가 스크립트에서

transform.forward라고 작성했기 때문에 파란색 방향을 앞으로 인식해서 이쪽으로 레이져를 쏴준다

하지만 캐릭터는 파란색 화살표기준 왼쪽을 바라보고 있기 때문에 아예 새 오브젝트를 만들어서 레이져를 재대로 쏴주게 스크립트를 넣어 준 것이다

새로 만들은 오브젝트는 정상적으로 파란색 화살표가 앞을 바라보고 있다

 

용량이 20mb를 초과해서 움짝을 못 올렸는데

이렇게 부딧힌 대상의 이름을 받아왔다

 

나무는 이상하게 받아오지 못했는데 일단 진행해보자

 

여기까지 진행했으면 문제가 3가지 생기는데

첫번째로 팔이 카메라를 기준으로 따라다니지 않아서 따로놀게 되는 것

두번째로 카메라로 볼때 팔이 짤리는 문제

세번째로 나무에 팔이 가려지는 문제가 있다

 

첫번째 문제는

팔을 카메라의 하위 오브젝트로 만들면 해결이 된다

 

두번째는

 

Main Camera를 복사해서 Weapon Camera라고 명명한뒤 하위 오브젝트로 위치시켜주고

Weapon Camera의 속성 중 체크한 부분을 스샷처럼 바꿔준다

Culling Mask는 그냥 선택하면 Weapon이 없는데

 

이렇게 Add Layer으로 들어가서 Weapon을 만들어주면 나오게 된다

이 부분이 안가려지게 해주는 것이다

그리고 Main Camera는 이렇게 레이어를 선택해준다

그러면 이렇게 오브젝트에 안 가려지게 된다

 

그리고 카메라가 여러개가 있으면 꼭 터지는 오류가 하나 있는데

 

모든 카메라 오브젝트는 만들다 보면 이렇게 Audio Listener이라고 하는 소리를 듣는 컴포넌트가 생길 수 있다

Main Camera에 있는 것만 유지해주고 다른 카메라에 있는건 지워주자

 

그리고 우리가 Physics.Raycast를 쏴줘서 접촉한 오브젝트의 데이터를 받아오게 만들었는데 간혹 플레이어만 나오는 경우가 있다

 

그 부분은 Layer에서 2번학목 Ignore Raycast를 오브젝트의 레이어로 선택해주면 레이어에서 접촉을 패스하기 때문에 검출이 안되게 해줄 수 있다

 

스크린샷에 표시한 부분은 전부 Ignore Raycast로 선택해주고 Hand는 그대로 Weapon으로 바꿔주자

 

이번 강좌는 여기까지!

'FPS서바이벌 디펜스' 카테고리의 다른 글

5.총 구현과 총구섬광  (0) 2025.04.15
3.지형 제작  (0) 2025.04.14
2.심화 캐릭터 움직임  (0) 2025.04.02
1.기본 캐릭터 움직임  (1) 2025.04.01