유니티 프로젝트/뱀서라이크
[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 함수).

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 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. 소환 적용하기


// 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; //몬스터 스피드
}


