양쪽 총에 붙인 Gun 스크립트이다.
public class Gun : MonoBehaviour
{
public Transform firePosition;
public enum Type { None, LeftGun, RightGun}
[SerializeField]
private Type gunType;
public Type GunType { get => gunType; private set => gunType = value; }
public void GunShot()
{
print(GunType);
Instantiate(Resources.Load("Bullet"), firePosition.position, Quaternion.identity, transform.root.Find("Magazine"));
}
}
1. 왼쪽이면 왼쪽, 오른쪽이면 오른쪽으로 GunType을 맞춰주고 FirePosition은 빈 게임 오브젝트를 만들어서 총의 앞부분에 배치했다.
public class PlayerMediator : MonoBehaviourPunCallbacks
{
private Dictionary<Gun.Type, Gun> gunDic = new();
void Awake()
{
InitGuns();
}
private void InitGuns()
{
Gun[] guns = GetComponentsInChildren<Gun>();
if (guns.Length > System.Enum.GetValues(typeof(Gun.Type)).Length - 1)
throw new System.Exception("guns count exceeded");
foreach (Gun gun in guns)
{
Gun.Type checkType = gun.GunType;
if (checkType == Gun.Type.None)
throw new System.Exception(checkType + " gun Requires type spectification");
if (gunDic.ContainsKey(checkType))
throw new System.Exception(checkType + " type duplication");
gunDic[checkType] = gun;
}
}
}
1. PlayerMediator 스크립트에서 Gun.Type을 키로 가지고 Gun을 값으로 가지는 Dictionry를 선언하고
InitGun에서 설정해줬다.
2. 지역변수로 Gun[] guns를 선언하고 자식 개체에서 Gun 스크립트들을 찾은 다음에 할당해 줬다.
3. 만약에 guns.Length가 Gun.type Enum의 Length - 1 즉 Gun.Type의 갯수 - 1보다 많다면 Log를 출력한다.
여기서 왜 -1을 했냐면 손은 두개기 때문에 Left, Right Gun만 쓸 것이기 때문이다.
4. 그리고 그 뒤에 오는 Foreach문을 실행해서 가져온 guns 객체들을 확인하고 필드에 선언한
private Dictionary<Gun.Type, Gun> gunDic에 넣는다.
이런 예외처리 방법은 지금은 수료한 학원 팀원이 알려준 건데 역시 현업자는 다르다... 가르침 받은 거라서 쓰지만 아니었다면 그냥 gunDic[checkType] = gun만 하지 않았을까...
애니메이션 이벤트로 불릴 함수
public void GunShot(Gun.Type type)
{
gunDic[type].GunShot();
}
이것 역시 PlayerMediator 스크립트에 구현한 함수이다.
1. Gun.type 을 매개변수로 받아서 만약 LeftGun type을 받았다면 gunDic[LeftGun].GunShot()을 실행할 것이다.
Animation
1. Animation에서 이벤트를 추가하고
2. 0.08초에 실행될 함수는 GunShot(GunType)이고 매개변수로 LeftGun을 넣어줬다.
반대쪽 애니메이션도 똑같이 해준다.
위쪽 Gun 스크립트대로 하니까 총알 발사에 문제가 생겼다. 이왕 이렇게 된거 오브젝트 풀을 만들고 조금 바꿔야겠다.
오브젝트 풀 스크립트이다. 킹갓 고박사의 유니티 노트!! 밑에 링크 첨부하겠다.
이후 이것을 바탕으로 필자의 입맛대로 확장성을 추가한 오브젝트 풀 포스팅도 있다. 캐주얼 슈팅 액션 대전 게임 10을 참고 바람
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MemoryPool : MonoBehaviour
{
private class PoolItem
{
public bool isActive;
public GameObject gameObject;
}
private int increaseCount = 5; //오브젝트가 부족할 때 Instantiate()로 추가 생성되는 오브젝트 개수
private int maxCount; //현재 리스트에 등록되어 있는 오브젝트 개수
private int activeCount; //현재 게임에 사용되고 있는 활성화 오브젝트 개수
private GameObject poolObject; //오브젝트 풀링에서 관리하는 게임 오브젝트 프리팹 // 불릿
private List<PoolItem> poolItemList; //관리되는 모든 오브젝트를 저장하는 리스트
public MemoryPool(GameObject poolObject)
{
maxCount = 0;
activeCount = 0;
this.poolObject = poolObject;
poolItemList = new List<PoolItem>();
InstantiateObjects();
}
// increraseCount 단위로 오브젝트를 생성
public void InstantiateObjects()
{
maxCount += increaseCount;
for( int i =0; i < increaseCount; i++ )
{
PoolItem poolItem = new PoolItem();
poolItem.isActive = false;
poolItem.gameObject = GameObject.Instantiate(poolObject);
poolItem.gameObject.SetActive(false);
poolItemList.Add(poolItem);
}
}
// 현재 관리중인 (활성 / 비활성) 모든 오브젝트를 삭제
public void DestroyObjects()
{
if (poolItemList == null)
return;
int count = poolItemList.Count;
for(int i = 0; i < count; i++)
{
GameObject.Destroy(poolItemList[i].gameObject);
}
poolItemList.Clear();
}
// poolItemList에 저장되어 있는 오브젝트를 활성화 해서 사용
// 현재 모든 오브젝트가 사용중이면 InstantiateObjects()로 추가 생성
public GameObject ActivePoolItem()
{
if (poolItemList == null)
return null;
if (activeCount == maxCount)
InstantiateObjects();
int count = poolItemList.Count;
for (int i = 0; i < count; i++)
{
PoolItem poolItem = poolItemList[i];
if(poolItem.isActive == false)
{
activeCount++;
poolItem.isActive = true;
poolItem.gameObject.SetActive(true);
return poolItem.gameObject;
}
}
return null;
}
// 현재 사용이 완료된 오브젝트를 비활성화 상태로 설정
public void DeactivatePoolItem(GameObject usedObject)
{
if (poolItemList == null || usedObject == null)
return;
int count = poolItemList.Count;
for (int i = 0; i < count; i++)
{
PoolItem poolItem = poolItemList[i];
if(poolItem.gameObject == usedObject)
{
activeCount--;
poolItem.isActive = false;
poolItem.gameObject.SetActive(false);
return;
}
}
}
// 게임에서 사용중인 모든 오브젝트를 비활성화 상태로 설정
public void DeactiveAllPoolItems()
{
if (poolItemList == null)
return;
int count = poolItemList.Count;
for (int i = 0; i < count; i++)
{
PoolItem poolItem = poolItemList[i];
if(poolItem.gameObject != null && poolItem.isActive == true)
{
poolItem.isActive = false;
poolItem.gameObject.SetActive(false);
}
}
activeCount = 0;
}
}
수정한 Gun 스크립트
public class Gun : MonoBehaviour
{
public Transform firePosition;
public enum Type { None, LeftGun, RightGun}
[SerializeField]
private Type gunType;
public Type GunType { get => gunType; private set => gunType = value; }
MemoryPool memoryPool;
public GameObject bullet;
private void Start()
{
memoryPool = new MemoryPool(bullet);
}
public void GunShot()
{
GameObject tempBullet = memoryPool.ActivePoolItem();
tempBullet.transform.position = firePosition.position;
tempBullet.transform.forward = firePosition.forward;
}
}
문제 발생: 좌우로 움직이거나 앞뒤로 움직이면서 총을 쏘면 여러발이 나감
애니메이터 창을 열고 trigger가 제대로 작동하는지 봤는데 좌우로 움직이거나 앞뒤로 움직일 때 벡터 zero가 되는 순간이 생기는 듯 하다... 이런...
public void OnAttack(Vector3 lookDir)
{
if (lookDir == Vector3.zero && (!stateInfo.IsName("MovingLeftAttack") && !stateInfo.IsName("MovingRightAttack")))
{
ani.ResetTrigger("onMoveAttack");
ani.SetTrigger("onAttack");
}
else if(lookDir != Vector3.zero && (!stateInfo.IsName("LeftAttack") && !stateInfo.IsName("RightAttack")))
{
ani.ResetTrigger("onAttack");
ani.SetTrigger("onMoveAttack");
}
}
그래서 조건을 조금 추가했다.
1 - 1. vector3.zero(움직이고 있지 않는 상태)고, MovingLeftAttack 이나 MovingRightAttack 애니메이션이 재생 중이 아닐때, OnMoveAttack 트리거를 끄고 onAttack 트리거를 킨다.
1 - 2. 반대의 경우엔 onAttack 트리거를 끄고 onMoveAttack 트리거를 킨다.
이렇게 바꾸니까 조금 괜찮아지긴 했는데 아직 맘에 들지 않는다.
enum을 사용해서 나눠놓는 방법도 생각을 해봤는데 어차피 저 상태 전이들은 각 공격이 끝난 후에 불리는 것이기 때문에 똑같을 것 같다.
이 문제에 대한 해결법은 두가지가 있을 것 같다.
1. 기존의 정지 공격 모션을 버리고 layer로 상체만 재생키기도록 한 공격 모션만 남기는 것인데
그러면 정지해 있을 때 애니메이션이 흔들흔들 움직이는 거라 총쏘는 느낌이 덜 할 것 같다.
2. 코루틴으로 딜레이를 주는 방법
1번은 시간도 오래걸리고 단점도 있기 때문에 2번 채택
public void OnAttack(Vector3 lookDir)
{
if (lookDir == Vector3.zero && (!stateInfo.IsName("MovingLeftAttack") && !stateInfo.IsName("MovingRightAttack")) && canAttack)
{
ani.ResetTrigger("onMoveAttack");
ani.SetTrigger("onAttack");
}
else if(lookDir != Vector3.zero && (!stateInfo.IsName("LeftAttack") && !stateInfo.IsName("RightAttack")))
{
ani.ResetTrigger("onAttack");
ani.SetTrigger("onMoveAttack");
StopCoroutine("CRDelay");
StartCoroutine("CRDelay");
}
}
IEnumerator CRDelay()
{
canAttack = false;
yield return new WaitForSeconds(0.5f);
canAttack = true;
}
문제 해결 전
문제 해결 후
참조: 오브젝트 풀링 https://www.youtube.com/watch?v=WVnuA6Jay8Q
'Unity > Portfolio Daily Log' 카테고리의 다른 글
캐주얼 슈팅 액션 대전 게임 (모바일) - 7 포톤_3 체력바, 닉네임 색깔로 적과 차별점 두기 (0) | 2022.07.22 |
---|---|
캐주얼 슈팅 액션 대전 게임 (모바일) - 6 체력바 (0) | 2022.07.22 |
캐주얼 슈팅 액션 대전 게임 (모바일) - 4 포톤_2 로컬 플레이어한테 카메라, 조이스틱 할당해주기 (0) | 2022.07.19 |
캐주얼 슈팅 액션 대전 게임 (모바일) - 3 포톤 환경 (0) | 2022.07.19 |
캐주얼 슈팅 액션 대전 게임 (모바일) - 2 공격로직 및 공격 애니메이션 (0) | 2022.07.19 |