멈추지 않고 끈질기게

[Unity][포트폴리오] 진짜 혹은 가짜(True or False) 발판 본문

포트폴리오

[Unity][포트폴리오] 진짜 혹은 가짜(True or False) 발판

sam0308 2023. 6. 10. 11:48

※ 해당 포스팅은 개인의 공부 정리용 글입니다. 틀린 내용이 있다면 추후 수정될 수 있습니다.

※ 해당 포스팅은 Unity 2021.3.15f1 버전을 기준으로 작성되었습니다.

 개발 중인 유니티 3D 포트폴리오의 소스 코드를 포함하고 있습니다.

 

 

 

1. True or False 발판

 폴가이즈를 보면 착지해보기 전까지 진짜인지 가짜인지 모르는 발판이 있습니다. 진짜일 경우 발판 색이 바뀌면서 고정되고, 가짜일 경우 사라집니다. 이번에는 해당 발판을 구현해서 신규 스테이지를 추가하려고 합니다. 

public class TrueOrFalsePlatform : MonoBehaviour
{
    MeshRenderer myRenderer;
    Animator animator;

    [Header("낙하")]
    [SerializeField] float fallDelay;
    bool isTrue;

    [Header("머리티얼 변경")]
    [SerializeField] Material trueMaterial;

    void Awake()
    {
        myRenderer = GetComponent<MeshRenderer>();
        animator = GetComponent<Animator>();
    }

 착지 시 진짜 발판이라면 머티리얼을 변경하기 위해 MeshRenderer 변수를, 가짜라면 애니메이션을 실행하기 위해 Animator 변수를 선언하고 Awake()에서 초기화했습니다. 가짜 발판일 경우의 낙하 딜레이와 진짜 발판일 경우 변경할 머티리얼은 인스펙터 창에서 수정할 수 있도록 SerializeField로 선언하였고, 진짜 여부를 판단하는 bool 변수를 선언하였습니다. 

 

    // 외부 호출용 함수
    public void IsTrue()
    {
        isTrue = true;
    }

    void OnCollisionEnter(Collision coll)
    {
        // 플레이어 외 충돌은 패스
        if (!coll.gameObject.CompareTag(Tags.Player))
            return;

        if(isTrue)
            myRenderer.material = trueMaterial;
        else
            StartCoroutine(Fall());
    }

    IEnumerator Fall()
    {
        animator.SetTrigger("False");
        yield return WfsManager.Instance.GetWaitForSeconds(fallDelay);
        gameObject.SetActive(false);
    }
}

 진짜 여부는 SceneController에서 설정할 수 있도록 외부 호출용 함수 IsTrue()를 정의하였고, OnCollisionEnter()에서 플레이어와 충돌 시 진짜라면 머티리얼 변경, 가짜일 경우 추락 코루틴을 실행하도록 했습니다. 추락 코루틴의 경우 간단하게 Animator에서 애니메이션 실행 후 fallDelay만큼 대기, 이후 오브젝트 비활성화로 구현했습니다. 

 

 

 

2. 경로 구현

 True or False 발판을 이용한 스테이지는 해당 발판을 n * m 구조로 배치 후, 다음과 같은 규칙을 적용하기로 했습니다.

 

  • 각 행에는 최소 1개 이상의 진짜 발판이 존재
  • 각 행의 진짜 발판중 최소 1개 이상은 같은 열 앞쪽의 진짜 발판과 연결

 

 경로 설정은 SceneController를 상속받은 해당 스테이지용 클래스에서 처리하고, 다음과 같은 로직으로 구현하기로 했습니다.

 

  1. 무작위로 1개의 인덱스 선택
  2. 이전 행의 1번에서 도출한 인덱스 추출(첫 행 제외)
  3. 1번에서 도출한 인덱스부터 2번에서 도출한 인덱스까지 진짜 발판으로 설정
  4. 1~3번 과정을 모든 행에서 반복

 

 예를 들어 5 * m 배치일 경우 첫 행에서 3을 랜덤으로 뽑아 진짜로 설정했고, 그 다음 행에서 1을 랜덤으로 뽑았다면 1~3 인덱스의 발판은 진짜로 설정하는 식입니다. 코드는 다음과 같습니다.

public class Survive_01 : StageController
{
    [Header("랜덤 발판 설정")]
    [SerializeField] TrueOrFalsePlatform[] tofPlatforms;
    [SerializeField] int column;

    protected override void Initialize()
    {
        // 랜덤 루트 설정
        // 5개의 발판 중 하나는 true 발판으로 설정
        // 이전 발판 1칸 앞 발판과 새로 지정한 발판 사이는 true 발판으로 설정
        Queue<int> que = new Queue<int>();
        for(int i = 0; i < tofPlatforms.Length; i += column) 
        {
            int idx = Random.Range(0, 5);

            // 첫행이 아닐 경우
            if(que.Count > 0)
            {
                int high = que.Peek() > idx ? que.Peek() : idx;
                int low = que.Peek() < idx ? que.Peek() : idx;
                for (int j = low; j <= high; j++)
                    tofPlatforms[i + j].IsTrue();
                // 큐 비우기
                que.Dequeue();
            }
            // 첫 행일 경우
            else
                tofPlatforms[i + idx].IsTrue();
                
            que.Enqueue(idx);
        }
    }
}

 발판의 배치는 2차원 배열 형식으로 하지만, 2차원 배열은 인스펙터 창에서 등록할 수 없다는 문제가 있습니다. 따라서 TrueOrFalsePlatform 배열은 1차원 배열로 선언하고, column 값을 따로 SerializeField로 선언하여 열의 개수(각 행의 원소 개수)를 입력하도록 했습니다.

 

 반복문을 돌며 각 행마다 0~column 사이의 값을 뽑고, 첫 행이 아니라면(que.Count > 0) 이전 행에서 뽑은 인덱스와 이번 행에서 뽑은 인덱스 중 낮은 값을 low, 높은 값을 high로 저장한 뒤 low부터 high까지의 발판을 진짜로 설정합니다. 이후 큐를 비우고 이번행에서 뽑은 인덱스를 새로 저장하여 다음 행에서 확인하도록 했습니다. 

 

사진 1. 랜덤 경로 설정 테스트

 위 사진은 상기 코드를 결과를 테스트하기 위해 약간 수정하여 비쥬얼 스튜디오에서 몇번 실행해본 결과입니다. 진짜 발판은 □, 가짜 발판은 ■으로 출력하도록 했습니다. 프로젝트에서 실제로 구현한 결과는 다음과 같습니다.

 

 

영상 1. True Or False Platform 스테이지

 

 

 

3. 추가 구현 사항

  추가로 구현해야 할 부분들은 다음과 같습니다.

 

  • 가짜 발판 착지 시 애니메이션 추가
  • 경로 설정 로직 개선할 방법 있는지
    - 현재 로직은 이전 행에서 최소값, 다음 행에서 최대값을 뽑을 경우 진짜 발판이 많아지는 경향이 있음

 

 경로 설정 로직 쪽은 개선 방법을 찾을 경우 해당 포스팅에 내용 추가할 예정입니다.