Chapter 7-9. AI : 고기 해체

Date:     Updated:

카테고리:

태그:

인프런에 있는 케이디님의 [유니티 3D] 실전! 생존게임 만들기 - Advanced 강의를 듣고 정리한 필기입니다. 😀
🌜 강의 들으러 가기 Click

Chapter 7. 동물들의 공격형 AI, 도망형 AI 구현

🚖 칼 만들기 (고기를 해체할 수 있는)

칼 질하는 손 모델

image

고기를 해체할 때는 칼을 들고 있는 손이 나와야 하기 때문에 Holder에 빈 오브젝트 Meat Knife Holder를 만들고 그의 자식으로 칼을 들고 있는 손 모델을 넣어 주었다.

  • Meat Knife Holder
    • 레이어는 다른 손, 무기들과 마찬가지로 Weapon 으로 해주어야 Weapon Camera 에서 렌더링 될 수 있다.
    • 트랜스폼 상태도 다른 무기 Holder 들과 마찬가지로
    • Meat Knife
      • 애니메이터 붙이기
      • 고기를 해체할 때만 나와야 하기 때문에 디폴트 비활성화

칼질 애니메이션

image

Meat Knife가 활성화되자마자 고기를 해체하는 애니메이션이 재생될 것이다. 디폴트 State 이므로.


🚖 고기 아이템

고기 프리팹

image

  • Meat Raw Item 프리팹
    • 태그와 레이어 Item
    • Box Collider
    • Rigidbody
      • Mass 0.001
      • Drag 0.5
      • Angular Drag 0.5
    • 📜ItemPickUp


고기 아이템화

image

방금 만든 고기 프리팹 할당. 고기 프리팹의 📜ItemPickUp 에도 이 Meat_Raw Item 을 할당해준다.

image


📜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 리턴

image

돼지는 죽으면 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);
      
  • 따라서 isActivated True 일 때만 Update 실행

📜WeaponSway.cs

public static bool isActivated = true;

    void Update()
    {
        if (!Inventory.invectoryActivated && isActivated)
            TrySway();
    }

고기를 해체하는 손은 📜WeaponSway 를 적용하지 않도록 힐 것이기 때문에 isActivated 설정. 따라서 isActivated True 일 때만 Update 실행


Animal Layer

image

먼저 돼지와 사자의 Layer 를 Animal로 설정해주었다. 📜ActionController.cs 에서 액션 텍스트를 띄울 때 Item Layer 를 가진 것들에 대해서만 Raycast 를 하고 있기 때문에 따로 동물들용 레이어를 만들어 주었고, Item 뿐만 아니라 동물들에 대해서도 Raycast 를 하도록 하였다.

image

죽은 돼지가 메인 카메라의 Raycast를 받아 액션 텍스트를 띄울 수 있도록

image

동물들 레이어를 따로 만들어 주었으니 메인 카메라에 잘 담기도록 Culling Mask 추가

image

무기에 맞을 수 있도록 동물 레이어 추가

📜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>";
                }
            }
            
        • 화면 가운데 뭔가 있긴한데 아이템 태그도 아니고 Weak Animal 태그도 아니라면
          • InfoDisappear()
            • 돼지가 죽으면 📜WeakAnimal의 GetItem() 에 의해 태그를 “Untagged”로 바꿀 것이기 때문에, 이렇게 이미 고기를 얻어 태그가 “Untagged”가 된 죽은 돼지일 경우에는 텍스트를 띄우지 않도록 else 처리를 해주었다.
              private void InfoDisappear()
              {
                pickupActivated = false;  // 아이템 주울 수 없도록
                dissolveActivated = false;  // 해체가 가능하지 않도록
                actionText.gameObject.SetActive(false);  // 텍스트 비활
              }
              
  • 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;
      

image

사운드 매니저에 고기 해체 소리 추가함.

image

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()



🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기

Unity Lesson 3 카테고리 내 다른 글 보러가기

댓글 남기기