[C#] 값 형식과 참조형식
값 형식 : 변수가 값을 담는 데이터 형식.
참조 형식 : 변수가 값 대신 값이 있는 곳의 위치(참조)를 담는 데이터 형식.
이 둘을 이해하려면 두 가지 메모리 영역에 대해 알고있어야 한다.
바로 스택(Stack)과 힙(Heap)이다.
이 메모리 영역 중 값 형식과 관련이 있는 것은 스택 메모리 영역이고,
참조 형식과 관련 있는 것은 힙 메모리 영역이다.
1. 스택과 값 형식.
스택에 대해 이야기 해보자.
스택의 구조는 마치 책상 위에 쌓인 책이라고 생각하면 된다.
맨 밑에 있는 책을 읽고 싶다면, 위에 있는 책들을 모두 걷어낸 후에나 가능할 것이다.
스택 메모리도 이렇게 동작한다. 다음과 같은 코드를 작성했다고 하자.
1 2 3 4 5 | { // 코드 블록 시작 int a = 10; int b = 20; int c = 30; } // 코드 블록 끝 | cs |
이렇게 선언된 세 변수 a,b,c는 차례대로 스택에 쌓였다가 코드 블록이 끝나면서 스택에서 걷혀 제거된다.
※ 블록을 닫는 괄호 "}"을 만나는 순간 스택에 있는 데이터들이 c,b,a의 순서대로 걷혀진다.
값 형식의 변수는 모두 이 스택에 저장된다.
다시 말해 코드 블록 안에서 생성된 모든 값 형식의 변수들은 중괄호 "}"을 만나면 메모리에서 제거된다.
2. 힙과 참조 형식
메모리 관리가 깔끔한 스택에 반해 힙은 데이터를 스스로 제거할 수 있는 매커니즘을 가지고 있지 않다.
대신 청소부 같은 존재를 고용하고 있는데, 그 청소부의 이름은 가비지 컬렉터(Garbage Collector)이다.
힙에 더 이상 사용하지 않는 객체가 있으면 그 객체를 쓰레기로 간주하고 수거해 가는 기능을 가지고 있다.
그렇다면 왜 굳이 가비지 컬렉터가 필요한 힙 영역을 사용하는 걸까?
스택에 쌓인 데이터들은 코드 블록이 사라지는 시점에서 함께 제거된다.
이것은 스택에 장점이기도 하지만, 동시에 한계이기도 하다.
코드 블록이 끝나는 시점과 상관없이 데이터를 유지하고 싶을 때는 스택의 구조가 발목을 잡는 요소가 된다.
그래서 또 다른 메모리 영역인 힙을 제공하는 것이다.
힙은 코드 블록이 종료되는 것과 관계없이 그 데이터를 게속 유지할 수 있다.
그리고 이 데이터는 프로그래머가 더 이상 사용하지 않을 때가 됐을 때 가비지 컬렉터가 수거해 제거 한다.
참조 형식의 변수는 힙과 스택을 함께 사용하는데, 힙 영역에는 데이터를 저장하고, 스택 영역에는 데이터가 저장된
힙의 메모리의 주소를 저장한다. 그래서 참조 형식이라는 이름이 붙은 것이다.
참조 형식으로 변수를 선언해보자.
1 2 3 4 | { object a = 10; object b = 20; } | cs |
실제 값 10과 20은 힙 영역에 저장하고, a와 b는 값이 저장된 힙의 주소만 스택에 저장하게 된다.
블록이 끝난 시점에서 스택의 값은 사라진다. 하지만 힙에 남은 값을 사라지지 않는다.
힙의 남은 값은 더 이상 데이터를 참조하는 곳이 없을 때 가비지 컬렉터가 수거해 간다.
'C#' 카테고리의 다른 글
[C#] 메소드 오버로딩, 가변길이 매개 변수, 선택적 매개 변수 (0) | 2017.03.25 |
---|---|
[C#] 참조에 의한 매개 변수 전달. ref / 출력 전용 매개 변수. out (0) | 2017.03.24 |
[C#] 공용 형식 시스템. (0) | 2017.03.24 |
[C#] var형식 (0) | 2017.03.24 |
[C#] Object형식, 박싱과 언박싱. (1) | 2017.03.23 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 2]
※ 주의
이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.
Menu
0. 미리보기
1. 오브젝트를 미리 생성.
- 프리팹 제작.
- 싱글톤 제작.
- 오브젝트를 생성시킬 함수 제작.
- 오브젝트를 찾을 함수를 제작.
2. 만들어진 오브젝트 활용하기.
2. 만들어진 오브젝트 활용하기.
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 == null) return; _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 > Project' 카테고리의 다른 글
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 4] (0) | 2017.03.28 |
---|---|
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 3] (0) | 2017.03.27 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 1] (0) | 2017.03.20 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 0] (0) | 2017.03.20 |
[Unity3D] UI - HP, MP 에너바 조절하기. [Part 2] (1) | 2017.03.17 |
[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; } } |
빈 오브젝트를 생성하여 이름을 ObjectManager로 변경한다.
그뒤 만들어 두었던 ObjManager스크립트를 ObjectManager 객체에 드래그하여 컴포넌트를 생성한다.
Obj Manager스크립트 - Origin배열에 사이즈를 1로 늘리고, Bullet프리팹을 드래그하여 넣는다.
이 스크립트에는 싱글톤을 제외하고 몇 가지 기능을 가진 함수들이 있다.
1. 오브젝트를 생성시켜주는 함수.
2. 필요한 오브젝트를 찾아 반환시켜주는 함수.
3. 메모리를 삭제해주는 함수.
2번의 함수는 리스트내의 객체의 이름을 검색, 비교하여 필요한 오브젝트를 찾아주는데,
만약 필요한 객체가 없거나 모두 사용중이면 새롭게 생성해 그 객체를 반환해주는 역할을 수행하고 있다.
결과.
총알이 여러개 생성되었다.
'Unity3D > Project' 카테고리의 다른 글
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 3] (0) | 2017.03.27 |
---|---|
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 2] (0) | 2017.03.21 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 0] (0) | 2017.03.20 |
[Unity3D] UI - HP, MP 에너바 조절하기. [Part 2] (1) | 2017.03.17 |
[Unity3D] UI - HP, MP 에너바 조절하기. [Part 1] (1) | 2017.03.16 |