Tower Defense Template

Unity/Unity Tutorial 2019. 5. 3. 17:58

유니티 자습서에 있는 Tower Defense Template를 분해하여 나만의 코드로 다시 재구현 해보았다.

구현한 부분은 길 만들기(Navmesh), 적군 오브젝트 생성(Object Pool이용), 노드 단위로 이동(Collider이용), 도착시 일정시간 후 비활성화 및 재활용이다.

느낀점 : Tutorial 맞나싶다.


//분석

카메라 컨트롤

  - 바운더리의 마우스 위치에 따라 카메라 이동

  - 마우스 중간휠로 줌인/줌아웃

타워선택

- 스냅

- 건설 불가 지역에는 빨간색 표시

- 타워에 원 생성 (Radius Visualiser?)

- 건설 가능 위치에서 타워 로테이션

- 우클릭시 선택된 타워 설치 취소

타워설치

- 애니메이션 실행

- 원이 사라짐

- 타워 설치 된 곳 (바닥)에 색이 칠해짐(mesh)

적기생성

- 오브젝트 풀링(PoolManager)

- 풀링 후 오브젝트를 꺼내지 않는다.

지형

- Navmesh 사용

- Areas : (Flyable)

- Level04_Pt{n}_Road 여러개에 Navmesh적용

Hoverbuggy

- Navmesh Agent

- Capsule Collider

- Rigid Body (Is Kinematic Active)

- Targetter.cs, TargetterEditor.cs (target collider를 editor에서 표시)

- 이동 : Agent.cs, MoveToNode() method 실행, 한번에 목표지점으로 이동이 아닌 노드 단위로 나누어 이동

Node(Node.cs)

- MonoBehaviour

- NodeEditor.cs => Inspector Customizing

- Level1Configuration/Nodes/{n}


//계획

1. new project

2. 바닥에 mesh(navmesh 적용)

3. 시작위치, 끝 위치 설정

4. 버튼 생성

5. 버튼 누를 시 적군 생성하여 시작위치부터 끝 위치까지 navmesh통하여 이동하는지 확인

6. 노드 제작

7. 노드 단위로 이동하게끔 변경


//코드읽고 생각을 정리한 것

Node

AreaMeshCreator Type 이용하여 area에 Mesh를 지정하는 탭을 생성

NodeSelector를 이용하여 다음 Node를 리턴한다.

GetRandomPointInNodeArea : Node의 MeshCreator를 이용하여 영역 내 랜덤 위치값을 찾아 리턴함

OnTriggerEnter를 이용하여 다른 gameObject의 Agent가 node의 area에 enter할 때, 현재 노드를 기준으로 다음 노드를 가져옴.

OnValidate Method에서 Collider가 있으면 isTrigger를 활성화하고, AreaMeshCreator가 null일 경우 자식에게서 찾는다.

OnDrawGizmos를 통해 노드 상단에 이미지를 가지고 기즈모로 출력함.

Vector3.up = Vector3(0,1,0)

NodeSelector

linkedNodes라는 List에 Node들을 저장하고 NodeSelector를 통해 노드를 선택한다.

GetNextNode()를 이용하여 리스트에서 다음 노드를 찾아 리턴하고, 다음 노드가 null일 경우 endPoint를 리턴한다.




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
 
public class Level01 : MonoBehaviour
{
    public Button btn;
    public GameObject objPool;
    public GameObject startPosition, endPosition;
 
    private Level01_ObjectPool pool;
    private int idx;
    
    void Start()
    {
        this.pool = this.objPool.GetComponent<Level01_ObjectPool>();
        this.btn.onClick.AddListener(() =>
        {
            this.CreateEnemy();
        });    
    }
 
    private void CreateEnemy()
    {
        var list = this.pool.listEnemy;
        if(this.idx == list.Length - 1)
        {
            this.idx = 0;
        }
        for(; this.idx < list.Length; idx++)
        {
            if (list[this.idx].gameObject.activeSelf == false)
            {
                list[this.idx].transform.position = startPosition.transform.position;
                list[this.idx].SetActive(true);
                list[this.idx].GetComponent<Level01_Enemy>().Move(this.startPosition.GetComponent<TestNode>().GetNextNode().transform.position);
                break;
            }
        }
    }
}
 
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class TestNode : MonoBehaviour
{
    public TestNode GetNextNode()
    {
        var selector = this.gameObject.GetComponent<TestNodeSelector>();
        if(selector != null)
        {
            return selector.GetNextNode();
        }
        return null;
    }
 
    public void OnTriggerEnter(Collider other)
    {
        var agent = other.gameObject.GetComponent<Level01_Enemy>();
        if(agent != null)
        {
            if(this.gameObject.name == "End")
            {
                StartCoroutine(this.WaitForSeconds(() =>
                {
                    agent.gameObject.SetActive(false);
                    StopAllCoroutines();
                }));
            }
            else
            {
                var coordinate = this.GetNextNode().transform.position;
                agent.Move(coordinate);
                Debug.Log("다음 node coordinate 전달완료");
            }
        }
    }
 
    private IEnumerator WaitForSeconds(System.Action OnCompleteWait)
    {
        yield return new WaitForSeconds(1.0f);
        OnCompleteWait();
    }
}
 
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
 
public class Level01_Enemy : MonoBehaviour
{
    public void Move(Vector3 target)
    {
        NavMeshAgent agent = this.GetComponent<NavMeshAgent>();
        agent.enabled = false;
        if(this.gameObject.activeSelf == true)
        {
            agent.enabled = true;
            agent.destination = target;
        }        
    }
}
 
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
 
public class Level01 : MonoBehaviour
{
    public Button btn;
    public GameObject objPool;
    public GameObject startPosition, endPosition;
 
    private Level01_ObjectPool pool;
    private int idx;
    
    void Start()
    {
        this.pool = this.objPool.GetComponent<Level01_ObjectPool>();
        this.btn.onClick.AddListener(() =>
        {
            this.CreateEnemy();
        });    
    }
 
    private void CreateEnemy()
    {
        var list = this.pool.listEnemy;
        if(this.idx == list.Length - 1)
        {
            this.idx = 0;
        }
        for(; this.idx < list.Length; idx++)
        {
            if (list[this.idx].gameObject.activeSelf == false)
            {
                list[this.idx].transform.position = startPosition.transform.position;
                list[this.idx].SetActive(true);
                list[this.idx].GetComponent<Level01_Enemy>().Move(this.startPosition.GetComponent<TestNode>().GetNextNode().transform.position);
                break;
            }
        }
    }
}
 
cs


'Unity > Unity Tutorial' 카테고리의 다른 글

Shooting Tutorial 자습서 분해하고 재구성하기  (0) 2019.05.02
: