이 포스트에 있는 코드는 모두 아래 링크에 있는 튜토리얼을 기반으로 이루어졌습니다. 

https://www.youtube.com/watch?v=payFhNs9hvs&list=PL4CCSwmU04MhfoJTJWA7n2AIB4dq6umeu




- Level

- Member

- Enemy

- MemberConfig


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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Level : MonoBehaviour {
 
    public Transform memberPrefab;
    public Transform enemyPrefab;
    public int numberOfMembers;
    public int numberOfEnemies;
    public List<Member> members;
    public List<Enemy> enemies;
    public float bounds; //이동 범위
    public float spawnRadius; //생성 범위
 
    void Start () {
        //리스트 생성
        members = new List<Member>(); 
        enemies = new List<Enemy>();
 
        //생성 함수
        Spawn(memberPrefab, numberOfMembers);
        Spawn(enemyPrefab, numberOfEnemies);
 
        //리스트에 member, enemy 추가   
        members.AddRange(FindObjectsOfType<Member>());
        enemies.AddRange(FindObjectsOfType<Enemy>());
    }
    
    void Spawn(Transform prefab, int count)
    {
        //임의의 위치에 agent와 enemy 생성하기 
        for (int i = 0; i < count; i++)
        {
            Instantiate(prefab, 
                        new Vector3(Random.Range(-spawnRadius, spawnRadius), 
                                    Random.Range(-spawnRadius, spawnRadius), 
                                    0),
                        Quaternion.identity);
        }
    }
 
    //이웃을 분류하는 리스트 
    public List<Member> GetNeighbors(Member member, float radius)
    {
        //이웃을 반환할 리스트 생성 
        List<Member> neighborsFound = new List<Member>();
 
        //members리스트 안에 있는 member중 하나를 othermember라는 변수로 지정 
        foreach(Member otherMember in members)
        {
            if (otherMember == member)
                continue//조건 성립하면 다시 돌아감 
        
            //본인의 위치가 othermember와 일정 거리 가까우면 
            if(Vector3.Distance(member.position, otherMember.position) <= radius)
            {
                //neighborsFound 리스트에 otherMember를 추가
                neighborsFound.Add(otherMember);
            }
        }
        return neighborsFound; //neighborsFound 반환 
    }
 
    //Enemy를 분류하는 리스트 
    public List<Enemy> GetEnemies(Member member, float radius)
    {
        //enemy를 반환할 리스트 생성 
        List<Enemy> returnEnemies = new List<Enemy>();
 
        //enemies리스트 안에 있는 enemy중 하나를 enemy라는 변수로 지정
        foreach (Enemy enemy in enemies)
        {
            //본인의 위치와 enemy의 위치가 일정 거리 가까우면 
            if(Vector3.Distance(member.position, enemy.transform.position) <= radius)
            {
                //returnEnemies 리스트에 enemy를 추가 
                returnEnemies.Add(enemy);
            }
        }
        return returnEnemies; //returnEnemies 반환 
    }
}
 
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Member : MonoBehaviour {
 
    public Vector3 position;
    public Vector3 velocity;
    public Vector3 acceleration;
 
    public Level level;
    public MemberConfig conf;
 
    Vector3 wanderTarget;
 
    private void Start()
    {
        position = transform.position;
 
        //FindObjectOfType은 getComponent와 유사하지만 전역적으로 component를 찾아온다 
        level = FindObjectOfType<Level>();
        conf = FindObjectOfType<MemberConfig>();
 
        position = transform.position;
        velocity = new Vector3(Random.Range(-33), Random.Range(-33), 0);
    }
 
    private void Update()
    {
        acceleration = Combine(); //이동함수 호출 
        acceleration = Vector3.ClampMagnitude(acceleration, conf.maxAccelaeration);//ClampMagnitude: max값에 도달하면 그 이후의 값은 max로 반환(상한선)
        velocity = velocity + acceleration * Time.deltaTime; //속도 
        velocity = Vector3.ClampMagnitude(velocity, conf.maxVelocity); 
        position = position + velocity * Time.deltaTime; //위치 
 
        WrapAround(ref position, -level.bounds, level.bounds); //범위 벗어날 경우
        //이동방향으로 회전
        transform.rotation = Quaternion.LookRotation(position - transform.position);
        //위치 할당 
        transform.position = position; 
    }
 
    //이동 함수1 - 모였다 퍼졌다 
    protected Vector3 Wander()
    {
        //이동 크기 
        float jitter = conf.wanderJitter * Time.deltaTime; //jitter(진폭)변수 생성
        wanderTarget += new Vector3(0, RandomBinomial() * jitter, 0); //랜덤이항식*jitter으로 이동 
        wanderTarget = wanderTarget.normalized; //정규화: 방향에 상관없이 똑같은 크기를 받도록 
        wanderTarget *= conf.wanderRadius; //?? 이동 지름을 곱해줌 
        //위치 좌표 
        Vector3 targetInLocalSpace = wanderTarget + new Vector3(conf.wanderDistance, conf.wanderDistance, 0); //타겟이 로컬좌표(상대위치)에 있는, ??wanderDistance변수만큼 y축으로 더해줌 
        Vector3 targetInWorldSpace = transform.TransformPoint(targetInLocalSpace); //월드좌표(절대위치), transformpoint: local좌표 -> world좌표로 변환 
        targetInLocalSpace -= this.position; //방향
        return targetInLocalSpace.normalized; //로컬좌표타겟 변수 정규화
    }
 
    //이동 함수2 - wander함수보다 더 랜덤하게 이동 
    Vector3 Cohesion()
    {
        Vector3 cohesionVector = new Vector3();
        int countMembers = 0;
        var neighbors = level.GetNeighbors(this, conf.cohesionRadius); 
        if (neighbors.Count == 0
            return cohesionVector;
        //??neighbors리스트에 있는 member에 관한 반복문 
        foreach(var member in neighbors)
        {
            //isInFOV: 시야각 공식 
            if (isInFOV(member.position))
            {
                cohesionVector += member.position;
                countMembers++;
            }
        }
        if (countMembers == 0)
            return cohesionVector;
        //??
        cohesionVector /= countMembers; 
        cohesionVector = cohesionVector - this.position;
        cohesionVector = Vector3.Normalize(cohesionVector);
        return cohesionVector;
    }
 
    //alignment(회전) - 이웃과 속도(벡터값 - 크기와 방향 가짐)를 맞추는 함수  
    Vector3 Alignment()
    {
        Vector3 alignVector = new Vector3(); //새로운 변수 공간 생성 
        var members = level.GetNeighbors(this, conf.alignmentRadius); //member로 현재 스크립트가 적용된 gameobject를 가져온다, conf.alignmentRadius는 alignment 적용거리의 범위(collider)이다
 
        if (members.Count == 0)
            return alignVector;
        // 자신의 시야각에 멤버가 존재하면 정렬을 맞춤 
        foreach (var member in members)
        {
            //만약 시야각에 member가 들어오면, alignVector에 시야각에 들어온 member의 벡터값을 더해준다
            if (isInFOV(member.position))
                alignVector += member.velocity; //alignVector에 member의 속도(벡터값)를 더해주는 
        }
        return alignVector.normalized;
    }
 
    //separation - 이웃과 일정한 거리를 띄우는 
    Vector3 Separation()
    {
        Vector3 separteVector = new Vector3();
        var members = level.GetNeighbors(this, conf.separationRadius);
        if (members.Count == 0)
            return separteVector;
 
        foreach(var member in members)
        {
            if(isInFOV(member.position))
            {
                Vector3 movingTowards = this.position - member.position; //member에서 멀어지는 자신의 방향
                if(movingTowards.magnitude > 0)//만약 자신이 member와 접촉되지 않았다면 
                {
                    separteVector += movingTowards.normalized / movingTowards.magnitude; //1의 값(정규화 값)을 기준으로 자신의 원래 스칼라 값을 나누어주어서 이동해야되는 크기에 따라 속도를 정의한다 ,magnitude: 벡터의 길이(크기) 
                }
            }
        }
        return separteVector.normalized;
    }
 
    Vector3 Avoidance()
    {
        Vector3 avoidVector = new Vector3();
        var enemyList = level.GetEnemies(this, conf.avoidanceRadius); //level스크립트에서 enemy리스트 가져오기 
        if (enemyList.Count == 0)
            return avoidVector;
        foreach(var enemy in enemyList)
        {
            avoidVector += RunAway(enemy.transform.position); //적과 반대로 이동하는 
        }
        return avoidVector.normalized;
    }
 
    Vector3 RunAway(Vector3 target)
    {
        Vector3 neededVelocity = (position - target).normalized * conf.maxVelocity; //적(target)의 위치와 반대방향으로 이동
        return neededVelocity - velocity; //적을 피해 이동하는 벡터 - member가 원래 가야되는 
    }
 
    //최종 조합 - wander() + cohesion() + alignment() + separation() + avoidance()의 결합 (snake game처럼 보이는)
    virtual protected Vector3 Combine()
    {
        Vector3 finalVec = conf.cohesionPriority * Cohesion() + conf.wanderPriority * Wander()
                               + conf.alignmentPriority * Alignment() + conf.separationPriority * Separation()
                               + conf.avoidancePriority * Avoidance();
        return finalVec;
    }
 
    //agent가 범위를 벗어나갈 때 초기값으로 이동시키는 함수  
    void WrapAround(ref Vector3 vector, float min, float max)
    {
        //wrapAroundfloat이라는 공식을 반환함 
        vector.x = WrapAroundFloat(vector.x, min, max);
        vector.y = WrapAroundFloat(vector.y, min, max);
        vector.z = 0;//WrapAroundFloat(vector.z, min, max); //2D, 3D
    }
 
    //wrapAroundFloat이라는 공식 
    float WrapAroundFloat(float value, float min, float max)
    {
        if (value > max)
            value = min;
        else if (value < min)
            value = max;
        return value;
    }
 
    //랜덤 매개변수 이항식 뺄셈 
    float RandomBinomial()
    {
        //random은 c언어에서는 0부터 1값만 나온다 
        //return Random.Range(0f, 1f) - Random.Range(0f, 1f);
        return Random.Range(-1.0f, 1.0f);
    }
 
    //fov: 시야각 - '속도'와 '(변수)-현재 위치' 사이의 각도의 반환값이 conf.maxFOV보다 작을 때 
    bool isInFOV(Vector3 vec)
    {
        return Vector3.Angle(this.velocity, vec - this.position) <= conf.maxFOV;
    }
 
}
 
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Enemy : MonoBehaviour { //member스크립트 상속
 
    private RaycastHit hit;
    private Vector3 movePos;
 
    private void Start()
    {
        movePos = transform.position;
    }
 
    private void Update()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        if (Physics.Raycast(ray, out hit, 10000))
        {
            movePos = hit.point;
        }
 
        //sqrMagnitude는 벡터의 길이의 제곱한 값을 반환.루트 연산을 하지 않기 때문에 Magnitude보다 속도가 훨씬 빠르다 
        if ((movePos - transform.position).sqrMagnitude >= 0.2f * 0.2f)
        {
            Quaternion rot = Quaternion.LookRotation(hit.point - transform.position);
            transform.rotation = Quaternion.Slerp(transform.rotation, rot, Time.deltaTime * 10.0f);
            transform.Translate(Vector3.forward * Time.deltaTime * 10.0f);
        }
 
    }
}
 
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class MemberConfig : MonoBehaviour {
 
    public float maxFOV = 180//시야각
    public float maxAccelaeration; //가속도
    public float maxVelocity; //속도(방향 + 속력)
 
    //Wander Varaibles
    public float wanderJitter; //진폭
    public float wanderRadius; //적용 범위
    public float wanderDistance; //거리
    public float wanderPriority; //가중치(weight)
 
    //Cohesion Varaibles
    public float cohesionRadius; 
    public float cohesionPriority;
 
    //Alignment Varaibles
    public float alignmentRadius; 
    public float alignmentPriority; 
 
    //Separation Varaibles
    public float separationRadius; 
    public float separationPriority; 
 
    //Avoidance Varaibles
    public float avoidanceRadius;
    public float avoidancePriority; 
 
}
 
cs


Posted by 도이(doi)
,