'Unity3D'에 해당되는 글 52건

  1. 2017.03.28 [Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 4]
  2. 2017.03.27 [Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 3]
  3. 2017.03.21 [Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 2]
  4. 2017.03.20 [Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 1]
  5. 2017.03.20 [Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 0]
  6. 2017.03.17 [Unity3D] UI - HP, MP 에너바 조절하기. [Part 2] 1
  7. 2017.03.16 [Unity3D] 배칭 & 드로우콜
  8. 2017.03.16 [Unity3D] UI - HP, MP 에너바 조절하기. [Part 1] 1
  9. 2017.03.15 [Unity3D] UI - HP, MP 에너바 조절하기. [Part 0]
  10. 2017.03.13 [Unity3D] 조이스틱으로 캐릭터 조종하기. [Part 2] 10

[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 4]

※ 주의 

이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.



Menu

0. 미리보기

1. 오브젝트를 미리 생성.

- 프리팹 제작.

- 싱글톤 제작.

- 오브젝트를 생성시킬 함수 제작.

- 오브젝트를 찾을 함수를 제작.

2. 만들어진 오브젝트 활용하기.


추가.

3. 적 생성

- 적 생성 후 프리팹으로 만들기.

- 적과 총알이 충돌하면 총알 비활성화.

- 적이 총알에 맞아 죽었을 때 단순한 객체로 이펙트 효과 표현하기.


4. 메모리 풀을 이용해서 적 생성하기.

- 적 리스폰

 * Enemy를 생산하는 Area만들기. (랜덤한 위치로 Area생산 및 Area간 간격조절.)

 * Araa범위 안에 Enemy 랜덤한 위치로 리스폰 시키기.

 * Enemy가 죽었을 경우 리스폰 되는 간격 조절.


5. FSM을 이용해 적 AI만들기.




미리보기





4. 메모리 풀을 이용해서 적 생성하기.


구조



ObjManager에서 Enemy를 자체 생산 해버리면 코드가 여러모로 더러워지고, 관리또한 힘들어진다.

현재 ObjManager는 잔해물 및 총알에 대해서 자체생산하고 있는데, 그 구조를 고쳐보도록 하자.


- 잔해물

- 총알

- 적


각 매니저는 ObjManager에게 필요한 객체의 생산을 요청하고 객체가 필요할때마다 ObjManager에게 요청하여

필요한 객체를 찾아 반환받아 사용할 것이다.


1). Player스크립트에서 ObjManager로 총알 생성 요청하기.

2). Wreckage스크립트에서 ObjManager로 잔해물 생성 요청하기.

3). CreateArea스크립트에서 ObjManager로 적 생성 요청하기.

4). CreateArea스크립트에서 Area생성하기.

5). 각 Area에서 ObjManager에게 비활성화인 Enemy객체 요청하기.

6). Area에서 Enemy소환하기.

7). Enemy가 죽을때 자신의 부모 Area에게 자신의 죽음을 알려, 다음 리스폰여부를 결정하게 한다.




0) ObjManager스크립트에 SetObject()함수 2번째 인자 값의 기본값을 설정


1
2
3
4
5
6
7
8
9
10
11
12
public void SetObject(string _Name, int _Count = 20)
{
    GameObject obj = null;
    int Count = Origin.Length;
    for(int i = 0; i < Count; i++)
    {
        if (Origin[i].name == _Name)
            obj = Origin[i];
    }
 
    SetObject(obj, _Count, _Name);
}
cs

SetObject함수 호출 시 2번째 인자값을 입력하지 않아도 기본 값으로 20이 셋팅된다.


ps. 각 객체의 매니저가 생산요청을 하기 때문에 ObjManager에서 Start()는 할일이 없어졌으므로 지워버리면 된다.


"ObjManager" 풀 소스



1). Player스크립트에서 ObjManager로 총알 생성 요청하기.


1
2
3
4
5
6
7
8
    void Start()
    {
        // 총알 생성 요청.
        ObjManager.Call().SetObject("Bullet");
        Vec = GameObject.Find("CameraVector").transform;
 
        Init();
    }
cs


Start() 함수에 한 줄만 추가하면 끝.



2). Wreckage스크립트에서 ObjManager로 잔해물 생성 요청하기.


Wreckage스크립트 생성 후 하이러키창 ObjManager객체에 스크립트를 추가.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class WreckageManager : MonoBehaviour {
 
    void Start () 
    {
        string Name = "NoData";
 
        for (int i = 0; i < 4; i++)
        {
            switch (i)
            {
                case 0: Name = "Cube";     break;
                case 1: Name = "Cylinder"break;
                case 2: Name = "Capsule";  break;
                case 3: Name = "Sphere";   break;
            }
            ObjManager.Call().SetObject(Name);
        }
    }
}
cs



3). CreateArea스크립트에서 ObjManager로 적 생성 요청하기.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class EnemyArea : MonoBehaviour {
 
    public List<Transform> RespawnArea; // 리스폰 구역.
 
    public int AreaCount;   // 에리어 개수.
    public int AreaGap;     // 에리어간 간격.
    public int CreateCount; // 적 머리수.
 
    void Start ()
    {
        CreateArea(AreaCount);
 
        // 에너미 생산 요청.
        ObjManager.Call().SetObject("Enemy", CreateCount);
    }
}
cs



4). CreateArea스크립트에서 Area생성하기.


설명은 주석으로.



하이러키창에 생성된 Area객체.





5). 각 Area에서 ObjManager에게 비활성화인 Enemy객체 요청하기. &

6). Area에서 Enemy소환하기.





7). Enemy가 죽을때 자신의 부모 Area에게 자신의 죽음을 알려, 다음 리스폰여부를 결정하게 한다.


'Enemy' Script



에너미의 HP가 0이 되었을 때

에너미 자신이 속한 에리어의 스크립트를 가져와 Dead함수를 호출하여 자신이 죽었다는 사실을 알린다.

에리어는 그 사실을 인지하고 현재 살아있는 에너미 머리수 중 1을 줄인다.

머리수가 0이 되면 일정 시간 후에 에너미를 리젠 시킨다.


[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 3]

※ 주의 

이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.

 

 

Menu

0. 미리보기

1. 오브젝트를 미리 생성.

- 프리팹 제작.

- 싱글톤 제작.

- 오브젝트를 생성시킬 함수 제작.

- 오브젝트를 찾을 함수를 제작.

2. 만들어진 오브젝트 활용하기.

 

추가.

3. 적 생성

- 적 생성 후 프리팹으로 만들기.

- 적과 총알이 충돌하면 총알 비활성화.

- 적이 총알에 맞아 죽었을 때 단순한 객체로 이펙트 효과 표현하기.

 

4. 메모리 풀을 이용해서 적 생성하기.

- 적 리스폰

 * Enemy를 생산하는 Area만들기. (랜덤한 위치로 Area생산 및 Area간 간격조절.)

 * Araa범위 안에 Enemy 랜덤한 위치로 리스폰 시키기.

 * Enemy가 죽었을 경우 리스폰 되는 간격 조절.

 

5. FSM을 이용해 적 AI만들기.

 

 

※ 미리보기

 

 

 

 

3. 적 생성.

 

- 적 생성 후 프리팹으로 만들기.

 

 

 

1. 캡슐을 적당히 생성해서 마테리얼을 노란색으로 씌워준다.

2. Enemy 인스펙터 정보

 - 이름은 'Enemy'

 - Rigidbody를 추가하여 isKinematic을 체크해준다.

 - Enemy라는 스크립트를 생성하여 에너미의 컴포넌트로 추가해준다.

 - Tag에 "Enemy"를 추가해 준 후 이 객체의 태그를 Enemy로 설정한다.

 - Layer에 "Enemy"를 추가해 준 후 이 객체의 레이어를 Enemy로 설정한다.

 

※ 

- Enemy태그 지정은 총알과 Enemy가 충돌했을 때 태그로 충돌체크를 하기 위한 것.

- Enemy레이어 지정은 적이 죽었을 때 소환되는 잔해물과 다른 적이 충돌할수 없도록 레이어를 나누어 놨다.

- 충돌체크, 즉 물리적인 충돌체크를 하기 때문에 Rigidbody를 추가하고 총알의 전진하는 물리적인 힘에 영향을 받지 않게 하기 위해 is Kinematic을 체크해 준다.

 

 

"Enemy" Script

 

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Enemy : MonoBehaviour {
 
    // 공개
    public float HP;         // 현재 체력
 
    // 비공개
    private float MAX_HP;    // 최대 체력
    private bool Life;       // 살아있는지에 대한 여부.
 
    // 정보 초기화 함수.
    void Init()
    {
        MAX_HP = 100;
        HP = MAX_HP;
 
        Life = true;
 
        StartCoroutine("StateCheck");
    }
 
    void Start()
    {
        Init();
    }
 
    // 상태체크.
    IEnumerator StateCheck()
    {
        // 살아있는 동안에만 반복문을 돌림.
        while (Life)
        {
            // 적 AI
            yield return null;
        }
 
        // 만약 죽는 애니메이션이 있는 객체라면,
        // 죽었을 때 다른 객체의 영향을 받지 않게 하기위해
        // 충돌에 관한 콜라이더를 제거하고 죽는 애니메이션을 재생.
        // 그 후 객체를 비활성화 한다.
        gameObject.SetActive(false);
    }
 
    // 정보 갱신.
    public void InfoUpdate(float _Damage)
    {
        HP -= _Damage;
 
        if (HP <= 0)
        {
            // 폭발 하는 잔해물 소환.
            
 
            HP   = 0;       // 체력의 수치가 음의 값으로 갔을 경우를 대비한 초기화.
            Life = false;   // 죽었음을 알림.
        }
    }
}
 
cs

 

 

스크립트 작성 후 

에너미 인스펙터 창에 정보를 수정.

 


HP 를 100으로.

 

 

- 적과 총알이 충돌하면 총알 비활성화.


적과 총알이 충돌하면 총알의 Active을 비활성화 시키는데, 이때 돌아가던 코루틴은 자동으로 중지한다.

 

총알 객체에 붙어있는 스크립트에 새로운 함수를 추가 하도록 하자.

 

"Bullet" Script

 

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Bullet : MonoBehaviour {
 
    public float BulletPower;
 
    // 총알의 움직임 및 일정 시간뒤 비 활성화.
    IEnumerator MoveBullet()
    {
        float timer = 0;
        while (true)
        {
            timer += Time.deltaTime;    // 시간 축적
            if(timer > 2)               // 2초뒤 반복문을 빠져나간다.
                break;
 
            transform.Translate(Vector3.forward * Time.deltaTime * 40f);    // 총알을 움직인다.
            yield return null;
        }
 
        // 총알 비활성화.
        gameObject.SetActive(false);
    }
    
    // 충돌체크
    void OnTriggerEnter(Collider _Col)
    {
        // 총알이 적과 충돌.
        if (_Col.transform.CompareTag("Enemy"))
        {
            // 총알에 맞은 객체의 Enemy컴포넌트를 가져와 Enemy에게 데미지를 준다.
            _Col.GetComponent<Enemy>().InfoUpdate(BulletPower);
            gameObject.SetActive(false);
        }
    }
}
cs

 

이젠 적이 총알을 맞을때 마다 HP가 감소하면서 HP가 0이하가 되면 사라지게 된다.

그렇게 하기 위해서 총알의 공격력을 설정해줘야 하는데, 고정 값으로 총알의 공격력을 설정해줘도 좋지만

플레이어가 버프를 받았을때 라던가를 고려하여 총알의 공격력을 업데이트 할 수 있게 함수를 하나 만들기로 하자.

 

물론 업데이트 되는 정보는 총알의 공격력 뿐만 아니라, HP, 움직임 스피드, 살아있는지에 대한 여부 등을 업데이트 할 것이며,

이렇게 업데이트를 해야 향후에 여러방면으로 써먹을수 있다.

 

예를들어 플레이어가 죽었을 경우 적들은 플레이어에 대한 공격을 멈춰야 한다.

어떻게 멈춰야 할까? 그건 플레이어의 Life값을 체크하여 생존 여부를 판단할수 있다.

 

이러한 변화가 있을때마다 정보들을 ObjManager에 업데이트 하고,

다른 객체들은 ObjManager에서 플레이어 정보를 가져와 여러가지 판단을 할 수 있게 한다.

 

특히나 플레이어에 대한 정보는 변화가 많으므로 모든 객체들을 관리하는 ObjManager가 플레이어에 대한

실시간 정보를 가지고 있는게 여러방면으로 유리하다고 생각했다. (물론 개인적인 생각이다.)

 

그러기 위한 업데이트 함수를 만들어 보자.

물론 아이템이라던가 플레이어의 능력치에 변화를 주는 요소가 아직은 없기 때문에

Start에서 한 번만 정보를 업데이트 시킬 예정이다.

 

"Player" Script

 

더보기
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Player : MonoBehaviour {
 
    // 구조체 정보.
    public struct PINFO
    {
        public float MAX_HP;            // 체력의 최대치.
        public float HP;                // 현재 체력
        public float BulletPower;       // 총알의 힘.
        public float MoveSpeed;         // 움직임 스피드.
        public bool Life;               // 플레이어의 생명.
    }
 
    public float Speed;         // 움직이는 스피드.
    public float AttackGap;     // 총알이 발사되는 간격.
 
    private PINFO pInfo;        // 플레이어 정보.
    private Transform Vec;      // 카메라 벡터.
    private Vector3 MovePos;    // 플레이어 움직임에 대한 변수.
 
    private bool ContinuouFire; // 게속 발사할 것인가? 에 대한 플래그.
 
    void Init()
    {
        // 구조체 정보 초기화.
        pInfo.MAX_HP        = 100;
        pInfo.HP            = pInfo.MAX_HP;
        pInfo.BulletPower   = 20;
        pInfo.MoveSpeed     = 5;
        pInfo.Life          = true;
 
        //공개.
        AttackGap = 0.2f;
 
        // 비공개
        MovePos = Vector3.zero;
        ContinuouFire = true;
 
        // 플레이어 정보갱신.
        ObjManager.Call().PlayerInfoUpdate(pInfo);
    }
 
    void Start()
    {
        Vec = GameObject.Find("CameraVector").transform;
 
        Init();
    }
 
    void Update () 
    {
        Run();
        KeyCheck();
    }
 
    // 플레이어 움직임.
    void Run()
    {
        int ButtonDown = 0;
        if (Input.GetKey(KeyCode.LeftArrow))    ButtonDown = 1;
        if (Input.GetKey(KeyCode.RightArrow))   ButtonDown = 1;
        if (Input.GetKey(KeyCode.UpArrow))      ButtonDown = 1;
        if (Input.GetKey(KeyCode.DownArrow))    ButtonDown = 1;
 
        // 플레이어가 움직임 버튼에서 손을 땠을 때 Horizontal, Vertical이 0으로 돌아감으로써
        // 플레이어의 회전상태가 다시 원상태로 돌아가지 않게 하기 위해서.
        if (ButtonDown != 0)
            Rotation();
        else
            return;
 
        transform.Translate(Vector3.forward * Time.deltaTime * pInfo.MoveSpeed * ButtonDown);
    }
 
    // 플레이어 회전.
    void Rotation()
    {
        MovePos.Set(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));   // 벡터 셋팅.
        Quaternion q = Quaternion.LookRotation(Vec.TransformDirection(MovePos));  // 회전
 
        if (MovePos != Vector3.zero)
            transform.rotation = q;
    }
 
    // 총알 키 체크.
    void KeyCheck()
    {
        if (Input.GetButtonDown("Jump"))
            StartCoroutine("NextFire");
        else if (Input.GetButtonUp("Jump")) 
            ContinuouFire = false;
 
        if (Input.GetKeyDown(KeyCode.Q))
            ObjManager.Call().MemoryDelete();
 
        if (Input.GetKeyDown(KeyCode.E))
            ObjManager.Call().CreateObject("Bullet"20);
 
    }
 
    // 연속발사.
    IEnumerator NextFire()
    {
        ContinuouFire = true;
        while (ContinuouFire)
        {
            // 총알을 리스트에서 가져온다.
            BulletInfoSetting(ObjManager.Call().GetObject("Bullet"));
            yield return new WaitForSeconds(AttackGap);
        }
    }
 
    // 총알정보 셋팅.
    void BulletInfoSetting(GameObject _Bullet)
    {
        if (_Bullet == nullreturn;
 
        _Bullet.transform.position = transform.position;                // 총알의 위치 설정
        _Bullet.transform.rotation = transform.rotation;                // 총알의 회전 설정.
        _Bullet.SetActive(true);                                        // 총알을 활성화 시킨다.
        _Bullet.GetComponent<Bullet>().StartCoroutine("MoveBullet");    // 총알을 움직이게 한다.
    }
}
 
cs

 

 

플레이어의 실시간 정보를 가지고 있는 함수.

"ObjManager" Script

 

더보기
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class ObjManager : MonoBehaviour {
 
    // 싱글톤
    static ObjManager st;
    public static ObjManager Call() { return st; }
    void Awake()                    { st = this; }
    // 게임종료 후 메모리 날려버림.
    void OnDestroy()                
    {
        MemoryDelete();
        st = null;
    }
    
    // 공개
    public GameObject[] Origin;         // 프리팹 원본.
    public List<GameObject> Manager;    // 생성된 객체들을 저장할 리스트.
 
    // 비공개.
    private Player.PINFO pInfo;         // 플레이어 정보.
 
    void Start()
    {
        // 총알 생성.
        SetObject(Origin[0], 20"Bullet");
    }
 
    // 오브젝트를 받아 생성.
    public void SetObject( GameObject _Obj, int _Count, string _Name)
    {
        for (int i = 0; i < _Count; i++)
        {
            GameObject obj = Instantiate(_Obj) as GameObject;
            obj.transform.name = _Name;                     // 이름을 정한다.
            obj.transform.localPosition = Vector3.zero;     // 위치를 정한다.
            obj.SetActive(false);                           // 객체를 비활성화.
            obj.transform.parent = transform;               // 매니저 객체의 자식으로.
            
            if(_Name != "Bullet") // 총알이 아니면 색을 랜덤으로 설정.
                obj.GetComponent<Renderer>().material.color = new Color(Random.value, Random.value, Random.value, 1.0f);
 
            Manager.Add(obj);                               // 리스트에 저장.
        }
    }
 
    // 필요한 오브젝트를 찾아 반환.
    public GameObject GetObject(string _Name)
    {
        // 리스트가 비어있으면 종료.
        if (Manager == null)
            return null;
 
        int Count = Manager.Count;
        for (int i = 0; i < Count; i++)
        {
            // 이름이 같지 않으면.
            if (_Name != Manager[i].name)
                continue;
 
            GameObject Obj = Manager[i];
 
            // 활성화가 되어있다면.
            if (Obj.active == true)
            {
                // 리스트의 마지막까지 돌았지만 모든 객체가 사용중이라면.
                if (i == Count - 1)
                {
                    // 총알을 새롭게 생성.
                    SetObject(Obj, 1, _Name);
                    return Manager[i + 1];
                }
                continue;
            }
            return Manager[i]; 
        }
        return null;
    }
 
    // 메모리 삭제.
    public void MemoryDelete()
    {
        if (Manager == null)
            return;
 
        int Count = Manager.Count;
 
        for (int i = 0; i < Count; i++)
        {
            GameObject obj = Manager[i];
            GameObject.Destroy(obj);
        }
        Manager = null;
    }
 
    // 원하는 오브젝트를 만든다.
    public void CreateObject(string _Name, int _Count)
    {
        if (Manager == null)
            Manager = new List<GameObject>();
 
        int Count = Origin.Length;
        for (int i = 0; i < Count; i++)
        {
            GameObject obj = Origin[i];
            if (obj.name == _Name)
            {
                SetObject(obj, _Count, _Name);   // 총알을 생성.
                break;
            }
        }
    }
 
    // 플레이어의 정보갱신.
    public void PlayerInfoUpdate(Player.PINFO _Info)
    {
        // 플레이어 정보 업데이트.
        pInfo = _Info;
 
        int Count = Manager.Count;
 
        for (int i = 0; i < Count; i++)
        {
            GameObject obj = Manager[i];
            if (obj.name == "Bullet") // 총알의 데미지 정보 업데이트.
                obj.GetComponent<Bullet>().BulletPower = _Info.BulletPower;
        }
    }
 
    // 플레이어의 정보를 가져간다.
    public object GetPlayerInfo(string _Type)
    {
        switch (_Type)
        {
            case "Life":        return pInfo.Life;
            case "MAXHP":       return pInfo.MAX_HP;
            case "HP":          return pInfo.HP;
            case "Speed":       return pInfo.MoveSpeed;
            case "BulletPower"return pInfo.BulletPower;
            case "All":         return pInfo;
        }
        return null;
    }
}
cs

 

 

적이 총알을 맞아 HP가 깎이면 사라지는것을 볼 수 있다.

 

 

 

이펙트가 없으니 밋밋하다.

그러니 큐브 조각들로 이펙트를 흉내 내보기로 하자.

 

1). 잔해물 오브젝트를 만든다. (4종류: 큐브, 실린더, 캡슐 등...)

2). 잔해물의 인스페터창 정보를 수정한다.

- 잔해물의 크기(Scale)조정

- 잔해물이 생성 후 퍼져나가는 효과를 내기 위한 Material추가.

 

 

- 렌더러에서 Cast Shadows를 off하고 Receive Shadows 체크를 해제한다. (그림자가 필요 없기 때문)

- Rigidbody 생성.

- "ExplosinHide" 잔해물을 비활성화 해주는 스크립트를 생성

- "Wreckage" 레이어 추가 후 각 객체(잔해물)에 설정.

 

 

 

 

"ExplosinHide" Script

 

더보기

 

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class ExplosionHide : MonoBehaviour {
 
    // 블럭이 사라진다.
    IEnumerator StartExplosionHide()
    {
        int Rand = Random.Range(37);
        float timer = 0;
        while (true)
        {
            timer += Time.deltaTime;
            if(timer > Rand)
                break;
 
            yield return null;
        }
 
        // 물체가 사라진 후 다시 소환됐을 때 전에 받던 물리력을 없애주기위한 체크.
        transform.GetComponent<Rigidbody>().isKinematic = true;
        // 물리력을 다시 받기 위해 체크 해제.
        transform.GetComponent<Rigidbody>().isKinematic = false;
        gameObject.SetActive(false);
    }
}
cs

 

3). 잔해물과 적이 충돌되지 않게 PhysicsManager를 설정한다.

 

 

3.5) 다 만들어진 오브젝트를 프리팹으로 만든다.

4). 메모리 풀을 이용하여 잔해물을 미리 생성한다.

 

"ObjManager" 스크립트의 Start()함수를 수정.

 

더보기

 

 

 

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class ObjManager : MonoBehaviour {
 
    // 싱글톤
    static ObjManager st;
    public static ObjManager Call() { return st; }
    void Awake()                    { st = this; }
    // 게임종료 후 메모리 날려버림.
    void OnDestroy()                
    {
        MemoryDelete();
        st = null;
    }
    
    // 공개
    public GameObject[] Origin;         // 프리팹 원본.
    public List<GameObject> Manager;    // 생성된 객체들을 저장할 리스트.
 
    // 비공개.
    private Player.PINFO pInfo;         // 플레이어 정보.
 
    void Start()
    {
        int Count = Origin.Length;  // 총알, 잔해물 등의 기본 프리팹을 가지고있는 배열의 크기.
        string Name = "NoData";
 
        for (int i = 0; i < Count; i++)
        {
            switch (i)
            {
                case 0: Name = "Bullet";   break;
                case 1: Name = "Cube";     break;
                case 2: Name = "Cylinder"break;
                case 3: Name = "Capsule";  break;
                case 4: Name = "Sphere";   break;
            }
 
            // 오브젝트 생성.
            SetObject(Origin[i], 20, Name);
        }
    }
 
    // 오브젝트를 받아 생성.
    public void SetObject( GameObject _Obj, int _Count, string _Name)
    {
        for (int i = 0; i < _Count; i++)
        {
            GameObject obj = Instantiate(_Obj) as GameObject;
            obj.transform.name = _Name;                     // 이름을 정한다.
            obj.transform.localPosition = Vector3.zero;     // 위치를 정한다.
            obj.SetActive(false);                           // 객체를 비활성화.
            obj.transform.parent = transform;               // 매니저 객체의 자식으로.
            
            if(_Name != "Bullet")
                obj.GetComponent<Renderer>().material.color = new Color(Random.value, Random.value, Random.value, 1.0f);
 
            Manager.Add(obj);                               // 리스트에 저장.
        }
    }
 
    // 필요한 오브젝트를 찾아 반환.
    public GameObject GetObject(string _Name)
    {
        // 리스트가 비어있으면 종료.
        if (Manager == null)
            return null;
 
        int Count = Manager.Count;
        for (int i = 0; i < Count; i++)
        {
            // 이름이 같지 않으면.
            if (_Name != Manager[i].name)
                continue;
 
            GameObject Obj = Manager[i];
 
            // 활성화가 되어있다면.
            if (Obj.active == true)
            {
                // 리스트의 마지막까지 돌았지만 모든 객체가 사용중이라면.
                if (i == Count - 1)
                {
                    // 총알을 새롭게 생성.
                    SetObject(Obj, 1, _Name);
                    return Manager[i + 1];
                }
                continue;
            }
            return Manager[i]; 
        }
        return null;
    }
 
    // 메모리 삭제.
    public void MemoryDelete()
    {
        if (Manager == null)
            return;
 
        int Count = Manager.Count;
 
        for (int i = 0; i < Count; i++)
        {
            GameObject obj = Manager[i];
            GameObject.Destroy(obj);
        }
        Manager = null;
    }
 
    // 원하는 오브젝트를 만든다.
    public void CreateObject(string _Name, int _Count)
    {
        if (Manager == null)
            Manager = new List<GameObject>();
 
        int Count = Origin.Length;
        for (int i = 0; i < Count; i++)
        {
            GameObject obj = Origin[i];
            if (obj.name == _Name)
            {
                SetObject(obj, _Count, _Name);   // 총알을 생성.
                break;
            }
        }
    }
 
    // 플레이어의 정보갱신.
    public void PlayerInfoUpdate(Player.PINFO _Info)
    {
        // 플레이어 정보 업데이트.
        pInfo = _Info;
 
        int Count = Manager.Count;
 
        for (int i = 0; i < Count; i++)
        {
            GameObject obj = Manager[i];
            if (obj.name == "Bullet")
                obj.GetComponent<Bullet>().BulletPower = _Info.BulletPower;
        }
    }
 
    // 플레이어의 정보를 가져간다.
    public object GetPlayerInfo(string _Type)
    {
        switch (_Type)
        {
            case "Life":        return pInfo.Life;
            case "MAXHP":       return pInfo.MAX_HP;
            case "HP":          return pInfo.HP;
            case "Speed":       return pInfo.MoveSpeed;
            case "BulletPower"return pInfo.BulletPower;
            case "All":         return pInfo;
        }
        return null;
    }
}
cs

 

 

5). 적이 죽은 후 일정 개수의 잔해물을 소환하고 일정 시간이 지나면 잔해물을 사라지게 한다.

 

"Enemy" Script에 explosion() 함수 추가 후 InfoUpdate() 함수에서 호출.

 

더보기

 

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Enemy : MonoBehaviour {
 
    // 공개
    public float HP;        // 현재 체력
 
    // 비공개
    private float MAX_HP;   // 최대 체력
    private bool Life;      // 살아있는지에 대한 여부.
 
    // 정보 초기화 함수.
    void Init()
    {
        MAX_HP = 100;
        HP = MAX_HP;
 
        Life = true;
 
        StartCoroutine("StateCheck");
    }
 
    void Start()
    {
        Init();
    }
 
    // 상태체크.
    IEnumerator StateCheck()
    {
        // 살아있는 동안에만 반복문을 돌림.
        while (Life)
        {
            // 적 AI
            yield return null;
        }
 
        // 만약 죽는 애니메이션이 있는 객체라면,
        // 죽었을 때 다른 객체의 영향을 받지 않게 하기위해
        // 충돌에 관한 콜라이더를 제거하고 죽는 애니메이션을 재생.
        // 그 후 객체를 비활성화 한다.
        gameObject.SetActive(false);
    }
 
    // 정보 갱신.
    public void InfoUpdate(float _Damage)
    {
        HP -= _Damage;
 
        if (HP <= 0)
        {
            // 폭발한다.
            explosion();
 
            HP   = 0;       // 체력의 수치가 음의 값으로 갔을 경우를 대비한 초기화.
            Life = false;   // 죽었음을 알림.
        }
    }
 
    // 폭발은 예술이다.
    void explosion()
    {
        string Name = "NoData";
        // 잔해물 소환.
        for (int i = 0; i < 4; i++)
        {
            // 잔해물 4종류.
            switch (i)
            {
                case 0: Name = "Cube";      break;
                case 1: Name = "Cylinder";  break;
                case 2: Name = "Capsule";   break;
                case 3: Name = "Sphere";    break;
            }
 
            // 잔해물을 4종류를 5개씩 총 20개를 생성 후 뿌린다.
            for (int j = 0; j < 5; j++)
            {
                GameObject obj = ObjManager.Call().GetObject(Name);
                
                if (obj == null)
                    continue;
 
                obj.transform.position = transform.position;
                obj.SetActive(true);
                obj.GetComponent<ExplosionHide>().StartCoroutine("StartExplosionHide");
            }
        }
    }
}
 
cs

 

 

 

결과.

 

 

[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 2]

※ 주의 

이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.



Menu

0. 미리보기

1. 오브젝트를 미리 생성.

- 프리팹 제작.

- 싱글톤 제작.

- 오브젝트를 생성시킬 함수 제작.

- 오브젝트를 찾을 함수를 제작.

2. 만들어진 오브젝트 활용하기.





2. 만들어진 오브젝트 활용하기.


- 총알 발사하기.

 * 총알을 발사하는 키로는 스페이스바로 하자.
 * 스페이스바를 한 번 눌렀다 땠을 때, 한 번만 발사된다.
 * 스페이스바를 게속 누르고 있으면 총알이 연속해서 발사된다. (코루틴을 이용한다.)
 * 총알이 발사되는 과정은 이렇다.
   - 비활성화인 총알을 리스트에서 찾아서 가져온다.
   - 총알의 위치를 설정한다.
   - 총알의 회전 상태를 설정한다.
   - 총알 객체를 활성화 시킨다.
   - 총알을 움직인다.


위의 과정을 수행하기 위해서 'Player'스크립트에 3개의 함수를 만들거다.

첫 번째 함수는 입력버튼을 감지하는 'KeyCheck()' 함수.
두 번째 함수는 연속 버튼을 수행하는 'NextFire()' 코루틴 함수.
세 번째 함수는 총알 정보를 셋팅하는 'BulletInfoSetting()' 함수.

그리고 총알을 움직여주기 위한 구문은 Bullet객체에 새로운 스크립트 'Bullet'을 만들어 수행하도록 하자.
이 스크립트에서는 총알을 움직여주기 위한것 뿐만 아니라, 총알을 재활용하기 위한 비활성화 구분도 수행될 것이다.


'Player Script'

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Player : MonoBehaviour {
 
    public float Speed;         // 움직이는 스피드.
    public float AttackGap;     // 총알이 발사되는 간격.
 
    private Transform Vec;      // 카메라 벡터.
    private Vector3 MovePos;    // 플레이어 움직임에 대한 변수.
 
    private bool ContinuouFire; // 게속 발사할 것인가? 에 대한 플래그.
 
    void Init()
    {
        //공개.
        AttackGap = 0.2f;
 
        // 비공개
        MovePos = Vector3.zero;
        ContinuouFire = true;
    }
 
    void Start()
    {
        Vec = GameObject.Find("CameraVector").transform;
 
        Init();
    }
 
    void Update () 
    {
        Run();
        KeyCheck();
    }
 
    // 플레이어 움직임.
    void Run()
    {
        int ButtonDown = 0;
        if (Input.GetKey(KeyCode.LeftArrow))    ButtonDown = 1;
        if (Input.GetKey(KeyCode.RightArrow))   ButtonDown = 1;
        if (Input.GetKey(KeyCode.UpArrow))      ButtonDown = 1;
        if (Input.GetKey(KeyCode.DownArrow))    ButtonDown = 1;
 
        // 플레이어가 움직임 버튼에서 손을 땠을 때 Horizontal, Vertical이 0으로 돌아감으로써
        // 플레이어의 회전상태가 다시 원상태로 돌아가지 않게 하기 위해서.
        if (ButtonDown != 0)
            Rotation();
        else
            return;
 
        transform.Translate(Vector3.forward * Time.deltaTime * Speed * ButtonDown);
    }
 
    // 플레이어 회전.
    void Rotation()
    {
        MovePos.Set(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));   // 벡터 셋팅.
        Quaternion q = Quaternion.LookRotation(Vec.TransformDirection(MovePos));  // 회전
 
        if (MovePos != Vector3.zero)
            transform.rotation = q;
    }
 
    // 총알 키 체크.
    void KeyCheck()
    {
        if (Input.GetButtonDown("Jump"))
            StartCoroutine("NextFire");
        else if (Input.GetButtonUp("Jump")) 
            ContinuouFire = false;
    }
 
    // 연속발사.
    IEnumerator NextFire()
    {
        ContinuouFire = true;
        while (ContinuouFire)
        {
            // 총알을 리스트에서 가져온다.
            BulletInfoSetting(ObjManager.Call().GetObject("Bullet"));
            yield return new WaitForSeconds(AttackGap);                    // 시간지연.
        }
    }
 
    // 총알정보 셋팅.
    void BulletInfoSetting(GameObject _Bullet)
    {
        if (_Bullet == nullreturn;
 
        _Bullet.transform.position = transform.position;                // 총알의 위치 설정
        _Bullet.transform.rotation = transform.rotation;                // 총알의 회전 설정.
        _Bullet.SetActive(true);                                        // 총알을 활성화 시킨다.
        _Bullet.GetComponent<Bullet>().StartCoroutine("MoveBullet");    // 총알을 움직이게 한다.
    }
}
 
cs




'Bullet Script'


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Bullet : MonoBehaviour {
 
    // 총알의 움직임 및 일정 시간뒤 비 활성화.
    IEnumerator MoveBullet()
    {
        float timer = 0;
        while (true)
        {
            timer += Time.deltaTime;    // 시간 축적
            if(timer > 2)               // 2초뒤 반복문을 빠져나간다.
                break;
 
            transform.Translate(Vector3.forward * Time.deltaTime * 40f);    // 총알을 움직인다.
            yield return null;
        }
 
        // 총알 비활성화.
        gameObject.SetActive(false);
    }
}
cs




결과




[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 1]

※ 주의 

이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.



Menu

0. 미리보기

1. 오브젝트를 미리 생성.

- 프리팹 제작.

- 싱글톤 제작.

- 오브젝트를 생성시킬 함수 제작.

- 오브젝트를 찾을 함수를 제작.

2. 만들어진 오브젝트 활용하기.




1. 오브젝트 미리 생성.


- 총알 프리팹 제작.



구체를 하나 생성한다.





1. 생성한 오브젝트의 이름을 'Bullet'으로 설정.

2. 스케일 x,y,z를 (0.2, 0.2, 0.5)로 설정.

3. Mesh Renderer컴포넌트에 Cast Shadows를 off로 설정.

4. Mesh Renderer컴포넌트에 Receive Shadows 체크박스 해제.

※ 3,4번 빛 연산 및 빛으로 인한 그림자 연산을 하지 않을 거다.

5. Rigidbody추가 후 Is Kinematic체크

※ Rigidbody없이 물리운동을 할 경우 시스템에 부담이 걸리므로 리지드바디 추가 후 키네틱을 체크해준다.

6. 마테리얼을 적색으로 생성 후 Bullet오브젝트에 적용.





하이러키창에 있는 Bullet을 프로젝트창에 드래그해서 프리팹을 생성한다.

그 뒤 하이러키창에 있는 Bullet을 삭제.




- 싱글톤 제작.


※ 지정한 클래스 인스턴스가 절대로 한 개밖에 존재하지 않는 것을 보증하고 싶을 경우 싱글톤을 사용한다.

보통 객체 생성 시 new키워드를 사용하여 객체화 하는데, 객체를 생성하게되면 Heap메모리에 올라가게 된다.

그 인스턴스를 가리키고 있는 변수는 Stack메모리 영역에 생기게 된다.


이러한 작업 자체가 시간이 걸리는 일이며, 한 객체를 여러번 new하게 되면 시간이 더욱 오래 걸리게 된다.

그래서 자주 사용되는 객체는 한 번만 생성하고, Heap에 존재하는 이 객체를 가르키도록 만들면 된다.

즉, 객체가 생성될 때 Heap영영에 올라가는 시간과 메모리를 줄일 수 있다.



단점.

- 싱글톤을 사용하여 여러곳에서 효과적으로 호출할 수 있어서 편하지만 결과적으로 프로그램의 Coupling을 높이게 되어 한곳에서의 변경이 다른 부분에 영향을 미치게 될 확률이 높아지게 된다.



'ObjManager' 스크립트 제작.


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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class ObjManager : MonoBehaviour {
 
    // 싱글톤
    static ObjManager st;
    public static ObjManager Call() { return st; }
    void Awake()                    { st = this; }
    // 게임종료 후 메모리 날려버림.
    void OnDestroy()                
    { 
        MemoryDelete();
        st = null
    } 
 
    public GameObject[] Origin;         // 프리팹 원본.
    public List<GameObject> Manager;    // 생성된 객체들을 저장할 리스트.
 
    void Start()
    {
        SetObject(Origin[0], 20"Bullet");   // 총알을 생성.
    }
 
    // 오브젝트를 받아 생성. (생성한 원본 오브젝트, 생성할 갯수, 생성할 객체의 이름)
    public void SetObject( GameObject _Obj, int _Count, string _Name)
    {
        for (int i = 0; i < _Count; i++)
        {
            GameObject obj = Instantiate(_Obj) as GameObject;
            obj.transform.name = _Name;                     // 이름을 정한다.
            obj.transform.localPosition = Vector3.zero;     // 위치를 정한다.
            obj.SetActive(false);                           // 객체를 비활성화.
            obj.transform.parent = transform;               // 매니저 객체의 자식으로.
            Manager.Add(obj);                               // 리스트에 저장.
        }
    }
 
    // 필요한 오브젝트를 찾아 반환.
    public GameObject GetObject(string _Name)
    {
        if (Manager == null)
            return null;
 
        int Count = Manager.Count;
        for (int i = 0; i < Count; i++)
        {
            // 이름이 같지 않으면.
if ( _Name != Manager[i].name )
             continue;
            
            GameObject Obj = Manager[i];
 
            // 활성화가 되어있다면.
            if (Obj.active == true)
            {
                // 리스트의 마지막까지 돌았지만 모든 객체가 사용중이라면.
                if (i == Count - 1)
                {
                    // 총알을 새롭게 생성.
                    SetObject(Obj, 1"Bullet");
                    return Manager[i + 1];
                }
                continue;
            }
            return Manager[i]; 
        }
        return null;
    }
 
    // 메모리 삭제.
    public void MemoryDelete()
    {
        if (Manager == null)
            return;
 
        int Count = Manager.Count;
 
        for (int i = 0; i < Count; i++)
        {
            GameObject obj = Manager[i];
            GameObject.Destroy(obj);
        }
        Manager = null;
    }
}

cs



빈 오브젝트를 생성하여 이름을 ObjectManager로 변경한다.

그뒤 만들어 두었던 ObjManager스크립트를 ObjectManager 객체에 드래그하여 컴포넌트를 생성한다.


Obj Manager스크립트 - Origin배열에 사이즈를 1로 늘리고, Bullet프리팹을 드래그하여 넣는다.



이 스크립트에는 싱글톤을 제외하고 몇 가지 기능을 가진 함수들이 있다.


1. 오브젝트를 생성시켜주는 함수.

2. 필요한 오브젝트를 찾아 반환시켜주는 함수.

3. 메모리를 삭제해주는 함수.


2번의 함수는 리스트내의 객체의 이름을 검색, 비교하여 필요한 오브젝트를 찾아주는데,

만약 필요한 객체가 없거나 모두 사용중이면 새롭게 생성해 그 객체를 반환해주는 역할을 수행하고 있다.




결과.



총알이 여러개 생성되었다.

[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 0]

※ 주의 

이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.



Menu

0. 미리보기

1. 오브젝트를 미리 생성.

- 프리팹 제작.

- 싱글톤 제작.

- 오브젝트를 생성시킬 함수 제작.

- 오브젝트를 찾을 함수를 제작.

2. 만들어진 오브젝트 활용하기.



0. 미리보기


메모리 풀을 활용하여 만든 오브젝트는 총알이며, 활성화 및 비활성화를 통한 오브젝트 재활용을 실시할 것이다.



※ 메모리 풀을 사용하는 이유


많은 양의 객체들을 실시간으로 생성하고, 삭제하면 메모리가 빠르게 고갈되면서

쌓인 가비지로인해 가비지 컬렉터가 호출되고, 가비지를 처리하는 과정에서 버벅거리는 현상이 발생한다.

이러한 현상을 방지하기 위해서 메모리 풀 시스템을 활용하는 것이 좋다.


[Unity3D] UI - HP, MP 에너바 조절하기. [Part 2]

※ 주의 

이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.



Menu

0. 미리보기

1. HP,MP껍데기 및 기능 만들기.

2. HP,MP 조절을 위한 데미지, 리커버리 만들기.




2. HP,MP 조절을 위한 데미지, 리커버리 만들기.



에셋 스토어에서 무료 파티클을 받아서 하이러키창에 배치하자.

하나는 데미지 또 하나는 리커버리라고 명명하면 된다.




왼쪽이 데미지, 오른쪽이 리커버리 파티클이다.

두개의 오브젝트에 Rigidbody와 BoxColider를 부착하자.



※ 파티클 오브젝트 인스펙터창 정보.



각각에 오브젝트에 일어난 충돌들을 체크하여 플레이어에게 적용할 것이다.

충돌이 일어났을 때 플레이어가 가진 정보를 가져와서 그 정보를 이용하여 HP,MP에 영향을 준다.


충돌을 체크하는 방법에는 2가지가 있다.


OnCollision( 물리현상 O ) 

 OnTrigger( 물리현상 X )

OnCollisionEnter()

 - 처음 한 번 충돌 시 한 번 호출.

- 다시 호출 하려면 충돌 영역을  벗어난 뒤에 다시 부딪히면 된다.

 OnTriggerEnter()

 기능은 같다.

OnCollisionStay() 

 - 충돌 영역에 존재하는동안 게속  하여 호출한다.

 OnTriggerStay()

 기능은 같다.

OnCollisionExit()

 - 충돌 영역에 들어온 후 그 영역을 벗어나면 호출된다.

 OnTriggerExit()

 기능은 같다.


OnCollision을 사용할 시에는 앞에 벽이 존재하는것처럼 충돌을 수행 하지만,

OnTrigger는 충돌만 체크할뿐 통과해 버린다.


이중 우리는 OnTrigger를 사용할 것이다.

OnTrigger를 사용하려면 각 오브젝트에 붙어있는 BoxCollider의 is Trigger를 체크해줘야 한다.

※ 충돌에 의한 움직임이나, 중력은 사용하지 않을 것이므로 Rigidbody에 Is Kinematic을 체크 하도록 하자.


각 기능이 다르기 때문에 2가지의 스크립트로 작성해 보도록 하자.

각각의 스크립트 이름은 Damage, Recover이다.


데미지 스크립트.

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Damage : MonoBehaviour {
 
    private PlayerInfo pInfo; // 플레이어의 스크립트가 저장될 변수.
 
    // 지속적으로 플레이어의 HP 감소.
    IEnumerator StartDamage()
    {
        while (true)
        {
            // 플레이어가 가지고 있는 UIUpdate호출.
            pInfo.UIUpdate("Damage""HP"0.1f);
            yield return null;
        }
    }
 
    // 충돌 했을 때.
    void OnTriggerEnter(Collider _Col)
    {
        if (_Col.transform.CompareTag("Player"))
        {
            // 플레이어의 스크립트 컴포넌트를 가져온다.
            pInfo = _Col.GetComponent<PlayerInfo>();
            StartCoroutine("StartDamage");
        }
    }
 
    // 충돌이 끝났을 때.
    void OnTriggerExit(Collider _Col)
    {
        if (_Col.transform.CompareTag("Player"))
        {
            // 코루틴을 멈춘다.
            StopCoroutine("StartDamage");
 
            // 충돌이 끝난 후 정보를 플레이어의 정보를 게속 가지고 있을 필요가
            // 없기 때문에 null로 초기화 해준다.
            pInfo = null;
        }
    }
}
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Recover : MonoBehaviour {
 
    private PlayerInfo pInfo;   // 플레이어의 스크립트가 저장될 변수.
 
    // 지속적으로 플레이어의 HP회복.
    IEnumerator StartRecover()
    {
        while (true)
        {
            pInfo.UIUpdate("Recover""HP"0.1f);
            yield return null;
        }
    }
 
    // 충돌 했을 때.
    void OnTriggerEnter(Collider _Col)
    {
        if (_Col.transform.CompareTag("Player"))
        {
            pInfo = _Col.GetComponent<PlayerInfo>();
            StartCoroutine("StartRecover");
        }
    }
 
    // 충돌이 끝났을 때.
    void OnTriggerExit(Collider _Col)
    {
        if (_Col.transform.CompareTag("Player"))
        {
            // 코루틴을 멈춘다.
            StopCoroutine("StartRecover");
 
            // 충돌이 끝난 후 정보를 플레이어의 정보를 게속 가지고 있을 필요가
            // 없기 때문에 null로 초기화 해준다.
            pInfo = null;
        }
    }
}
cs




결과.



[Unity3D] 배칭 & 드로우콜


1. 드로우 콜(Draw Call)

- CPU가 GPU에게 어떠한 물체를 그리라고 요청하는 것.

- 드로우 콜의 개수가 적을수록 가벼운 게임이라 할 수 있다. 기기의 성능에 따라 특정 개수를 넘어가면 프레임 저하가 나타난다.

- 모바일의 경우 100개정도를 상한선으로 잡고있다. (VR은 절반 이하다.)

- 일반적으로 오브젝트를 그릴 때, 오브젝트 단위로 한 개씩 증가한다. 그 외에도 쉐이더에 따라서 추가로 증가 할 수 있다.



2. 배치(Batches)

- Draw Call과 혼용하여 사용하지만, 사실 드로우 콜을 포함하는 상위 개념이다.

- Draw Call + Set VB/IB + Set Transform + Set Pass Call 


※ Set Pass Call = Set Shader + Set Texture 0 ~ 7 + Set Blending + Set Z enable . . .



※ 배치 개수는 어떻게 결정되는가?


1. 매쉬의 개수 * 라이트의 개수 + 재질의 개수 - 동적 배칭으로 절약된 개수.

2. 파티클은 Material이나 Mesh에 관계없이 개당 1개로 계산된다. (만약, 자식으로 달려있는게 있다면 자식 하나당 1개씩.)



3. Set Pass Call


마테리얼과 쉐이더와 관련된 것에 대한 배치를 말한다.


ex) 마테리얼을 공유하여 사용하는 오브젝트가 1개 있다고 하자.

이때, Batch : 10개, Set Pass Call : 1개

총 Batch : 11개.


ex) 마테리얼이나 쉐이더를 공유하지 않고 따로 사용하는 오브젝트 10개.

이때, Batch : 10개, Set Pass Call : 10개

총 Batch : 20개


이러한 이유 때문에 아틀라스를 활용하여 여러 오브젝트들을 한 개의 Material로 묶어서 Set Pass Call을 줄이는 것이 중요하다.



4. 배칭(Batching)


복수의 드로우 콜을 하나의 드로우 콜로 묶어서 처리하는 작업이다.



※ 동적 배칭 (Dynamic Batching)

 - 동일한 마테리얼을 공유하고, 특정 조건들을 만족했을 때, 유니티에서 자동적으로 일어나는 배칭을 말한다.

 - 정적 배칭에 비해서 조건도 까다롭고 눈에띄는 효율이 아니므로 사실 크게 신경쓰지 않아도 되는 부분이다.

 - 동일한 모델의 모양일 필요가 없다.

 - 큰 효율을 보이는 곳은 파티클과 메쉬쪽이다.


*특정 조건


1. -버텍스 개수가 총 900개 이하의 메쉬만 적용가능.

   - 쉐이더가 정점 위치와 법선이나 다른 UV정보를 사용하면 300이하.

   - 정점위치, 법선, UV0, UV1, 탄젠ㅌ트까지 사용하면 180이하.

   - 해당 제한 값을 넘으면 배칭을 안하는 편이 이득이다.


2. 오브젝트가 Mirror의 Transform 값을 가지고 있으면 함께 배칭 되지 않는다. ex) 스케일 +1, -1

3. 마테리얼의 인스턴스가 다를경우(동일한 마테리얼일 경우도.) 배칭이 이루어 지지 않는다.

4. 동적으로 라이트 매핑된 오브젝트가 동일한 라이트 맵의 위치가 아닌 한 배칭이 이루어 지지 않는다.

5. 멀티 패스 쉐이더는 배칭이 이루어 지지 않는다. ex) 카룬 쉐이더의 아웃라인.

6. Skinned Mesh 사용불가.

7. 리얼타임 쉐도우의 영향을 받는 오브젝트는 배칭이 이루어지지 않는다. (Receive Shadow를 체크 했을 때)



※ 정적 배칭(Static Batching)

 

동일한 마테리얼을 공유하고, 움직이지 않는 오브젝트가 인스펙터창에서 Static을 체크해줄 경우 일어나는 배칭.


- 메쉬를 강제적으로 합쳐서 넘져누는 방식이기 때문에 메모리에 부담을 준다.

너무 규모가 크다면 렌더링 쪽을 희생시키는게 나을 수도 있다.

- 눈에 띄는 효과가 나오기 때문에 CPU파워를 줄일 필요가 있을 경우 꼭 정적 배칭을 사용해야 한다.



※ 드로우 콜이 중요하다면, 모든 배경을 하나로 합쳐 내보내는게 이득일까?


아니다. 배경 중 극히 일부분만이 보일 경우에도 전체를 무조건 그리게 된다.

하지만 모듈화 했을 경우에는 오클루전 컬링과 프러스텀 컬링을 통해 일부 메쉬를 그리지 않게 할 수 있다.

그렇다고 매우 세분화해서 나누면 합치는 과정에서 코스트가 많이 발생하기 때문에 적정한 선에서 시야각에 들어오는

구역별로 나누는 것이 중요하다. 

[Unity3D] UI - HP, MP 에너바 조절하기. [Part 1]

※ 주의 

이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.



Menu

0. 미리보기

1. HP,MP껍데기 및 기능 만들기.

2. HP,MP 조절을 위한 데미지, 리커버리 만들기.




1. HP, MP껍데기 및 기능 만들기.


- 배칭(드로우콜)을 줄이기 위해 유니티에서 제공하는 아틀라스 사용.


텍스쳐 타입 : sprite (2D and UI)
스프라이트 모드 : Multiple

패킹 태그 : UI


위의 셋팅이 끝난 뒤 스프라이트 에디터를 누른다.





Slice를 누면 밑에 창이 나오는데, 그 창에서 Slice를 누르면 이미지들이 자신의 크기에 맞게 잘린다.

이미지가 알맞게 잘렸으면 Apply를 눌러 빠져나온다.



메인 이미지에 붙어있는 ◀단추를 누르면 잘린 이미지들이 자식으로 들어가 있는것을 확인 할 수 있다.

 




※ 다른 이미지가 필요해졌을 때 패킹 태그를 똑같은 이름으로 지정하면 똑같은 스프라이트 팩에 들어가게 된다.



이러한 설정(아틀라스)을 하지 않으면, 캔버스에 이미지를 하나 만들어 쓸때마다 배치(Batches)가 증가하게 된다.

모바일 게임에 경우 이러한 배치수를 100개를 상한선으로 두고 있다.


※ 배치수가 너무 많다면 게임을 원할하게 돌아가게 할 수 없다.


스프라이트 팩에들어있는 이미지는 몇개를 꺼내쓰든 1개의 배치만을 소모한다.




게임창에 Stats를 누르면 배치수를 확인 할수 있는데, SkyBox 배치 1개, 이미지 배치수가 1개가 들어간 것을 볼 수 있다.


※ 사용한 이미지는 총 5개다.



여기서 아틀라스화 하지 않은 이미지를 쓰면 배치는 바로 증가하게 된다.



-------------------------------------------------------------------------------------------------------------------


- HP 및 MP를 눌러 이미지 타입을 Simple에서 Filled로 전환한다.



그 후 

Fill Method : Horizontal

Fill Origin : Left

으로 전환한 뒤


Fill Amount를 마우스로 수동으로 조절해보면 밑의 그림처럼 변환되는것을 확인 할 수 있다.



Fill Amount의 값은 0 ~ 1의 값만을 가지고 있다.


실시간으로 변하는 체력을 표시하고 싶다면 Fill Amount = (현재체력 / 체력의 최대치)로 처리하여 넣으면 된다.



이제 코드로 플레이어의 체력을 변화시켜보도록 하자.

PlayerInfo스크립트를 만들어 플레이어 컴포넌트에 추가한다.


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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; // 이걸 using시켜줘야 Image의 자료형을 쓸수 있다.
 
public class PlayerInfo : MonoBehaviour
{
    // 공개
    public GameObject[] Effect;
    public Image[] Img;          // HP,MP이미지.
 
    public float MAX_HP;         // HP 최대치.
    public float MAX_MP;         // MP 최대치.
    public float p_HP;           // 현재 HP.
    public float p_MP;           // 현재 MP.
 
    // 플레이어 UI정보 갱신.
    // 인자 : (HP냐? MP냐?, 데미지냐? 체력회복이냐?, 데미지 혹은 체력회복의 수치는 얼마만큼이냐?)
    public void UIUpdate(string _Type, string _InfoType, float _Value)
    {
        float Type    = 0;   // HP또는 MP의 현재 수치.
        float MAXType = 0;   // HP또는 MP의 최대치.
        int Index     = 0;   // HP또는 MP의 이미지 인덱스.
 
        switch (_InfoType)
        {
            case "HP":
            {
                Index   = 0;
                Type    = p_HP;
                MAXType = MAX_HP;
 
                // 리커버리면 회복
                if (_Type == "Recover")
                    p_HP += _Value;
                // 리커버리가 아니면 데미지.
                else
                    p_HP -= _Value;
 
                break;
            }
            case "MP":
            {
                Index   = 1;
                Type    = p_MP;
                MAXType = MAX_MP;
 
                if (_Type == "Recover")
                    p_MP += _Value;
                else
                    p_MP -= _Value;
 
                break;
            }
        }
 
        // 인덱스 번째의 이미지를 갱신시킨다.
        // 만약, 변화시켜야 되는 이미지가 HP이고, 체력의 최대치가 100이라고 하자.
        // 20의 데미지를 받아 체력이 80남았다고 하자, 그러면 체력이 80이 남은것을 보여줘야 한다.
        // fillAmount의 값은 0 ~ 1가 끝이기 때문에 체력을 소수점으로 변환해줘야 표현이 가능해진다.
        // 80 / 100 = 0.8 요컨데 퍼센트로 바꿔서 표현한다 생각하면 된다.
        Img[Index].fillAmount = Type / MAXType;
    }
}
 
cs



플레이어의 인스펙터창 정보.


[Unity3D] UI - HP, MP 에너바 조절하기. [Part 0]

※ 주의 

이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.



Menu

0. 미리보기

1. HP,MP껍데기 및 기능 만들기.

2. HP,MP 조절을 위한 데미지, 리커버리 만들기.




0. 미리보기







[Unity3D] 조이스틱으로 캐릭터 조종하기. [Part 2]

※ 주의 

이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.



Menu


1. 조이스틱 만들기.

2. 조이스틱의 벡터를 받아 캐릭터를 회전 및 이동 시키기.





2. 조이스틱의 벡터를 받아 캐릭터를 회전 및 이동 시키기.


필요한 재료.

- 플레이어의 Trsnform 컴포넌트

- 움직임 플래그 값.


조이스틱을 만들었다면 움직이는것은 그다지 어렵지 않다.

조이스틱을 움직이기 위해서 구했던 벡터를 그대로 캐릭터를 회전 시키는데 쓰면 되기 때문이다.


물론 그 벡터를 그대로 갔다 쓰면 안되고 약간의 변경이 필요하다.

아크탄젠트를 사용해서 각도를 바꿔주고 그것을 디그리로 변경하면 되는데,

이러한 함수도 유니티에서 제공해주고 있다.


※ 삼각비,삼각함수,라디안,디그리 등은 블로그내에 게임수학 부분을 참고하기 바란다.




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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
 
public class JoyStick : MonoBehaviour {
 
    // 공개
    public Transform Player;        // 플레이어.
    public Transform Stick;         // 조이스틱.
 
    // 비공개
    private Vector3 StickFirstPos;  // 조이스틱의 처음 위치.
    private Vector3 JoyVec;         // 조이스틱의 벡터(방향)
    private float Radius;           // 조이스틱 배경의 반 지름.
    private bool MoveFlag;          // 플레이어 움직임 스위치.
 
    void Start()
    {
        Radius = GetComponent<RectTransform>().sizeDelta.y * 0.5f;
        StickFirstPos = Stick.transform.position;
 
        // 캔버스 크기에대한 반지름 조절.
        float Can = transform.parent.GetComponent<RectTransform>().localScale.x;
        Radius *= Can;
 
        MoveFlag = false;
    }
 
    void Update()
    {
        if (MoveFlag)
            Player.transform.Translate(Vector3.forward * Time.deltaTime * 10f);
    }
 
    // 드래그
    public void Drag(BaseEventData _Data)
    {
        MoveFlag = true;
        PointerEventData Data = _Data as PointerEventData;
        Vector3 Pos = Data.position;
        
        // 조이스틱을 이동시킬 방향을 구함.(오른쪽,왼쪽,위,아래)
        JoyVec = (Pos - StickFirstPos).normalized;
 
        // 조이스틱의 처음 위치와 현재 내가 터치하고있는 위치의 거리를 구한다.
        float Dis = Vector3.Distance(Pos, StickFirstPos);
        
        // 거리가 반지름보다 작으면 조이스틱을 현재 터치하고 있는 곳으로 이동.
        if (Dis < Radius)
            Stick.position = StickFirstPos + JoyVec * Dis;
        // 거리가 반지름보다 커지면 조이스틱을 반지름의 크기만큼만 이동.
        else
            Stick.position = StickFirstPos + JoyVec * Radius;
 
        Player.eulerAngles = new Vector3(0, Mathf.Atan2(JoyVec.x, JoyVec.y) * Mathf.Rad2Deg, 0);
    }
 
    // 드래그 끝.
    public void DragEnd()
    {
        Stick.position = StickFirstPos; // 스틱을 원래의 위치로.
        JoyVec = Vector3.zero;          // 방향을 0으로.
        MoveFlag = false;
    }
}
 
cs



MoveFlag같은 경우 조이스틱을 드래그 할때만 true로 바뀌고 드래그가 끝난다면 곧바로 false로 돌아간다.

플래그 값이 true인 동안에만 transform.Translate를 통해 캐릭터가 움직인다.


플레이어를 움직이는 구문을 Drag함수 안에 놓으면 드래그 하는동안만 움직이지 않냐고 물을수도 있다.


맞다. 드.래.그 하는 동안 움직인다. 조이스틱을 잡고 끝으로 땡겨놓고 가만히 있으면 조이스틱을 땡기는 동안만 움직이고

가만히 있을때에는 움직이지 않는다. 말 그대로 조이스틱을 잡고 움직이는 동안만 함수가 들어오는 것이다.

아무튼 움직이는 것은 지속적으로 체크가 가능한 Update함수에서 해야한다. (코루틴을 사용해도 좋다.)


플레이어를 어떤 축을 중심으로 회전시켜야 할까?

y축이다. y축을 원하는 방향으로 회전만 시킨다면 플레이어는 그 방향으로 나아가게 될 것이다.

그렇다면 그 방향을 어떻게 구할수 있을까?

바로 조이스틱의 벡터이다. 하지만 이 벡터는 바로 쓸수 없다.

쓰기 위해선 아크탄젠트를 통해 변환하고, 나온 라디안 값을 디그리로 변환해주고 나온 디그리 값으로

플레이어의 y축 회전에 쓰면된다.




아탄을 통해 각a를 구할수 있다.

하지만 이 값은 라디안 값이므로 디그리(각도)로 변환해 주어야 한다.



결과.


 

prev 1 2 3 4 5 6 next