Chapter 7-9. AI : 고기 해체
카테고리: Unity Lesson 3
태그: Unity Game Engine
인프런에 있는 케이디님의 [유니티 3D] 실전! 생존게임 만들기 - Advanced 강의를 듣고 정리한 필기입니다. 😀
🌜 강의 들으러 가기 Click
Chapter 7. 동물들의 공격형 AI, 도망형 AI 구현
🚖 칼 만들기 (고기를 해체할 수 있는)
칼 질하는 손 모델
고기를 해체할 때는 칼을 들고 있는 손이 나와야 하기 때문에 Holder
에 빈 오브젝트 Meat Knife Holder
를 만들고 그의 자식으로 칼을 들고 있는 손 모델을 넣어 주었다.
Meat Knife Holder
- 레이어는 다른 손, 무기들과 마찬가지로 Weapon 으로 해주어야 Weapon Camera 에서 렌더링 될 수 있다.
- 트랜스폼 상태도 다른 무기 Holder 들과 마찬가지로
Meat Knife
- 애니메이터 붙이기
- 고기를 해체할 때만 나와야 하기 때문에 디폴트 비활성화
칼질 애니메이션
Meat Knife
가 활성화되자마자 고기를 해체하는 애니메이션이 재생될 것이다. 디폴트 State 이므로.
🚖 고기 아이템
고기 프리팹
Meat Raw Item
프리팹- 태그와 레이어
Item
- Box Collider
- Rigidbody
- Mass 0.001
- Drag 0.5
- Angular Drag 0.5
- 📜ItemPickUp
- 태그와 레이어
고기 아이템화
방금 만든 고기 프리팹 할당. 고기 프리팹의 📜ItemPickUp 에도 이 Meat_Raw
Item 을 할당해준다.
📜WeakAnimal.cs
[SerializeField]
protected Item item_Prefab; // 해당 동물에게서 얻을 아이템
[SerializeField]
public int itemNumber; // 아이템 획득 개수
public Item GetItem()
{
this.gameObject.tag = "Untagged";
Destroy(this.gameObject, 3f);
return item_Prefab;
}
약한 동물들(돼지)은 죽으면 아이템화(고기) 시킬 것이라서 추가했다.
- GetItem()
- 해당 동물이 죽으면 이 함수를 호출하여 아이템화 할 것이다.
- 동물이 죽어도 태그가 계속 “Weak_Animal”인 상태면 같은 여러 곳에서 Raycast 충돌 처리가 일어날 수 있기 때문에 태그를 “Untagged”로 바꿔준다.
- 이 과정을 해주지 않으면 돼지가 죽어도 태그가 계속 “Weak_Animal”이기 때문에 곡괭이로 죽은 돼지를 떄리면 소리가 나고 데미지도 돼지에게 또 들어가게 된다. 📜PickaxeController
- 이 과정을 해주지 않으면 추후 서술될 📜ActionController 에서 돼지가 죽었음에도 불구하고 죽은 돼지를 향해 바라보면 계속 ‘고기 해체’ 액션 텍스트가 활성화 될 것이므로 태그를 “Untagged”로 바꿔준다.
else
에 걸리 도록 👇else if (hitInfo.transform.tag == "Weak_Animal") MeatInfoAppear(); else InfoDisappear();
- 돼지가 죽으면 돼지를 3초 후에 없어지게 한다.
Mear_Raw
Item 이 할당 되어 있는item_Prefab
을 리턴한다.- 고기 Item 리턴
- 동물이 죽어도 태그가 계속 “Weak_Animal”인 상태면 같은 여러 곳에서 Raycast 충돌 처리가 일어날 수 있기 때문에 태그를 “Untagged”로 바꿔준다.
- 해당 동물이 죽으면 이 함수를 호출하여 아이템화 할 것이다.
돼지는 죽으면 Mear_Raw
Item 아이템화 된다. 돼지를 사냥하면 고기 2 개를 얻을 수 있다.
🚖 고기 해체 처리
📜PlayerControllder.cs
static public bool isActivated = true;
void Update()
{
if (isActivated)
{
IsGround();
TryJump();
TryRun();
TryCrouch();
Move();
MoveCheck();
if (!Inventory.invectoryActivated)
{
CameraRotation();
CharacterRotation();
}
}
}
- 고기를 해체할 땐 플레이어가 움직일 수 없도록 할 것이다.
- 고기 해체 중에는
isActivated
를 해체 중일 동안만 False 로 만들 것이다.
- 고기 해체 중에는
- 이렇게 해주는 이유
- 밑에 서술할 📜ActionController 에서, 고기 해체 중에 플레이어가 고개를 돌리거나 움직여서 메인 카메라에서 쏘는 Raycast 방향이 바뀌어
hitInfo
가 죽은 돼지가 아닌, Terrain 이 되버린다거나 하면, 돼지로부터 고기 아이템을 얻으려는 아래 코드에서 런타임 에러가 발생하기 때문이다.hitIfno
에 돼지가 있지 않기 때문에! 따라서 고기를 해체하는 애니메이션을 할 때 동안에는hitInfo
에 다른게 들어가지 않도록 플레이어가 움직이지 못 하도록 설정.theInventory.AcquireItem(hitInfo.transform.GetComponent<WeakAnimal>().GetItem(), hitInfo.transform.GetComponent<WeakAnimal>().itemNumber);
- 밑에 서술할 📜ActionController 에서, 고기 해체 중에 플레이어가 고개를 돌리거나 움직여서 메인 카메라에서 쏘는 Raycast 방향이 바뀌어
- 따라서
isActivated
True 일 때만 Update 실행
📜WeaponSway.cs
public static bool isActivated = true;
void Update()
{
if (!Inventory.invectoryActivated && isActivated)
TrySway();
}
고기를 해체하는 손은 📜WeaponSway 를 적용하지 않도록 힐 것이기 때문에 isActivated
설정. 따라서 isActivated
True 일 때만 Update 실행
Animal Layer
먼저 돼지와 사자의 Layer 를 Animal
로 설정해주었다. 📜ActionController.cs 에서 액션 텍스트를 띄울 때 Item
Layer 를 가진 것들에 대해서만 Raycast 를 하고 있기 때문에 따로 동물들용 레이어를 만들어 주었고, Item 뿐만 아니라 동물들에 대해서도 Raycast 를 하도록 하였다.
죽은 돼지가 메인 카메라의 Raycast를 받아 액션 텍스트를 띄울 수 있도록
동물들 레이어를 따로 만들어 주었으니 메인 카메라에 잘 담기도록 Culling Mask 추가
무기에 맞을 수 있도록 동물 레이어 추가
📜ActionController.cs
- 죽은 돼지를 화면 가운데 들어오게 바라보면 고기를 주울 수 있다는 액션 텍스트를 띄운다.
- 카메라에서 Raycast 쏨
- 죽은 돼지를 화면 가운데 들어오게 바라본 상태에서
E
키를 누르면- 고기를 해체하는 손으로 바뀌고
- 고기를 해체하는 애니메이션이 나오며
- 고기가 아이템화 되어 인벤토리에 들어가게 한다.
public class ActionController : MonoBehaviour
{
private bool pickupActivated = false; // 아이템 습득 가능할시 True
private bool dissolveActivated = false; // 고기 해체 가능할 시 True (돼지 시체를 바라 볼 때)
private bool isDissolving = false; // 고기 해체 중일 때 True (중복해서 해체하지 않도록)
aponManager; // 고기 해체할 때 기존에 들고 있던 무기를 비활성화하기 위해서
[SerializeField]
private Transform tf_MeatDissolveTool; // 고기 해체 손. 즉 Meat Knife. 고기 해체할 때 활성화 해야 함
[SerializeField]
private string sound_meat; // 고기 해체 소리
void Update()
{
CheckAction(); // 고기 or 아이템 액션 텍스트 띄우기 시도
TryAction();
}
private void TryAction()
{
if(Input.GetKeyDown(KeyCode.E)) // E 키 입력이 들어 오면
{
CheckAction(); // 고기 or 아이템 액션 텍스트 띄우기 시도
CanPickUp(); // 아이템을 주울 수 있는지
CanMeat(); // 고기를 주울 수 있는지
}
}
public IEnumerator WhenInventoryIsFull()
{
itemFullText.gameObject.SetActive(true);
itemFullText.text = "아이템이 꽉 찼습니다.";
yield return new WaitForSeconds(3.0f);
itemFullText.gameObject.SetActive(false);
}
private void CheckAction()
{
if (Physics.Raycast(transform.position, transform.forward, out hitInfo, range, layerMask))
{
if (hitInfo.transform.tag == "Item")
ItemInfoAppear();
else if (hitInfo.transform.tag == "Weak_Animal")
MeatInfoAppear();
else
InfoDisappear();
}
else
InfoDisappear();
}
private void ItemInfoAppear()
{
pickupActivated = true;
actionText.gameObject.SetActive(true);
actionText.text = hitInfo.transform.GetComponent<ItemPickUp>().item.itemName + " 획득 " + "<color=yellow>" + "(E)" + "</color>";
}
private void MeatInfoAppear()
{
// 📜Animal.cs의 animalName, isDead 을 public 으로 바꿨다.
if (hitInfo.transform.GetComponent<Animal>().isDead) // 죽은 돼지를 바라봤을 경우에만 (카메라에서 쏜 Raycast가 죽은 돼지와 충돌)
{
dissolveActivated = true;
actionText.gameObject.SetActive(true);
actionText.text = hitInfo.transform.GetComponent<Animal>().animalName + " 해체하기 " + "<color=yellow>" + "(E)" + "</color>";
}
}
private void InfoDisappear()
{
pickupActivated = false;
dissolveActivated = false;
actionText.gameObject.SetActive(false);
}
private void CanPickUp()
{
if(pickupActivated)
{
if(hitInfo.transform != null)
{
Debug.Log(hitInfo.transform.GetComponent<ItemPickUp>().item.itemName + " 획득 했습니다.");
theInventory.AcquireItem(hitInfo.transform.GetComponent<ItemPickUp>().item);
Destroy(hitInfo.transform.gameObject);
InfoDisappear();
}
}
}
IEnumerator MeatCoroutine()
{
WeaponManager.isChangeWeapon = true; // 고기 해체 중에 무기가 교체되지 않도록
WeaponSway.isActivated = false;
// 들고 있던 무기 비활
WeaponManager.currentWeaponAnim.SetTrigger("Weapon_Out");
PlayerController.isActivated = false;
yield return new WaitForSeconds(0.2f); // 애니메이션 재생 후 비활되도록
WeaponManager.currentWeapon.gameObject.SetActive(false);
// 칼 꺼내기
tf_MeatDissolveTool.gameObject.SetActive(true); // 애니메이션은 이때 자동으로 실행됨 (디폴트상태니까)
yield return new WaitForSeconds(0.2f); // 애니메이션 0.2초 진행 후
SoundManager.instance.PlaySE(sound_meat); // 고기 해체 소리
yield return new WaitForSeconds(1.8f); // 칼 해체하는 애니메이션 다 끝나길 기다림
// 고기 아이템 얻기
theInventory.AcquireItem(hitInfo.transform.GetComponent<WeakAnimal>().GetItem(), hitInfo.transform.GetComponent<WeakAnimal>().itemNumber);
// 칼 해체 손은 집어 넣고 다시 원래 무기로
WeaponManager.currentWeapon.gameObject.SetActive(true);
tf_MeatDissolveTool.gameObject.SetActive(false);
PlayerController.isActivated = true;
WeaponSway.isActivated = true;
WeaponManager.isChangeWeapon = false; // 다시 무기 교체가 가능하도록
isDissolving = false;
}
private void CanMeat()
{
if (dissolveActivated)
{
if (hitInfo.transform.tag == "Weak_Animal" && hitInfo.transform.GetComponent<Animal>().isDead && !isDissolving)
{
isDissolving = true;
InfoDisappear();
StartCoroutine(MeatCoroutine()); // 고기 해체 실시
}
}
}
}
- 평소에
- CheckAction
- 화면 가운데에 무언가가 있을 때(카메라로부터 쏜 Raycast에서 True 리턴 받았을 때)
- 그게 아이템이라면 주울 수 있다는 액션 텍스트를 활성화 ‘시도’ 한다.
- ItemInfoAppear();
- 그게 돼지라면 고기를 얻을 수 있다는 액션 텍스트를 활성화 ‘시도’ 한다.
- MeatInfoAppear(); 👉 그
hitInfo
가 죽은 돼지일 때private void MeatInfoAppear() { // 📜Animal.cs의 animalName, isDead 을 public 으로 바꿨다. if (hitInfo.transform.GetComponent<Animal>().isDead) // 죽은 돼지를 바라봤을 경우에만 (카메라에서 쏜 Raycast가 죽은 돼지와 충돌) { dissolveActivated = true; // 이제부터 해체 가능하도록 actionText.gameObject.SetActive(true); // 아래 텍스트 띄우기 actionText.text = hitInfo.transform.GetComponent<Animal>().animalName + " 해체하기 " + "<color=yellow>" + "(E)" + "</color>"; } }
- MeatInfoAppear(); 👉 그
- 화면 가운데 뭔가 있긴한데 아이템 태그도 아니고 Weak Animal 태그도 아니라면
- InfoDisappear()
- 돼지가 죽으면 📜WeakAnimal의 GetItem() 에 의해 태그를 “Untagged”로 바꿀 것이기 때문에, 이렇게 이미 고기를 얻어 태그가 “Untagged”가 된 죽은 돼지일 경우에는 텍스트를 띄우지 않도록 else 처리를 해주었다.
private void InfoDisappear() { pickupActivated = false; // 아이템 주울 수 없도록 dissolveActivated = false; // 해체가 가능하지 않도록 actionText.gameObject.SetActive(false); // 텍스트 비활 }
- 돼지가 죽으면 📜WeakAnimal의 GetItem() 에 의해 태그를 “Untagged”로 바꿀 것이기 때문에, 이렇게 이미 고기를 얻어 태그가 “Untagged”가 된 죽은 돼지일 경우에는 텍스트를 띄우지 않도록 else 처리를 해주었다.
- InfoDisappear()
- 그게 아이템이라면 주울 수 있다는 액션 텍스트를 활성화 ‘시도’ 한다.
- 화면 가운데에 무언가가 있을 때(카메라로부터 쏜 Raycast에서 True 리턴 받았을 때)
- CheckAction
E
키 입력이 들어 왔을 때- CheckAction() 액션 텍스트 띄우기
- CanPickUp() 아이템 줍기
- CanMeat() 고기 아이템 얻기
dissolveActivated
해체가 가능한 상태일 때만hitInfo
가 약한 동물이며 + 죽은 상태고 + 해체 중이 아닐 때만(중복 해체되면 안되니까)isDissolving
해체 중임을 표시- 텍스트는 비활 InfoDisappear()
- 실제로 해체 처리를 진행한다. StartCoroutine(MeatCoroutine());
- MeatCoroutine() 👉 실제 고기 해체 처리
- 고기 해체 중에는 무기가 교체되지 않도록
- 고기 해체 중에는 무기가 흔들리지 않도록 (📜WeaponSway에 의하여 마우스 회전에 따라 손을 자연스럽게 반대 방향으로 쏠리게 했었다. 고기 해체 손에는 이게 부자연스러워서)
WeaponManager.isChangeWeapon = true; // 고기 해체 중에 무기가 교체되지 않도록 WeaponSway.isActivated = false;
- 들고 있던 무기 비활성화
- 들고 있던 무기 집어넣는 애니메이션 재생
- 고기 해체 중에는 플레이어가 못 움직이게 하며
- 들고 있던 무기를 집어 넣는 애니메이션이 충분히 재생 된 후 비활 되도록 0.2 초 기다리고
- 돌고 있던 무기 비활성화
WeaponManager.currentWeaponAnim.SetTrigger("Weapon_Out"); PlayerController.isActivated = false; yield return new WaitForSeconds(0.2f); // 애니메이션 재생 후 비활되도록 WeaponManager.currentWeapon.gameObject.SetActive(false);
- 칼 들고 있는 손 모델 꺼내기
- 칼 들고 있는 손 모델 활성화
Meat Knife
오브젝트 활성화 - 이 오브젝트가 활성화 되자마자 칼질 하는 애니메이션이 재생될 것이므로, 자연스럽게 0.2초 대기 후 고기 해체 소리 재생
- 해체 애니메이션이 전부 다 진행될 수 있도록 1.8초 기다림
tf_MeatDissolveTool.gameObject.SetActive(true); // 애니메이션은 이때 자동으로 실행됨 (디폴트상태니까) yield return new WaitForSeconds(0.2f); // 애니메이션 0.2초 진행 후 SoundManager.instance.PlaySE(sound_meat); // 고기 해체 소리 yield return new WaitForSeconds(1.8f); // 칼 해체하는 애니메이션 다 끝나길 기다림
- 칼 들고 있는 손 모델 활성화
- 고기 아이템 얻기
hitInfo
에 딱 돼지가 계속 유지되야 런타임 에러가 안날 수 있는데, 📜PlayerContoller가 움직이지 않도록 해주었기 때문에 괜찮다. 강제로 돼지만 바라보고 있게 되서 괜찮다.theInventory.AcquireItem(hitInfo.transform.GetComponent<WeakAnimal>().GetItem(), hitInfo.transform.GetComponent<WeakAnimal>().itemNumber);
- 칼 들고 있는 손 모델은 다시 비활성화 하고 원래 무기 활성화
WeaponManager.currentWeapon.gameObject.SetActive(true); tf_MeatDissolveTool.gameObject.SetActive(false);
- 다른 작업 할 수 있도록 다시 원상 복귀
PlayerController.isActivated = true; WeaponSway.isActivated = true; WeaponManager.isChangeWeapon = false; // 다시 무기 교체가 가능하도록 isDissolving = false;
사운드 매니저에 고기 해체 소리 추가함.
Layer Mask 에는 아이템, 돼지 에게 다 카메라의 Raycast 가 적용되도록 Animal Layer 를 추가로 선택해준 상태다. 칼 들고 있는 손 모델 할당. 고기 해체 소리 이름 할당.
📜Animal.cs
protected void Dead()
{
PlaySE(sound_Dead);
isWalking = false;
isRunning = false;
isDead = true;
isAttacking = false;
anim.SetTrigger("Dead");
nav.ResetPath();
}
동물이 죽으면 더 이상 공격하지 못하도록, 움직이지 않도록 하였다. nav.ResetPath()
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글 남기기