public class WaveSpawner : MonoBehaviour
{
public Transform enemyPrefab;
public Transform spawnPoint; //몬스터 스폰 위치
public float timeBetweenWaves = 5f; //웨이브 사이 대기 시간
private float countdown = 2f;
private int waveIndex = 0;// 웨이브 번호
private void Update()
{
if (countdown <= 0f) //카운트다운이 0 보다 작아지먄 Spawn Wave 실행
{
SpawnWave();
countdown = timeBetweenWaves;//카운트 다운을 중간 시간으로 초기화
}
//deltaTime//마지막 프레임을 그린 후 경과한 시간
countdown -= Time.deltaTime; //시간을 계속 줄인다.
}
void SpawnWave()
{
waveIndex++; //웨이브가 올때마다 레벨업
for (int i = 0; i < waveIndex; i++) //웨이브 레벨만큼 몬스터 소한
{
SpawnEnemy();
}
}
void SpawnEnemy()
{
//미리 지정해둔 스폰 포인트에서 몬스터를 복사해서 소환
Instantiate(enemyPrefab, spawnPoint.position, spawnPoint.rotation);
}
}
위 코드가 잘 작동하도록 시작 노드를 SpawnPoint로 지정해준다. 이제 실행해보면 에너미 생성을 잘 되고 움직임 또한 잘 작동하지만 여러 마리의 몬스터가 거의 동시에 생성되면서 한마리처럼 겹쳐서 움직이고 있다.
따라서 일종의 카운트 다운을 재설정하거나 코루틴을 사용해 해당 문제를 해결해줄 수 있다.
IEnumerator SpawnWave() //코루틴
{
for (int i = 0; i < waveIndex; i++) //웨이브 레벨만큼 몬스터 소한
{
SpawnEnemy();
yield return new WaitForSeconds(0.5f);
}
waveIndex++; //웨이브가 올때마다 레벨업
}
적이 시간차를 두고 잘 생성되는 것을 확인할 수 있다.
UI > Text를 하나 추가해준 후 상단에 앵커 설정을 해준다.
제공되는 터렛 모델을 임포트해보자. Turrent.fbx를 프로젝트창에 드래그드랍해서 임포트 시킬 수 있다. 임포트 시킨 후 속성을 아래와 같이 설정한다.
Model
Rig
import 설정은 이 문서를 참고하자.
이제 터렛을 씬창에 드래그해서 트랜스폼을 설정한다.
Pivot과 Local로 설정한 후에 트랜스폼을 수정한다.
y = 0.5로 설정한다.
자식 오브젝트를 각각 Head와 Base로 수정한다.
임포트 된 재질들을 복사해서 새 재질로 만들어준 후 해당 터렛에 드래그해서 설정해준다.
새 재질을 만들어서 해당 하는 부분에 맞게 재질을 넣어준 후 색을 조정해도 괜찮다.
이제 각 재질의 속성을 만지면서 터렛을 원하는 타입의 색과 재질로 설정한다.
위처럼 터렛의 모델 세팅이 끝났다면 자식 오브젝트로 Part To Rotate 오브젝트를 하나 생성해준다.
현재 Head를 회전 시키면 기준축이 이상하게 설정되어 있어 아래와 같이 회전한다
Shading Mode를 Wireframe으로 설정하면 위치를 정확하게 볼 수 있다.
이제 Head를 PartToRotate의 자식 오브젝트로 설정한다.
이제 PartToRotate를 회전 시켜보면 머리가 축을 기준으로 잘 회전되는 것을 확인할 수 있다.
이제 다 만들어진 터렛을 프리팹화 시킨다. 이제 터렛을 적절한 위치에 임의로 배치시킨 후 터렛 스크립트를 작성해보자.
주석으로 설명을 상세하게 달아놓았으므로 설명은 주석을 참고하자.
아래 그림과 같이 총구가 파랑색 화살표를 바라보고 있어야만 정상적으로 적을 향할 수 있다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Turret : MonoBehaviour
{
public Transform target; // 공격할목표 오브젝트
public float range = 15f; // 사거리는 15로 설정
public string enemyTag = "Enemy";
public Transform partToRotate; //실제로 base를 제외하고 회전될 오브젝트의 트랜스폼
public float turnSpeed = 10f;
// Start is called before the first frame update
void Start()
{
InvokeRepeating("UpdateTarget", 0f, 0.5f); //0f에 시작해서 0.5f마다 반복호출
}
void UpdateTarget() //가장 가까운 적을 찾아 목표로 업데이트
{
//매 프레임마다 모든 적을 확인하면서 업데이트하면 성능 시간 낭비
//"1초에 2번"과 같이 검색 횟수를 제한, 타겟을 가지고 있지 않은 경우에만 탐색하는 등의 방법이 가능
//0.5초에 한번 실행 되도록 start에 InvokeRepeating을 실행
GameObject[] enenmies = GameObject.FindGameObjectsWithTag(enemyTag); //태그에 enemyTag인 오브젝트를 모두 탐색
float shortestDistance = Mathf.Infinity; //최소거리를 구하기 위한 초기값을 Infinity로 설정
GameObject nearestEnemy = null;
foreach(GameObject enemy in enenmies)
{
//적과 내 거리를 구함
float distanceToEnemy = Vector3.Distance(transform.position, enemy.transform.position);
if(distanceToEnemy < shortestDistance) // 더 가까운 적을 찾았다면 nearestEnemy를 최단 거리 업데이트 후 해당 오브젝트로 설정
{
shortestDistance = distanceToEnemy;
nearestEnemy = enemy;
}
}
if (nearestEnemy != null && shortestDistance <= range) //적을 찾았고 + 사거리 안에 들어왔다면
{
target = nearestEnemy.transform; //이제 목표 오브젝트를 미리 찾아놓은 적으로 설정
}
else
{
target = null; //만족하지 않으면 target을 null로 초기화
}
}
// Update is called once per frame
void Update()
{
if (target == null) //타겟이 없으면 리턴 (아무런 행동도 하지 않음)
return;
//--- 만약 target이 있다면 ---
Vector3 dir = target.position - transform.position; //목표 방향 = 타겟 위치 - 내 위치
Quaternion lookRotation = Quaternion.LookRotation(dir); //dir 방향을 보도록 회전하는 정도
//유니티는 x, y, z를 오일러 각도를 기준으로 사용하고 있다.
//Vector3 rotation = lookRotation.eulerAngles; //따라서 우리가 원하는회전을 오일러 각도로 변환해준다.
//윗줄 코드를 아래의 부드럽게 회전하는 코드로 수정
Vector3 rotation = Quaternion.Lerp(partToRotate.rotation, lookRotation, Time.deltaTime * turnSpeed).eulerAngles;
//partToRate의 회전에서 lookRotation의 회전까지 turnSpeed 단위로 변경되면서 회전을 내보내면 해당 회전을 오일러 각도로 변환해서 rotation Vector에 저장함.
//y축을 중심으로만 회전하기를 원하기 때문에 y회전 정도만 불러와서 사용한다.
partToRotate.rotation = Quaternion.Euler(0f, rotation.y, 0f);
}
private void OnDrawGizmosSelected() //기즈모를 그려주는 유니티 함수
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, range); //내 위치를 기준으로 range를 반지름을 구를 그려줌
}
}
OnDrawGizmosSelected 함수를 작성하면 씬 창에 임의로 기즈모를 그릴 수 있어서 사거리를 확인하는 용도로 아래와 같이 사용할 수 있다.
private void OnDrawGizmosSelected() //기즈모를 그려주는 유니티 함수 { Gizmos.color = Color.red; Gizmos.DrawWireSphere(transform.position, range); //내 위치를 기준으로 range를 반지름을 구를 그려줌 }
타겟을 향해 회전하는 부분은 구현 방법도 많고 실제 코드도 간단하지만 유니티에서 사용하는 오일러 각도에 대해 이해하고 코딩하는 것이 좋다.