[Unity][2D-Game] Undead Survivor (6) + a

suhan0304·2023년 11월 5일
0
post-thumbnail
post-custom-banner

preview Link : https://velog.io/@suhan0304/Unity2D-Game-Undead-Survivor-6
gitHub Link : https://github.com/suhan0304/Undead-Survivor


Review

  • 프리팹을 담을 Prefabs 리스트와 풀을 담을 Pools 리스트와 생성하여 오브젝트 풀링 시스템을 구축했다.
  • 생성된 오브젝트의 Target 자동 추적을 위해 로직을 보완했다.
    - Prefab에는 인스펙터를 통한 오브젝트 연결이 안 된 상태로 복제가 된다.
  • Point 오브젝트를 플레이어 주변에 위치시켜 해당 위치에서 랜덤하게 몬스터들이 생성되도록 구현했다.

강의영상 (6+) - 소환 레벨 적용하기


개발

시간에 따른 난이도

시간에 따른 난이도 조절을 위해 게임 시간을 변수로 관리해야하므로 GameManger에 게임 시간과 최대 게임 시간을 담당할 변수를 선언해주었다.

GameManager
public float gameTime;                  //게임 시간
public float maxGameTime = 2 * 10f;     //게임 최대 시간 (test용 20초)

void Update()
{
    //DeltaTime : 한 프레임에 걸린 시간
    gameTime += Time.deltaTime;

    //1초마다 Spawn 실행
    if (gameTime > maxGameTime)
    {
        gameTime = maxGameTime
    }
}

이제 게임 시간을 알 수 있으므로 Spawn에서 시간에 따른 레벨을 조정하도록 수정한다.

Spawner

int level; //시간에 따른 레벨 지정

void Update()
{
    //DeltaTime : 한 프레임에 걸린 시간
    timer += Time.deltaTime;

    //gameTime을 10으로 나누어서 내림(Floor) 한 후 정수형으로 레벨 지정
    level = Mathf.FloorToInt(GameManager.Instance.gameTime / 10f);


    //1초마다 Spawn 실행 
    if (timer > (level == 0 ? 0.5f : 0.2f)) //level 값에 따라 스폰 주기 변경
    {
        timer = 0;
        Spawn();
    }
}

void Spawn()
{
    //0번 또는 1번 몬스터를 랜덤으로 지정
    GameObject enemy = GameManager.Instance.pool.Get(level); //레벨에 해당하는 몬스터가 스폰되도록 지정

    //랜덤 포인트에서 생성되도록 설정
    enemy.transform.position = spawnPoint[Random.Range(1, spawnPoint.Length)].position;
    //1번부터 하는 이유 -> GetComponentsInChildren에는 자기 자신도 포함이라 0번은 자기 자신 transform이 들어가있음
}

위와 같이 수정해서 gameTime에 따라 레벨이 증가하고, 레벨에 따라 몬스터 소환 주기 및 등장 몬스터 프리팹이 결정되도록 할 수 있다.


소환 데이터

이러한 레벨에 따른 소환 주기, 소환 몬스터 종류와, 몬스터 체력, 속도 같은 데이터를 레벨 별로 Spawner 클래스에 작성하지 않고 개별로 꺼내서 소환 데이터 SpawnData를 클래스로 추가 선언해 소환에 관련된 데이터를 해당 클래스에서 관리한다.

public class Spawner : MonoBehaviour
{
	//몬스터들의 데이터가 들어갈 배열
    public SpawnData[] spawnData;
    
    ~~(생략)~~
}

[System.Serializable] //복잡한 클래스를 인스펙터에서 볼 수 있도록 설정
public class SpawnData
{
    public int spriteType; // 등장할 몬스터의 스프라이트 종류
    public float spawnTime; // 몬스터 소환 주기
    public int health; // 몬스터 체력
    public float speed; // 몬스터 속도
}

직접 작성한 클래스를 Spawner 클래스 안에서 배열의 행태로 선언을 해줘야 내가 작성한 클래스를 생성하고 사용할 수 있다.

[System.Serializable] : 직접 작성한 클래스를 직렬화를 통하여 인스펙터에서 초기화가 가능하다.


몬스터 다듬기

위에서 초기화 시킨 몬스터 데이터를 몬스터에게 주입한다.

이제 몬스터의 스프라이트나 체력, 속도는 소환 데이터에서 관리하므로 몬스터 프리팹을 하나만 남기고 다 삭제한다. 그 이후에 Enemy 스크립트에서 애니메이션을 관리할 애니메이터 컨트롤러, 체력과 같이 추가적으로 받아올 변수들을 선언한 후에 소환 데이터에서 해당 변수에 데이터가 주입되도록 작성했다.

Enemy
public float health;        //체력
public float maxHealth;     //최대 체력
public RuntimeAnimatorController[] animCon;     //몬스터의 애니메이터를 바꾸기 위한 컨트롤러

이후에 인스펙터를 통해 RuntimeAnimatorController, 애니메이션 컨트롤러 변수를 초기화 해준다.

이후 OnEable에서 활성화 될 때 생존여부와 health를 초기화시켜준 후 init 함수를 작성해 SpwanData를 초기 속성으로 적용시켜준다.

public float speed;         //속도
public float health;        //체력
public float maxHealth;     //최대 체력
public RuntimeAnimatorController[] animCon;     //몬스터의 애니메이터를 바꾸기 위한 컨트롤러
public Rigidbody2D target;  //목표 Rigidbody

bool isLive; //생존여부

Rigidbody2D rigid;
Animator anim;
SpriteRenderer spriter;

~~(생략)~~

void OnEnable() //활성화 될 때 한 번 실행
{
    target = GameManager.Instance.player.GetComponent<Rigidbody2D>();
    isLive = true; //생존여부 초기화
    health = maxHealth;
}

public void Init(SpawnData data) //초기 속성을 적용하는 함수 작성
{
    anim.runtimeAnimatorController = animCon[data.spriteType];  //애니메이션 적용
    speed = data.speed;         //속도 적용
    maxHealth = data.health;    //체력 적용
    health = data.health;                                           
}

소환 적용하기

이제 몬스터 프리팹이 1개 이므로 인스펙터로 PoolManager의 Prefabs에 Enemy Prefab 하나 남기고 모두 삭제한 후 Spawner에서 0번째 Prefab만 가져다가 몬스터를 생성하도록 수정했다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Spawner : MonoBehaviour
{
    public Transform[] spawnPoint;

    //몬스터들의 데이터가 들어갈 배열
    public SpawnData[] spawnData;

    int level; //시간에 따른 레벨 지정
    float timer;

    void Awake()
    {
        // 배열로 여러개를 가져올 것이기 때문에 Component's'를 써야함
        spawnPoint = GetComponentsInChildren<Transform>();
    }

    void Update()
    {
        //DeltaTime : 한 프레임에 걸린 시간
        timer += Time.deltaTime;

        //gameTime을 10으로 나누어서 내림(Floor) 한 후 정수형으로 레벨 지정
        level = Mathf.FloorToInt(GameManager.Instance.gameTime / 10f);


        //1초마다 Spawn 실행 
        if (timer > spawnData[level].spawnTime) //level 값에 따라 스폰 주기 변경
        {
            timer = 0;
            Spawn();
        }
    }

    void Spawn()
    {
        //0번 또는 1번 몬스터를 랜덤으로 지정
        GameObject enemy = GameManager.Instance.pool.Get(0); 

        //랜덤 포인트에서 생성되도록 설정
        enemy.transform.position = spawnPoint[Random.Range(1, spawnPoint.Length)].position;
        //1번부터 하는 이유 -> GetComponentsInChildren에는 자기 자신도 포함이라 0번은 자기 자신 transform이 들어가있음

        //enemy는 게임 오브젝트이기 때문에 Enemy의 init을 실행하기 위해선 Enemy를 GetComponent로 가져와야함
        enemy.GetComponent<Enemy>().Init(spawnData[level]); //SpawnData 값을 enemy에 넘겨줌
    }
}


[System.Serializable] //복잡한 클래스를 인스펙터에서 볼 수 있도록 설정
public class SpawnData
{
    public float spawnTime; // 몬스터 소환 주기
    public int spriteType; // 등장할 몬스터의 스프라이트 종류
    public int health; // 몬스터 체력
    public float speed; // 몬스터 속도
}

인덱스 에러는 현제 maxGameTime이 2초라서 레벨이 2까지 오르는데 2에는 몹 데이터가 없기 때문에 오류가 발생, 따라서 다음과 같이 작성해서 레벨이 최대값을 넘지 않도록 조정

level = Mathf.Min(Mathf.FloorToInt(GameManager.Instance.gameTime / 10f), spawnData.Length - 1);

결과물

profile
Be Honest, Be Harder, Be Stronger
post-custom-banner

0개의 댓글