TowerDefense 프로젝트 - 2 : Enemy 생성 (AI)
첫번째로 Start, End point 및 path 경로를 만들어 줬으며, 이번에는 저번에 만든 Ground에 WayPoint를 줘서, Enemy를 움직이도록 설정 할것이다. 이 프로젝트는 역시 Brackey 유튜버의 영상을 따라하면서 만드는 프로젝트이므로, 여기서 하는것만이 정답은 아님을 미리 말한다.
https://www.youtube.com/watch?v=aFxucZQ_5E4&list=PLPV2KyIb3jR4u5jX8za5iU1cqnQPmbzG0&index=2
또한 이번에 작성하는 자료까지 이미 다른환경에서 만들었기 때문에, 1번에서 만들었던 자료에 이어 제작을 한다.
또한 이번 자료 역시, 다음 즉 3번 글에서는 기존에 작업하던 프로젝트를 사용할것이므로, 겉보기엔 달라질것이다.
하지만 만드는 원리 및, 기능은 동일하므로, 비주얼적인 부분만 다름을 알아두자
일단 기존 MainCamera의 위치를 변경하자.

현재 위와같이 구성이 되어있는데, Game에서 보는것이 실제로 프로그램을 실행했을때의 모습이므로, 이는 게임화면이 전혀 나타나지 않게 된다. 따라서, 먼저는 이 카메라를 위에서 아래로 바라볼 수 있도록 구성 할 것이다.

MainCamera를 클릭후, Transform 값을 위와 같이 맞춰주면, Game화면에서, 일반 타워디펜스에서 보듯이 화면이 나타남을 알 수 있다.

이제 GroundPlane이라 하는 판을 하나 더 만들어서, 빈 배경을 채울것이다.
Cube 오브젝트를 하나 생성하여, Reset후 x,z를 1000으로 Scale값을 조정한다. 이후, y값을 조정하여, 위와 같이 배경을 이루도록 한다. 색상은 이전 방식과 마찬가지로 Material을 하나 생성 후, 원하는 색상을 준다.

이후 Enemy를 생성하기 위해 Hierarchy에서 Sphere를 추가해준다.

이제 Enemy 사이즈를 조쟁해준다. Scale값을 x,y,z 각각 2배씩 늘려준다.
이후, Path위에 있도록 해야하는데, 먼저는 Start Point 에서 출발해야하므로, 여기에 출발점을 두도록 해야한다. (초록박스)

Enemy도 Material을 이용해서 색상을 원하는색으로 조정해준다 (나는 밝은 파랑색을 택함)

이후 Enemy 오브젝트를 그대로 Asset에 드래그 시켜 Prefab화 시켜준다. (Node 때와 동일)

이제 WayPoint를 만들거다. Empty Object로 하나 생성후, Inspector 창에서 cube 모양을 클릭하면 Icon을 선택하도록 창이 위와 같이 뜬다. 여기서 루비 모양을 클릭하여, 게임에서는 시각적으로 보이진 않으나, 제작편의상 Scene에서만 보이는 아이콘을 만들어, Enemy가 움직이는 이정표 개념으로 세워둘 오브젝트를 만들것이다.

먼저 아이콘을 생성하면 되게 작게 보여서, 알아볼수 없기에, Scene 창의 Gizmo 를 눌러보면 아이콘의 크기를 조절할수 있는 곳이 있는데 (3D icon) 이를 키워서, WayPoint로 세운 아이콘을 눈에 보이도록 할것이다.
여기까지 왔으면 이제 WayPoint를 다시 Prefab 시켜 (Asset에 드래그) 여러개를 만들어주겠다.

그전에 Empty Object를 생성하여, WayPoint를 관리하는 부모객체를 하나 생성하자.

또한, 앞으로 Prefab과 Material을 많이 만들면 뭐가뭔지 헷갈리기에, Prefab 폴더, Material 폴더를 만들어 이전에 만들어준 객체들을 해당하는 폴더에 넣어 관리해주자. (알아보기 편하도록)


사람마다 쓰는 스타일이 다르기에, 기존 처럼 큰 아이콘으로 배치되길 원하면, Project 창에 설정을 눌러, TwoColumn Layout을 설정하고, 이거 말고 작은 아이콘으로 관리하려면 One Column Layout으로 쓰면 된다.
WayPoint는 다음과 같이 여러개 복사하여, 코너길에 각각 배치하면 된다.

각 코너에 배치후, 그리고 End Point에도 배치를 해야한다.
Enemy는 Start 포인트에서 출발하여 WayPoint를 따라가나, EndPoint는 따로 모르기에, 이를 위해 EndPoint 위치에도 WayPoint 를 둔다고 한다.

이제 WayPoint 스크립트를 작성해야한다. WayPoints 부모 오브젝트를 누른 후, Add Component를 눌러 스크립트를 추가해준다.

해당 코드를 편집하고자 스크립트를 열면 위와 같이 디폴트 값이 있지만, 여기서 쓰지 않는것은 지우고, 최종 스크립트만 나타내면 아래와 같이 나타낼수 있다.
using UnityEngine;
public class WayPoints : MonoBehaviour
{
//Transform은 유니티 내의 모든 객체들을 담아둘수 있음.
//여기서는 WayPoints에 대한 객체들을 담을것이다
public static Transform[] points; //publid static으로 선언함으로 어디서든 접근할수있도록 설정.
private void Awake()
{
//WayPoints의 자식을 배열에 담아줌.
points = new Transform[transform.childCount];
for (int i = 0; i < points.Length; i++)
{
//5개의 WayPoint가 담겨질것임.
points[i] = transform.GetChild(i);
}
}
}
(안쓰는 것들은 지워버림)
주석으로 5개의 WayPoint가 담겨질것이라는것은 유니티 내에서 생성한 자기의 WayPoint 자식객체들을 말합니다.
따라서 본인이 만든 갯수 만큼 담겨질 겁니다.
이제 Enemy에 대한 스크립트를 추가해줄겁니다.
먼저 기존 Enemy를 Start Point 박스로 위치를 다시 옮겨 줍니다.

이후 Enemy 오브젝트를 클릭 후 따로 스크립트를 추가해줍니다.

이 Enemy에 다양한 기능을 넣느냐 마느냐에 따라 스크립트가 여러개 생성될 수 있으나, 본 프로젝트에서는 움직임만 구현할것이기에, Enemy는 스크립트 하나로 제어하도록하겠습니다. 작성된 Enemy 코드는 아래와 같습니다.
using UnityEngine;
public class Enemy : MonoBehaviour
{
//WayPoint에 따라 움직여야 하므로, target은 WayPoint로 설정한다.
//Move speed
public float speed = 10f;
private Transform target;
private int wavepointIndex = 0; //maximum 5
private void Start()
{
//WayPoints에서 public static으로 지정한 이유
target = WayPoints.points[0]; //Enemy의 target으로 WayPoint로 지정
}
private void Update()
{
//Vector3는 x,y,z 움직임을 컨트롤해주기 위해 있는 기능이라고 생각하면 된다.
//여기서는 target에 따라 움직임을 주기 위해 사용함.
Vector3 dir = target.position - transform.position;
transform.Translate(dir.normalized * speed * Time.deltaTime, Space.World); //프레임에 따른 스피드 안정화를 위한 코드
}
}
여기까지 코드를 작성하면, 아직 WayPoint 변경에 따른 움직임은 설정하지 않아서, 첫번째 WayPoint에 가면 멈출겁니다.

재생버튼을 눌러 현재 작성한 스크립트까지 보면 위와 같이 첫번째 WayPoint까지만 간것을 확인할수 있습니다.
Enemy Script 코드는 아래와 같습니다.
using UnityEngine;
public class Enemy : MonoBehaviour
{
//WayPoint에 따라 움직여야 하므로, target은 WayPoint로 설정한다.
//Move speed
public float speed = 10f;
private Transform target;
private int wavepointIndex = 0; //maximum 5
private void Start()
{
//WayPoints에서 public static으로 지정한 이유
target = WayPoints.points[0]; //Enemy의 target으로 WayPoint로 지정
}
private void Update()
{
//Vector3는 x,y,z 움직임을 컨트롤해주기 위해 있는 기능이라고 생각하면 된다.
//여기서는 target에 따라 움직임을 주기 위해 사용함.
Vector3 dir = target.position - transform.position;
transform.Translate(dir.normalized * speed * Time.deltaTime, Space.World); //프레임에 따른 스피드 안정화를 위한 코드
//만약 WayPoint에 도달했다면 다음 WayPoint로 가게하는 코드
if(Vector3.Distance(transform.position, target.position) <= 0.4f)
{
GetNextWayPoint();
}
}
void GetNextWayPoint()
{
//End Point의 WayPoint 범위보다 큰경우, 오브젝트를 없앰
if(wavepointIndex >= WayPoints.points.Length - 1)
{
Destroy(gameObject);
return;
}
wavepointIndex++;
target = WayPoints.points[wavepointIndex];
}
}

추가적으로 여러개의 Enemy가 있을때에도 제대로 동작하는지 확인하고자, 위와같이 Enemy 2개를 더 추가하여 확인한 결가, 몇개가 더 추가되도, 에러없이 잘 작동되는 모습을 확인 할 수 있었습니다.