기록 보관소

[Unity/유니티] 기초-뱀서라이크: 소환 레벨 적용하기[06+] 본문

유니티 프로젝트/뱀서라이크

[Unity/유니티] 기초-뱀서라이크: 소환 레벨 적용하기[06+]

JongHoon 2023. 5. 29. 19:14

개요

유니티 독학을 위해 아래 링크의 골드메탈님의 영상들을 보고 직접 따라 해보면서 진행 상황을 쓰고 배웠던 점을 요약한다.

https://youtube.com/playlist?list=PLO-mt5Iu5TeYI4dbYwWP8JqZMC9iuUIW2 

 

📚유니티 기초 강좌

유니티 게임 개발을 배우고 싶은 분들을 위한 기초 강좌

www.youtube.com


뱀서라이크: 소환 레벨 적용하기[06+]

1. 시간에 따른 난이도

//GameManager Script

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

public class GameManager : MonoBehaviour {
	public static GameManager instance;
	public PoolManager pool;
	public Player player;

	public float gameTime;	//게임 시간 변수
	public float maxGameTime = 2 * 10f;	//최대 게임 시간 변수(20초).

	void Awake() {
		instance = this;
	}

	void Update() {
		gameTime += Time.deltaTime;

		if (gameTime > maxGameTime) {
			gameTime = maxGameTime;
		}
	}
}
// Spawner Script

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

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

    int level;  //소환 레벨
    float timer;

    void Awake() {
        spawnPoint = GetComponentsInChildren<Transform>();
    }

    void Update() {
        timer += Time.deltaTime;
        //FloorToInt : 소수점 아래는 버리고 Int형으로 바꾸는 함수. CeilToInt : 소수점 아래를 올리고 Int형으로 바꾸는 함수.
        level = Mathf.FloorToInt(GameManager.instance.gameTime / 10f);  //10초마다 레벨 증가.

        if (timer > (level == 0 ? 0.5f : 0.2f)) {   //초기에는 몬스터를 0.5초마다, 레벨이 오르면 0.2초마다 생성
            timer = 0;
            Spawn();
        }
    }

    void Spawn() {
        GameObject enemy = GameManager.instance.pool.Get(level);    //레벨에 따라 몬스터 생성
        enemy.transform.position = spawnPoint[Random.Range(1, spawnPoint.Length)].position; // Random Range가 1부터 시작하는 이유는 spawnPoint 초기화 함수 GetComponentsInChildren에 자기 자신(Spawner)도 포함되기 때문에.
    }
}
  • 이번 강의 챕터 1에서는 위와 같이 코드 수정을 통해서 시간이 지나면 레벨이 증가하고, 그에따라 몬스터 생성 주기와 종류가 바뀌도록 만들었다.
  • 위와 같이 변경한 후에 테스트를 하게되면, Spawner 스크립트의 변경사항 때문에 30초부터는 에러가 발생할 것이다(level 변수, Spawn 함수).
레벨 테스트. 30초 전까지는 시간대에 따라 몬스터가 문제 없이 생성되고, 생성 주기도 10초 이후로 빨라진게 보인다.

2. 소환 데이터

// Spawner Script

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() {
        spawnPoint = GetComponentsInChildren<Transform>();
    }

    void Update() {
        timer += Time.deltaTime;
		//FloorToInt : 소수점 아래는 버리고 Int형으로 바꾸는 함수. CeilToInt : 소수점 아래를 올리고 Int형으로 바꾸는 함수.
		level = Mathf.FloorToInt(GameManager.instance.gameTime / 10f);  //10초마다 레벨 증가.

        if (timer > (level == 0 ? 0.5f : 0.2f)) {   //초기에는 몬스터를 0.5초마다, 레벨이 오르면 0.2초마다 생성
            timer = 0;
            Spawn();
        }
    }

    void Spawn() {
        GameObject enemy = GameManager.instance.pool.Get(level);    //레벨에 따라 몬스터 생성
        enemy.transform.position = spawnPoint[Random.Range(1, spawnPoint.Length)].position; // Random Range가 1부터 시작하는 이유는 spawnPoint 초기화 함수 GetComponentsInChildren에 자기 자신(Spawner)도 포함되기 때문에.
    }
}

//직렬화(Serialization) : 개체를 저장/전송하기 위해 변환
[System.Serializable]
public class SpawnData {
    public int spriteType;  //몬스터 스프라이트 타입
    public float spawnTime; //몬스터 소환 시간
    public int health;      //몬스터 체력
    public float speed;     //몬스터 스피드
}
직렬화 하기 전
직렬화 후
  • 직렬화 과정이 필요한 이유는 SpawnData를 public 변수로 선언했음에도 위 캡쳐처럼 Inspector 창에서 클래스 내 변수 세부 설정이 불가능하기 때문이다.
소환할 몬스터에 맞춰서 설정해주자
  • 다음 챕터에서는 이렇게 만든 데이터를 이제 Enemy에 초기화할 수 있도록 만들 것이다.

3. 몬스터 다듬기

데이터를 기반으로 생성한다면, 더 이상 프리펩이 여러 개일 이유가 없다
프리펩 삭제 후 하나만 남기자
이름을 그냥 Enemy로 변경하고, Speed를 0으로 초기화
// Enemy Script

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

public class Enemy : MonoBehaviour {
    public RuntimeAnimatorController[] animCon; //애니메이터 컨트롤러
    public float health;    //몬스터 현재 체력
    public float maxHealth; //몬스터 최대 체력
    public float speed;     //몬스터 스피드
    public Rigidbody2D target;  //쫓아갈 타겟(플레이어)

    bool isLive;    //체력이 생겼으니 true로 초기화 했던 것에서 변경.

    Animator anim;
    Rigidbody2D rigid;
    SpriteRenderer spriter;

    void Awake() {
        anim = GetComponent<Animator>();
        rigid = GetComponent<Rigidbody2D>();
        spriter = GetComponent<SpriteRenderer>();
    }

    void FixedUpdate() {
        if (!isLive)
            return;
        Vector2 dirVec = target.position - rigid.position;  // 방향 = 위치 차이의 정규화(Normalized). 위치 차이 = 타겟 위치 - 나의 위치.
        Vector2 nextVec = dirVec.normalized * speed * Time.fixedDeltaTime;
        rigid.MovePosition(rigid.position + nextVec);   //플레이어의 키 입력값을 더한 이동 = 몬스터의 방향 값을 더한 이동
        rigid.velocity = Vector2.zero; //물리 속도가 이동에 영향을 주지 않도록 속도 제거
    }

    void LateUpdate() {
        if (!isLive)
            return;
        spriter.flipX = target.position.x < rigid.position.x;
    }

    void OnEnable() {
        target = GameManager.instance.player.GetComponent<Rigidbody2D>();   //플레이어 할당
        isLive = true;  //생존 상태 true
        health = maxHealth; //최대 체력으로 설정
    }

    //데이터를 가져오기 위한 초기화 함수
    public void Init(SpawnData data) {
        anim.runtimeAnimatorController = animCon[data.spriteType];
        speed = data.speed;
        maxHealth = data.health;
        health = data.health;
    }
}
변경될 스프라이트에 맞게 애니메이터 컨트롤러 추가

4. 소환 적용하기

앞에서 Enemy 프리펩을 하나로 만들었으니 PoolManager의 Prefabs가 비어있다
하나로 수정
// Spawner Script

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() {
        spawnPoint = GetComponentsInChildren<Transform>();
    }

    void Update() {
        timer += Time.deltaTime;
        //FloorToInt : 소수점 아래는 버리고 Int형으로 바꾸는 함수. CeilToInt : 소수점 아래를 올리고 Int형으로 바꾸는 함수.
        level = Mathf.Min(Mathf.FloorToInt(GameManager.instance.gameTime / 10f), spawnData.Length - 1); //오류 수정

        if (timer > spawnData[level].spawnTime) {
            timer = 0;
            Spawn();
        }
    }

    void Spawn() {
        GameObject enemy = GameManager.instance.pool.Get(0);    //프리펩이 하나가 되었으므로 0으로 변경
        enemy.transform.position = spawnPoint[Random.Range(1, spawnPoint.Length)].position; // Random Range가 1부터 시작하는 이유는 spawnPoint 초기화 함수 GetComponentsInChildren에 자기 자신(Spawner)도 포함되기 때문에.
        enemy.GetComponent<Enemy>().Init(spawnData[level]);
    }
}

//직렬화(Serialization) : 개체를 저장/전송하기 위해 변환
[System.Serializable]
public class SpawnData {
    public float spawnTime; //몬스터 소환 시간
    public int spriteType;  //몬스터 스프라이트 타입
    public int health;      //몬스터 체력
    public float speed;     //몬스터 스피드
}
레벨 1 몬스터 정보. Spawner에서 미리 설정했던대로 체력, 스피드가 설정되어있다.
레벨 2 몬스터 정보. 이 또한 미리 설정했던 체력, 스피드가 정상적으로 반영되었다.
최종 테스트. 문제 없이 시간에 따라 몬스터가 소환된다.