본문 바로가기
C#/UNITY_FPS 3D 서바이벌게임

Chapter 2-6. 무기 : Weapon Manager

by w1z 2024. 3. 11.

Chapter 2. 무기

Weapon Manager

🚖 무기 교체

📜WeaponManager.cs

Holder에 붙인다.

  • 매니저이니만큼 모두가 공유할 수 있는 static 멤버들을 가지고 있다.
    • static
      • 클래스 변수
      • 접근이 쉽고 게임 내내 메모리에 남아 있다. 공유 되야 하므로.
        • 따라서 남발하면 안되고 적재적소에 사용 하기를 권장
  • 무기 교체
  • 여러 무기들의 동작 관리
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class WeaponManager : MonoBehaviour
{
    public static bool isChangeWeapon = false;  // 무기 교체 중복 실행 방지. (True면 못하게)

    [SerializeField]
    private float changeweaponDelayTime;  // 무기 교체 딜레이 시간. 총을 꺼내기 위해 손 집어 넣는 그 시간. 대략 Weapon_Out 애니메이션 시간.
    [SerializeField]
    private float changeweaponEndDelayTime;  // 무기 교체가 완전히 끝난 시점. 대략 Weapon_In 애니메이션 시간.

    [SerializeField]
    private Gun[] guns;  // 모든 종류의 총을 원소로 가지는 배열
    [SerializeField]
    private Hand[] hands;  // 모든 종류의 Hand 형 무기를 가지는 배열

    // 관리 차원에서 이름으로 쉽게 무기 접근이 가능하도록 Dictionary 자료 구조 사용.
    private Dictionary<string, Gun> gunDictionary = new Dictionary<string, Gun>();
    private Dictionary<string, Hand> handDictionary = new Dictionary<string, Hand>();

    [SerializeField]
    private string currentWeaponType;  // 현재 무기의 타입 (총, 도끼 등등)
    public static Transform currentWeapon;  // 현재 무기. static으로 선언하여 여러 스크립트에서 클래스 이름으로 바로 접근할 수 있게 함.
    public static Animator currentWeaponAnim; // 현재 무기의 애니메이션. static으로 선언하여 여러 스크립트에서 클래스 이름으로 바로 접근할 수 있게 함.

    [SerializeField]
    private GunController theGunController;  // 총 일땐 📜GunController.cs 활성화, 손일 땐 📜GunController.cs 비활성화 
    [SerializeField]
    private HandController theHandController; // 손 일땐 📜HandController.cs 활성화, 📜HandController.cs 비활성화

    void Start()
    {
        for (int i = 0; i < guns.Length; i++)
        {
            gunDictionary.Add(guns[i].gunName, guns[i]);
        }
        for (int i = 0; i < hands.Length; i++)
        {
            handDictionary.Add(hands[i].handName, hands[i]);
        }
    }

    void Update()
    {
        if(!isChangeWeapon)
        {
            
            if (Input.GetKeyDown(KeyCode.Alpha1)) // 1 누르면 '맨손'으로 무기 교체 실행
            {
                StartCoroutine(ChangeWeaponCoroutine("HAND", "맨손"));
            }
            else if (Input.GetKeyDown(KeyCode.Alpha2)) // 2 누르면 '서브 머신건'으로 무기 교체 실행
            {
                StartCoroutine(ChangeWeaponCoroutine("GUN", "SubMachineGun1"));
            }

        
        }
    }

    public IEnumerator ChangeWeaponCoroutine(string _type, string _name)
    {
        isChangeWeapon = true;
        currentWeaponAnim.SetTrigger("Weapon_Out");

        yield return new WaitForSeconds(changeweaponDelayTime);

        CancelPreWeaponAction();
        WeaponChange(_type, _name);

        yield return new WaitForSeconds(changeweaponEndDelayTime);

        currentWeaponType = _type;
        isChangeWeapon = false;
    }

    private void CancelPreWeaponAction()
    {
        switch(currentWeaponType)
        {
            case "GUN":
                theGunController.CancelFineSight();
                theGunController.CancelReload();
                GunController.isActivate = false;
                break;
            case "HAND":
                HandController.isActivate = false;
                break;
        }
    }

    private void WeaponChange(string _type, string _name)
    {
        if(_type == "GUN")
        {
            theGunController.GunChange(gunDictionary[_name]);
        }
        else if(_type == "HAND")
        {
            theHandController.HandChange(handDictionary[_name]);
        }
    }
}

무기 타입 종류 👉 Gun(SubMachineGun1 등등), Hand(Hand, Knuckle 등등))

멤버 변수

  • static 변수들은 다른 스크립트들에서 WeaponManager 클래스 이름으로 호출할 수 있고 여러 곳에서 공유 된다.
  • isChangeWeapon 👉 무기 교체 중인지.
    • 현재 무기 교체 중일 때 무기 교체 명령이 또 들어오면 안되므로 중복 실행을 방지 하기 위해 필요.
    • static 멤버
    • Gun 종류든 Hand 종류든 어떤 무기던 간에 일단 무기 교체를 실행 중이면 또 무기 교체하면 안된다.
      • 따라서 모든 무기들이 무기 교체 중인지 상황을 알고 있어야 한다. 따라서 static이어야 함.
  • 총 종류인 무기 오브젝트들과, 핸드형 종류인 무기 오브젝트들을 각각 guns, hands 배열에 할당할 것이다.
    • 그러나 어떤 총이 guns[2] 인덱스 2인지 기억하기가 힘드니까 Dictionary 자료 구조로 무기들을 관리할 것이다. 무기 이름으로 무기 오브젝트에 접근할 수 있도록! 개발자 입장에서 구분이 쉽도록.
  • currentWeapon 👉 현재 들고 있는 무기 오브젝트
    • Transform 타입인 이유
      • Gun 종류든 Hand 종류든 어떤 무기던 간에 모두 Transform 컴포넌트를 갖고 있기 때문이다! 따라서 모든 종류의 무기를 이 멤버 하나로 참조할 수 있도록 하기 위하여! 다형성 느낌..
    • static 멤버
      • 현재 들고 있는 무기의 Controller.cs 에서 WeaponManager.currentWeapon에 자기 자신을 할당하게 될 것이다.
  • currentWeaponAnim 👉 현재 들고 있는 무기의 애니메이터 컴포넌트 static 멤버
    • 현재 들고 있는 무기의 Controller.cs 에서 WeaponManager.currentWeaponAnim에 자기 자신의 Animator 컴포넌트를 할당하게 될 것이다.
  • 무기 교체시 어떤 무기를 활성화,비활성화할지 알아야 하기 때문에 📜GunController.cs, 📜HandController.cs 필요.

 

멤버 함수

  • Start()
    • 게임이 시작되면 guns, hands 배열 원소들을 각각의 Dictionary 에 넣어준다.
    • Key 는 무기 이름(string), Value는 무기 오브젝트(Gun 혹은 Hand)
  • Update()
    • 매프레임마다 키 입력을 검사한다.
    • 단 이 과정은 isChangeWeapon가 False 일 때만 이루어져야 한다. 이미 무기 교체 중일땐 안돼.
      • 1 키를 누르면 무기 교체 함수를 실행 하되 인수를 맨손으로 넘긴다.
      • 2 키를 누르면 무기 교체 함수를 실행 하되 인수를 SubMachineGun1으로 넘긴다.
      • 예시로 그냥 하드코딩 된 부분이라 나중에 수정할 예정
  • ChangeWeaponCoroutine(string _type, string _name)
    • Gun 종류든 Hand 종류든 어떤 무기던 간에 무기를 교체하는 로직은 같다.(그래서 📜WeaponManager.cs 에서 담당하는 것이다.) 심지어 애니메이션 이름도 똑같다. 따라서 동일한 함수를 쓰되 무기에 따라 인수만 다르게 해주면 된다.
      • isChangeWeapon를 True로 만들기 (무기 교체 과정을 진행할거니 중복 실행 방지)
      • 무기꺼내는 애니메이션 재생
        • 어떤 무기던 간에, 무기 꺼내는 조건 애니메이션 Trigger 파라미터는 다 “Weapon_Out”라고 이름 지어놓음
      • 무기 집어 넣는 애니메이션 재생할 동안 대기
        • 코루틴
      • CancelPreWeaponAction() 실행으로 기존에 들고 있는 무기는 해제한다.
      • WeaponChange(_type, _name) 실행으로 바꾸고자 하는 무기로 교체한다.
      • 무기 꺼내는 애니메이션 재생할 동안 대기
        • 코루틴
      • currentWeaponType 현재 무기 타입 업뎃
      • isChangeWeapon를 False로 만들기 (이제 무기 교체 또 해도 됨)
  • 1️⃣ CancelPreWeaponAction() 👉 들고 있던 무기 내리기
    • 들고 있던 무기가 “GUN” 타입이라면
      • 정조준 해제 (혹시라도 정조준 중일 것을 대비하여)
      • 재장전 해제 (혹시라도 재장전 중일 것을 대비하여)
        • 📜GunController.cs 에 CancelReload() 추가 함
      • 해당 무기 비활성화
        • 📜GunController.cs 의 isActive를 False로
    • 들고 있던 무기가 “HAND” 타입이라면
      • 해당 무기 비활성화
        • 📜HandController.cs 의 isActive를 False로
  • 2️⃣ WeaponChange(string _type, string _name) 👉 들고자 하는 새 무기로 들기
    • 들고 있던 무기가 “GUN” 타입이라면
      • “GUN” 종류 무기를 활성화 하고 여러 초기화 작업을 하는 📜GunController.cs 의 GunChange() 함수 실행
    • 들고 있던 무기가 “HAND” 타입이라면
      • “HAND” 종류 무기를 활성화 하고 여러 초기화 작업을 하는 📜HandController.cs 의 HandChange() 함수 실행

 

📜GunController.cs

// 추가된 멤버 변수

public static bool isActivate = true;  // 활성화 여부
    void Update()
    {
        if (isActivate)
        {
            GunFireRateCalc();
            TryFire();
            TryReload();
            TryFineSight();
        }
    }

    void Start()
    {
        originPos = Vector3.zero;
        audioSource = GetComponent<AudioSource>();
        theCrosshair = FindObjectOfType<Crosshair>();

        WeaponManager.currentWeapon = currentGun.GetComponent<Transform>();
        WeaponManager.currentWeaponAnim = currentGun.anim;
    }

    public void CancelReload()
    {
        if(isReload)
        {
            StopAllCoroutines();
            isReload = false;
        }
    }

    public void GunChange(Gun _gun)
    {
        if (WeaponManager.currentWeapon != null)
            WeaponManager.currentWeapon.gameObject.SetActive(false);

        currentGun = _gun;
        WeaponManager.currentWeapon = currentGun.GetComponent<Transform>();
        WeaponManager.currentWeaponAnim = currentGun.anim;

        currentGun.transform.localPosition = Vector3.zero;
        currentGun.gameObject.SetActive(true);

        isActivate = true;
    }
  • Update()
    • isActive 할 때만, 이 무기가 활성화 되있을 때만 실행하도록 한다.
      • 발사하고 재장전하고 정조준하거나 그런 모든 Gun과 관련된 일들.
  • Start()
    • 기본적인 디폴트 무기를 Gun으로 삼을 것이라서 특별히 static 변수인 📜WeaponManager의 currentWeapon에 📜GunController.cs만 게임이 시작되자마자 자기 자신을 할당해준다.
      • Transform 타입으로 할당 해주어야.
  • GunChange(Gun _gun)
    • 총으로 교체하고자 할 때 필요. 호출은 📜WeaponManager 에서 이루어짐
    • 현재 들고 있는 무기가 있다면 해당 무기 오브젝트는 비활성화 해주기. SetActive(False)
      • 📜WeaponManager의 currentWeapon가 공유 변수이기 때문에 가능한 일.
      • 현재 들고 있는 무기안 WeaponManager.currentWeapon가 맨손이든 도끼든 뭐든 간에 일단 비활성화 한다. 총으로 바꿔야 하니까.
    • 업데이트
      • 자신의 currentGun
      • WeaponManager.currentWeapon를 자기 자신으로 업뎃 (Transform으로)
      • WeaponManager.currentWeaponAnim을 자기 자신의 Animator로 업뎃
    • 총을 교체함으로서 무기 위치가 달라졌을 것을 대비하여 원점으로 다시 초기화 (로컬로서 원점이 총의 시작 위치임)
    • 현재 총 오브젝트 활성화 SetActive(True)
    • isActive 활성화

 

📜HandController.cs

// 추가된 멤버 변수

public static bool isActivate = true;  // 활성화 여부
    void Update()
    {
        if(isActivate)
            TryAttack();
    }

    public void HandChange(Hand _hand)
    {
        if (WeaponManager.currentWeapon != null)
            WeaponManager.currentWeapon.gameObject.SetActive(false);

        currentHand = _hand;
        WeaponManager.currentWeapon = currentHand.GetComponent<Transform>();
        WeaponManager.currentWeaponAnim = currentHand.anim;

        currentHand.transform.localPosition = Vector3.zero;
        currentHand.gameObject.SetActive(true);

        isActivate = true;
    }
  • Update()
    • isActive 할 때만, 이 무기가 활성화 되있을 때만 실행하도록 한다.
  • GUN과 다르게 기본 무기가 손인건 아니기 때문에 Start() 에서 따로 설정해준건 없었음
  • HandChange(Hand _hand)
    • 위와 동일

 

무기 교체 애니메이션

 

SubMachineGun1, Hand 둘 다 각각 Weapon_Out 애니메이션도 연결해주었다. 파라미터 추가해주고 AnyState 👉 Weapon_Out 로 전이하도록 하였음. (Has Exit Time 체크 해제, Time Duration 0.1, 전이 조건은 “Weapon_Out”이 True일 때)

 

 

들고 있는 무기에 따른 걷기, 뛰기 애니메이션 재생

📜Crosshair.cs

    public void WalkingAnimation(bool _flag)
    {
        WeaponManager.currentWeaponAnim.SetBool("Walk", _flag);  // 걷기시 무기 애니메이션
        animator.SetBool("Walking", _flag); // 걷기시 크로스헤어 애니메이션
    }

    public void RunningAnimation(bool _flag)
    {
        WeaponManager.currentWeaponAnim.SetBool("Run", _flag); // 뛰기시 무기 애니메이션
        animator.SetBool("Running", _flag); // 뛰기시 크로스헤어 애니메이션
    }
    }

    public void JumpingAnimation(bool _flag)
    {
        animator.SetBool("Running", _flag); // 점프시 크로스헤어 애니메이션
    }
    }

무기에 따른 걷기, 뛰기 애니메이션을 구현하지 않았었다.

 
  • 걷기, 뛰기는 총이든 손이든 간에 어떤 무기든 간에 애니메이션이 재생되는 로직이 동일하다. 심지어 걷기, 뛰기 상태에 따른 크로스헤어 애니메이션 조건과도 동일하다.
    • 걷기
      • 📜PlayerConotrller.cs 의 MoveCheck() 함수에서 isWalk 값을 설정하고
      • 그에 따라 📜Crosshair.cs의 WalkingAnimation(bool _flag) 의 인수로 넘겨 애니메이션 파라미터 “Walking”의 Bool 값을 설정하고 애니메이션을 재생했었다.
    • 달리기
      • 📜PlayerConotrller.cs 의 Run() 함수에서 isWalk 값을 True로 설정하거나, RunCancel() 함수에서 isWalk 값을 False로 설정했었고
      • 그에 따라 📜Crosshair.cs의 RunningAnimation(bool _flag) 의 인수로 넘겨 애니메이션 파라미터 “Running”의 Bool 값을 설정하고 애니메이션을 재생했었다.
  • 따라서 한 문장으로 어떤 무기 종류든 간에 걷기, 뛰기 애니메이션을 재생할 수 있도록
    • WalkingAnimation(bool _flag) 
      • WeaponManager.currentWeaponAnim.SetBool(“Walk”, _flag); 추가
    • RunningAnimation(bool _flag) 
      • WeaponManager.currentWeaponAnim.SetBool(“Run”, _flag); 추가
  • JumpingAnimation(bool _flag) 을 따로 만들어 준 이유는, 점프 하거나 추락할 때도, 즉 isGround가 False일 때도 크로스 헤어 애니메이션을 “Running” 파라미터로 쓰기 때문에
    • 진짜 뛸 떄는 무기도 애니메이션을 적용하고
    • 그냥 추락할 때는 무기는 애니메이션 적용 X 크로스헤어만 달리기로 적용될 수 있도록 따로 함수를 팠다.

 

📜PlayerConotrller.cs

    // 지면 체크
    private void IsGround()
    {
        isGround = Physics.Raycast(transform.position, Vector3.down, capsuleCollider.bounds.extents.y + 0.1f);
        theCrosshair.JumpingAnimation(!isGround);
    }
  • RunningAnimation(!isGround); 에서 JumpingAnimation(!isGround); 로 바꿨다.