[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 == null) return;
_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(3, 7);
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 > Project' 카테고리의 다른 글
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 5] (0) | 2017.04.03 |
---|---|
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 4] (0) | 2017.03.28 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 2] (0) | 2017.03.21 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 1] (0) | 2017.03.20 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 0] (0) | 2017.03.20 |
[C#] 메소드 오버로딩, 가변길이 매개 변수, 선택적 매개 변수
1. 메소드 오버로딩
하나의 메소드 이름에 여러 개의 구현을 올리는 것.
1 2 3 4 | int Plus(int a, int b) { return a + b; } | cs |
int형식의 매개 변수와 반환 형식을 가진 메소드가 있다고 해보자.
그런데, double형식을 지원하는 버전도 추가해야되는 상황이 되었다.
그러면 PlusDouble()이라는 메소드를 새로 만들어줘야 할까?
아니다. 이 때 메소드 오버로딩을 사용하면 Plus()라는 이름을 그대로 사용할 수 있다.
하나의 메소드 이름에 여러 개의 구현을 올리는 메소드 오버로딩이다.
1 2 3 4 5 6 7 8 9 | int Plus(int a, int b) { return a + b; } double Plus(double a, double b) { return a + b; } | cs |
이렇게 오버로딩 해놓으면 컴파일러가 메소드를 호출했을 때,
매개 변수의 수와 형식을 분석해서 어떤 메소드를 호출해야 되는지 찾아준다.
이렇게, 메소드 오버로딩은 이름에 대한 고민을 줄여주는 동시에 코드에 일관성을 유지해준다.
※ 유의사항
- 반환형을 다르게 한다.
- 매개변수의 자료형을 다르게 한다.
- 매개변수의 숫자를 다르게 한다.
2. 가변길이 매개 변수
그저 매개 변수의 "수"가 다르다는 이유만으로 똑같은 메소드를 여러가지 버전으로 오버로딩 해야 할 때가 있다.
이런 경우를 위해 C#은 "가변길이 매개 변수"라는 기능을 제공한다.
가변길이 매개 변수란, 그 개수가 유연하게 변할 수 있는 매개 변수를 말한다.
1 2 3 4 5 6 7 8 | int result = 0; result = Sum(1,2); result = Sum(1,2,3); result = Sum(1,2,3,4); result = Sum(1,2,3,4,5); // ... | cs |
가변길이 매개 변수는 params 키워드와 배열을 이용해서 선언한다.
1 2 3 4 5 6 7 8 9 10 11 12 | int Sum( params int[] array) { int Sum = 0; int Count = array.Length; for(int i = 0; i < Count; i++) { Sum += array[i]; } return Sum; } | cs |
"이게 있으면 메소드 오버로딩은 필요 없네?" 라고 생각할수 있을것이다.
하지만, 매개 변수의 개수가 유한하게 정해져 있다면 가변길이 매개 변수보다는 메소드 오버로딩을 사용하는 것이 적절하다.
3. 선택적 매개 변수
메소드의 매개 변수는 기본값을 가질 수 있다.
매개 변수를 특정 값으로 초기화 하듯 메소드를 선언할 수 있다는 것이다.
1 2 3 4 | void MyMethod( int a = 0, int b = 0) { Console.WriteLine("{0}, {1}", a, b); } | cs |
결과 : 0, 0
위와같이 기본값을 가지는 매개 변수는 메소드를 호출할 때 데이터 할당을 생략할 수 있다.
1 | MyMethod( 3 ); | cs |
결과 : 3
기본값을 가지는 매개 변수는 필요에 따라 데이터를 할당하거나 할당하지 않을 수 있기 때문에 이를
"선택적 매개 변수"라고 한다.
※ 유의사항
- 선택적 매개 변수는 항상 필수 매개 변수 뒤에 와야 한다.
- 선택적 매개 변수는 메소드 오버로딩과 함께 사용하면 혼란이 야기될수 있으므로
함께 사용하는 것을 지양한다.
'C#' 카테고리의 다른 글
[C#] interface (0) | 2017.04.03 |
---|---|
[C#] 참조에 의한 매개 변수 전달. ref / 출력 전용 매개 변수. out (0) | 2017.03.24 |
[C#] 공용 형식 시스템. (0) | 2017.03.24 |
[C#] var형식 (0) | 2017.03.24 |
[C#] Object형식, 박싱과 언박싱. (1) | 2017.03.23 |
[C#] 참조에 의한 매개 변수 전달. ref / 출력 전용 매개 변수. out
이런 코드가 있다고 하자.
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; namespace SwapByValue { class MainApp { public static void Swap(int a, int b) { int temp = b; b = a; a = temp; } static void Main(string[] args) { int x = 3; int y = 4; Console.WriteLine("x:{0}, y:{1}",x,y); // 결과 : x:3,y:4 Swap(x,y); Console.WriteLine("x:{0}, y:{1}",x,y); // 결과 : x:3,y:4 } } } | cs |
우리는 Swap메소드를 사용하므로써 x와 y의 값이 교환 되는 결과를 바라고 있었을 것이다.
그런데 위의 코드에서는 값이 교환되지 않았다.
그럼 x랑 y값에 변환를 주기 위해선 어떻게 해야할까?
바로 ref키워드를 사용하면 된다.
ref키워드를 매개 변수 앞에 붙여주면 된다.
1. 참조에 의한 매개변수 전달.
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; namespace SwapByValue { class MainApp { public static void Swap(ref int a, ref int b) { int temp = b; b = a; a = temp; } static void Main(string[] args) { int x = 3; int y = 4; Console.WriteLine("x:{0}, y:{1}",x,y); // 결과 : x:3,y:4 Swap(ref x, ref y); Console.WriteLine("x:{0}, y:{1}",x,y); // 결과 : x:4,y:3 } } } | 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 | using System; namespace SwapByValue { class MainApp { public static void Divide(int a, int b, ref int quotient, ref int remainder) { quotient = a / b; remainder = a % b; } static void Main(string[] args) { int a = 20; int b = 3; int c = 0; int d = 0; Divide(a, b, ref c, ref d); Console.WriteLine("a:{0}, b:{1}:, a/b:{2}, a%b:{3}", a,b,c,d); // 결과 a:20, b:3, a/b:6, a%b:2 } } } | cs |
2. 출력 전용 매개 변수.
ref을 이용하여 메소드로 부터 여러개의 결과를 얻어올 수 있지만,
C#은 조금 더 안전한 방법으로 똑같은 일을 할수 있게 했다.
out 키워드를 사용한 것이다.
위의 예제 코드에서 ref키워드를 out키워드로 바꿔 사용하면 똑같은 결과를 도출할 수 있다.
그럼 똑같은거 아닌가? 라고 생각할 수 있지만, 그렇지 않다.
out은 ref에게는 없는 안전장치가 있다.
예를들어 ref키워드를 이용해 매개 변수를 넘기는 경우 메소드가 해당 매개 변수에 결과를 저장하지 않아도
컴파일은 아무런 경고를 하지 않는다.
out키워드는 메소드가 해당 매개변수에 결과를 저장하지 않으면 에러 메세지를 출력한다.
물론 초기화 되지 않은 매개변수를 넘기는 것은 가능하다.
왜냐하면 컴파일러가 호출당하는 메소드에서 그 지역 변수에 할당할 것을 보장하기 때문이다.
이렇게 버그를 만들 가능성을 제거할 수 있다면 우리는 그 방법을 사용해야 한다.
'C#' 카테고리의 다른 글
[C#] interface (0) | 2017.04.03 |
---|---|
[C#] 메소드 오버로딩, 가변길이 매개 변수, 선택적 매개 변수 (0) | 2017.03.25 |
[C#] 공용 형식 시스템. (0) | 2017.03.24 |
[C#] var형식 (0) | 2017.03.24 |
[C#] Object형식, 박싱과 언박싱. (1) | 2017.03.23 |