'Unity3D/Project'에 해당되는 글 36건

  1. 2017.04.12 [Unity3D] 인벤토리 [Part 2] - Item Add 13
  2. 2017.04.07 [Unity3D] 인벤토리 [Part 1] - Inventory UI 5
  3. 2017.04.06 [Unity3D] 인벤토리 [Part 0]
  4. 2017.04.05 [Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 5] 추가사항 2
  5. 2017.04.03 [Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 5]
  6. 2017.03.28 [Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 4]
  7. 2017.03.27 [Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 3]
  8. 2017.03.21 [Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 2]
  9. 2017.03.20 [Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 1]
  10. 2017.03.20 [Unity3D] 객체 미리 생성 후 재활용 - Memory pool [Part 0]

[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] 인벤토리 [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] 인벤토리 [Part 0]

※ 주의 

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



Menu

1. 인벤토리 껍데기 만들기.

- 인벤토리의 Pivot 설정.

- 슬롯의 Pivot 설정.

- 슬롯 사이즈 설정.

- 슬롯 간 거리 설정.

- 슬롯의 가로, 세로 개수 설정.

- 인벤토리의 가로 사이즈, 세로 사이즈 설정.

- 슬롯 생성 및 부모설정.

- 모든 슬롯을 관리해줄 리스트를 생성.


2. 아이템 획득 시 검사조건 만들기 및 슬롯 기능 만들기.

- 아이템을 먹었을 때, 인벤토리 내의 슬롯을 모두 검사한다.

  *검사조건 : -> 슬롯 내에 같은 아이템이 존재하는지?

-> 슬롯내에 같은 아이템이 존재할 경우 겹칠수 있는지?

-> 슬롯내에 같은 아이템이 존재하지만, 겹칠수 없는경우 빈 슬롯이 존재 하는지?

- 슬롯을 스택으로 만들어 아이템 관리하기

  ->아이템이 슬롯에 들어갔을때 이미지 변경.  

  ->아이템을 겹칠경우 텍스트 갱신.

  ->아이템을 사용할 경우 텍스트 갱신.

  ->아이템을 모두 사용했을 때 이미지 변경.


3. 아이템 만들기 및 획득

 - 아이템 타입 설정.

 - 아이템의 이미지 설정.

 - 아이템 겹칠수 있는 최대치 설정.

 - 아이템과의 충돌로 아이템 획득하기.


4. 인벤토리내에 아이템 드래그 앤 드랍으로 아이템 옮기기 및 자리 바꾸기.

 - 처음 마우스 버튼을 누를 때

 - 누르고 이동할 때

 - 드래그가 끝났을 때

 - 누른 버튼을 땠을 때

의 4가지 상태로 나눠 드래그 앤 드랍 구현.


5. XML문서를 이용한 인벤토리 아이템 저장.

- Save

- Load




미리보기




[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] 객체 미리 생성 후 재활용 - 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] 객체 미리 생성 후 재활용 - 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. 미리보기


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



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


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

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

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


prev 1 2 3 4 next