카테고리 없음

유니티 레벨 디자인을 위한 장치 - 2. 자석 효과와 폐기 장소 만들기

akama 2024. 10. 3. 09:40

게임 레벨 디자인에 필요한 장치에서 목표 오브젝트인 쓰레기를 폐기하는 장소와 쓰레기 최대 수용량을 만들어서 난이도에 영향을 주도록 해보겠습니다. 해당 기능의 디자인은 캐릭터가 쓰레기를 수거하면 게이지를 통해서 수거된 부분을 UI로 보여주며 최대 수용량에 다다르면 게이지가 가득찬 상태라 되고 더 이상 쓰레기를 흡입하지 못 하도록 만듭니다. 이럴 경우 폐기 장소를 찾아서 쓰레기를 버리도록 만드는데 가는 길목이 좁거나 장애물을 배치해서 난이도를 상승시키고자 합니다. 이전 포스팅에서 로봇 청소기는 목표물에 충돌하면 OnTriggerEnter2D 함수를 통해서 쓰레기 오브젝트 Tag를 구분하고 회수하면서 점수를 얻고 남은 목표물 개수를 카운트 다운시켰습니다. 쓰레기를 흡입하고, 모아서 버리는 사이클을 수행하기 위해서는 흡입하는 부분, 모으는 과정 표시와 한계치 적용, 모은 것을 버리는 곳 등의 기능을 수행할 요소들을 만들어야 합니다. 먼저 로봇 청소기가 목표물인 쓰레기 오브젝트를 회수할 때 마치 진공청소기가 부스러기를 빨아들이는 느낌이 들도록 기능을 추가할 생각입니다.

 

1. 진공 흡입하기

실제로 청소기를 사용하다보면 흡입력에 의해서 청소기에서 약간 떨어져 있는 물체도 빨려져서 청소기로 들어가는 것을 본적이 있을 것입니다. 어떻게 보면 자석 같은 느낌인데 캐릭터가 근처에 있을 때 목표 오브젝트가 캐릭터 방향으로 이끌려 들어가도록 하기 위해서 이를 처리할 오브젝트를 생성해야 합니다. 캐릭터 세팅을 먼저 해야하는데 캐릭터 오브젝트를 클릭한 상태에서 2D Object -Sprite - Square 를 생성해주고 Sprite Reneder 컴포넌트의 Color 알파값을 조정해서 투명으로 만들어 줍니다. 해당 오브젝트의 태그에는 자석처럼 끌어 당긴다는 뜻으로 Magnet 이라는 태그를 만들어서 지정해 줍니다.

Magnet collider 박스를 캐릭터보다 크게 세팅: 콜라이더에 닿은 trashObjects는 캐릭터 중심부를 향함.

스크립트에서는 캐릭터와 목표 오브젝트간의 서로 당기는 상호작용을 만들기 위해서 Vector3.MoveTowards를 이용합니다.

Vector3.MoveTowards는 해당 스크립트를 컴포넌트로 가지고 있는 오브젝트의 transform.position의 값을 조정합니다. 조정하는 방법은 오브젝트의 위치를 target.position으로 이동시키며 세기나 강도를 magnetStrength로 조절해 줍니다. 위 그림의 스크립트를 Update함수에 넣어 처리하면 오브젝트는 target.position으로 이동하게 만들 수 있습니다. 목표물 오브젝트가 빨려서 흡입되어 가는 현상을 만들려면 이 명령어가 있는 스크립트가 캐릭터 보다는 목표 오브젝트가 되면 좋을 것 같습니다. 따라서 이전 포스팅 '유니티 게임 미션 성공 이벤트' 편에서 다룰 때 만들었던 TrashObject 스크립트를 열어줍니다.

public class TrashObject : MonoBehaviour
{

Transform target;
bool isEvacuated = false;
public float magnetStrength = 3f;


 void Start ()
{
      target = GameObject.Find("Player").GetComponent<Transform>();
}

  void Update()
    {
            if (isEvacuated)
            {
                transform.position = Vector3.MoveTowards(transform.position, target.position, Time.deltaTime * magnetStrength);
            }
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
          if (collision.CompareTag("Player")) 
          {
                gameObject.SetActive(false); 
          }

          if (isEvacuated)
           {
                if (collision.CompareTag("Destroyer"))
                {
                SoundManager.Instance.PlaySFX("trash");
                gameObject.SetActive(false);
                }
           }

          if (collision.CompareTag("Magnet"))
          {
                isEvacuated = true;
          }
    }

    private void OnTriggerExit2D(Collider2D collision)
    {
            if (collision.CompareTag("Magnet"))
            {
                isEvacuated = false;
            }
    }
}

몇 가지가 추가되고 일부는 삭제 및 변경되었습니다. 이 스크립트는 목표물인 쓰레기 오브젝트에 컴포넌트로 존재합니다. 게임이 시작되면 Player 태그가 있는 오브젝트를 타겟으로 삼게 되어 있습니다. 바로 작동하지 않도록 isEvacuated 불리언 변수로 막아 놓았습니다. Magnet이라는 태그가 있는 오브젝트에 충돌했을 때 isEvacuated = true 바꾸면서 Update 함수에 있는 명령이 매초 실행되면서 오브젝트가 target = Player 방향으로 이동을 하게 됩니다. 단 여기서 Magnet 태그 오브젝트콜라이더 범위를 벗어나게 되면 즉시 isEvacuated = false로 변경되면서 Update 함수의 명령이 중지되고 오브젝트의 target방향 이동이 멈추게 됩니다. 추가로 이전에는 Player 태그가 있는 캐릭터 콜라이더에 닿으면 바로 해당 오브젝트가 비활성화 되었는데 이 보다는 캐릭터 밑으로 쓰레기가 흡입되도록 만들기 위해서 쓰레기를 없애는 오브젝트 및 태그를 추가로 만들었습니다. 캐릭터를 클릭한 상태에서 2D Object -Sprite - Circle을 만들고 투명하게 해줍니다. 해당 오브젝트에는 Destroyer라는 태그를 생성하고 지정해 줍니다. 캐릭터 오브젝트와 쓰레기 오브젝트에 있는 Sprite Renderer의 Order in layer의 값을 조정해 줍니다. 캐릭터가 위로 보여져야 하기 때문에 캐릭터 오브젝트의 Order in layer 값이 상대적으로 더 크도록 값을 설정해 줍니다. 그리고 흡입 범위는 magnet 콜라이더의 범위이기 때문에 콜라이더 크기를 조정해서 흡입되는 영역을 지정해 줍니다. 게임을 실행해서 쓰레기 오브젝트로 캐릭터를 이동시키면 마치 빨려 들어가는 것처럼 흡입되는 것을 볼 수 있습니다.

 

2. 목표물 회수 과정 표현 및 한계치

목표물인 쓰레기를 회수하면 그 과정을 UI 게이지에 표현하려고 합니다. 회수하는 과정은 UI Image를 통해서 이전에 다루었던 게이지바 형태로 보여 줍니다. Hierarchy뷰에서 Canvas를 클릭해서 UI - image 를 만들고 이름을 trashBar라고 해줍니다. 같은 방법으로 하나 더 만들어서 trashBarBack을 만들어서 게이지바의 뒷배경 게이지로 사용합니다. Hierarchy뷰에서 trashBar가 하단으로 내려오도록 위치 시킵니다.

스크립트는 GameController와 PlayMovement 모두 관련 명령어들을 추가해야 합니다. 먼저 GameController 스크립트에 관련 변수 및 명령어들을 추가해 줍니다.

public class GameController : MonoBehaviour
{
    public Image trashBar;
    float trashQuantity = 0f;
    public float trashQuantityMax = 10f;
    PlayerMovement playerMovement;

    void Update ()
    {
        trashBar.fillAmount = Mathf.Lerp(trashBar.fillAmount, trashQuantity/trashQuantityMax, lerpSpeed);
    }

    public void UpdateTrashCan(float t)
    {
        if(trashQuantity < trashQuantityMax)
        {
            trashQuantity += t;
        }

        if(trashQuantity >= trashQuantityMax)
        {
            trashQuantity = trashQuantityMax;
            
            playerMovement = GameObject.Find("Player").GetComponent<PlayerMovement>();
            playerMovement.StopEvacuating();
        }
    }
}

public 선언된 trashBar에는 Canvas에서 생성한 trashBar UI 오브젝트를 지정해 줍니다. 부드러운 게이지바 처리를 위해 Mathf.Lerp 처리된 명령을 Updade 함수에 넣어 줍니다. 회수된 쓰레기 및 수용치 최대가 되면 흡입을 멈추도록 UpdateTrashCan(float t)이라는 함수를 만들어 주었습니다. 원래 이 부분은 캐릭터 조작을 관리하는 PlayerMovement에서 처리해도 되지만 UI 관리(trashBar) 및 관련 변수가 필요해서 GameController에서 처리하도록 만들어 보았습니다. 캐릭터에서 만든 Destroyer 콜라이더를 통해서 쓰레기가 처리되면 GameController로 쓰레기 양을 추가해 주는 것입니다. 예를 들어서 작은 먼지라면 1개를 큰 덩어리 먼지나 쓰레기라면 3개를 주어서 trashQuantity 개수를 늘려 나갑니다. 한편 최대치를 설정해서 쓰레기 수용량 최대치에 도달하면 캐릭터 오브젝트에게 흡입작동 중지를 전달해야 합니다. 따라서 캐릭터에게 전달할 PlayerMovement 컴포넌트를 가져와서 StopEvacuating(); 호출합니다. 이제 이 호출을 받아줄 PlayerMovement에 함수를 추가하기 위해 PlayerMovement 스크립트를 열어줍니다.

public class PlayerMovement : MonoBehaviour
{
    public GameObject magnet = true;
    public GameObject destroyZone = true;

    public void StopEvacuating()
    {
        magnet.SetActive(false);
        destroyZone.SetActive(false);
    }
}

간단하게 목표물 오브젝트인 쓰레기를 흡입하거나 제거하는 콜라이더를 지닌 오브젝트를 비활성화 해주도록 합니다. 이 방법 말고 각 오브젝트의 콜라이더에 접근해서 해당 콜라이더의 Enabled 를 false로 처리해서 콜라이더만 비활성화 시키는 방법도 있습니다. 이렇게 되면 더 이상의 쓰레기 회수가 안 되는 상태가 됩니다. 한편 쓰레기 양의 처리는 흡수 및 제거되는 TrashObject에서 GameController로 전달하도록 해줍니다. 기전 TrashObject에서 몇 줄만 추가해 주면 끝납니다.

public class TrashObject : MonoBehaviour
{

  Transform target;
  bool isEvacuated = false;
  public float magnetStrength = 3f;
  public float quantity = 1;
  private GameController gameManager;

    void Start ()
   {
         target = GameObject.Find("Player").GetComponent();
         gameManager = GameObject.Find("GameManager").GetComponent<GameController>();
   }

     private void OnTriggerEnter2D(Collider2D collision)
    {
          if (isEvacuated)
           {
                if (collision.CompareTag("Destroyer"))
                {
                SoundManager.Instance.PlaySFX("trash");
                gameManager.UpdateTrashCan(quantity);
                gameObject.SetActive(false);
                }
           }

          if (collision.CompareTag("Magnet"))
          {
                isEvacuated = true;
          }
    }
}

public으로 선언된 quantity는 쓰레기의 양을 뜻하며 Inspector에서 수치 변경을 통해 조절 가능합니다. 정수타입인 int가 아닌 소수 타입인 float을 사용하는 이유는 UI에서 trashBar로 보여주기 위해 필요한 타입이 float이기 때문입니다.

 

3. 회수한 목표 오브젝트(쓰레기) 폐기 장소 만들기

이제 열심히 청소한 AI 로봇에게는 한가득 찬 먼지 및 쓰레기가 있습니다. 더 이상 수용할 수 없어서 흡입 기능을 잠시 중단시킨 상태입니다. 이 쓰레기들을 버릴 장소가 필요합니다. 버릴 장소에 대한 장치를 만들기 위해서 먼저 관련 이미지를 구상하고 어떤 애니메이션을 줄지도 정해 놓아야 합니다.

쓰레기를 폐기할 오브젝트의 이미지를 준비 (예시 DumpSite sprite)

예시에서는 공장 같은 느낌의 철문이 양쪽에서 열리고 닫히는 애니메이션을 만들어 준비하고 캐릭터에 반응하도록 하겠습니다. 먼저 2D - Square 오브젝트를 만들고 이름을 dumpSite라고 바꾸고, image Renderer 컴포넌트에는 '폐기장소'의 닫힌 모습을 넣어 줍니다. 선택된 상태에서 Animation 창을 열고 열고 닫히는 animation clip들을 만듭니다. 2D 애니메이션 만드는 방법은 이전 포스팅인 유니티 2D 애니메이션에서 다루었으니 여기서는 만드는 방법은 생략합니다. 가장 중요한 부분은 이 오브젝트에 BoxCollider 컴포넌트를 추가해 주어서 캐릭터와 상호작용하도록 만들어야 합니다. 그림에서는 이미지보다 작도록 콜라이더 사이즈를 조정했습니다. 오브젝트에 대한 세팅이 끝나면 이제 스크립트를 한 개 만들고 이름을 DumpSite라고 지어주고 해당 스크립트를 dumpSite 오브젝트에 추가해 줍니다. 

public class DumpSite : MonoBehaviour
{

    PlayerMovement playerMovement;
    GameController gameManager;
    Animator animator;

 void Start ()
 {
      playerMovement = GameObject.Find("Player").GetComponent();
      gameManager = GameObject.Find("GameManager").GetComponent<GameController>();
      animator = GetComponent Animator>();
 }

    private void OnTriggerEnter2D(Collider2D collision)
    {
           if (collision.CompareTag("Player"))
           {
                SoundManager.Instance.PlaySFX("gateSfx");
                gameManager.UpdateTrashCan(0);
                playerMovement.reWorking();
                animator.SetBool("gateState", true);
                StartCoroutine(CloseGateAnim());
           }
    }

    IEnumerator CloseGateAnim ()
    {
          yield return new WaitForSeconds(1.5f);
          animator.SetBool("gateState", false);
    }
}

dumpSite 오브젝트의 콜라이더에 Player가 닿으면 SoundManager를 통한 효과음과 Animator 컴포넌트에 의한 순차적 애니메이션이 발생하게 됩니다. 그리고 gameManager에는 UpdateTrashCan 함수에 쓰레기 모두 버렸다는 뜻으로 0을 전달하면서 UI 게이지에서 0인 빈 게이지로 보여지도록 하고, playerMovement에는 reWorking 함수에 다시 흡입 및 쓰레기 제거가 가능하도록 명령을 전달합니다. 따라서 PlayerMovement 스크립트를 열고 호출에 응답할 수 있도록  reWorking 함수를 추가해 주어야 합니다.

public class PlayerMovement : MonoBehaviour
{
    public void reWorking()
    {
        magnet.SetActive(true);
        destroyZone.SetActive(true);
    }
}

게임을 실행해서 월드에 배치되어 있는 쓰레기들을 치우고, 수용치 한계에 도달하면 폐기장을 찾아서 버리고 다시 치우는 것으로 미션 목표를 달성할 수 있게 되었습니다. 물론 이 부분도 배터리 용량에 따른 시간 제약이 있습니다. 반대로 배터리를 충전해주는 아이템을 만들 수도 있고 쓰레기 폐기장을 복사해서 더 만들어서 레벨을 쉽게 할 수도 있습니다. 레벨 디자인에서 각 스테이지의 난이도는 각 요소를 얼만큼 어디에 배치 하느냐에 따라서 달라집니다. 다음은 캐릭터의 이동을 방해하거나 캐릭터에게 피해를 주는 장애물 요소에 대해서 포스팅해 보겠습니다.