Chapter 2-6. 무기 : Weapon Manager
카테고리: Unity Lesson 3
태그: Unity Game Engine
인프런에 있는 케이디님의 [유니티 3D] 실전! 생존게임 만들기 - Advanced 강의를 듣고 정리한 필기입니다. 😀
🌜 강의 들으러 가기 Click
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
에 자기 자신을 할당하게 될 것이다.
- 현재 들고 있는 무기의 Controller.cs 에서
- Transform 타입인 이유
currentWeaponAnim
👉 현재 들고 있는 무기의 애니메이터 컴포넌트 static 멤버- 현재 들고 있는 무기의 Controller.cs 에서
WeaponManager.currentWeaponAnim
에 자기 자신의 Animator 컴포넌트를 할당하게 될 것이다.
- 현재 들고 있는 무기의 Controller.cs 에서
- 무기 교체시 어떤 무기를 활성화,비활성화할지 알아야 하기 때문에 📜GunController.cs, 📜HandController.cs 필요.
멤버 함수
- Start()
- 게임이 시작되면
guns
,hands
배열 원소들을 각각의 Dictionary 에 넣어준다. - Key 는 무기 이름(string), Value는 무기 오브젝트(Gun 혹은 Hand)
- 게임이 시작되면
- Update()
- 매프레임마다 키 입력을 검사한다.
- 단 이 과정은
isChangeWeapon
가 False 일 때만 이루어져야 한다. 이미 무기 교체 중일땐 안돼.- 1 키를 누르면 무기 교체 함수를 실행 하되 인수를
맨손
으로 넘긴다. - 2 키를 누르면 무기 교체 함수를 실행 하되 인수를
SubMachineGun1
으로 넘긴다. - 예시로 그냥 하드코딩 된 부분이라 나중에 수정할 예정
- 1 키를 누르면 무기 교체 함수를 실행 하되 인수를
- ChangeWeaponCoroutine(string _type, string _name)
- Gun 종류든 Hand 종류든 어떤 무기던 간에 무기를 교체하는 로직은 같다.(그래서 📜WeaponManager.cs 에서 담당하는 것이다.) 심지어 애니메이션 이름도 똑같다. 따라서 동일한 함수를 쓰되 무기에 따라 인수만 다르게 해주면 된다.
isChangeWeapon
를 True로 만들기 (무기 교체 과정을 진행할거니 중복 실행 방지)- 무기꺼내는 애니메이션 재생
- 어떤 무기던 간에, 무기 꺼내는 조건 애니메이션 Trigger 파라미터는 다 “Weapon_Out”라고 이름 지어놓음
- 무기 집어 넣는 애니메이션 재생할 동안 대기
- 코루틴
- CancelPreWeaponAction() 실행으로 기존에 들고 있는 무기는 해제한다.
- WeaponChange(_type, _name) 실행으로 바꾸고자 하는 무기로 교체한다.
- 무기 꺼내는 애니메이션 재생할 동안 대기
- 코루틴
currentWeaponType
현재 무기 타입 업뎃isChangeWeapon
를 False로 만들기 (이제 무기 교체 또 해도 됨)
- Gun 종류든 Hand 종류든 어떤 무기던 간에 무기를 교체하는 로직은 같다.(그래서 📜WeaponManager.cs 에서 담당하는 것이다.) 심지어 애니메이션 이름도 똑같다. 따라서 동일한 함수를 쓰되 무기에 따라 인수만 다르게 해주면 된다.
- 1️⃣ CancelPreWeaponAction() 👉 들고 있던 무기 내리기
- 들고 있던 무기가 “GUN” 타입이라면
- 정조준 해제 (혹시라도 정조준 중일 것을 대비하여)
- 재장전 해제 (혹시라도 재장전 중일 것을 대비하여)
- 📜GunController.cs 에 CancelReload() 추가 함
- 해당 무기 비활성화
- 📜GunController.cs 의
isActive
를 False로
- 📜GunController.cs 의
- 들고 있던 무기가 “HAND” 타입이라면
- 해당 무기 비활성화
- 📜HandController.cs 의
isActive
를 False로
- 📜HandController.cs 의
- 해당 무기 비활성화
- 들고 있던 무기가 “GUN” 타입이라면
- 2️⃣ WeaponChange(string _type, string _name) 👉 들고자 하는 새 무기로 들기
- 들고 있던 무기가 “GUN” 타입이라면
- “GUN” 종류 무기를 활성화 하고 여러 초기화 작업을 하는 📜GunController.cs 의 GunChange() 함수 실행
- 들고 있던 무기가 “HAND” 타입이라면
- “HAND” 종류 무기를 활성화 하고 여러 초기화 작업을 하는 📜HandController.cs 의 HandChange() 함수 실행
- 들고 있던 무기가 “GUN” 타입이라면
📜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
가 맨손이든 도끼든 뭐든 간에 일단 비활성화 한다. 총으로 바꿔야 하니까.
- 📜WeaponManager의
- 업데이트
- 자신의
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 의 MoveCheck() 함수에서
- 달리기
- 📜PlayerConotrller.cs 의 Run() 함수에서
isWalk
값을 True로 설정하거나, RunCancel() 함수에서isWalk
값을 False로 설정했었고 - 그에 따라 📜Crosshair.cs의 RunningAnimation(bool _flag) 의 인수로 넘겨 애니메이션 파라미터 “Running”의 Bool 값을 설정하고 애니메이션을 재생했었다.
- 📜PlayerConotrller.cs 의 Run() 함수에서
- 걷기
- 따라서 한 문장으로 어떤 무기 종류든 간에 걷기, 뛰기 애니메이션을 재생할 수 있도록
- WalkingAnimation(bool _flag) 에
- WeaponManager.currentWeaponAnim.SetBool(“Walk”, _flag); 추가
- RunningAnimation(bool _flag) 에
- WeaponManager.currentWeaponAnim.SetBool(“Run”, _flag); 추가
- WalkingAnimation(bool _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); 로 바꿨다.
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글 남기기