[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 19회차 미션 시작합니다.

02 디아블로 게임 - 33. 34 챕터를 진행합니다.




PlayerInventory를 만들어서 등록 가능한 아이템들의 데이터 정보를 확인할 수 있습니다. 아직 등록된 아이템이 없기 때문에 내용은 없고 id도 -1인 상태로 등록된 아이템이 없음을 나타냅니다.




인벤토리에 사용하는 이미지들도 위와 같이 등록하여 설정해 줍니다.





현재까지 기본적인 Interface UI는 구현이 된 상태이고, DynamicInventoryUI를 구현해 보도록 하겠습니다.

public class DynamicInventoryUI_New : InventoryUI_New
{
    [SerializeField]
    protected GameObject slotPrefab; // slot에 대한 UI 오브젝트 Prefab
    
    // 격자 형태의 slot 구성.
    [SerializeField]
    protected Vector2 start;
    [SerializeField]
    protected Vector2 size;
    [SerializeField]
    protected Vector2 space;
    
    [Min(1), SerializeField]
    protected int numberOfColumn = 4;
    
    public override void CreateSlotUIs() {
        slotUIs = new Dictionary<GameObject, InventorySlot_New>();
        
        for (int i = 0; i < inventoryObject.Slots.Length; i++) {
            GameObject go = Instantiate(slotPrefab, Vector3.zero, Quaternion.identity, transform);
            go.GetComponent<RectTransform>().anchoredPosition = CalculatePosition(i);
            
            // 마우스 이벤트 트리거들 추가.
            AddEvent(go, EventTriggerType.PointerEnter, delegate { OnEnterSlot(go); });
            AddEvent(go, EventTriggerType.PointerExit, delegate { OnExitSlot(go); });
            AddEvent(go, EventTriggerType.BeginDrag, delegate { OnStartDrag(go); });
            AddEvent(go, EventTriggerType.EndDrag, delegate { OnEndDrag(go); });
            AddEvent(go, EventTriggerType.Drag, delegate { OnDrag(go); });
            
            inventoryObject.Slots[i].slotUI = go;
            slotUIs.Add(go, inventoryObject.Slots[i]);
            
            go.name += ": " + i;
        }
    }
    
    public void CalculatePosition(int i) {
        float x = start.x + ((space.x + size.x) * (i % numberOfColumn));
        float y = start.y + (-(start.y) + size.y) * (i / numberOfColumn));
        return new Vector3(x, y, 0f);
    }
}


CreateSlotUIs()가 가장 중요한 함수라고 볼 수 있겠습니다.

이와 관련하여 SlotPrefab의 구성은 다음과 같아야 합니다.





Slot의 배경 이미지와 Icon이 들어갈 위치, 그리고 하단에 Text가 배치되어 있어야 합니다. 왜냐면 소스 코드 구성시 Slot의 구성이 Icon과 Text가 무조건 존재해야만 구동이 되도록 프로그래밍되어 있기 때문입니다.


그리고 위에 구성한 DynamicInventoryUI_New 스크립트를 Unity UI 상에서 설정해줍니다.



DynamicInventory에다가 DynamicInventoryUI_New 스크립트를 추가하여주고,
+ Inventory Object = "PlayerInventory"
+ Slot Prefab = "SlotPrefab"
+ Start = -70, 120
+ Size = 50, 50
+ Space = 1, 1
+ Number Of Column = 4
으로 설정하여 줍니다.




플레이를 해보면 위와 같이 인벤토리 구역 내에 Slot 24개가 잘 배치되어 나타나는 것을 확인할 수 있습니다.

이것이 잘 동작하는지를 확인하는 TestItems 스크립트를 작성하면 쉽게 동작여부를 확인하기 수월합니다.




AddNewItem() 함수는 databaseObject에서 랜덤하게 아이템을 하나 얻어와서 AddItem()으로 추가해주고,
CreateInventory() 함수는 등록된 inventoryObject를 모두 제거해주는 함수입니다.




플레이를 진행해 봅니다.
Add Item을 클릭하면 랜덤하게 하나씩 추가되고, Slot간 아이템 이동과, 아이템 교체, 인벤토리가 아닌 영역에 드래그하여 아이템 버리기, Cleate Items를 클릭하여 모두 비우기 등의 모든 테스트를 해볼 수 있습니다.




물약과 음식과 같은 겹쳐질 수 있는 아이템 테스트입니다.
그림과 같이 동일한 아이템이 추가되는 경우 Stackable[v]이 켜져 있는 아이템의 경우에는 비어 있는 Slot에 또 추가되는 것이 아니라 기존에 있던 아이템에 동일하게 추가를 하고 Text로 개수를 표시하는 방식으로 구현되었습니다.



이번에는 장비 창 인벤토리를 구현해 보도록 하겠습니다.


public class StaticInventoryUI_New : MonoBehaviour
{
    public GameObject[] staticSlots = null; // 고정된 Slot.
    
    public override void CrateSlotUIs() {
        slotUIs = new Dictionary<GameObject, FastCampus.InventorySystem.Inventory.InventorySlot_New>();
        for (int i = 0; i < inventoryObject.Slots.Length; i++) {
            GameObject go = staticSlots[i]; // Scene에 등록된 고정된 UI를 사용함이 다른점.
            go.GetComponent<RectTransform>().anchoredPosition = CalculatePosition(i);
            
            // 마우스 이벤트 트리거들 추가.
            AddEvent(go, EventTriggerType.PointerEnter, delegate { OnEnterSlot(go); });
            AddEvent(go, EventTriggerType.PointerExit, delegate { OnExitSlot(go); });
            AddEvent(go, EventTriggerType.BeginDrag, delegate { OnStartDrag(go); });
            AddEvent(go, EventTriggerType.EndDrag, delegate { OnEndDrag(go); });
            AddEvent(go, EventTriggerType.Drag, delegate { OnDrag(go); });
            
            inventoryObject.Slots[i].slotUI = go;
            slotUIs.Add(go, inventoryObject.Slots[i]);
            
            go.name += ": " + i;
        }
    }
}





여기까지 작업하고 플레이를 해봅니다.
보시는 것과 같이 장비 착용 인벤토리에 아이템을 드래그하여 설정이 가능하고, 또한 동일한 타입이 아닌 아이템은 착용이 되지 않는 것도 확인할 수 있습니다.





이제는 캐릭터 장비 교체를 구현해보는 시간입니다 ^^~
캐릭터는 보통 다수의 Skinned mesh와 Static mesh로 구현되어 있습니다.

Skinned mesh는 그림의 사람처럼 Bone이라는 뼈대로 구성된 것 위에 영향을 받는 Vertex들이 구성되어 있는 mesh 구조입니다. 투구나 갑옷 등 몸에 착용되는 아이템은 1개의 뼈대를 가지는 아이템으로 구성되는 경우가 많습니다.
Static mesh는 우측 하단 그림의 칼처럼 Bone이 없이 Vertex들로만 구성된 Mesh Object 구조입니다.

Skinned mesh, 즉 사람 GameObject에 아이템을 입히는 효과를 처리하려면, Human Bone 구조를 가지는 것 하나에 아이템들을 얻혀서 하나처럼 조합시키는 작업을 해주어야 합니다.




EquipmentCombiner가 뼈대 정보를 가지고 있다고 보면 됩니다.


public class EquipmentCombiner_New
{
    private readonly Dictionary<int, Transform> rootBoneDictionary = new Dictionary<int, Transform>(); // 모든 뼈대 정보
    private readonly Transform transform;
    
    public EquipmentCombiner_New(GameObject rootGO) {
        transform = rootGO.transform;
        TraverseHierachy(transform);
    }
    
    public Transform AddLimb(GameObject itemGO, List<string> boneNames) {
        Transform limb = ProcessBoneObject(itemGO.GetComponentInChildren<SkinnedMeshRenderer>(), boneNames);
        limb.SetParent(transform);
        return limb;
    }
    
    // 새로운 게임 오브젝트에 기존의 Skinned Mesh를 복사.
    private Transform ProcessBoneObject(SkinnedMeshRenderer renderer, List<string> boneNames) {
        Transform itemTransform = new GameObject().transform;
        SkinnedMeshRenderer meshRenderer = itemTransform.gameObject.AddComponent<SkinnedMeshRenderer>();
        Transform[] boneTransforms = new Transform[boneNames.Count];
        for (int i = 0; i < boneNames.Count; i++) {
            boneTransforms[i] = rootBoneDictionary[boneNames[i].GetHashCode()];
        }
        
        meshRenderer.bones = boneTransforms;
        meshRenderer.sharedMesh = renderer.sharedMesh;
        meshRenderer.materials = renderer.sharedMaterials;
        
        return itemTransform;
    }
   
    // Bone을 포함하지 않는 Static Mesh 처리
    public Transform[] AddMesh(GameObject itemGO) {
        Transform[] itemTransforms = ProcessMeshObject(itemGO.GetComponentsInChildren<MeshRenderer>());
        return itemTransforms;
    }
    
    public Transform[] ProcessMeshObject(MeshRenderer[] meshRenderers) {
        List<Transform> itemTransforms = new List<Transform>();
        foreach (MeshRenderer renderer in meshRenderers) {
            if (renderer.transform.parent != null) {
                Transform parent = rootBoneDictionary[renderer.transform.parent.name.GetHashCode()];
                GameObject itemGO = GameObject.Instantiate(renderer.gameObject, parent);
                itemTransforms.Add(itemGO.transform);
            }
        }
        return itemTransforms.ToArray();
    }
    
    private void TraverseHierachy(Transform root) {
        foreach (Transform child in root) {
            rootBoneDictionary.Add(child.name.GetHashCode(), child);
            TraverseHierachy(child);
        }
    }
}




public class PlayerEquipment_New : MonoBehaviour
{
    public InventoryObject equipment;
    private EquipmentCombiner_New combiner;
    private ItemInstances_New[] itemInstances = new ItemInstances_New[8];
    public ItemObject[] defaultItemObjects = new ItemObject[8];
}

여기까지가 구현의 일부입니다. 다음 시간에 이어서 구현을 합니다.



오늘은 굉장히 중요한 내용들이 많았습니다. 작성하기도 힘들었네요 헉헉..
인벤토리 구성을 위한 스크립트와 Unity 설정까지 모두 알아보았으며,
인벤토리에서 장비장착 인벤토리로 아이템을 설정할 때 실제 캐릭터에 장착되는 구현을 하고 있습니다.
그러다보니 중요한 내용도 많고 코드도 많고, Unity 설정도 엄청 많습니다. ㅎㅎ
실제로 처음부터 백지에서 시작해서 구현하려면 엄청난 시간이 필요할 것 같네요 ㅠ.,ㅜ;





<위의 코드들은 제가 보면서 주요한 함수, 코드를 확인하기 위해 타이핑한 용도로, 전체 소스코드가 아님에 주의해 주세요. 전체 코드는 교육 수강을 하면 완벽하게 받으실 수가 있답니다 ^^>

패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0

 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

+ Recent posts