본문 바로가기
게임 프로그래밍/게임개발 중급

게임개발 중급(43) - Monster Killer(5)

by jyppro 2023. 6. 3.

Monster Killer(5)

어제는 체력바에 텍스트로 현재,최대체력 표시와 애니메이션 컨트롤러를 통한 애니메이션 조작에 대해 다루었습니다. 오늘은 간단한 레벨디자인에 대해 이야기 해보겠습니다.

 

레벨디자인

단어 자체만 보면 뭔가 어렵고 복잡한 것처럼 들리지만, 실제로는 게임을 해본 사람이라면 거의 대부분이 경험해봤고, 어떻게 해야 하는지 어느정도 알고있습니다. 정말 단순하게 보면 레벨을 설계하여 단계별로 게임을 플레이하는데 재미를 느낄 수 있도록 만들어주는 것입니다.

하지만, 해당 분야에는 전문가들도 존재하는데 그 이유는 쉽게 디자인하는 건 누구나 할 수 있지만, 이 레벨디자인은 게임의 전반에 밸런스, 난이도 등에 영향을 끼치므로 더 재밌고 잘 만든 게임을 만들기 위해선 상당히 복잡하고 어려워질 수 있기 때문입니다. 저는 이전에도 스테이지를 통해 간단한 레벨디자인을 했었는데, 이번에는 체력과 몬스터를 사용하여 한번 진행해 보겠습니다.

 

이전에 작성하였던 MonsterController를 발전시켜 몬스터를 잡으면 죽고 다음몬스터가 생성되도록하고, 다음 몬스터는 체력이 증가한 상태로 등장하도록 만들어 보겠습니다.

 

MonsterController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class MonsterController : MonoBehaviour
{
    public float maxHealth = 100f; // 최대 체력
    public float currentHealth = 100f; // 현재 체력
    public float delay = 3f; // 파괴될 대기 시간
    public Slider healthSlider; // 체력바 슬라이더 컴포넌트
    public Slider healthSliderCopy;
    public TextMeshProUGUI textHP; //체력바 텍스트
    public TextMeshProUGUI textHPCopy;
    private Animator animator; //애니메이션

    // 다른 몬스터로 전환할 때 사용할 프리팹
    public GameObject nextMonsterPrefab;

    void Start()
    {
        animator = GetComponent<Animator>();
        healthSliderCopy = this.healthSlider;
        textHPCopy = this.textHP;
        UpdateHealthSlider();
    }

    public void TakeDamage(int damage)
    {
        currentHealth -= damage;
        if (currentHealth <= 0f)
        {
            currentHealth = maxHealth;
            // 체력이 전부 소진되면 다른 몬스터로 전환하고 체력 증가
            Transform MonsterTransform = this.transform;
            animator.SetBool("Death", true);
            Invoke("DestroyMonster", delay);

            if (nextMonsterPrefab != null)
            {
                GameObject nextMonster = Instantiate(nextMonsterPrefab, MonsterTransform.position, MonsterTransform.rotation);
                nextMonster.GetComponent<MonsterController>().healthSlider = this.healthSliderCopy;
                nextMonster.GetComponent<MonsterController>().textHP = this.textHPCopy;
                nextMonster.GetComponent<MonsterController>().maxHealth = this.maxHealth;
                nextMonster.GetComponent<MonsterController>().currentHealth = this.currentHealth;
                nextMonster.GetComponent<MonsterController>().UpdateHealthSlider();
                nextMonster.GetComponent<MonsterController>().IncreaseHealth();
            }
        }
        UpdateHealthSlider();
    }

    public void UpdateHealthSlider()
    {
        float decreaseHp = (float)currentHealth / (float)maxHealth;
        // 체력바 슬라이더의 값을 현재 체력 비율로 설정
        healthSlider.value = decreaseHp;
        textHP.text = $"{currentHealth} / {maxHealth}";
    }

    public void IncreaseHealth()
    {
        maxHealth = (maxHealth + 23f) * 2.2f;

        if (currentHealth != maxHealth)
        {
            currentHealth = maxHealth;
            UpdateHealthSlider();
        }
    }

    private void DestroyMonster()
    {
        Destroy(gameObject);
    }
}

한번에 많은 기능이 추가되었습니다. 하나씩 차근차근 확인해 보겠습니다.

먼저, 씬에 소환된 처음에 등장하는 몬스터에게는 아웃렛 접속으로 체력바와 텍스트를 연결해준 상태입니다. 그리고 몬스터가 죽었을 때, 다음 몬스터를 소환하기 위해 nextMonsterPrefab을 만들어 다음 몬스터를 연결시켜 줍니다. 이 프리팹 연결은 씬에 없는 프리팹들에게도 서로 다른 몬스터들을 전부 연결해주어야 합니다.

 

TakeDamage를 통해 이전에 계산했던 n을 Damage로 이름을 바꾸어 주고 사용합니다. 체력이 0이 되면 죽은 몬스터의 위치정보를 저장한다음, 죽는 애니메이션을 실행하고 Destroy합니다. 그리고 연결된 프리팹이 있다면 해당 프리팹 몬스터를 저장된 기존 위치에 생성하고, 첫 몬스터에게 연결된 체력바와 텍스트 그리고 현재,최대체력을 가져와 적용시켜줍니다.

 

이렇게 연결시켜주는 이유는 몬스터가 죽고 다음몬스터가 생성될 때마다 체력을 늘려주기 위함입니다. 체력을 늘리는 계산식은 간단하게 처리했습니다.

 

Destroy를 따로 만들어서 사용한 이유는 딜레이타임을 설정하여 죽는 애니메이션을 실행한 후 없애기 위함입니다. 그리고 체력바와 텍스트를 가져올 때 주의해야 할 점은 Copy에 각각 저장하여 가져왔다는 점입니다. 이렇게 따로 사용하지 않으면 기존에 코드에 연결되어있던 체력바와 텍스트가 꼬여 게임이 정상적으로 작동하지 않을 수 있습니다. 그리고 몬스터가 다음몬스터로 전환될 때, 카메라무빙에서 오류가 발생합니다. 몬스터를 기준으로 연결해놓았기 때문입니다. 몬스터와 같은 위치에 빈 오브젝트를 생성하여 연결시켜주면 해결됩니다.

 

다음-몬스터
다음 몬스터 생성

첫번째에 체력 100으로 설정된 몬스터가 죽고, 다음 몬스터가 체력이 270.6으로 증가한 상태로 생성되었습니다. 이 몬스터도 사냥하여 다음 몬스터로 다시 넘어가 보겠습니다.

 

세번째-몬스터
세번째 몬스터

두번째의 270.6에서 다시 계산식을 통해 상승한 645.92의 체력을 가진 몬스터가 소환되었습니다. 코드가 정상적으로 작동하는 것을 확인할 수 있었습니다. 죽는 애니메이션의 경우는 해당 몬스터 프리팹들에게 동일한 컨트롤러가 적용되어 있어 모두 같은 동작을 수행하도록 설정되어있는 상태입니다. 만약 다른 종류의 몬스터프리팹을 사용한다면 해당 몬스터의 애니메이션 컨트롤러를 따로 조작해주어야 합니다.

 

<NEXT>

오늘은 조금 헷갈릴 수 있는 내용들을 진행했습니다. 전반적인 레벨디자인 및 단계별 몬스터 생성에 대해 알아보았습니다. 다음에는 여기까지 진행하면서 발생한 문제점 혹은 유저입장에서 필요한 UI/UX에 대해 점검해보는 시간을 가져보겠습니다. 감사합니다.