본문 바로가기

유니티 개발 정보/인공지능

Space Shooter AI(우주 발사체 인공지능) - 2

원문은 이곳에서 보실수 있습니다.
내용이 길어 총 3편으로 나눠서 올리겠습니다.
본문에서 프로젝트 파일을 다운받아서 같이 따라하시면 더 이해가 쉽습니다.:)



Spinning around
주위를 돌게하기

우리는 삼각법 함수를 계속해서 수정할 것이고, 이번 시간은 한 정점의 주위를 돌게끔 만들 것이다.
우리가 이루고자하는 것은 한 정점 주위의 원안에 위치한 우주선들이, 정점 주위를 돌면서 플레이어 우주선을 향해 움직이게 하는 것이다. 흠, 당신이 이 요점을 이해했다고 확신하지는 않지만, 이것이 더 이상 마법이 아니라는 것을 보게 될 것이다. 단지 수학이다.


우리는 spawn 객체가 x축에 따라서 움직이게하는 새로운 스크립트가 필요하다. 크게 어렵지 않다:

1
2
3
4
5
6
7
8
9
using UnityEngine;
using System.Collections;
 
public class CirclePattern : MonoBehaviour {
 
    void Update () {
        _transform.Translate ( -Time.deltaTime,0,0,Space.World);
    }
}



위 스크립트를 spawn object에 첨부할 것이고, 그곳은 외롭게 홀로 움직일 것이다.

이제 인스턴스화 시키고 우주선을 Spawn 객체 주위에 위치시키는 함수가 필요하다. 사실 우리는 문제없이 이 함수를 재사용하기를 원한다. 우리는 10개의 우주선을 위치시키기를 원하지만, 약간의 패턴을 부수기 위해서 오직 5개의 우주선만을 위치시킬 것이다.  다시, 삼각함수로 뛰어 들어 보자.

우리 이제 단위원에 관해서 생각해볼 것이다. 이는 반지름이 1인 간단한 원인데, 라디안 단위로 cos과 sin의 값을 포함하고 있다.


우리는 각 우주선이 단위원에서 특별한 좌표를 얻는 방법을 찾아야 한다. 만약 4개의 우주선을 가지고 있다면, 그들을 각 주요 좌표에 위치시켜야 한다..우리는 0, PI/2, PI, 그리고 3PI/2의 값을 얻어야 한다. 만약 8개의 우주선을 가졌다면, 우리는 주요 좌표 좌표와 빨간색 좌표에 대한 값을 얻어야 한다. 그러므로 우리의 우주선은 spawn 객체 주위에 고르게 위치 된다.


01
02
03
04
05
06
07
08
09
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
using UnityEngine;
using System.Collections;
 
public class CirclePattern : MonoBehaviour {
 
    public GameObject ship;
    public int amount; //Give a value in the Inspector for how many ships
    void Start () {
        for(int i = 0 ;i< amount ;i++){
            //Create object and parent it to the Spawn object
            GameObject obj = (GameObject) Instantiate (ship, transform.position, transform.rotation);  
            obj.transform.parent = transform;
 
            PlaceShip (obj,i,amount);
 
            // Getting the angle between the ship and the Spawn object
            // and use it as phase shift
            CircleMove sc = obj.GetComponent<CircleMove>();
            float angle = Vector3.Angle(obj.transform.localPosition, transform.forward);
            Vector3 cross = Vector3.Cross(obj.transform.localPosition, transform.forward);
                    if (cross.y < 0) angle = -angle;
                 sc.phase = angle * Mathf.Deg2Rad;
        }
    }
 
    void Update () {
        _transform.Translate ( -Time.deltaTime,0,0,Space.World);
    }
 
    // Using the index of the newly created object from the loop
    // Divide it by the amount of object and multiply by 2PI (full circle)
    void PlaceShip(GameObject obj, int i, int amount) {
        float u = ((float)i / (float)(amount)) * (Mathf.PI*2);
        float x = (float)Mathf.Cos(u);
        float z = (float)Mathf.Sin(u);
 
        obj.transform.localPosition = new Vector3(x, 0, z)*2;
    }
}


자, 제발 겁먹지마라(당신은 사실 그렇지 않을수도 있다). 이 스크립트는 주석이 있다. 우리는 phase 라는 멤버 변수를 가진 CircleMove 스크립트를 Enemy 프리팹에 첨부해야한다. 다른 스크립트에서 필요로하는 어떤 변수가 아직 선언되지 않았기 때문에 약간의 에러를 보게 될 것이다.

PlaceShip 함수는 간단한 생일 케익 법칙이다.  몇명의 사람이 있으며, 그리고 어떻게 균등하게 케익을 나눌 수 있는지 말이다. 똑같은 방법이다.


우주선은 다음과 같은 스크립트를 사용한다:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
using UnityEngine;
using System.Collections;
 
public class CircleMove : Enemy {
 
    public float phase;
    public override void Start () {
        base.Start();
        _primaryShoot = transform.Find ("SpawnLeft").transform;
        _secondaryShoot = transform.Find ("SpawnRight").transform;    
        InvokeRepeating("Shoot",Random.Range(2.0f,4.0f),Random.Range(2.0f,4.0f));
    }
 
    public override void Update () {
        transform.localPosition= new Vector3(Mathf.Cos (Time.time+phase ),0,Mathf.Sin (Time.time+phase));
    }
}

상속 덕분에, 오직 움직임만을 다룰 것이다, 하지만 여전히 미사일은 발사되고 있다.
우주선은 localPosition으로 움직일 것이다. 왜냐하면 우리는 그들이 spawn객체 주위를 돌기를 원하기 때문이다. phase 변수 덕분에, 우주선은 균등하게 원점 주위에 위치하게 된다. 
우주선의 수를 변경해봐라 그러면 너무 큰 값을 주지 않는 한 원하는 것을 얻을 수 있을 것이다.

우주선의 Update문에서 2개의 phase변수를 제거하면 그들은 모두 같은 지점에 있게 된다. 
오직 하나를 제거하라 그들은 한 축으로 정렬 될 것이다.

당신은 또한 두번째 phase 변수를 선언해서 다음과 같이 사용할 수 있다:
01
02
03
04
05
06
07
08
09
10
public float phase;
float phaseA;
 
public override void Start () {
    phaseA = phase +30f;
}
 
public override void Update () {
    transform.localPosition= new Vector3(Mathf.Cos (Time.time+phaseA ),0,Mathf.Sin (Time.time+phase));
}


Shrink and grow
줄어들었다 늘어나기


똑같은 규칙을 계속해서 실행해보자. 우리는 우주선 그룹이 중점 주위를 돌게끔 원하면서도, 왔다 갔다 하기를 원한다

1
2
3
4
public override void Update () {
    transform.localPosition= new Vector3((Mathf.Cos (Time.time)+2) * Mathf.Cos (Time.time + phase),0,
        (Mathf.Cos (Time.time)+2) * Mathf.Sin (Time.time+phase));
}

내가 cos 과 sing은 선형 보간을 하고 -1과 1사이로 값이 고정되어 있다고 말한 것을 기억하라. 나는 여기서 그 특징을 사용할 것이다. 나는 계속해서 각 우주선들을 중점과 거리가 늘어났다가 줄어들게 할 것이다. +2는 중점과의 최소한의 거리를 유지하기 위해 주어진 값이다. 모든 우주선이 중점으로 모이는 일은 없을 것이다. 이것은 보이는 것처럼 크게 어렵지 않다.

여기서도 phaseA 변수를 사용할 수 있다.


Elliptic pattern
타원 패턴


계속해서 삼각법 함수를 수정했다. 우리 타원 혹은 그와 유사한 모양을 만들 것이다. 잠깐 타원이 무엇인지 한번 생각해보자. 우선 원은 무엇인가? 중심점으로부터 모든 점이 등거리로 떨어져 있는 기하학적 모양이다. 타원은 특정점이 다른 점보다 중싱점에 더 가까운 점들을 가지고 있는 원이다.


그래서 우리는 어떤 점은 더 가까워 지고, 어떤 점은 더 멀이지는 것을 원한다. 그러나 그동안의 거리의 변화는 느닷없이 우주선이 갑작스럽게 큰 거리를 움직이게 하지는 못할 것이다. cos 와 sin은 선형보간되고 1 에서 -1로 값이 고정이 되어있다는 것을 기억하고, 이전 예제에서 그 특징들을 사용하였고, x와 z값을 위해 cos함수를 amplitude에 적용하였다. 우리는 각각 다른 것들과 다르게 변화하기 위해서 간단한 변화를 줄 것이다.

1
2
3
4
public override void Update () {
    transform.localPosition= new Vector3((Mathf.Cos (Time.time<strong>+phase</strong>)+2)*Mathf.Cos (Time.time + phase),0,
        (Mathf.Cos (Time.time)+2)*Mathf.Sin (Time.time+phase));
}

나는 하나의 변수를 추가했고, 그것이 전부이다. 우주선이 특정 위치에 도착했을 때,  중심의 일부를 움직이는 것을 보게 될 것이다. 이는 타원에 대한 방정식과 다르기 때문에, 완벽한 타원은 아니다. 이것은 단지 타원 패턴을 의미한다.


실제 타원에 대해서, 이는 사실 더 간단하다, 단지 cos와 sin 함수에 다른 amplitude를 주기만 하면 된다:


1
2
3
4
void Update () {
    transform.localPosition= new Vector3(2*Mathf.Cos (Time.time + phase),0,
        3*Mathf.Sin (Time.time+phase));
}

앞에서 언급했드시, 목적은 객체에 생명을 주는 것을 보여주기 위한 것이다. 당신은 비행기 게임, 그리고 그들이 보여준 것처럼 그것들을 사용하는 것을 원하겠지만, 당신은 이것을 또한 공포 영화에서 사용할 수도 있다. "Boooooooo"소리를 내는 AudioSource를 가지는 빈 오브젝트를 가질 수 있을 것이다. 메인 캐릭터주위를 도는 elliptic 패턴을 사용하여, 객체는 주위를 도는 것 처럼 느껴질 것이고, 플레이어로 부터 앞으로 가거나 뒤로 움직일 것이다.  약간의 변수의 수정을 통해서 혼란을 주는 사운드 이펙트를 만들 수 있을 것이다.


(다음 포스트에서 계속)