'Unity3D'에 해당되는 글 52건
- 2017.04.25 [Unity3D] 셰이더 및 물리 기반 셰이딩
- 2017.04.24 [Unity3D] 인벤토리 [Part 4] - Load 3
- 2017.04.23 [Unity3D] 인벤토리 [Part 4] - Save 3
- 2017.04.21 [Unity3D] Sprite 동적할당.
- 2017.04.13 [Unity3D] 인벤토리 [Part 3] - Item Drag & Swap 5
- 2017.04.12 [Unity3D] 인벤토리 [Part 2] - Item Add 13
- 2017.04.07 [Unity3D] 인벤토리 [Part 1] - Inventory UI 5
- 2017.04.06 [Unity3D] 인벤토리 [Part 0]
- 2017.04.05 [Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 5] 추가사항 2
- 2017.04.03 [Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 5]
[Unity3D] 셰이더 및 물리 기반 셰이딩
셰이더는 머테리얼에 적용한 텍스처를 렌더링할 때 표면의 재질감을 표현하는 방식을 결정한다.
셰이더 프로그래밍은 DirectX, OpenGL에 따라 서로 다른 언어로 구현되고, 3D그래픽스의 이론적인 배경을 많이 알아야 할 정도로 깊의 있는 지식을 요구하는 분야이기도 하다.
유니티는 셰이더 프로그래밍 지식이나 경험이 없더라도 손쉽게 사용할 수 있게 내장 셰이더(Built in Shader)를 제공한다.
유니티 5에서 처음 선보인 물리 기반 셰이딩(PBS, Physically-Based-Shading)은 물체가 갖고 있는 고유의 특성에 맞게 재질을 표현하는 것으로 현실세계에 존재하는 돌, 나무, 유리, 플라스틱, 금속 등과 같은 재질감을 하나의 셰이더에서 표현할 수 있도록 설계돼 있다.
유니티 5의 기본 셰이더인 Standard 셰이더는 물리 기반 셰이딩을 위해 다양한 옵션을 제공한다.
1. 렌더링 모드 (Rendering Mode)
2. 알베도 (Albedo)
3. 메탈릭 속성 (Metallic)
4. 노멀 맵 (Normal Map)
5. 하이트 맵 (Height Map)
6. 오클루전 (Occlusion)
7. 이미션 (Emission)
8. 디테일 마스크 (Detail Mask)
1. 렌더링 모드
렌더링 모드는
- 불투명 (Opaque)
- 그물망 표현 (Cutout)
- 홀로그램 효과 (Fade)
- 투명 (Transparent)
의 네가지 설정을 통해 다양한 재질감을 표현할 수 있다.
불투명 (Opaque) |
기본값으로 불투명한 텍스처를 표현하는 옵션이다. 투명한 부분이 전혀없는 Solid객체에 적합. |
그물망 표현 (Cutout) |
불 투명한 부분과 투명한 부분을 동시에 표현하는 옵션. 주로 풀, 그물망 등을 표현. |
투명 (Transparent) | 투명한 플라스틱 또는 유리와 같은 재질을 표현하는 옵션. |
홀로그램 효과 (Fade) | 투명 속성값을 갖고 있는 객체를 페이드 아웃 시키는 옵션. 불 투명한 객체를 부분적으로 페이드 아웃시킬 수 있어 홀로그램 효과를 구현할 수 있다. |
2. 알베도
알베도는 빛을 반사하는 정도를 말하며, 반사율이라고도 한다.
유니티 5에 도입된 물리 기반 셰이딩을 구현하기 위해 추가된 텍스처다.
즉, 현실세계에 있는 모든 물체는 각각 다른 빛 반사율을 갖고 있는 것에서 비롯됐다.
3. 메탈릭 속성
메탈릭은 객체 표면에 금속의 재질을 표현하기 위한 텍스처이다.
이 텍스처 슬롯 옆에 있는 슬라이드 값이 1에 가까워 질수록 금속 재질에 가까워 지는 특성이 있다.
4. 노멀 맵
노멀 맵은 표면의 굴곡을 표현하기 위한 텍스처의 일종으로 3D 모델링으로 많은 폴리곤을 소모하지 않고 같은 효과를 낼 수 있다. 노멀 맵 텍스처 슬롯 오른쪽에 있는 속성 값이 커질 수록 거친 음영효과를 낸다.
5. 하이트 맵
하이트 맵은 텍스처로 높 낮이를 표현하는 것으로 보통 지형(Terraun)을 표시할 때 사용된다.
텍스처가 연결되면 슬라이드가 표시되고 돌출되는 높이를 설정할 수 있다.
6. 오클루전
오클루전은 흑백의 텍스처로 간접조명에 의해 생기는 명암을 더욱 뚜렷히 표시해 사물의 입체감과 깊이감을 살리는데 사용한다.
오클루전 맵에 사용될 텍스처는 일반적으로 3D 모델링 툴 또는 서드 파티 툴에서 추출한다.
7. 이미션
이미션은 스스로 빛을 방출하는 속성을 말한다.
속성값을 변경하면 객체의 표면에서 방출되는 빛의 강도와 빛의 색상을 설정할 수 있는 항목이 나타난다.
또한 하단에 저녁조명에 반영하기 위한 옵션이 나타난다.
8. 디테일 마스크
디테일 마스크는 Secondary Maps에 적용할 마스크를 설정하는 텍스처 슬롯이다.
특정 부분만 좀 더 세부적인 텍스처를 표현할 때 사용된다.
'Unity3D > Tip' 카테고리의 다른 글
[Unity3D] AddExplosionForce(); - 폭발 시 여파 적용. (0) | 2017.04.27 |
---|---|
[Unity3D] 일정 범위내의 존재하는 Collider 객체를 추출. (0) | 2017.04.27 |
[Unity3D] 비균등스케일 (non-uniform scale) (0) | 2017.04.27 |
[Unity3D] Sprite 동적할당. (0) | 2017.04.21 |
[Unity3D] 배칭 & 드로우콜 (0) | 2017.03.16 |
[Unity3D] 인벤토리 [Part 4] - Load
※ 주의
이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.
Menu
1. 인벤토리 껍데기 만들기.
- 인벤토리의 Pivot 설정.
- 슬롯의 Pivot 설정.
- 슬롯 사이즈 설정.
- 슬롯 간 거리 설정.
- 슬롯의 가로, 세로 개수 설정.
- 인벤토리의 가로 사이즈, 세로 사이즈 설정.
- 슬롯 생성 및 부모설정.
- 모든 슬롯을 관리해줄 리스트를 생성.
2. 아이템 획득 시 검사조건 만들기 및 슬롯 기능 만들기.
- 아이템을 먹었을 때, 인벤토리 내의 슬롯을 모두 검사한다.
*검사조건 : -> 슬롯 내에 같은 아이템이 존재하는지?
-> 슬롯내에 같은 아이템이 존재할 경우 겹칠수 있는지?
-> 슬롯내에 같은 아이템이 존재하지만, 겹칠수 없는경우 빈 슬롯이 존재 하는지?
- 슬롯을 스택으로 만들어 아이템 관리하기
->아이템이 슬롯에 들어갔을때 이미지 변경.
->아이템을 겹칠경우 텍스트 갱신.
->아이템을 사용할 경우 텍스트 갱신.
->아이템을 모두 사용했을 때 이미지 변경.
3. 아이템 만들기 및 획득
- 아이템 타입 설정.
- 아이템의 이미지 설정.
- 아이템 겹칠수 있는 최대치 설정.
- 아이템과의 충돌로 아이템 획득하기.
4. 인벤토리내에 아이템 드래그 앤 드랍으로 아이템 옮기기 및 자리 바꾸기.
- 처음 마우스 버튼을 누를 때
- 누르고 이동할 때
- 드래그가 끝났을 때
- 누른 버튼을 땠을 때
의 4가지 상태로 나눠 드래그 앤 드랍 구현.
5. XML문서를 이용한 인벤토리 아이템 저장.
- Save
- Load
5. XML문서를 이용한 인벤토리 아이템 저장. - Load
우선 흐름부터 살펴보도록 하자.
1. InventoryData.xml이라는 파일이 존재하는지 확인한다.
=> 파일이 존재하지 않으면 함수종료.
=>System.IO.File.Exists(경로) : 이 함수는 경로내의 파일이 존재하면 true 존재하지 않으면 false를 되돌려주는 함수이다.
2. xml문서를 하나 만든다.
3. 만든 xml문서에 InventoryData.xml을 불러온다.
4. 요소를 하나 만들어서 불러온 xml파일로 초기화 시킨다.
5. 필드를 하나 만들어서 InventoryData.xml문서의 최상위에 있는 필드로 초기화 시킨다.
6. 필드의 존재하는 슬롯번호를 하나 꺼내와서 슬롯의 n번째 번호를 가져와, 그 번호를 이용하여 슬롯의 'Slot'스크립트를 꺼내온다.
7. 아이템 클래스로 아이템 객체를 하나 만든다.
8. 필드에서 Name, MaxCount를 꺼내와서 새로 생성된 아이템의 내용을 초기화 시킨다.
Item Script 추가사항.
9. 필드에서 Count를 꺼내와서 Count의 수만큼 반복문을 돌린다.
=> 위에서 6에서 가져왔던 Slot스크립트를 이용하여 이 슬롯에 아이템을 AddItem()을 호출하여 집어넣는다.
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 | public static List<GameObject> Load(List<GameObject> SlotList) { if (!System.IO.File.Exists(Application.dataPath + "/Save/InventoryData.xml")) return SlotList; XmlDocument XmlDoc = new XmlDocument(); // 문서를 만듬. XmlDoc.Load(Application.dataPath + "/Save/InventoryData.xml"); // 경로상의 XML파일을 로드 XmlElement Xmlel = XmlDoc["ItemDB"]; // 속성 ItemDB에 접속. foreach (XmlElement ItemElement in Xmlel.ChildNodes) { // 슬롯의 n번째 스크립트를 가져온다. Slot slot = SlotList[System.Convert.ToInt32(ItemElement.GetAttribute("SlotNumber"))].GetComponent<Slot>(); // 아이템 생성. Item item = new Item(); // 아이템의 정보를 셋팅한다. string Name = ItemElement.GetAttribute("Name"); // 아이템 이름을 가져옴. int MaxCount = System.Convert.ToInt32(ItemElement.GetAttribute("MaxCount")); // 겹칠수 있는 한계. item.Init(Name, MaxCount); // 위의 가져온 정보를 토대로 아이템의 정보를 초기화. int Count = System.Convert.ToInt32(ItemElement.GetAttribute("Count")); // 슬롯에 아이템을 n개 집어넣기 위해서 개수를 가져옴. for (int i = 0; i < Count; i++) slot.AddItem(item); } return SlotList; } | cs |
'Inventory' 스크립트에 Awake()에서는 인벤토리와 슬롯이 만들어진다.
슬롯이 만들어지게 되면 슬롯객체 안에 존재하는 'Slot' 스크립트의 Start()가 수행되는데,
이 Start()의 수행이 끝난뒤 우리가 저장한 정보를 Load해야한다.
만약 Inventory 스크립트의 Awake()가 수행되기전에 Load()를 수행하면 아직 만들어지지도 않은 슬롯에
정보를 할당하려 했으므로 에러를 내뿜게 된다.
마찬가지로 Slot 스크립트의 Start()가 수행되기전에 Load()를 수행한다면 슬롯의 아이템을 담아놓는 Stack자체가
생성되지 않았기 때문에 에러를 내뿜게 된다.
그렇기 때문에 모든 슬롯의 Start()가 끝나게 되면 Load를 수행하면 된다.
하지만 모든 슬롯의 Start()의 수행이 언제 끝날지는 알 수 없으므로 사용자가 임의로 시작을 지연시키고 그 뒤 정보를 Load시킨다. 불안정한 방법이지만 Awake() 및 Start() 수행속도는 빠른 편이므로 시간을 아주 조금만 지연시켜주면 된다.
Inventory스크립트에 Awake()안에 Invoke를 이용하여 Init()함수를 호출해주고, 시간을 0.01f초 지연시키도록 하자.
1 2 3 4 5 6 | Invoke("Init", 0.01f); void Init() { ItemIO.Load(AllSlot); } | cs |
'Unity3D > Project' 카테고리의 다른 글
[Unity3D] 헥사게임 만들기 두 번째 - 블럭보드 만들기. (0) | 2018.02.13 |
---|---|
[Unity3D] 헥사게임 만들기 첫 번째 - 리소스 준비하기 및 캔버스 크기 설정. (0) | 2018.02.11 |
[Unity3D] 인벤토리 [Part 4] - Save (3) | 2017.04.23 |
[Unity3D] 인벤토리 [Part 3] - Item Drag & Swap (5) | 2017.04.13 |
[Unity3D] 인벤토리 [Part 2] - Item Add (13) | 2017.04.12 |
[Unity3D] 인벤토리 [Part 4] - Save
※ 주의
이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.
Menu
1. 인벤토리 껍데기 만들기.
- 인벤토리의 Pivot 설정.
- 슬롯의 Pivot 설정.
- 슬롯 사이즈 설정.
- 슬롯 간 거리 설정.
- 슬롯의 가로, 세로 개수 설정.
- 인벤토리의 가로 사이즈, 세로 사이즈 설정.
- 슬롯 생성 및 부모설정.
- 모든 슬롯을 관리해줄 리스트를 생성.
2. 아이템 획득 시 검사조건 만들기 및 슬롯 기능 만들기.
- 아이템을 먹었을 때, 인벤토리 내의 슬롯을 모두 검사한다.
*검사조건 : -> 슬롯 내에 같은 아이템이 존재하는지?
-> 슬롯내에 같은 아이템이 존재할 경우 겹칠수 있는지?
-> 슬롯내에 같은 아이템이 존재하지만, 겹칠수 없는경우 빈 슬롯이 존재 하는지?
- 슬롯을 스택으로 만들어 아이템 관리하기
->아이템이 슬롯에 들어갔을때 이미지 변경.
->아이템을 겹칠경우 텍스트 갱신.
->아이템을 사용할 경우 텍스트 갱신.
->아이템을 모두 사용했을 때 이미지 변경.
3. 아이템 만들기 및 획득
- 아이템 타입 설정.
- 아이템의 이미지 설정.
- 아이템 겹칠수 있는 최대치 설정.
- 아이템과의 충돌로 아이템 획득하기.
4. 인벤토리내에 아이템 드래그 앤 드랍으로 아이템 옮기기 및 자리 바꾸기.
- 처음 마우스 버튼을 누를 때
- 누르고 이동할 때
- 드래그가 끝났을 때
- 누른 버튼을 땠을 때
의 4가지 상태로 나눠 드래그 앤 드랍 구현.
5. XML문서를 이용한 인벤토리 아이템 저장.
- Save
- Load
미리보기
5. XML문서를 이용한 인벤토리 아이템 저장. - Save
인벤토리에 들어있는 아이템의 정보를 XML문서에 Save하여,
게임 시작 시 XML문서를 토대로 정보를 Load하려고 한다.
시작하기 전에 Save및 Load의 호출를 어디에서 할 것인가 부터 살펴보자.
Save는 인벤토리 내에 아이템에 대한 정보변환이 일어났을 때 수행해야한다.
그렇다면 정보변환는 언제 일어나는 것일까?
- 아이템을 먹었을 때
- 아이템을 드래그하여 다른 슬롯으로 옮겼을 때.
- 아이템 끼리 Swap했을 때.
이 모든 변화가 일어났을 때 Save는 호출되어야 한다.
우리가 만든 함수 중 위의 변화가 일어났을 때
어김없이 호출되는 함수가 하나 있다.
'Slot' 스크립트에 존재하는 UpdateInfo() 함수이다.
이 함수는 위의 모든 상황에서 호출되는 함수다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 슬롯에 대한 각종 정보 업데이트. public void UpdateInfo(bool isSlot, Sprite sprite) { this.isSlot = isSlot; transform.GetChild(0).GetComponent<Image>().sprite = sprite; if (slot.Count > 1) text.text = slot.Count.ToString(); else text.text = ""; ItemIO.SaveDate(); } | cs |
※ 이렇게 맨 밑에다 Save를 호출하면 된다.
그렇다면 Load는 언제 호출되어야 할까?
인벤토리가 만들어지기 전에 Load가 호출되면 안된다.
인벤토리가 생성되고.
슬롯이 생성되고(Inventory 스크립트의 Awake()함수의 호출).
슬롯객체에 붙어있는 Slot 스크립트의 Start()가 호출이 끝난 뒤.
Load가 호출되어야 한다.
Save함수를 호출할 때 아이템에대한 정보가 XML문서로 만들어지게 된다.
이 스크립트를 이해하기 위해서는 우리는 XML문서가 어떻게 구성되어 있는지 파악할 필요가 있다.
정보가 저장된 XML문서의 구성은 이렇다.
1 2 3 4 5 6 7 8 9 10 11 | <ItemDB> <Item SlotNumber="1" Name="MP" Count="3" MaxCount="3" /> <Item SlotNumber="2" Name="MP" Count="3" MaxCount="3" /> <Item SlotNumber="4" Name="HP" Count="3" MaxCount="3" /> <Item SlotNumber="5" Name="MP" Count="3" MaxCount="3" /> <Item SlotNumber="6" Name="MP" Count="3" MaxCount="3" /> <Item SlotNumber="8" Name="HP" Count="3" MaxCount="3" /> <Item SlotNumber="9" Name="HP" Count="3" MaxCount="3" /> <Item SlotNumber="17" Name="HP" Count="1" MaxCount="3" /> <Item SlotNumber="18" Name="MP" Count="1" MaxCount="3" /> </ItemDB> | cs |
<요소>
<필드이름 정보1 정보2 정보3 정보4.../>
<필드이름 정보1 정보2 정보3 정보4.../>
</요소>
이런식으로 구성이 되어있는데, 이것을 스크립트로 만들어 내보낼 것이다.
XML문서를 만드는 스크립트의 흐름은 이렇다.
1. XML문서를 하나 만든다.
2. 요소를 하나 만든다.
3. 만든 요소를 XML문서에 첨부한다.
여기까지 코드를 짜면 XML문서는
---------------------------------
<요소>
</요소>
---------------------------------
까지 생성이 된다.
4. 필드(요소)를 생성.
5. 필드(요소)의 내용을 셋팅.
6. 첫 번째 요소의 방금만든 필드(요소)를 첨부.
까지 하면 위의 완성된 XML문서가 완성되며,
마지막으로 XML를 정해진 경로로 내보내면 저장은 끝난다.
코드를 한 번 보도록 하자.
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; using System.Xml; // sealed을 사용하여 다른 클래스가 상속되지 못하도록 함. public sealed class ItemIO { public static void SaveDate() { // 인벤토리에서 슬롯을 관리해주는 리스트를 받아온다. List<GameObject> item = ObjManager.Call().IV.AllSlot; XmlDocument XmlDoc = new XmlDocument(); // XML문서 생성. XmlElement XmlEl = XmlDoc.CreateElement("ItemDB"); // 요소 생성. XmlDoc.AppendChild(XmlEl); // 요소를 XML문서에 첨부. // 리스트의 총 크기(슬롯의 개수.) int Count = item.Count; for (int i = 0; i < Count; i++) { // 슬롯 리스트에서 슬롯을 하나씩 꺼내온다. Slot itemInfo = item[i].GetComponent<Slot>(); // 슬롯이 비어있으면 저장할 필요가 없으므로 넘긴다. if (!itemInfo.isSlots()) continue; // 필드(요소)를 생성. XmlElement ElementSetting = XmlDoc.CreateElement("Item"); // 필드(요소)의 내용을 셋팅. ElementSetting.SetAttribute("SlotNumber", i.ToString()); // n번째 슬롯에 아이템. ElementSetting.SetAttribute("Name", itemInfo.ItemReturn().Name); // 아이템의 이름. ElementSetting.SetAttribute("Count", itemInfo.slot.Count.ToString()); // 아이템의 개수. (ex: 현 슬롯에 겹쳐진 아이템 10개임.) ElementSetting.SetAttribute("MaxCount", itemInfo.ItemReturn().MaxCount.ToString()); // 아이템을 겹칠수 있는 한계. XmlEl.AppendChild(ElementSetting); // ItemDB요소에 위의 셋팅한 요소를 문서에 첨부. } // XML문서로 내보낸다. 인자로는 문서를 내보낼 경로. XmlDoc.Save(Application.dataPath + "/Save/InventoryData.xml"); } } | cs |
1 | using System.Xml; | cs |
필수는 아니지만 각 함수를 사용할 때 System.Xml을 쓰는건 효율이 떨어지므로 using해둡시다.
1 | XmlDocument XmlDoc = new XmlDocument(); | cs |
Document는 문서라는 뜻으로 XML문서를 만듭니다.
1 | XmlElement XmlEl = XmlDoc.CreateElement("ItemDB"); | cs |
Element는 요소,성분이라는 뜻으로 새로운 요소를 인자의 이름으로 만든다.
이 구문에서 XML문서의 <ItemDB> </ItemDB>가 완성된다.
1 | XmlDoc.AppendChild(XmlEl); | cs |
Append는 첨부라는 뜻으로 요소를 XML문서에 첨부하는데 사용한다.
쭉 보면
1. XML문서를 만들고
2. <ItemDB> </ItemDB>라는 요소를 만들어서
3. XML문서에 첨부.
라는 것이 된다.
그 다음 for문을 돌려 필드를 생성시켜야 하는데, 이것은 무엇에 대한 for문일까?
우리는 인벤토리에 각 슬롯이 가지고있는 아이템에 대한 정보를 저장하는 것이다
만약 슬롯이 30개 존재한다고 하면 for문을 30개 돌아 각 슬롯이 가진 정보를 필드에 옮겨 저장하는 것이다.
슬롯 개수에 대한 정보는 인벤토리 스크립트가 가지고 있으므로 가져와서
슬롯의 숫자만큼 for문을 돌면 된다.
for문을 돌때 슬롯에 아무것도 존재하지 않는다면 저장할 필요가 없으므로 continue해준다.
여기서 우리가 저장할 내용은 이렇다.
1. 슬롯의 번호
2. 아이템의 이름.
3. 아이템의 개수.
4. 아이템을 겹칠수 있는 한계 개수.
이렇게 4개에 대한 정보를 저장해야 한다.
슬롯의 번호를 저장하는 이유는
아이템이 2번째 칸에 있고, 게임을 껏다 켰을 때 그 아이템은 여전히 2번째 칸에 존재 해야한다.
Load할때 슬롯의 번호를 모르고 있다면 몇번째 슬롯에 아이템정보를 가져와야할지 알지 못하기 때문이다.
아무튼
for문을 돌면서
1 | XmlElement ElementSetting = XmlDoc.CreateElement("Item"); | cs |
또 하나의 필드(요소)를 생성한다. 이름은 "Item"이다.
이 필드(요소)에 위의 4가지 정보를 셋팅해준다.
1 2 3 4 | ElementSetting.SetAttribute("SlotNumber", i.ToString()); // n번째 슬롯에 아이템. ElementSetting.SetAttribute("Name", itemInfo.ItemReturn().Name); // 아이템의 이름. ElementSetting.SetAttribute("Count", itemInfo.slot.Count.ToString()); // 아이템의 개수. (ex: 현 슬롯에 겹쳐진 아이템 10개임.) ElementSetting.SetAttribute("MaxCount", itemInfo.ItemReturn().MaxCount.ToString()); // 아이템을 겹칠수 있는 한계. | cs |
※ 모두 문자열로 저장시켜야 한다.
Item이라는 필드에 위의 4가지 정보가 추가되면
이 추가된 정보를 첫 번째 요소인 "ItemDB"에 첨부시켜야 한다.
1 | XmlEl.AppendChild(ElementSetting); | cs |
이렇게 기본적인 XML문서가 완성된다.
이제 이 XML문서를 내보내면 저장이 되는 것이다.
1 | XmlDoc.Save(Application.dataPath + "/Save/InventoryData.xml"); | cs |
Application.dataPath는 현재 자신의 유니티 프로젝트에 Assets폴더까지의 경로를 반환해준다.
필자같은경우 Assets폴더 안에 Save라는 폴더를 만들었고 그 안에 XML문서가 저장되는데,
문서가 저장될때 이름이 InventoryData.xml이다.
'Unity3D > Project' 카테고리의 다른 글
[Unity3D] 헥사게임 만들기 첫 번째 - 리소스 준비하기 및 캔버스 크기 설정. (0) | 2018.02.11 |
---|---|
[Unity3D] 인벤토리 [Part 4] - Load (3) | 2017.04.24 |
[Unity3D] 인벤토리 [Part 3] - Item Drag & Swap (5) | 2017.04.13 |
[Unity3D] 인벤토리 [Part 2] - Item Add (13) | 2017.04.12 |
[Unity3D] 인벤토리 [Part 1] - Inventory UI (5) | 2017.04.07 |
[Unity3D] Sprite 동적할당.
※ Sprite를 public으로 인스펙터창에 노출시킨뒤 드래그로 넣는것이 아닌,
폴더안에 존재하는 이미지의 경로를 받아 할당하는 방법이다.
개발하다보면 Sprite를 변수에 동적으로 넘겨줘야 할때가 있다.
게임 시작 시 'Resources'폴더 안에 있는 1이라는 이미지가 public으로 선언된 Sprite변수인 spr에
동적으로 들어가게 된다.
코드는 이렇다.
1 2 3 4 5 6 | public Sprite spr; void Start() { spr = Resources.Load<Sprite>("경로"); } | cs |
※ 주의사항
- 이미지는 Resources폴더에 있어야 한다.
이때 폴더 이름은 정확히 Resources여야 한다.
- 이미지의 Texture Type은 Sprite여야 한다.
한 가지더.
만약 이러한 이미지가 있다고 하자.
이 이미지의 모드는 Multiple로써
많은 자식들의 이미지를 가지고 있다.
이 모든 이미지를 동적으로 받고자 한다.
위의 소스를 조금만 수정하면 된다.
밑에와 같이 수정해주자.
1 2 3 4 5 6 | public Sprite[] spr; void Start() { spr = Resources.LoadAll<Sprite>("경로/이미지 "); } | cs |
※ 필자같은 경우 경로는 Img폴더에 이미지 이름이 Imgs이므로
spr = Resources.LoadAll<Sprite>("Img/Imgs"); 가 된다.
그리고 게임을 수행하면
위와같이 이미지들이 들어가게 된다.
저렇게 하나의 이미지 말고 폴더안에 존재하는 모든 이미지들을 할당 받고 싶다면
경로/이미지이름 에서 이미지이름을 지워주고 이미지가 존재하는 폴더까지만 경로를 써주면
그 폴더안에 존재하는 모든 이미지들을 모두 받아올수 있게 된다.
1 2 3 4 5 6 | public Sprite[] spr; void Start() { spr = Resources.LoadALL<Sprite>("경로"); } | cs |
※ Img폴더 안에 있는 모든 이미지를 받아오게 되었다.
'Unity3D > Tip' 카테고리의 다른 글
[Unity3D] AddExplosionForce(); - 폭발 시 여파 적용. (0) | 2017.04.27 |
---|---|
[Unity3D] 일정 범위내의 존재하는 Collider 객체를 추출. (0) | 2017.04.27 |
[Unity3D] 비균등스케일 (non-uniform scale) (0) | 2017.04.27 |
[Unity3D] 셰이더 및 물리 기반 셰이딩 (0) | 2017.04.25 |
[Unity3D] 배칭 & 드로우콜 (0) | 2017.03.16 |
[Unity3D] 인벤토리 [Part 3] - Item Drag & Swap
※ 주의
이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.
Menu
1. 인벤토리 껍데기 만들기.
- 인벤토리의 Pivot 설정.
- 슬롯의 Pivot 설정.
- 슬롯 사이즈 설정.
- 슬롯 간 거리 설정.
- 슬롯의 가로, 세로 개수 설정.
- 인벤토리의 가로 사이즈, 세로 사이즈 설정.
- 슬롯 생성 및 부모설정.
- 모든 슬롯을 관리해줄 리스트를 생성.
2. 아이템 획득 시 검사조건 만들기 및 슬롯 기능 만들기.
- 아이템을 먹었을 때, 인벤토리 내의 슬롯을 모두 검사한다.
*검사조건 : -> 슬롯 내에 같은 아이템이 존재하는지?
-> 슬롯내에 같은 아이템이 존재할 경우 겹칠수 있는지?
-> 슬롯내에 같은 아이템이 존재하지만, 겹칠수 없는경우 빈 슬롯이 존재 하는지?
- 슬롯을 스택으로 만들어 아이템 관리하기
->아이템이 슬롯에 들어갔을때 이미지 변경.
->아이템을 겹칠경우 텍스트 갱신.
->아이템을 사용할 경우 텍스트 갱신.
->아이템을 모두 사용했을 때 이미지 변경.
3. 아이템 만들기 및 획득
- 아이템 타입 설정.
- 아이템의 이미지 설정.
- 아이템 겹칠수 있는 최대치 설정.
- 아이템과의 충돌로 아이템 획득하기.
4. 인벤토리내에 아이템 드래그 앤 드랍으로 아이템 옮기기 및 자리 바꾸기.
- 처음 마우스 버튼을 누를 때
- 누르고 이동할 때
- 드래그가 끝났을 때
- 누른 버튼을 땠을 때
의 4가지 상태로 나눠 드래그 앤 드랍 구현.
5. XML문서를 이용한 인벤토리 아이템 저장.
- Save
- Load
미리보기
4. 인벤토리내에 아이템 드래그 앤 드랍으로 아이템 옮기기 및 자리 바꾸기.
- 빈 이미지를 만든다.
이 이미지는 슬롯에 있는 아이템을 눌렀을 때 그 아이템의 이미지를 빈 이미지에 가져와 띄워주는 역할을 하는 이미지이다.
빈 이미지를 위와같이 Canvas의 자식으로 하나 만든다.
그리고, 태그를 하나 추가할 건데, 이 태그는 스크립트로 이 이미지를 가져오기 위함니다.
태그의 이름은 DragImg이다.
1 2 3 4 5 6 | public Transform Img; // 비어있는 이미지. void Start() { Img = GameObject.FindGameObjectWithTag("DragImg").transform; } | cs |
다음은 슬롯에 4가지 이벤트를 추가 해보자.
Event Trigger를 추가하면 된다.
드래그 앤 드롭은 4가지 형태로 볼 수 있다.
- 처음 눌렀을 때, (Pointer Down - Down)
- 누르고 드래그 할 때, (Drag - Drag)
- 드래그를 끝냈을 때, (End Drag - DragEnd)
- 마우스 버튼을 땠을 때. (Pointer Up - Up)
1). 처음 눌렀을 때.
- 슬롯에 아이템이 존재하지 않는다면 함수를 종료한다.
- 누른 버튼이 마우스 오른쪽 버튼이면 아이템을 사용한다.
- 빈 이미지 객체를 활성화 시킨다.
- 빈 이미지의 사이즈를 슬롯의 사이즈와 똑같이 변경한다.
=> 해상도가 달라졌을 때 슬롯의 크기가 바뀌므로 빈 객채의 이미지도 유동적으로 달라져야 한다.
- 빈 이미지의 sprite를 슬롯이 가지고 있는 아이템 이미지로 변경한다.
- 빈 이미지 객체의 위치를 마우스의 위치로 가져온다.
- 슬롯의 이미지를 없애준다.
- 슬롯의 텍스트 숫자를 없애준다.
2). 누르고 드래그 할 때.
- 슬롯에 아이템이 존재하지 않으면 함수를 종료한다.
- 빈 이미지의 위치를 마우스의 위치로 가져온다.
3). 드래그가 끝났을 때.
- 슬롯에 아이템이 존재하지 않으면 함수를 종료한다.
- 싱글톤을 이용해 인벤토리 내에 있는 Swap함수를 호출. 인자로 자신의 슬롯 스크립트와 빈 이미지의 현재 위치를 보낸다.
4). 마우스 버튼을 땠을 때.
- 슬롯에 아이템이 존재하지 않으면 함수를 종료한다.
- 빈 이미지 객체를 비활성화 시킨다.
- 현재 슬롯의 정보를 업데이트 시킨다.
마우스를 땠을 때 이벤트는 필요 없을것 같지만 필요하다.
빈 이미지 객체의 비활성화 및 슬롯의 이미지 업데이트에 필요하다.
빈 이미지 객체의 비활성화의 경우 드래그가 끝났을 때 수행해도 되지만,
아이템을 눌렀을 때 마우스를 움직이지 않으면 DragEnd함수는 호출되지 않는다.
그렇기 때문에 빈 이미지 객체의 활성화 조정을 이곳에서 할 필요가 있으며,
마찬가지로 없애주었던 이미지의 복구 또한 이곳에서 해 주어야 한다.
'ItemDrag' 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 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class ItemDrag : MonoBehaviour { public Transform Img; // 빈 이미지 객체. private Image EmptyImg; // 빈 이미지. private Slot slot; // 현재 슬롯에 스크립트 void Start() { // 현재 슬롯의 스크립트를 가져온다. slot = GetComponent<Slot>(); // 빈 이미지 객체를 태그를 이용하여 가져온다. Img = GameObject.FindGameObjectWithTag("DragImg").transform; // 빈 이미지 객체가 가진 Image컴포넌트를 가져온다. EmptyImg = Img.GetComponent<Image>(); } public void Down() { // 슬롯에 아이템이 없으면 함수종료. if (!slot.isSlots()) return; // 아이템 사용시. if (Input.GetMouseButtonDown(1)) { slot.ItemUse(); return; } // 빈 이미지 객체를 활성화 시킨다. Img.gameObject.SetActive(true); // 빈 이미지의 사이즈를 변경한다.(해상도가 바뀔경우를 대비.) float Size = slot.transform.GetComponent<RectTransform>().sizeDelta.x; EmptyImg.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, Size); EmptyImg.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, Size); // 빈 이미지의 스프라이트를 슬롯의 스프라이트로 변경한다. EmptyImg.sprite = slot.ItemReturn().DefaultImg; // 빈 이미지의 위치를 마우스위로 가져온다. Img.transform.position = Input.mousePosition; // 슬롯의 아이템 이미지를 없애준다. slot.UpdateInfo(true, slot.DefaultImg); // 슬롯의 텍스트 숫자를 없애준다. slot.text.text = ""; } public void Drag() { // isImg플래그가 false이면 슬롯에 아이템이 존재하지 않는 것이므로 함수 종료. if (!slot.isSlots()) return; Img.transform.position = Input.mousePosition; } public void DragEnd() { // isImg플래그가 false이면 슬롯에 아이템이 존재하지 않는 것이므로 함수 종료. if (!slot.isSlots()) return; // 싱글톤을 이용해서 인벤토리의 스왑함수를 호출(현재 슬롯, 빈 이미지의 현재 위치.) ObjManager.Call().IV.Swap(slot, Img.transform.position); //slot = null; } public void Up() { // isImg플래그가 false이면 슬롯에 아이템이 존재하지 않는 것이므로 함수 종료. if (!slot.isSlots()) return; // 빈 이미지 객체 비활성화. Img.gameObject.SetActive(false); // 슬롯의 아이템 이미지를 복구 시킨다. slot.UpdateInfo(true, slot.slot.Peek().DefaultImg); } } | cs |
드래그가 끝나는 함수, 그러니까 DragEnd함수의
Swap()함수를 살펴보도록 하자.
접근 방법은 싱글톤을 이용해서 Inventory스크립트에 접근해서 Swap함수를 호출한다.
이 함수의 인자는 2개를 받고 있는데, 현재 내가 누른 슬롯의 Slot 스크립트와 빈 이미지 객체의 위치이다.
'Inventory' 스크립트의 추가된 함수들을 살펴보자.
이곳 에서는 3가지 함수가 추가되었다.
public Slot NearDisSlot(Vector3 Pos) // 빈 이미지와 가까운 슬롯을 찾아 반환해주는 함수.
public void Swap(Slot slot, Vector3 Pos) // 슬롯 끼리의 내용물을 전체적으로 교환 해주는 함수.
void Swap(Slot xFirst, Slot oSecond) // Swap함수를 보조 해주는 함수. (두 개의 슬롯의 내용물을 교환한다.)
이번엔 UpdateInfo() 함수를 살펴보자.
이 함수는 슬롯에 추가된 함수로 아이템 존재유무와 이미지 업데이트의 역할을 맡고 있다.
Slot 스크립트에 아래의 함수를 추가해 주면 된다.
1 2 3 4 5 6 7 8 9 10 11 | // 슬롯에 대한 각종 정보 업데이트. public void UpdateInfo(bool isSlot, Sprite sprite) { this.isSlot = isSlot; transform.GetChild(0).GetComponent<Image>().sprite = sprite; if (slot.Count > 1) text.text = slot.Count.ToString(); else text.text = ""; } | cs |
또한 이러한 함수가 생겼으므로 Slot 스크립트의 AddItem() 함수에 대해서도
아래와 같이 고칠수 있게 된다.
1 2 3 4 5 6 | public void AddItem(Item item) { // 스택에 아이템 추가. slot.Push(item); UpdateInfo(true, item.DefaultImg); } | cs |
'Unity3D > Project' 카테고리의 다른 글
[Unity3D] 인벤토리 [Part 4] - Load (3) | 2017.04.24 |
---|---|
[Unity3D] 인벤토리 [Part 4] - Save (3) | 2017.04.23 |
[Unity3D] 인벤토리 [Part 2] - Item Add (13) | 2017.04.12 |
[Unity3D] 인벤토리 [Part 1] - Inventory UI (5) | 2017.04.07 |
[Unity3D] 인벤토리 [Part 0] (0) | 2017.04.06 |
[Unity3D] 인벤토리 [Part 2] - Item Add
※ 주의
이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.
Menu
1. 인벤토리 껍데기 만들기.
- 인벤토리의 Pivot 설정.
- 슬롯의 Pivot 설정.
- 슬롯 사이즈 설정.
- 슬롯 간 거리 설정.
- 슬롯의 가로, 세로 개수 설정.
- 인벤토리의 가로 사이즈, 세로 사이즈 설정.
- 슬롯 생성 및 부모설정.
- 모든 슬롯을 관리해줄 리스트를 생성.
2. 아이템 획득 시 검사조건 만들기 및 슬롯 기능 만들기.
- 아이템을 먹었을 때, 인벤토리 내의 슬롯을 모두 검사한다.
*검사조건 : -> 슬롯 내에 같은 아이템이 존재하는지?
-> 슬롯내에 같은 아이템이 존재할 경우 겹칠수 있는지?
-> 슬롯내에 같은 아이템이 존재하지만, 겹칠수 없는경우 빈 슬롯이 존재 하는지?
- 슬롯을 스택으로 만들어 아이템 관리하기
->아이템이 슬롯에 들어갔을때 이미지 변경.
->아이템을 겹칠경우 텍스트 갱신.
->아이템을 사용할 경우 텍스트 갱신.
->아이템을 모두 사용했을 때 이미지 변경.
3. 아이템 만들기 및 획득
- 아이템 타입 설정.
- 아이템의 이미지 설정.
- 아이템 겹칠수 있는 최대치 설정.
- 아이템과의 충돌로 아이템 획득하기.
4. 인벤토리내에 아이템 드래그 앤 드랍으로 아이템 옮기기 및 자리 바꾸기.
- 처음 마우스 버튼을 누를 때
- 누르고 이동할 때
- 드래그가 끝났을 때
- 누른 버튼을 땠을 때
의 4가지 상태로 나눠 드래그 앤 드랍 구현.
5. XML문서를 이용한 인벤토리 아이템 저장.
- Save
- Load
미리보기
2. 아이템 획득 시 검사조건 만들기 및 슬롯 기능 만들기.
를 진행하기 전에 3번부터 합시다.
3. 아이템 만들기 및 획득.
만약 아이템이 플레이어와 충돌한다면
아이템은 플레이어 인벤토리 스크립트에 존재하는 AddItem()함수를 호출한다.
※ 아이템쪽에서 플레이어와의 충돌을 감지한다.
※ 왜 하필 인벤토리 스크립트에서 AddItem()을 호출하는가?
슬롯에 직접 아이템을 넣으면 안되는가?
아이템을 먹었다고 해서 인벤토리에 아이템이 무조건 들어가는것이 아니다.
인벤토리가 꽉 차 있는경우 아이템 획득에 실패할수 있다.
또한 인벤토리가 꽉 차 있지만 인벤토리 내에 똑같은 아이템이 존재하여
먹을수 있는 경우도 있다.
이러한 경우에 수를 다 시도해 보기 위해 모든 슬롯을 관리하는 인벤토리 스크립트에
AddItem()을 호출해줄 필요가 있다.
'Item' 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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class Item : MonoBehaviour { public enum TYPE { HP, MP } public TYPE type; // 아이템의 타입. public Sprite DefaultImg; // 기본 이미지. public int MaxCount; // 겹칠수 있는 최대 숫자. void AddItem() { // 싱글톤을 이용해서 인벤토리 스크립트를 가져온다. Inventory iv = ObjManager.Call().IV; // 아이템 획득에 실패할 경우. if (!iv.AddItem(this)) Debug.Log("아이템이 가득 찼습니다."); else // 아이템 획득에 성공할 경우. gameObject.SetActive(false); // 아이템을 비활성화 시켜준다. } // 충돌체크 void OnTriggerEnter(Collider _col) { // 플레이어와 충돌하면. if (_col.gameObject.layer == 10) AddItem(); } } | cs |
싱글톤이 없을 경우. 'Item' Script
아이템을 먹었을 때 엉뚱한 아이템이 인벤토리에 들어가면 안된다.
그렇기 때문에 아이템 자체에 아이템의 타입(종류)과 그 아이템에 해당하는 이미지 정보,
그리고 아이템을 몇개까지 겹칠수 있는지? 에 대한 정보를 가지고 있을 필요가 있다.
아이템에 대한 인스펙터창 정보이다.
플레이어에 대한 레이어 설정도 잊지 말도록 하자.
2. 아이템 획득 시 검사조건 만들기 및 슬롯 기능 만들기.
아이템이 인벤토리 스크립트에 존재하는 AddItem() 함수를 호출했다.
AddItem()안에서 해야 될것은 두 가지가 있다.
첫 번째는 넣을려는 아이템과 똑같은 아이템이 슬롯에 존재하는가?
존재 한다면 겹칠수 있는 최대치는 넘지 않았는가?
위의 조건에 해당되면 아이템을 슬롯에 넣는다.
두 번째는 위에 조건을 검사했지만 해당되지 않을 때 하는 검사이다.
똑같은 아이템이 없기 때문에 그냥 비어있는 슬롯에 아이템을 넣어주면 된다.
위의 모든 조건에 해당되지 않는다면 아이템을 먹지 못하기 때문에 AddItem()함수는 false를 반환하면 된다.
슬롯에 존재하는 아이템과 넣을려는 아이템이 똑같은지를 검사하기 위해서는
슬롯에 존재하는 아이템의 정보를 끌어올 필요가 있다.
그러기 위해서는 위에 조건의 작성보다 선행되어야 하는 것이 있는데
바로 슬롯 스크립트의 작성이다.
'slot' 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 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class Slot : MonoBehaviour { public Stack<Item> slot; // 슬롯을 스택으로 만든다. public Text text; // 아이템에 개수를 표현해줄 텍스트. public Sprite DefaultImg; // 슬롯에 있는 아이템을 다 사용할 경우 아무것도 없는 이미지를 넣어줄 필요가 있다. private Image ItemImg; private bool isSlot; // 현재 슬롯이 비어있는지? public Item ItemReturn() { return slot.Peek(); } // 슬롯에 존재하는 아이템이 뭔지 반환. public bool ItemMax(Item item) { return ItemReturn().MaxCount > slot.Count; } // 겹칠수 있는 한계치를 넘으면. public bool isSlots() { return isSlot; } // 슬롯이 현재 비어있는지? 에 대한 플래그 반환. public void SetSlots(bool isSlot) { this.isSlot = isSlot; } void Start() { // 스택 메모리 할당. slot = new Stack<Item>(); // 맨 처음엔 슬롯이 비어있다. isSlot = false; // 인벤토리 및 슬롯의 크기가 커지가나 작아지면 // 텍스트 폰트의 크기도 유동적으로 바뀌어야 한다. // 텍스트 폰트의 크기를 슬롯에 크기에 따라 변경해주는 구문이다. //RectTransform rect = text.gameObject.GetComponent<RectTransform>(); float Size = text.gameObject.transform.parent.GetComponent<RectTransform>().sizeDelta.x; text.fontSize = (int)(Size * 0.5f); // 텍스트 컴포넌트의 RectTransform을 가져온다. // 텍스트 객체의 부모 객체의 x지름을 가져온다. // 폰트의 크기를 부모 객체의 x지름 / 2 만큼으로 지정해준다. ItemImg = transform.GetChild(0).GetComponent<Image>(); } public void AddItem(Item item) { // 스택에 아이템 추가. slot.Push(item); UpdateInfo(true, item.DefaultImg); } // 아이템 사용. public void ItemUse() { // 슬롯이 비어있으면 함수를 종료. if (!isSlot) return; // 슬롯에 아이템이 1개인 경우. // 아이템이 1개일 때 사용하게 되면 0개가 된다. if (slot.Count == 1) { // 혹시 모를 오류를 방지하기 위해 slot리스트를 Clear해준다 slot.Clear(); // 아이템 사용으로 인해 아이템 개수를 표현하는 텍스트가 달라졌으므로 업데이트 시켜준다. UpdateInfo(false, DefaultImg); return; } slot.Pop(); UpdateInfo(isSlot, ItemImg.sprite); } // 슬롯에 대한 각종 정보 업데이트. public void UpdateInfo(bool isSlot, Sprite sprite) { SetSlots(isSlot); // 슬롯이 비어있다면 false 아니면 true 셋팅. ItemImg.sprite = sprite; // 슬롯안에 들어있는 아이템의 이미지를 셋팅. text.text = slot.Count > 1 ? slot.Count.ToString() : ""; // 아이템이 2개 이상일때면 텍스트로 표현. ItemIO.SaveDate(); // 인벤토리에 변동사항이 생겼으므로 저장. } } | cs |
텍스트 정보.
슬롯 스크립트의 작성이 끝났으면 인벤토리의 AddItem()함수를 작성할 수 있게 된다.
'Inventory' 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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class Inventory : MonoBehaviour { // 공개 public List<GameObject> AllSlot; // 모든 슬롯을 관리해줄 리스트. public RectTransform InvenRect; // 인벤토리의 Rect public GameObject OriginSlot; // 오리지널 슬롯. public float slotSize; // 슬롯의 사이즈. public float slotGap; // 슬롯간 간격. public float slotCountX; // 슬롯의 가로 개수. public float slotCountY; // 슬롯의 세로 개수. // 비공개. private float InvenWidth; // 인벤토리 가로길이. private float InvenHeight; // 인밴토리 세로길이. private float EmptySlot; // 빈 슬롯의 개수. void Awake() { // 인벤토리 이미지의 가로, 세로 사이즈 셋팅. InvenWidth = (slotCountX * slotSize) + (slotCountX * slotGap) + slotGap; InvenHeight = (slotCountY * slotSize) + (slotCountY * slotGap) + slotGap; // 셋팅된 사이즈로 크기를 설정. InvenRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, InvenWidth); // 가로. InvenRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, InvenHeight); // 세로. // 슬롯 생성하기. for (int y = 0; y < slotCountY; y++) { for (int x = 0; x < slotCountX; x++) { // 슬롯을 복사한다. GameObject slot = Instantiate(OriginSlot) as GameObject; // 슬롯의 RectTransform을 가져온다. RectTransform slotRect = slot.GetComponent<RectTransform>(); // 슬롯의 자식인 투명이미지의 RectTransform을 가져온다. RectTransform item = slot.transform.GetChild(0).GetComponent<RectTransform>(); slot.name = "slot_" + y + "_" + x; // 슬롯 이름 설정. slot.transform.parent = transform; // 슬롯의 부모를 설정. (Inventory객체가 부모임.) // 슬롯이 생성될 위치 설정하기. slotRect.localPosition = new Vector3((slotSize * x) + (slotGap * (x + 1)), -((slotSize * y) + (slotGap * (y + 1))), 0); // 슬롯의 자식인 투명이미지의 사이즈 설정하기. slotRect.localScale = Vector3.one; slotRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, slotSize); // 가로 slotRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, slotSize); // 세로. // 슬롯의 사이즈 설정하기. item.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, slotSize - slotSize * 0.3f); // 가로. item.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, slotSize - slotSize * 0.3f); // 세로. // 리스트에 슬롯을 추가. AllSlot.Add(slot); } } // 빈 슬롯 = 슬롯의 숫자. EmptySlot = AllSlot.Count; } // 아이템을 넣기위해 모든 슬롯을 검사. public bool AddItem(Item item) { // 슬롯에 총 개수. int slotCount = AllSlot.Count; // 넣기위한 아이템이 슬롯에 존재하는지 검사. for (int i = 0; i < slotCount; i++) { // 그 슬롯의 스크립트를 가져온다. Slot slot = AllSlot[i].GetComponent<Slot>(); // 슬롯이 비어있으면 통과. if (!slot.isSlots()) continue; // 슬롯에 존재하는 아이템의 타입과 넣을려는 아이템의 타입이 같고. // 슬롯에 존재하는 아이템의 겹칠수 있는 최대치가 넘지않았을 때. (true일 때) if (slot.ItemReturn().type == item.type && slot.ItemMax(item)) { // 슬롯에 아이템을 넣는다. slot.AddItem(item); return true; } } // 빈 슬롯에 아이템을 넣기위한 검사. for (int i = 0; i < slotCount; i++) { Slot slot = AllSlot[i].GetComponent<Slot>(); // 슬롯이 비어있지 않으면 통과 if (slot.isSlots()) continue; slot.AddItem(item); return true; } // 위에 조건에 해당되는 것이 없을 때 아이템을 먹지 못함. return false; } } | cs |
'Unity3D > Project' 카테고리의 다른 글
[Unity3D] 인벤토리 [Part 4] - Save (3) | 2017.04.23 |
---|---|
[Unity3D] 인벤토리 [Part 3] - Item Drag & Swap (5) | 2017.04.13 |
[Unity3D] 인벤토리 [Part 1] - Inventory UI (5) | 2017.04.07 |
[Unity3D] 인벤토리 [Part 0] (0) | 2017.04.06 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 5] 추가사항 (2) | 2017.04.05 |
[Unity3D] 인벤토리 [Part 1] - Inventory UI
※ 주의
이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.
Menu
1. 인벤토리 껍데기 만들기.
- 인벤토리의 Pivot 설정.
- 슬롯의 Pivot 설정.
- 슬롯 사이즈 설정.
- 슬롯 간 거리 설정.
- 슬롯의 가로, 세로 개수 설정.
- 인벤토리의 가로 사이즈, 세로 사이즈 설정.
- 슬롯 생성 및 부모설정.
- 모든 슬롯을 관리해줄 리스트를 생성.
2. 아이템 획득 시 검사조건 만들기 및 슬롯 기능 만들기.
- 아이템을 먹었을 때, 인벤토리 내의 슬롯을 모두 검사한다.
*검사조건 : -> 슬롯 내에 같은 아이템이 존재하는지?
-> 슬롯내에 같은 아이템이 존재할 경우 겹칠수 있는지?
-> 슬롯내에 같은 아이템이 존재하지만, 겹칠수 없는경우 빈 슬롯이 존재 하는지?
- 슬롯을 스택으로 만들어 아이템 관리하기
->아이템이 슬롯에 들어갔을때 이미지 변경.
->아이템을 겹칠경우 텍스트 갱신.
->아이템을 사용할 경우 텍스트 갱신.
->아이템을 모두 사용했을 때 이미지 변경.
3. 아이템 만들기 및 획득
- 아이템 타입 설정.
- 아이템의 이미지 설정.
- 아이템 겹칠수 있는 최대치 설정.
- 아이템과의 충돌로 아이템 획득하기.
4. 인벤토리내에 아이템 드래그 앤 드랍으로 아이템 옮기기 및 자리 바꾸기.
- 처음 마우스 버튼을 누를 때
- 누르고 이동할 때
- 드래그가 끝났을 때
- 누른 버튼을 땠을 때
의 4가지 상태로 나눠 드래그 앤 드랍 구현.
5. XML문서를 이용한 인벤토리 아이템 저장.
- Save
- Load
1. 인벤토리 껍데기 만들기.
이렇게 생긴 인벤토리를 만들려고 한다.
그러면 필요한 이미지가 3개 있다.
1). 인벤토리 배경 이미지.
2). 슬롯 이미지.
3). 투명 이미지.
1). 인벤토리 배경 이미지 생성 후 Pivot설정하기.
(1). 하이러키 창에서 마우스 오른쪽 버튼을 클릭해서 UI에 Image를 생성한다. (객체이름 Inventory)
(2). Image를 생성하면 Canvas가 생성되고 그 자식으로 Image가 생성된 것을 볼 수 있을 것이다.
(3). 새로 생성된 Image를 클릭한 뒤, Image컴포넌트에 Source Image에 자신이 가져온 인벤토리 배경 이미지를 넣는다.
(4). Scene으로 돌아와 인벤토리 배경 이미지의 pivot을 사진과 같이 옮긴다.
동그라미를 인벤토리 이미지에 왼쪽 상단에 옮기는 이유는 저 곳을 기준으로 슬롯을 그려줄 것이기 때문이다.
2). 슬롯 이미지 생성 후 Pivot정해주기.
(1). Canvas에 자식으로 Image를 하나 만들어준다. (객체이름 OriginSlot)
(2). Image컴포넌트 Source Image에 슬롯 이미지를 넣어준다.
(3). 슬롯의 Pivot을 설정해준다.
(4). 슬롯의 자식으로 Image를 하나 생성한 후 Source Image에 투명 이미지를 씌운다. (객체이름 Item)
(5). 투명 이미지의 위치를 슬롯의 가장자리에 위치시킨다.
※ 투명이미지의 Pivot은 옮길필요 없다.
투명 이미지를 슬롯안에 넣어준 이유는 아이템을 획득했을 때 이 투명이미지를 아이템 이미지로 바꿔줄 것이기 때문이다.
(6). 다 만든 슬롯을 Project창으로 옮겨 프리팹으로 만든다.
(7). 하이러키창에 슬롯을 지운다.
3). 설정하기.
(1). 슬롯 사이즈 설정하기.
(2). 슬롯 간 거리 설정하기.
(3). 슬롯의 가로 세로 개수 설정하기.
(4). 인벤토리 배경 이미지의 가로,세로 사이즈 설정하기.
이렇게 설정을 하는 이유는 (슬롯 사이즈 * 슬롯의 가로(세로)개수) + (슬롯간 거리 * 슬롯의 가로(세로)개수)로 인벤토리 이미지의 크기를 정하기 때문이다.
만약 슬롯의 개수가 5개이고 슬롯의 가로 사이즈가 30이라고 하자. 5 * 30 = 150이 된다.
그럼 적어도 인벤토리 이미지의 가로(세로)사이즈는 150은 넘어야지 슬롯 이미지를 인벤토리 안으로 넣을수 있다.
그런데 이것만이 아니라 슬롯들은 서로 붙어있지 않고 일정 간격으로 떨어져있다. 이것이 슬롯간 거리인데,
이 거리도 계산해주어야 한다. 그렇지 않으면 슬롯이 인벤토리 이미지 밖으로 삐져나가기 때문이다.
슬롯간 거리는 슬롯과 슬롯과의 떨어진 거리인데 이 간격도 슬롯에 개수 + 1만큼 계산해주면 된다.
슬롯의 개수가 5개라고 하자. 그럼 간격도 5개 + 1개 존재하게 된다.
이렇게 모든 사이즈 및 개수가 설정되면 인벤토리 이미지의 가로(세로)사이즈를 측정할수 있게 된다.
4). Inventory스크립트 작성하기.
'Inventory' Script
※ 설명은 주석을 읽고 분석하도록 하자.
Inventory스크립트의 인스펙터창 정보.
완성
'Unity3D > Project' 카테고리의 다른 글
[Unity3D] 인벤토리 [Part 3] - Item Drag & Swap (5) | 2017.04.13 |
---|---|
[Unity3D] 인벤토리 [Part 2] - Item Add (13) | 2017.04.12 |
[Unity3D] 인벤토리 [Part 0] (0) | 2017.04.06 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 5] 추가사항 (2) | 2017.04.05 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 5] (0) | 2017.04.03 |
[Unity3D] 인벤토리 [Part 0]
※ 주의
이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.
Menu
1. 인벤토리 껍데기 만들기.
- 인벤토리의 Pivot 설정.
- 슬롯의 Pivot 설정.
- 슬롯 사이즈 설정.
- 슬롯 간 거리 설정.
- 슬롯의 가로, 세로 개수 설정.
- 인벤토리의 가로 사이즈, 세로 사이즈 설정.
- 슬롯 생성 및 부모설정.
- 모든 슬롯을 관리해줄 리스트를 생성.
2. 아이템 획득 시 검사조건 만들기 및 슬롯 기능 만들기.
- 아이템을 먹었을 때, 인벤토리 내의 슬롯을 모두 검사한다.
*검사조건 : -> 슬롯 내에 같은 아이템이 존재하는지?
-> 슬롯내에 같은 아이템이 존재할 경우 겹칠수 있는지?
-> 슬롯내에 같은 아이템이 존재하지만, 겹칠수 없는경우 빈 슬롯이 존재 하는지?
- 슬롯을 스택으로 만들어 아이템 관리하기
->아이템이 슬롯에 들어갔을때 이미지 변경.
->아이템을 겹칠경우 텍스트 갱신.
->아이템을 사용할 경우 텍스트 갱신.
->아이템을 모두 사용했을 때 이미지 변경.
3. 아이템 만들기 및 획득
- 아이템 타입 설정.
- 아이템의 이미지 설정.
- 아이템 겹칠수 있는 최대치 설정.
- 아이템과의 충돌로 아이템 획득하기.
4. 인벤토리내에 아이템 드래그 앤 드랍으로 아이템 옮기기 및 자리 바꾸기.
- 처음 마우스 버튼을 누를 때
- 누르고 이동할 때
- 드래그가 끝났을 때
- 누른 버튼을 땠을 때
의 4가지 상태로 나눠 드래그 앤 드랍 구현.
5. XML문서를 이용한 인벤토리 아이템 저장.
- Save
- Load
미리보기
'Unity3D > Project' 카테고리의 다른 글
[Unity3D] 인벤토리 [Part 2] - Item Add (13) | 2017.04.12 |
---|---|
[Unity3D] 인벤토리 [Part 1] - Inventory UI (5) | 2017.04.07 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 5] 추가사항 (2) | 2017.04.05 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 5] (0) | 2017.04.03 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 4] (0) | 2017.03.28 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 5] 추가사항
※ 주의
이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.
Menu
0. 미리보기
1. 오브젝트를 미리 생성.
- 프리팹 제작.
- 싱글톤 제작.
- 오브젝트를 생성시킬 함수 제작.
- 오브젝트를 찾을 함수를 제작.
2. 만들어진 오브젝트 활용하기.
추가.
3. 적 생성
- 적 생성 후 프리팹으로 만들기.
- 적과 총알이 충돌하면 총알 비활성화.
- 적이 총알에 맞아 죽었을 때 단순한 객체로 이펙트 효과 표현하기.
4. 메모리 풀을 이용해서 적 생성하기.
- 적 리스폰
* Enemy를 생산하는 Area만들기. (랜덤한 위치로 Area생산 및 Area간 간격조절.)
* Araa범위 안에 Enemy 랜덤한 위치로 리스폰 시키기.
* Enemy가 죽었을 경우 리스폰 되는 간격 조절.
5. FSM을 이용해 적 AI만들기.
6. 추가.
미리보기
6. 추가. (EnemyAI Script)
1. 적이 플레이어에게 피격 시 Material을 빨간색으로 반짝이게 함.
플레이어에게 피격되면 HP를 감소시켜 주기 위해
InfoUpdate() 함수가 호출되는데, 이때 에너미가 가지고있는 마테리얼을 가져와서
색깔을 반복적으로 바꿔주면 된다. 하지만 InfoUpdate()함수는 피격 될때만 호출되는 함수로
색을 반복적으로 바꿔주는 것이 불가능 하다.
그래서 InfoUpdate()함수 안에서 코루틴을 호출시켜 for문 + 시간지연을 이용하여 색깔을 깜빡거려 준다.
에너미가 가지고있는 마테리얼의 색을 바꿔줄것이기 때문에 마테리얼을 가지고 오기로 하자.
Awake에서 초기화 시켜주면 된다.
1 2 3 4 5 6 7 | private Material Mate; // 자신이 가지고있는 마테리얼. void Awake() { player = GameObject.FindGameObjectWithTag("Player").transform; Mate = GetComponent<Renderer>().material; } | cs |
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 | // 정보 갱신. public void InfoUpdate(float _Damage) { // HP감소. HP -= _Damage; // 반짝거림. StartCoroutine("MaterialColorShine"); // HP가 0이하 이면. if (HP <= 0) { // 폭발한다. explosion(); HP = 0; // 체력의 수치가 음의 값으로 갔을 경우를 대비한 초기화. Life = false; // 죽었음을 알림. // 내 죽음을 부모에리어에게 알려라! // 부모 에리어가 가진 스크립트를 가져와 DeadEnemy()함수를 호출. transform.parent.GetComponent<CreateEnemy>().DeadEnemy(); gameObject.SetActive(false); } } | cs |
이런식으로 코루틴을 호출시켜 주고
코루틴의 내용으로는
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 색깔이 반짝반짝. IEnumerator MaterialColorShine() { for (float i = 0; i < 0.5f; i += 0.1f) { Mate.color = Color.red; yield return new WaitForSeconds(0.05f); Mate.color = Color.yellow; yield return new WaitForSeconds(0.05f); } Mate.color = Color.yellow; } | cs |
이렇게 해주면 된다.
for문이 한 번 돌 때 i값이 0.1씩 증가하는데 총 5번 for문을 돌게 된다.
이때 색을 빨간색으로 바꿔주고
시간을 0.05초간 지연시켜준다.
그리고 다시 색을 노란색으로 바꿔주고 시간을 다시 0.05초간 지연시켜 준다.
이러한 일을 반복하게되면 우리들의 눈에는 색깔이 깜박거리는 걸로 느껴진다.
시간지연을 0.05초간 2번씩 시켜줬기 때문에
0.05 + 0.05 = 0.1초가 되고
for문을 한 번 돌때마다 0.1초가 지연된다.
for문은 총 5번 돌기 때문에 결과적으로는 0.5초간 색깔이 깜박거린다고 생각하면 된다.
※ 색깔이 깜박거리는 도중 적이 죽을경우.
다시 리스폰 되면 색깔이 빨간색으로 바뀌어서 나올수도 있다.
그렇기 때문에 Init()함수에서 마테리얼의 색을 원래의 색깔로 초기화 하는 것이 좋다.
2. 플레이어에게 피격 시 추격거리 3배 증가.
적이 기존에 가지고있는 추적거리는 매우 짧다.
그렇기 때문에 플레이어에게 피격당해도 쫓아오지 않는 경우가 있다.
이러한 경우를 막기 위해 플레이어에게 피격 시 추적 거리를 대폭 늘려
쫓아오게 만들도록 하자.
추적상태가 끝나면 추적거리의 값을 원상태로 복구해줄 필요가 있다.
그렇기 때문에 본래 기본이 되는 값을 변수로 가지고 있어야 하는데, 이 값들을 먼저 정리해 보도록하자.
이 기본값들을 한눈에 보이도록 관리하기 위해 기본값 구조체를 하나 만들것이다.
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 | // 기본 값 구조체 public struct DEFAULT { public Vector3 AreaPos; // 부모 에리어의 위치. public float MAX_HP; // 체력 public float TRACE_DIS; // 추적거리 public float SPEED; // 스피드 } // 공개 public STATE state; // 에너미의 상태. public float HP; // 현재 체력 public float Speed; // 움직임 스피드. public float TraceDis; // 추적 거리. // 비공개 private DEFAULT DefaultVaule; // 기본 값 구조체 변수. private Material Mate; // 자신이 가지고있는 마테리얼. private Transform player; // 플레이어. private Vector3 RandomPoint; // 랜덤한 위치. private bool Life; // 살아있는지에 대한 여부. private bool trace; // 추적 플래그. // 각 상태의 코루틴을 시작하기 위한 플래그. private bool idle; private bool walk; private bool attack; private bool area_return; // 정보 초기화 함수. public void Init(Vector3 _AreaPos) { DefaultVaule.AreaPos = _AreaPos; DefaultVaule.TRACE_DIS = TraceDis; DefaultVaule.MAX_HP = 100; DefaultVaule.SPEED = Speed; HP = DefaultVaule.MAX_HP; Life = true; trace = false; state = STATE.IDLE; idle = false; walk = false; attack = false; area_return = false; Mate.color = Color.yellow; ReandomPos(); StartCoroutine("DisCheck"); StartCoroutine("StateCheck"); } | cs |
기본 값 구조체에는 4개의 변수가 있는데, 각 내용은 이렇다.
AreaPos는 부모 에리어의 처음 위치로 적이 부모 에리어로부터 너무 멀어졌을 경우 복귀하는 용도로 사용한다.
MAX_HP는 적이 리스폰 되거나 부모에리어로 복귀할 때 HP를 만땅으로 채워주기 위한 값이다.
TRACE_DIS는 추적거리로 부모 에리어로의 복귀나 플레이어가 일정거리 이상 멀어지면 초기화 시켜주기 위한 값이다.
SPEED는 부모 에리어로 복귀할때 빠르게 복귀시켜주기 위해 스피드를 올리는데, 부모 에리어로 복귀 했을 때 고유 스피드 값을
초기화 시켜주기 위한 값이다.
그래서 각 변수들을 정리해 위와같이 완성시켜주면 된다.
추적거리를 3배 증가시켜 주는 부분은 플레이어에게 피격당했을 때로 설정해주면 된다.
즉, 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 | // 정보 갱신. public void InfoUpdate(float _Damage) { // HP감소. HP -= _Damage; // 플레이어에게 선제공격 받을 시 추적거리 3배 증가. TraceDis = DefaultVaule.TRACE_DIS * 3; // 반짝거림. StartCoroutine("MaterialColorShine"); // HP가 0이하 이면. if (HP <= 0) { // 폭발한다. explosion(); HP = 0; // 체력의 수치가 음의 값으로 갔을 경우를 대비한 초기화. Life = false; // 죽었음을 알림. // 내 죽음을 부모에리어에게 알려라! // 부모 에리어가 가진 스크립트를 가져와 DeadEnemy()함수를 호출. transform.parent.GetComponent<CreateEnemy>().DeadEnemy(); gameObject.SetActive(false); } } | cs |
적이 Walk상태일 때 배회인지 추적인지 판단할수 있는 재료는 bool값인 trace변수이다.
trace값이 true면 적은 플레이어를 추적하는 상태이고, false이면 배회상태이다.
※ trace값은 DisCheck() 코루틴으로 0.2초에 한 번씩 플레이어와의 거리를 체크해 변환 시켜주고 있다.
플레이어 추적중에 둘 사이의 거리가 너무 멀어지면 trace값이 false로 바뀌면서
상태값을 IDLE로 바꿔버린다. 그러면서 적은 추적을 포기하게 된다.
이 때 추적거리를 원상태로 복구 시키면 된다.
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 | // 걷는상태. IEnumerator WalkState() { while (Life) { if(state != STATE.WALK) break; // 플레이어와의 거리가 가까우면 trace = true if (trace) { Rotations(player.position); DisCheckToState(player.position, 2f, STATE.ATTACK, true); } else // 추적 포기. { state = STATE.IDLE; TraceDis = DefaultVaule.TRACE_DIS; // 추적 길이 원상태 복구. } Run(); yield return null; } walk = false; } | cs |
※ Run()함수는 이동 부분을 함수로 빼준 함수이다. (여러곳에서 쓰기 때문에 함수로 뺐다.)
1 2 3 4 5 | // 앞을 바라보는 방향으로 전진. void Run() { transform.Translate(Vector3.forward * Speed * Time.deltaTime); } | cs |
3. 자신의 부모 에리에어서 멀어지면 에리어로 복귀
- 추적거리 기본 값으로 복구.
- 이동 스피드 3배 상승.
- 에리어 복귀까지 무적.
- 체력 MAX
부모 에리어로 복귀하는것이 한 가지에 상태이므로 상태 값을 추가했다.
1 | public enum STATE { IDLE, WALK, ATTACK, AREA_RETURN } | cs |
AREA_RETURN이다.
상태 체크 코루틴에 한 가지 상태(복귀)를 추가해준다.
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 | // 상태체크. IEnumerator StateCheck() { // 적 AI // 살아있는 동안에만 반복문을 돌림. while (Life) { switch (state) { case STATE.IDLE: // 평화. { if (!idle) { idle = true; StartCoroutine("IdleState"); } break; } case STATE.WALK: // 걷기 { if (!walk) { walk = true; StartCoroutine("WalkState"); } break; } case STATE.ATTACK: // 공격. { if (!attack) { attack = true; StartCoroutine("AttackState"); } break; } case STATE.AREA_RETURN: // 복귀 { if(!area_return) { area_return = true; StartCoroutine("Area_ReturnState"); } break; } } yield return new WaitForSeconds(0.2f); } } | cs |
복귀 상태도 Area_ReturnState라는 코루틴을 만들어 돌리는데,
그렇게 하는 이유는 상태체크 함수의 시간 체크 간격때문에 그렇다.
복귀 상태가 되면 적은 부모 에리어까지 뛰어가야 한다.
만약 코루틴을 사용하지 않고 case STATE.AREA_RETURN: { } 안에 복귀 구문을 수행해 버리면
0.2초당 한 번씩 움직이게 된다. (상태체크 코루틴은 0.2초에 한 번씩 시간을 지연시키기 때문이다.)
이러한 일을 막기 위하여 아에 다른 코루틴을 하나 만들어 구문을 수행시킨다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // 복귀 상태. IEnumerator Area_ReturnState() { Vector3 Pos = DefaultVaule.AreaPos; while(true) { // 복귀 상태가 아니면 반복문을 빠져나감. if(state != STATE.AREA_RETURN) break; Run(); // 열심히 뛴다. Rotations(Pos); // 부모 에리어 방향으로. DisCheckToState(Pos, 1.5f, STATE.IDLE); // 부모 에리어에 도착하면 상태 값을 IDLE로 바꾼다. yield return null; } area_return = false; } | cs |
이제 적의 상태가 복귀상태(AREA_RETURN)로 바뀌면 알아서 부모 에리어로 복귀할 것이다.
그럼 복귀 상태로 바꿔주는 타이밍은 어디서 잡아야 할까?
바로 WalkState코루틴에서 잡아주면 된다.
걷고있는 동안 자신과 부모 에리어의 거리를 지속적으로 체크한다.
그러다가 일정거리이상 멀어지면 상태값을 복귀상태로 바꿔주고
스피드를 증가시키고, 체력을 만땅으로 채우고, 추적 거리를 원상태로 만들고, 무적상태로 만든다.
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 | // 걷는상태. IEnumerator WalkState() { while (Life) { if(state != STATE.WALK) break; // 부모에리어와 거리가 너무 멀어지면 에리어로 복귀. AreaDisCheck(); // 플레이어와의 거리가 가까우면 trace = true if (trace) { Rotations(player.position); DisCheckToState(player.position, 2f, STATE.ATTACK, true); } else // 추적 포기. { state = STATE.IDLE; // 추적 길이 원상태 복구. TraceDis = DefaultVaule.TRACE_DIS; } Run(); yield return null; } walk = false; } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // 부모 에리어와의 거리 체크. void AreaDisCheck() { Vector3 Pos = transform.parent.position; float dis = Vector3.Distance(Pos, transform.position); // 거리가 15이상 넘어가면 부모 에리어로 복귀. if(dis >= 15f) { state = STATE.AREA_RETURN; Speed = DefaultVaule.SPEED * 3; // 스피드 3배 TraceDis = DefaultVaule.TRACE_DIS; // 추적거리 기본 값 HP = DefaultVaule.MAX_HP; // HP 기본 값 GetComponent<CapsuleCollider>().enabled = false; // 콜라이더를 꺼 무적으로. } } | cs |
부모 에리어로 복귀하는 순간.
Area_ReturnState()코루틴에서는 DisCheckToState()함수의 호출을 통해 각 값들을 기본 값으로 돌려준다.
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 | // 거리체크 및 상태변화. void DisCheckToState(Vector3 _Pos, float _Dis, STATE _state, bool _UpDown = true) { float dis = Vector3.Distance(transform.position, _Pos); if (_UpDown) { if (dis <= _Dis) { // 기본 값으로 복구. if(state == STATE.AREA_RETURN) { Speed = DefaultVaule.SPEED; GetComponent<CapsuleCollider>().enabled = true; } state = _state; } } else { if (dis > _Dis) state = _state; } } | cs |
Full Source
'Unity3D > Project' 카테고리의 다른 글
[Unity3D] 인벤토리 [Part 1] - Inventory UI (5) | 2017.04.07 |
---|---|
[Unity3D] 인벤토리 [Part 0] (0) | 2017.04.06 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 5] (0) | 2017.04.03 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 4] (0) | 2017.03.28 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 3] (0) | 2017.03.27 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 5]
※ 주의
이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.
Menu
0. 미리보기
1. 오브젝트를 미리 생성.
- 프리팹 제작.
- 싱글톤 제작.
- 오브젝트를 생성시킬 함수 제작.
- 오브젝트를 찾을 함수를 제작.
2. 만들어진 오브젝트 활용하기.
추가.
3. 적 생성
- 적 생성 후 프리팹으로 만들기.
- 적과 총알이 충돌하면 총알 비활성화.
- 적이 총알에 맞아 죽었을 때 단순한 객체로 이펙트 효과 표현하기.
4. 메모리 풀을 이용해서 적 생성하기.
- 적 리스폰
* Enemy를 생산하는 Area만들기. (랜덤한 위치로 Area생산 및 Area간 간격조절.)
* Araa범위 안에 Enemy 랜덤한 위치로 리스폰 시키기.
* Enemy가 죽었을 경우 리스폰 되는 간격 조절.
5. FSM을 이용해 적 AI만들기.
미리보기
5. FSM을 이용해 적 AI만들기.
유한 상태 기계 (Finite State Machine) 라고 불린다.
- 유한한 개수의 상태를 가진 기계.
- 한 번에 오로지 하나의 상태만을 가진다.
- 이러한 기계는 어떠한 이벤트에 의해서 다른 상태로 변화할 수 있다.
유한 상태 기계를 사용하면 코드의 가독성이 오르고 관리하기도 편해진다.
enum으로 에너미의 상태를 설정해주고,
각 상태들을 switch case문으로 갈라서 코루틴을 사용하여 지속적으로 체크해준다.
'Enemy' Script
Enemy 인스펙터창 정보
새로 추가된 사항의 구조.
'Unity3D > Project' 카테고리의 다른 글
[Unity3D] 인벤토리 [Part 0] (0) | 2017.04.06 |
---|---|
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 5] 추가사항 (2) | 2017.04.05 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 4] (0) | 2017.03.28 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 3] (0) | 2017.03.27 |
[Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 2] (0) | 2017.03.21 |