[C#] 디아블로 스타일 인벤토리 만들기

안녕하십니까! 밤말팅입니다! 두 번째 포스팅으로 만나 뵙게 되었네요~ 오랜만에 들고 온 컨텐츠는 바로! [디아블로 스타일 인벤토리 만들기]입니다! 가방 크기는 전혀 신경 쓰지 않으셔도 되겠

game-part-factory.tistory.com

이 글은 위에서 설명하는 클래스의 전체 코드입니다!

 

내용이 궁금하시면 해당 포스팅을 참고해주시면 감사드리겠습니다~

 

using System;
using System.Collections.Generic;
public class ItemInfo
{
    public static int nextId = 0;

    public int itemID;
    public int sizeX;
    public int sizeY;
    public int stackMax;

    public ItemInfo(int wantSizeX, int wantSizeY, int wantStackMax)
    {
        itemID = nextId++;
        sizeX = wantSizeX;
        sizeY = wantSizeY;
        stackMax = wantStackMax;
    }

    public static ItemInfo sword = new ItemInfo(1, 4, 1);
    public static ItemInfo sheild = new ItemInfo(2, 4, 1);
    public static ItemInfo armor = new ItemInfo(4, 6, 1);
    public static ItemInfo shoes = new ItemInfo(3, 3, 1);
    public static ItemInfo pants = new ItemInfo(4, 5, 1);
    public static ItemInfo bread = new ItemInfo(3, 2, 16);
}
public class ItemContain
{
    private int locationX = 0;
    private int locationY = 0;
    private int stack = 0;
    private ItemInfo itemCurrent = null;

    public ItemContain(int wantLocationX, int wantLocationY, int wantStack, ItemInfo wantItem)
    {
        locationX = wantLocationX;
        locationY = wantLocationY;
        stack = wantStack;
        itemCurrent = wantItem;
    }

    public ItemContain(int wantStack, ItemInfo wantItem)
    {
        stack = wantStack;
        itemCurrent = wantItem;
    }

    public bool ItemCheck(ItemInfo wantItem)
    {
        if (itemCurrent.itemID == wantItem.itemID) 
        {
            return true;
        };
        return false;
    }

    public int ItemStack(int wantNumber)
    {
        stack += wantNumber;
        if(stack > itemCurrent.stackMax)
        {
            int retval = stack - itemCurrent.stackMax;
            stack = itemCurrent.stackMax;
            return retval;
        }
        else
        {
            return 0;
        };
    }

    public int ItemDestack(int wantNumber)
    {
        stack -= wantNumber;
        return stack;
    }

    public ItemContain ItemSplit(int wantStack)
    {
        if (stack > wantStack)
        {
            ItemDestack(wantStack);
            return new ItemContain(locationX, locationY, wantStack, itemCurrent);
        }
        else
        {
            return this;
        };
    }

    public bool ItemOverlap(int targetLocationX, int targetLocationY)
    {
        if(targetLocationX >= locationX && targetLocationX < locationX + itemCurrent.sizeX && targetLocationY >= locationY && targetLocationY < locationY + itemCurrent.sizeY)
        {
            return true;
        }
        else
        {
            return false;
        };
    }

    public bool ItemOverlap(int targetLocationX, int targetLocationY, int targetSizeX, int targetSizeY)
    {
        if(targetLocationX >= locationX + itemCurrent.sizeX || targetLocationX + targetSizeX <= locationX || targetLocationY >= locationY + itemCurrent.sizeY || targetLocationY + targetSizeY <= locationY)
        {
            return false;
        }
        else
        {
            return true;
        };
    }

    public int GetItemStack()
    {
        return stack;
    }

    public ItemInfo GetItemInfo()
    {
        return itemCurrent;
    }

    public int GetLocationX()
    {
        return locationX;
    }

    public int GetLocationY()
    {
        return locationY;
    }

    public int GetSizeX()
    {
        return itemCurrent.sizeX;
    }

    public int GetSizeY()
    {
        return itemCurrent.sizeY;
    }

    public void SetItemStack(int wantStack)
    {
        stack = wantStack;
    }

    public void SetLocation(int wantX, int wantY)
    {
        locationX = wantX;
        locationY = wantY;
    }
}
public class XFinder
{
    public int startX;
    public int lastX;

    public XFinder(int wantStartX, int wantLastX)
    {
        startX = wantStartX;
        lastX = wantLastX;
    }
}
public class InventoryBase
{
    public List<ItemContain> itemList = new List<ItemContain>();
    public int inventorySizeX;
    public int inventorySizeY;
    
    public bool ItemPlacementFinder(int wantSizeX, int wantSizeY, out int returnLocationX, out int returnLocationY)
    {
        if(itemList.Count <= 0)
        {
            returnLocationX = 0;
            returnLocationY = 0;
            return true;
        }
        else
        {
            int currentFindX;
            int ignoreIndex = 0;
            List<XFinder> finderList = new List<XFinder>();
            List<int> heightList = new List<int>();
            heightList.Add(0);

            for(int heightIndex = 0; heightIndex < heightList.Count; ++heightIndex)
            {
                if (heightList[heightIndex] + wantSizeY > inventorySizeY)
                {
                    returnLocationX = 0;
                    returnLocationY = 0;
                    return false;
                };

                currentFindX = 0;

                int targetLastY;

                for (int i = ignoreIndex; i < itemList.Count; ++i)
                {
                    if (itemList[i].GetLocationY() == heightList[heightIndex])
                    {
                        targetLastY = itemList[i].GetLocationY() + itemList[i].GetSizeY();

                        HeightSave(heightList, heightIndex, targetLastY);

                        if ((itemList[i].GetLocationX() - currentFindX) >= wantSizeX)
                        {
                            finderList.Add(new XFinder(currentFindX, itemList[i].GetLocationX()));
                        };
                        currentFindX = itemList[i].GetLocationX() + itemList[i].GetSizeX();
                        ignoreIndex = i;
                    }
                    else if (itemList[i].GetLocationY() > heightList[heightIndex])
                    {
                        HeightSave(heightList, heightIndex, itemList[i].GetLocationY());
                        break;
                    };
                };
                if ((inventorySizeX - currentFindX) >= wantSizeX)
                {
                    finderList.Add(new XFinder(currentFindX, inventorySizeX));
                };

                bool leftPass;
                bool rightPass;
                for (int i = 0; i < itemList.Count; ++i)
                {
                    if (itemList[i].GetLocationY() < heightList[heightIndex] + wantSizeY)
                    {
                        if (itemList[i].GetLocationY() + itemList[i].GetSizeY() > heightList[heightIndex])
                        {
                            for (int j = 0; j < finderList.Count; ++j)
                            {
                                if (itemList[i].GetLocationX() + itemList[i].GetSizeX() >= finderList[j].startX && itemList[i].GetLocationX() <= finderList[j].lastX)
                                {
                                    leftPass = (itemList[i].GetLocationX() - finderList[j].startX) >= wantSizeX;
                                    rightPass = (finderList[j].lastX - (itemList[i].GetLocationX() + itemList[i].GetSizeX())) >= wantSizeX;

                                    if (rightPass)
                                    {
                                        if (leftPass)
                                        {
                                            finderList.Insert(j + 1, new XFinder(itemList[i].GetLocationX() + itemList[i].GetSizeX(), finderList[j].lastX));
                                            finderList[j].lastX = itemList[i].GetLocationX();
                                        }
                                        else
                                        {
                                            finderList[j].startX = itemList[i].GetLocationX() + itemList[i].GetSizeX();
                                        };
                                    }
                                    else
                                    {
                                        if (leftPass)
                                        {
                                            finderList[j].lastX = itemList[i].GetLocationX();
                                        }
                                        else
                                        {
                                            finderList.RemoveAt(j);
                                        };
                                    };
                                };
                            };
                        };
                    }
                    else
                    {
                        break;
                    };
                };

                if (finderList.Count > 0)
                {
                    returnLocationX = finderList[0].startX;
                    returnLocationY = heightList[heightIndex];
                    return true;
                };
            };
        };
        returnLocationX = 0;
        returnLocationY = 0;
        return false;
    }
    
    public bool ItemPlacement(ItemContain wantItem, int wantX, int wantY)
    {
        int touchItemIndex;

        if (ItemCollision(wantX,wantY,wantItem.GetSizeX(),wantItem.GetSizeY(),out touchItemIndex) > 0)
        {
            ItemContain itemCheck = itemList[touchItemIndex];

            if (itemCheck != null && itemCheck.ItemCheck(wantItem.GetItemInfo()))
            {
                wantItem.SetItemStack(itemCheck.ItemStack(wantItem.GetItemStack()));
                
                if(wantItem.GetItemStack() <= 0)
                {
                    return true;
                }
                else
                {
                    return false;
                };
            }
            else
            {
                return false;
            };
        }
        else
        {
            if (wantY + wantItem.GetSizeY() > inventorySizeY || wantX + wantItem.GetSizeX() > inventorySizeX)
            {
                return false;
            }
            else
            {
                ItemInsertList(wantItem, wantX, wantY);
                wantItem.SetLocation(wantX, wantY);
                return true;
            };
        };
    }
    
    public int ItemStackCheck(ItemInfo wantItem)
    {
        int itemLeft = 0;

        for (int i = 0; i < itemList.Count; ++i)
        {
            if (itemList[i].ItemCheck(wantItem))
            {
                itemLeft += itemList[i].GetItemStack();
            };
        };

        return itemLeft;
    }
    
    public int ItemStack(ItemInfo wantItem, int wantStack)
    {
        int itemLeft = wantStack;
        for (int i = 0; i < itemList.Count; ++i)
        {
            if (itemList[i].ItemCheck(wantItem))
            {
                itemLeft = itemList[i].ItemStack(itemLeft);
                if (itemLeft <= 0)
                {
                    return 0;
                };
            };
        };

        return itemLeft;
    }
    
    public int ItemDestack(ItemInfo wantItem, int wantStack)
    {
        int itemLeft = wantStack;
        int itemNumberChecker;
        for(int i = itemList.Count -1; i >= 0; --i)
        {
            if (itemList[i].ItemCheck(wantItem))
            {
                itemNumberChecker = itemList[i].ItemDestack(itemLeft);
                if (itemNumberChecker <= 0)
                {
                    itemLeft = Math.Abs(itemNumberChecker);
                    ItemRemoveList(i);
                }
                else
                {
                    return 0;
                };

                if (itemLeft <= 0)
                {
                    return 0;
                };
            };
        };
        return itemLeft;
    }
    
    public bool ItemInsertList(ItemContain wantItem, int wantX, int wantY)
    {
        int targetIndex = 0;

        int targetLocationX;
        int targetLocationY;

        for(int i = 0; i < itemList.Count; ++i)
        {
            targetLocationY = itemList[i].GetLocationY();
            if(wantY == targetLocationY)
            {
                targetLocationX = itemList[i].GetLocationX();
                if(wantX < targetLocationX)
                {
                    targetIndex = i;
                    break;
                }
                else if(wantX > targetLocationX)
                {
                    targetIndex = i + 1;
                }
                else
                {
                    return false;
                };
            }
            else if (wantY < targetLocationY)
            {
                targetIndex = i;
                break;
            }
            else
            {
                targetIndex = i + 1;
            };
        };

        if(targetIndex >= itemList.Count)
        {
            itemList.Add(wantItem);
        }
        else
        {
            itemList.Insert(targetIndex, wantItem);
        };

        return true;
    }
    
    public ItemContain ItemRemoveList(int wantIndex)
    {
        ItemContain resultItem = itemList[wantIndex];

        itemList.RemoveAt(wantIndex);

        return resultItem;
    }

    
    public void ItemRemoveList(ItemInfo wantItem)
    {
        for (int i = 0; i < itemList.Count; ++i)
        {
            if (itemList[i].ItemCheck(wantItem))
            {
                ItemRemoveList(i);
            };
        };
    }
    
    public int ItemCollision(int wantX, int wantY, int wantSizeX, int wantSizeY, out int firstTouch)
    {
        if(wantY + wantSizeY > inventorySizeY)
        {
            firstTouch = -4;
            return -1024;
        };
        int itemTouch = 0;
        firstTouch = -4;

        for(int i = 0; i < itemList.Count; ++i)
        {
            if (itemList[i].ItemOverlap(wantX, wantY, wantSizeX, wantSizeY))
            {
                ++itemTouch;
                if(firstTouch == -4)
                {
                    firstTouch = i;
                };
            };
        };

        return itemTouch;
    }
    
    public ItemContain ItemFind(int wantX, int wantY)
    {
        for (int i = 0; i < itemList.Count; ++i)
        {
            if (itemList[i].ItemOverlap(wantX, wantY))
            {
                return itemList[i];
            };
        };

        return null;
    }
    
    public ItemContain ItemFind(int wantX, int wantY, out int returnIndex)
    {
        for (int i = 0; i < itemList.Count; ++i)
        {
            if (itemList[i].ItemOverlap(wantX, wantY))
            {
                returnIndex = i;
                return itemList[i];
            };
        };

        returnIndex = -4;
        return null;
    }

    public void HeightSave(List<int> heightList, int findStartIndex, int targetY)
    {
        for (int j = findStartIndex; j < heightList.Count; ++j)
        {
            if (targetY == heightList[j])
            {
                return;
            }
            else if (targetY < heightList[j])
            {
                heightList.Insert(j, targetY);
                return;
            };
        };

        heightList.Add(targetY);
    }
}
public class Inventory
{
    public InventoryBase currentBase = new InventoryBase();
    public ItemContain itemGrab;

    public int ItemGet(ItemInfo wantItem, int wantStack)
    {
        int itemLeft = wantStack;

        if (wantItem.stackMax > 1)
        {
            itemLeft = currentBase.ItemStack(wantItem, wantStack);
        };

        int targetLocationX;
        int targetLocationY;

        while(itemLeft > 0)
        {
            if (currentBase.ItemPlacementFinder(wantItem.sizeX, wantItem.sizeY, out targetLocationX, out targetLocationY))
            {
                currentBase.ItemPlacement(new ItemContain(Math.Min(itemLeft, wantItem.stackMax), wantItem), targetLocationX, targetLocationY);
                itemLeft -= wantItem.stackMax;
            }
            else if(itemGrab == null)
            {
                itemGrab = new ItemContain(0, wantItem);
                itemLeft = itemGrab.ItemStack(itemLeft);
            }
            else
            {
                return itemLeft;
            };
        };

        return 0;
    }

    public void ItemRemove(ItemInfo wantItem, int wantStack)
    {
        int stackLeft = wantStack;

        if (itemGrab.ItemCheck(wantItem))
        {
            itemGrab.ItemDestack(stackLeft);

            if(itemGrab.GetItemStack() < 0)
            {
                stackLeft = Math.Abs(itemGrab.GetItemStack());
                itemGrab = null;
            }
            else
            {
                return;
            };
        };

        currentBase.ItemDestack(wantItem, stackLeft);
    }

    public void ItemRemove(ItemInfo wantItem)
    {
        if (itemGrab.ItemCheck(wantItem))
        {
            itemGrab = null;
        };

        currentBase.ItemRemoveList(wantItem);
    }

    public int ItemStackCheck(ItemInfo wantItem)
    {
        int returnValue = currentBase.ItemStackCheck(wantItem);

        if (itemGrab.ItemCheck(wantItem))
        {
            returnValue += itemGrab.GetItemStack();
        };

        return returnValue;
    }

    public ItemContain ItemThrow()
    {
        ItemContain returnItem = itemGrab;
        itemGrab = null;
        return returnItem;
    }

    public void ItemUngrab()
    {
        if (itemGrab != null && currentBase.ItemPlacement(itemGrab, itemGrab.GetLocationX(), itemGrab.GetLocationY()))
        {
            itemGrab = null;
        };
    }

    public ItemContain ItemInfo(int wantX, int wantY)
    {
        return currentBase.ItemFind(wantX, wantY);
    }

    public void ItemClickLeft(int wantX, int wantY)
    {
        if (itemGrab != null)
        {
            if (currentBase.ItemPlacement(itemGrab, wantX, wantY))
            {
                itemGrab = null;
            }
            else
            {
                int touchItemIndex;
                if (currentBase.ItemCollision(wantX, wantY, itemGrab.GetSizeX(), itemGrab.GetSizeY(), out touchItemIndex) <= 1)
                {
                    if (currentBase.itemList[touchItemIndex].ItemCheck(itemGrab.GetItemInfo()))
                    {
                        if(currentBase.itemList[touchItemIndex].GetItemStack() >= itemGrab.GetItemInfo().stackMax)
                        {
                            ItemContain targetItem = currentBase.ItemRemoveList(touchItemIndex);

                            if (currentBase.ItemPlacement(itemGrab, wantX, wantY))
                            {
                                itemGrab = targetItem;
                            }
                            else
                            {
                                currentBase.ItemInsertList(targetItem, targetItem.GetLocationX(), targetItem.GetLocationY());
                            };
                        }
                        else
                        {
                            itemGrab.SetItemStack(currentBase.itemList[touchItemIndex].ItemStack(itemGrab.GetItemStack()));
                        };
                    }
                    else
                    {
                        ItemContain targetItem = currentBase.ItemRemoveList(touchItemIndex);

                        if (currentBase.ItemPlacement(itemGrab, wantX, wantY))
                        {
                            itemGrab = targetItem;
                        }
                        else
                        {
                            currentBase.ItemInsertList(targetItem, targetItem.GetLocationX(), targetItem.GetLocationY());
                        };
                    };
                };
            };
        }
        else
        {
            int targetIndex;
            ItemContain targetContain = currentBase.ItemFind(wantX, wantY, out targetIndex);

            if (targetContain != null)
            {
                itemGrab = targetContain;
                currentBase.ItemRemoveList(targetIndex);
            };
        };
    }

    public void ItemClickRight(int wantX, int wantY)
    {
        if(itemGrab != null)
        {
            if (currentBase.ItemPlacement(itemGrab.ItemSplit(1), wantX, wantY)) 
            {
                if (itemGrab.GetItemStack() <= 0)
                {
                    itemGrab = null;
                };
            }
            else
            {
                itemGrab.ItemStack(1);
            };
        }
        else
        {
            int targetIndex;
            ItemContain targetContain = currentBase.ItemFind(wantX, wantY, out targetIndex);

            if (targetContain != null)
            {
                if(targetContain.GetItemStack() > 1)
                {
                    itemGrab = targetContain.ItemSplit((int)Math.Floor(targetContain.GetItemStack() / 2.0f));
                }
                else
                {
                    itemGrab = targetContain;
                    currentBase.ItemRemoveList(targetIndex);
                };
            };
        };
    }
}

'부품 설계도' 카테고리의 다른 글

[C#] 대화창 전체 코드  (0) 2021.01.30
[C#]Bit_Builder 전체 코드  (0) 2020.11.15

디아블로 스타일 인벤토리 만들기

안녕하십니까! 밤말팅입니다!

두 번째 포스팅으로 만나 뵙게 되었네요~ 오랜만에 들고 온 컨텐츠는 바로!

[디아블로 스타일 인벤토리 만들기]입니다!

 

가방 크기는 전혀 신경 쓰지 않으셔도 되겠습니다~

크기가 얼마나 크던지 상관없는 리스트형 인벤토리를 만들 예정이니까요!

 

한 칸에 한 아이템씩 들어가는 일반적인 인벤토리의 경우에는 2차원 배열이 참 안성맞춤이죠?

그런데 디아블로와 같은 아이템 창을 만들려고 한다면?

아이템 하나를 여러 배열에 넣으려고 하면 무결성도 걱정되고 주울 때 자동으로 빈 공간을 찾기도 힘들다!

가 주된 문제가 되겠네요~

 

아이템 하나당 하나의 인스턴스로 관리를 하면서, 동시에 아이템을 잘 주울 수 있는 방법을 찾아봅시다!

 

전체 코드를 보고싶으신 분들은 아래 글을 참조해주시면 되겠습니다~

 

[C#] 디아블로 스타일 인벤토리 전체 코드

[C#] 디아블로 스타일 인벤토리 만들기 안녕하십니까! 밤말팅입니다! 두 번째 포스팅으로 만나 뵙게 되었네요~ 오랜만에 들고 온 컨텐츠는 바로! [디아블로 스타일 인벤토리 만들기]입니다! 가방

game-part-factory.tistory.com

 

[이 클래스는 아래와 같은 네임스페이스 참조가 필요합니다]

using System;
using System.Collections.Generic;

 

<목표>

20x20 인벤토리가 다 찰 때까지 무작위 아이템을 획득시킨 모습

//왼쪽 클릭입니다.
ItemClickLeft(x, y);

//오른쪽 클릭입니다.
ItemClickRight(x, y);

//아이템 획득
ItemGet(ItemInfo.bread, 30);

//아이템 소모
ItemRemove(ItemInfo.bread, 15);

//해당 아이템 갯수 확인
ItemStackCheck(ItemInfo.bread);

//집은 아이템을 버립니다
ItemThrow();

//집은 아이템을 다시 놓습니다
ItemUngrab();

//해당 위치의 아이템 정보를 받습니다
ItemInfo();

이렇게만 써도 상호작용할 수 있는 인벤토리를 만듭시다!

 

아이템을 획득해서 놓을 수 있는 제일 왼쪽 위에 넣어주고
아이템을 획득해서 겹쳐줄 수 있는 만큼 겹쳐주고
해당 위치에 있는 아이템을 집어주고
해당 위치에 있는 아이템 정보를 알려주고
해당 위치에 있는 아이템과 들고 있는 아이템을 바꿔주고
해당 위치에 있는 아이템에 들고 있는 아이템을 겹쳐주고
해당 위치에 아이템을 놓아주고
원하는 아이템의 정보를 알려주고
원하는 아이템의 개수를 알려주고
원하는 아이템의 개수를 줄여주고
원하는 아이템을 인벤토리에서 없애주고

하는 것이 이번 인벤토리의 목표입니다!

 

 

<구상>

 

아이템의 순서

우선 상단부터 아이템이 들어가야하기 때문에, y축을 기준으로 아이템 순서를 매기도록 합시다.

그리고 마찬가지로 y축이 같은 아이템이 있는 경우 x축을 기준으로 다시 매기도록 합니다.

이렇게 하면, 리스트에서 좌상단->우하단 순서로 아이템을 정렬할 수 있게 됩니다!

 

모든 아이템은 직사각형으로 구성하며, 충돌은 위치와 사이즈로 확인합니다.

네 개의 조건 중 하나라도 만족하면 충돌하지 않는 것으로 친다

x좌표와 y좌표를 대조해서 확인해볼 수 있겠습니다!

 

 

그리고 아이템을 넣기 위해 빈 공간을 찾는 과정은 다음과 같습니다.

원하는 y축에 2개의 아이템이 있다

1. 찾으려는 y축에 있는 아이템을 모두 확인합니다.

 

아이템 사이에 거리를 구한다

2. 아이템과 인벤토리 양 끝을 비교해서 거리를 구합니다.

    이 때! 들어가려는 아이템 크기보다 거리가 작은 경우 해당 위치를 삭제합니다.

 

해당 y축 외에 충돌할 가능성이 있는 아이템을 확인했더니 아이템3이 감지되었다

3. 현재 위치에 아이템을 넣었을 때, 충돌하는 아이템을 모두 확인합니다.

    확인된 아이템이 표시된 거리 안에 있는 경우, 쪼개서 관리합니다.

    물론 이 때에도 아이템 크기보다 작은 거리가 되는 경우 삭제합니다.

 

 

최종적으로 살아남은 아이템을 넣을 수 있는 2개의 공간

4. 충돌하는 아이템을 모두 검출하였고, 아이템을 넣을 크기가 안되는 거리를 모두 제한 후에

   살아남은 위치 중 첫 번째 위치에 아이템을 넣으면 됩니다!

 

아이템이 추가된 모습

그럼 결과적으로 이런 모습이 되겠죠!

 

이런 느낌으로 아이템을 찾고, 집고, 얻고 한다면 인벤토리가 대충 될 것 같습니다.

 

 

 

<구현>

 

public class ItemInfo
{
    public static int nextId = 0;

    public int itemID;
    public int sizeX;
    public int sizeY;
    public int stackMax;

    public ItemInfo(int wantSizeX, int wantSizeY, int wantStackMax)
    {
        itemID = nextId++;
        sizeX = wantSizeX;
        sizeY = wantSizeY;
        stackMax = wantStackMax;
    }
}

가볍게 아이템 정보를 넣는 칸을 넣어봅시다.

 

간단하게 아이템 ID 사이즈, 최대 개수를 정의하도록 하겠습니다.

 

더 필요한 사항은 그냥 여기에다가 넣으시면 됩니다!

 

그리고 아이템 ID의 경우, 지금은 임시로 만든 거라 그냥 만든 순서대로 부여하고 있지만

그냥 직접 입력해서 원하는 ID를 넣으시는 것이 좋습니다~

 

public class ItemContain
{
    private int locationX = 0;
    private int locationY = 0;
    private int stack = 0;
    private ItemInfo itemCurrent = null;

    public ItemContain(int wantLocationX, int wantLocationY, int wantStack, ItemInfo wantItem)
    {
        locationX = wantLocationX;
        locationY = wantLocationY;
        stack = wantStack;
        itemCurrent = wantItem;
    }

    public ItemContain(int wantStack, ItemInfo wantItem)
    {
        stack = wantStack;
        itemCurrent = wantItem;
    }
}

그리고 위의 클래스는 실제로 아이템이 있는 내용입니다.

아이템의 위치와 개수, 정보를 표시합니다.

 

public int GetItemStack()
{
	return stack;
}

public ItemInfo GetItemInfo()
{
	return itemCurrent;
}

public int GetLocationX()
{
	return locationX;
}

public int GetLocationY()
{
	return locationY;
}

public int GetSizeX()
{
	return itemCurrent.sizeX;
}

public int GetSizeY()
{
	return itemCurrent.sizeY;
}

public void SetItemStack(int wantStack)
{
	stack = wantStack;
}

public void SetLocation(int wantX, int wantY)
{
	locationX = wantX;
	locationY = wantY;
}

이런 식으로 Get; Set;함수도 넣어주었습니다.

 

 

 

이제 틀이 준비되었으니, 내용을 넣어보아야겠죠!

 

ItemContain 클래스에서 준비하는 함수들은

 

인벤토리 클래스에서 쓰기 위한 기본적인 단위이니, 가볍게 훑어보고 가도록 할게요~

 

public bool ItemOverlap(int targetLocationX, int targetLocationY, int targetSizeX, int targetSizeY)
{
  if(targetLocationX >= locationX + itemCurrent.sizeX || targetLocationX + targetSizeX <= locationX || targetLocationY >= locationY + itemCurrent.sizeY || targetLocationY + targetSizeY <= locationY)
  {
  	return false;
  }
  else
  {
  	return true;
  };
}

우선, 원하는 아이템의 위치와 크기를 받아온 후에

이 아이템과 충돌하는지 확인하는 함수입니다.

충돌하지 않는 경우

 

아까 올렸던 이것을 그냥 if문 한 줄에 넣어놓은 상태입니다~

 

근데 마우스 클릭을 할 때에는, 크기가 없게 되겠죠!

 

한 점과 아이템이 충돌하는지도 한 번 확인해줍시다.

 

public bool ItemOverlap(int targetLocationX, int targetLocationY)
{
  if(targetLocationX >= locationX && targetLocationX < locationX + itemCurrent.sizeX && targetLocationY >= locationY && targetLocationY < locationY + itemCurrent.sizeY)
  {
  	return true;
  }
  else
  {
  	return false;
  };
}

한 점과 아이템의 충돌은 반대로 적용해보았습니다.

 

true와 false가 아이템간 충돌과 반대로 되어있지요?

 

아이템 위치 <= 원하는 점 위치 < (아이템 위치 + 아이템 크기)

 

로 원하는 점과 현재 아이템의 충돌을 확인하도록 하고 있습니다!

 

public bool ItemCheck(ItemInfo wantItem)
{
  if (itemCurrent.itemID == wantItem.itemID) 
  {
  	return true;
  };
  return false;
}

충돌을 한 아이템이 현재 아이템이랑 같은 것인지 확인할 필요도 있겠지요?

 

그래서 ItemCheck함수를 넣었는데요!

 

지금은 ID로만 확인을 하고 있지만, ItemInfo가 조금 더 많은 정보를 가지고 있다면

 

그 정보까지 추가로 확인해주시면 되겠습니다!

 

public int ItemStack(int wantNumber)
{
  stack += wantNumber;
  if(stack > itemCurrent.stackMax)
  {
    int retval = stack - itemCurrent.stackMax;
    stack = itemCurrent.stackMax;
    return retval;
  }
  else
  {
  	return 0;
  };
}

다음은 아이템을 쌓는 함수입니다~

 

일단 원하는 개수를 넣고 시작하는데요,

 

넣고 나서 최대 개수를 넘으면, 초과 분량을 리턴하고

 

최대 개수를 넘지 않았으면 초과 분량이 없다는 의미로 0을 리턴하도록 합시다.

 

이를 이용해서 아이템이 남지 않을 때까지 계속 쌓기 시도하는 함수를 만들 예정입니다~

 

public int ItemDestack(int wantNumber)
{
	stack -= wantNumber;
	return stack;
}

뭐, 아이템을 넣는 것이 있다면, 빼는 것도 있어야겠죠!

 

아이템 제거는 리스트에서 담당할 것이니 빼고 나서

 

그냥 현재 개수만 보여주도록 하겠습니다.

 

뺀 후 결과값이 마이너스인 경우에 더 윗단계의 함수에서 처리하도록 하죠.

 

public ItemContain ItemSplit(int wantStack)
{
  if (stack > wantStack)
  {
    ItemDestack(wantStack);
    return new ItemContain(locationX, locationY, wantStack, itemCurrent);
  }
  else
  {
    return this;
  };
}

마지막으로 필요한 함수는 개수를 나눠서 두 개의 아이템으로 분리하는 기능입니다~

 

이걸로 인벤토리에 있는 아이템을 적절하게 나눠서 배치하거나

 

원하는 갯수만 이동시키는 기능을 만들 수 있게 되겠죠!

 

 

 

 

 

 

 

이렇게 아이템의 기초는 대강 다졌습니다!

 

아직까지는 포함시킨 네임스페이스를 하나도 쓰지 않았네요~

 

이제부터 본격적인 인벤토리를 들어갈 예정인데요!

 

두 겹의 클래스로 이루어진 인벤토리를 만들 겁니다~

 

방금 만든 기본적인 함수들을 이용하여 인벤토리 기본 기능을 구현하는 InventoryBase 클래스와

 

InventoryBase에서 구현된 기능으로 유저와 쉽게 상호작용하는 Inventory 클래스를 만들어봅시다!

 

public class InventoryBase
{
    public List<ItemContain> itemList = new List<ItemContain>();
    public int inventorySizeX;
    public int inventorySizeY;
}

우선 인벤토리 베이스를 살펴보도록 합시다.

 

아주 간단한 구조죠?

 

아이템을 나열할 리스트 하나와 인벤토리 총 크기만 묘사해주도록 합시다.

 

y가 같은 상태에서 x에 따라 정렬
y가 다르면 따로 계산

우선 먼저 할 일은 아이템을 리스트에 넣는 일이겠죠!

 

위에서 정리한 아이템 순서에 따라 리스트에 넣도록 합시다~

 

public bool ItemInsertList(ItemContain wantItem, int wantX, int wantY)
{
  //아이템을 넣을 최종 위치를 저장해놓읍시다.
  int targetIndex = 0;

  //비교할 대상의 위치를 표시할 x,y좌표입니다.
  int targetLocationX;
  int targetLocationY;

  //아이템의 개수만큼 계속 반복합니다.
  for(int i = 0; i < itemList.Count; ++i)
  {
    //대상의 y축 값을 받아온 후에
    targetLocationY = itemList[i].GetLocationY();
    
    //대상과 y축이 겹쳤을 경우
    if(wantY == targetLocationY)
    {
      //대상의 x축도 받아오고
      targetLocationX = itemList[i].GetLocationX();
      
      //대상의 x축이 더 클 때 해당 위치에 아이템을 삽입합니다.
      //자동으로 대상은 한 칸 뒤로 밀립니다.
      if(wantX < targetLocationX)
      {
        targetIndex = i;
        break;
      }
      //대상의 x축이 작으면 삽입 위치를 뒤로 옮기고 다음 대상을 확인하도록 합니다.
      else if(wantX > targetLocationX)
      {
        targetIndex = i + 1;
      }
      //대상의 x축이 크지도 작지도 않으면 같은 위치이므로 불가능을 알립니다.
      else
      {
        return false;
      };
    }
    //만약 대상의 y축이 더 크면 그 자리에 삽입합니다.
    else if (wantY < targetLocationY)
    {
      targetIndex = i;
      break;
    }
    //대상의 y축이 작다면 아직 삽입할 때가 아니므로 뒤로 물립니다.
    else
    {
      targetIndex = i + 1;
    };
  };
  
  //위의 과정을 거치면 삽입할 위치가 적힌 targetIndex가 넘어올 것입니다.
  //만약 삽입할 위치가 인벤토리 안에 있는 개수를 넘어갈 경우
  if(targetIndex >= itemList.Count)
  {
    //끝에 넣도록 합니다.
  	itemList.Add(wantItem);
  }
  else
  {
    //아니면 정해준 위치에 넣도록 합시다.
  	itemList.Insert(targetIndex, wantItem);
  };
  
  //마무리로 삽입에 성공했다고 알립니다.
  return true;
}

간단한 건데 각주를 계속 달았더니 조금 길어보이는군요~

 

우선 y축을 기준으로 삽입할 위치를 검색한 뒤에, 같은 y축에 아이템이 있으면 x축을 기준으로 검색합니다.

 

이렇게 하면 위 사진과 같이 정렬을 해줄 수 있겠죠!

 

삽입할 위치를 정하면 리스트에 넣는 것이 이번 함수의 전부입니다~

 

그럼 넣었으면 빼는 것도 준비해야겠죠!

 

public ItemContain ItemRemoveList(int wantIndex)
{
  ItemContain resultItem = itemList[wantIndex];

  itemList.RemoveAt(wantIndex);

  return resultItem;
}

리스트에서 제거하는 것은 뺄 위치만 알면 쉽습니다~

 

어차피 다른 함수에서 빼는 위치를 정해줄 것이므로, 위치만 받는 삭제 함수를 넣도록 하죠!

 

대신 뺄 때, 해당 아이템을 반환해주면 유용하게 쓸 수 있겠네요~

 

public void ItemRemoveList(ItemInfo wantItem)
{
  for (int i = 0; i < itemList.Count; ++i)
  {
    if (itemList[i].ItemCheck(wantItem))
    {
    	ItemRemoveList(i);
    };
  };
}

마찬가지로 리스트에서 제거하는 기능을 하나 더 쓸건데요,

 

위치가 아니라 그냥 아이템 종류를 넣으면 해당하는 아이템을 모두 지워버리는 것도 있으면 좋겠네요~

 

퀘스트 완료이벤트가 끝난 아이템 같은 경우 한 번에 날리는 것이 편하겠죠?

 

 

이제 다음으로 구현해 볼 것은 아이템 개수가 무난하고 좋을 것 같네요!

 

우선 아이템 개수를 확인하는 것부터 만들어봅시다.

public int ItemStackCheck(ItemInfo wantItem)
{
  int itemLeft = 0;

  for (int i = 0; i < itemList.Count; ++i)
  {
    if (itemList[i].ItemCheck(wantItem))
    {
    	itemLeft += itemList[i].GetItemStack();
    };
  };

  return itemLeft;
}

아이템 개수 확인 쯤이야 뭐~

 

모든 아이템을 둘러보고 원하는 아이템이면 개수를 카운트해주면 되는 것이지요!

 

가볍게 넘어갑니다.

 

public int ItemStack(ItemInfo wantItem, int wantStack)
{
  int itemLeft = wantStack;
  
  for (int i = 0; i < itemList.Count; ++i)
  {
    if (itemList[i].ItemCheck(wantItem))
    {
      itemLeft = itemList[i].ItemStack(itemLeft);
      
      if (itemLeft <= 0)
      {
      	return 0;
      };
    };
  };

  return itemLeft;
}

그렇다면 아이템을 일정 수량만큼 증가시켜주도록 합시다!

 

원하는 양을 itemLeft에 기록을 해준 뒤에

 

개수 확인할 때처럼 모든 아이템을 돌면서 맞는지 확인합니다.

 

그리고 냅다 아이템을 넣어버립니다!

 

그러면 아이템 개수를 늘려주는 함수를 썼을 때, 초과분량을 반환했었죠!

 

그걸 itemLeft에 다시 넣어주면,

 

자연스럽게 최대치만큼 넣고 남는 값을 알 수 있게 됩니다~

 

남은 값이 없을 때까지 계속 돌면 되겠네요!

 

만약 값이 남은 상태로 모든 아이템을 확인했다면, 남은 양을 반환해줍시다!

 

 

public int ItemDestack(ItemInfo wantItem, int wantStack)
{
  int itemLeft = wantStack;
  int itemNumberChecker;
  
  for(int i = itemList.Count -1; i >= 0; --i)
  {
    if (itemList[i].ItemCheck(wantItem))
    {
      itemNumberChecker = itemList[i].ItemDestack(itemLeft);
      
      //여기서부터
      if (itemNumberChecker <= 0)
      {
          itemLeft = Math.Abs(itemNumberChecker);
          ItemRemoveList(i);
      }
      else
      {
          return 0;
      };
      //여기까지가 증가와 다릅니다!

      if (itemLeft <= 0)
      {
          return 0;
      };
    };
  };
  return itemLeft;
}

그럼 다음은 자연스럽게 아이템을 일정 수량만큼 감소시켜봅시다!

 

증가와는 다르게 하나의 과정이 더 추가되는데요~

 

아이템 개수를 확인해서 리스트에서 제거해주는 것입니다!

 

가운데에 표시되어있는 부분이 그 리스트에서 제거해주는 내용이 되겠네요~

 

ItemDestack함수는 빼고나서 해당 아이템에 남은 개수를 반환합니다.

 

남은 개수가 음수라면, 아직 뺄 것이 남았는데 못 뺐다는 의미이기 때문에

 

반환된 값을 양수로 바꿔줘서 못 뺀 수량을 기록한 뒤

 

음수가 된 아이템을 리스트에서 삭제해주기만 하면 됩니다!

 

그리고 아이템을 계속 돌면서 뺄 아이템을 찾는 것이지요!

 

근데 혹시 눈치 채셨나요?

 

for(int i = itemList.Count -1; i >= 0; --i)

 

감소에서는 반복문이 조금 달랐습니다!

 

아이템 개수 감소는 뒤에서부터 되는 것이 조금 더 자연스럽기 때문에,

 

뒤에서부터 검색하는 것으로 조정해두었습니다!

 

유의해두시면 좋겠네요~

 

public ItemContain ItemFind(int wantX, int wantY)
{
  for (int i = 0; i < itemList.Count; ++i)
  {
    if (itemList[i].ItemOverlap(wantX, wantY))
    {
    	return itemList[i];
    };
  };

  return null;
}

다음은 원하는 위치에 있는 아이템 찾기를 만듭시다.

 

그냥 돌면서 해당 위치에 닿는 녀석이 있다면, 알려줄 수 있겠죠?

 

 

public ItemContain ItemFind(int wantX, int wantY, out int returnIndex)
{
  for (int i = 0; i < itemList.Count; ++i)
  {
    if (itemList[i].ItemOverlap(wantX, wantY))
    {
      returnIndex = i;
      return itemList[i];
    };
  };

  returnIndex = -4;
  return null;
}

똑같이 찾는 것인데, 아이템 리스트에 들어있는 순서까지 알려주는 함수도 같이 만들어 둡시다.

 

 

 

public int ItemCollision(int wantX, int wantY, int wantSizeX, int wantSizeY, out int firstTouch)
{
  //만약 체크하는 범위가 인벤토리를 넘어서면 음수값을 반환합니다.
  if(wantY + wantSizeY > inventorySizeY)
  {
    firstTouch = -4;
    return -1024;
  };
  
  int itemTouch = 0;
  firstTouch = -4;

  for(int i = 0; i < itemList.Count; ++i)
  {
    //현재 아이템과 겹치는지 확인합니다.
    if (itemList[i].ItemOverlap(wantX, wantY, wantSizeX, wantSizeY))
    {
      //겹치면 겹친 개수를 늘려줍니다.
      ++itemTouch;
      
      //닿은 아이템을 표시해둔 것이 없다면, 이 아이템을 표시합니다.
      if(firstTouch == -4)
      {
        firstTouch = i;
      };
    };
  };

  //겹친 아이템의 개수를 반환합니다.
  return itemTouch;
}

원하는 위치의 아이템을 찾았다면,

 

원하는 범위에 있는 아이템도 찾아봐야겠죠?

 

위치와 범위를 지정해주면, 해당하는 아이템이 리스트에서 몇번째에 있는지 알려줍니다.

 

firstTouch로 값을 전해주는 것이구요, 반환값으로 몇 개가 부딪혔는지 가르쳐줄 수 있겠죠!

 

 

public bool ItemPlacement(ItemContain wantItem, int wantX, int wantY)
{
  int touchItemIndex;

  //충돌하는 아이템이 있는 경우
  if (ItemCollision(wantX,wantY,wantItem.GetSizeX(),wantItem.GetSizeY(),out touchItemIndex) > 0)
  {
    //리스트에서 해당 아이템을 불러옵니다.
    ItemContain itemCheck = itemList[touchItemIndex];

    //충돌한 아이템이 넣는 아이템과 같은 경우
    if (itemCheck != null && itemCheck.ItemCheck(wantItem.GetItemInfo()))
    {
      //충돌한 아이템에 넣는 아이템의 개수만큼 더합니다.
      //넘치는 분량은 다시 가져옵니다.
      wantItem.SetItemStack(itemCheck.ItemStack(wantItem.GetItemStack()));

      //만약 넘친 분량이 없는 경우 성공했다고 알립니다.
      if(wantItem.GetItemStack() <= 0)
      {
        return true;
      }
      //넘쳐버렸다면 모두 넣는 데에 실패했다고 알립니다.
      else
      {
        return false;
      };
    }
    //충돌한 아이템이 다른 종류인 경우 그냥 실패했다고 알립니다.
    else
    {
      return false;
    };
  }
  //충돌하는 아이템이 없는 경우
  else
  {
    //인벤토리를 벗어났으면 실패했다고 알립니다.
    if (wantY + wantItem.GetSizeY() > inventorySizeY || wantX + wantItem.GetSizeX() > inventorySizeX)
    {
      return false;
    }
    //모든 상황이 정상적인 경우
    else
    {
      //아이템 리스트에 넣으려던 아이템을 넣습니다.
      ItemInsertList(wantItem, wantX, wantY);
      //원하던 위치에 아이템을 둡니다.
      wantItem.SetLocation(wantX, wantY);
      //성공했다고 알립니다.
      return true;
    };
  };
}

인벤토리 베이스도 거의 끝나가네요!

 

이젠 아이템을 배치해보도록 합시다!

 

약간 길어보일 수 있는데, 역시나 간단하게 요약을 해보면

 

1. 원하는 위치에 충돌하는 아이템이 있는지 확인합니다.

 

2. 충돌하는 아이템이 있다면, 겹쳐봅니다.

 

3. 충돌하는 아이템이 없고 인벤토리도 벗어나지 않았다면, 바로 넣습니다.

 

이 정도가 되겠네요! 어렵지 않죠?

 

 

 

 

그렇다면 인벤토리에서 제일 난이도 높은 녀석을 만날 준비가 된 것 같습니다~

 

자동으로 인벤토리에 아이템을 넣어보죠!

 

 

 

public class XFinder
{
    public int startX;
    public int lastX;

    public XFinder(int wantStartX, int wantLastX)
    {
        startX = wantStartX;
        lastX = wantLastX;
    }
}

이것은 X위치를 표시하는 클래스입니다.

 

아이템 사이에 빈 공간을 표시

아이템 사이에 선으로 표시해둔 부분저장해두기 위해서 클래스 형태로 만든 것입니다!

 

public void HeightSave(List<int> heightList, int findStartIndex, int targetY)
{
  //넣을 위치를 찾기 위해 해당 리스트에서 원하는 시작위치부터 끝까지 확인합니다.
  for (int j = findStartIndex; j < heightList.Count; ++j)
  {
    //만약, 넣을 높이가 이미 있다면, 끝냅니다
    if (targetY == heightList[j])
    {
    	return;
    }
    //만약 찾은 높이가 넣을 높이보다 크다면, 뒤로 밀어두고 그 자리에 넣습니다.
    else if (targetY < heightList[j])
    {
    	heightList.Insert(j, targetY);
    	return;
    };
  };

  //반복문을 끝내도 넣을 위치를 찾지 못해서 여기에 도달했다면
  //해당 높이가 가장 높다는 의미이므로, 맨 마지막에 넣습니다.
  heightList.Add(targetY);
}

그리고 마찬가지로 y축을 저장할 필요도 있겠죠!

 

y축은 찾을 라인만 표시하기 때문에 int하나로 사용하도록 하겠습니다~

 

작은 y값부터 나열하기 위해서 저장용 함수 하나만 만들어놓도록 하겠습니다!

 

이건 static선언으로 해놓고 아무데나 놓아도 되겠지만

 

저는 그냥 InventoryBase에 넣어둘게요!

 

이걸 가지고 아이템이 들어갈 장소를 찾는 함수를 만들어보도록 하죠!

 

 

public bool ItemPlacementFinder(int wantSizeX, int wantSizeY, out int returnLocationX, out int returnLocationY)
{
    //인벤토리에 아이템이 없다면, (0,0)에 넣습니다.
    if(itemList.Count <= 0)
    {
        returnLocationX = 0;
        returnLocationY = 0;
        return true;
    }
    else
    {
        //현재 찾는 중인 x좌표를 저장합니다.
        int currentFindX;
        
        //이미 확인한 아이템 위치를 적어둡니다.
        int ignoreIndex = 0;
        
        //가능한 x범위를 표시할 리스트입니다.
        List<XFinder> finderList = new List<XFinder>();
        
        //확인해야하는 y축 위치를 저장합니다.
        List<int> heightList = new List<int>();
        
        //최초로 확인할 y축 위치를 0으로 넣어줍니다.
        heightList.Add(0);

        //확인할 y축이 남은 경우 반복합니다.
        for(int heightIndex = 0; heightIndex < heightList.Count; ++heightIndex)
        {
            //해당 y축이 인벤토리를 넘은 경우 실패했다고 알립니다.
            if (heightList[heightIndex] + wantSizeY > inventorySizeY)
            {
                returnLocationX = 0;
                returnLocationY = 0;
                return false;
            };

            //찾는 x좌표를 0으로 초기화합니다.
            currentFindX = 0;
            
            //현재 확인하고 있는 아이템의 최종 높이를 저장합니다.
            int targetLastY;
            
            //아직 확인하지 않은 아이템들을 돌면서 범위를 탐색합니다.
            for (int i = ignoreIndex; i < itemList.Count; ++i)
            {
                //현재 확인중인 y축과 대상의 y축이 같은 경우
                if (itemList[i].GetLocationY() == heightList[heightIndex])
                {
                    //최종 높이를 설정하고, 저장 여부를 초기화합니다.
                    targetLastY = itemList[i].GetLocationY() + itemList[i].GetSizeY();
                    
                    //최종 높이를 확인할 높이에 추가합니다.
                    HeightSave(heightList, heightIndex, targetLastY);

                    //확인할 높이를 채우는 것이 끝났으니 이제 실제 비교를 시작합시다.
                    //현재 확인중인 x값과 다음 아이템의 x위치값의 거리가
                    //들어가는 아이템의 사이즈보다 크면 가능성 있는 범위로 할당합니다.
                    if ((itemList[i].GetLocationX() - currentFindX) >= wantSizeX)
                    {
                        finderList.Add(new XFinder(currentFindX, itemList[i].GetLocationX()));
                    };
                    
                    //확인중인 x값을 확인한 아이템의 끝에 맞춥니다.
                    currentFindX = itemList[i].GetLocationX() + itemList[i].GetSizeX();
                    
                    //해당 아이템을 확인했다고 알립니다.
                    ignoreIndex = i;
                }
                //만약, 더 높이 있는 아이템에 도달한 경우, 일단 탐색을 중단합니다.
                else if (itemList[i].GetLocationY() > heightList[heightIndex])
                {
                    //그리고 이번 줄을 모두 확인한 뒤에, 이 줄을 확인해야 하므로
                    //해당 높이도 확인해야한다고 알립니다.
                    HeightSave(heightList, heightIndex, itemList[i].GetLocationY());
                    break;
                };
            };
            //마지막으로 확인한 위치와 인벤토리 끝을 확인해서
            //아이템이 들어갈 공간이 있다면, 그것도 범위에 추가합니다.
            if ((inventorySizeX - currentFindX) >= wantSizeX)
            {
                finderList.Add(new XFinder(currentFindX, inventorySizeX));
            };

            //이제부터 해당 범위 안에 다른 아이템이 충돌하는지 확인하도록 하겠습니다.
            //충돌하는 경우 범위를 줄여야하기 때문에, 양 옆쪽에 남은 공간을 확인합니다.
            bool leftPass;
            bool rightPass;
            
            //아이템 전체를 돌면서 충돌여부를 확인합니다.
            for (int i = 0; i < itemList.Count; ++i)
            {
                //해당 아이템의 높이가 (넣는 높이 + 넢는 크기)보다 작으면
                //충돌할 가능성이 있는 것으로 판단합니다.
                if (itemList[i].GetLocationY() < heightList[heightIndex] + wantSizeY)
                {
                    //동시에 해당 아이템의 최종 높이가 넣는 높이보다는 높아야
                    //충돌할 가능성이 있는 것으로 봅니다.
                    if (itemList[i].GetLocationY() + itemList[i].GetSizeY() > heightList[heightIndex])
                    {
                        //충돌할 가능성이 있다고 판단한 경우, 해당 아이템을
                        //모든 범위에 대해 충돌을 확인하도록 합니다.
                        for (int j = 0; j < finderList.Count; ++j)
                        {
                            //해당 아이템의 x범위가 현재 확인 중인 범위에 닿는지 확인합니다.
                            if (itemList[i].GetLocationX() + itemList[i].GetSizeX() >= finderList[j].startX && itemList[i].GetLocationX() <= finderList[j].lastX)
                            {
                                //아이템의 시작 x위치 - 범위의 시작위치가 넣는 크기보다 크면
                                //왼쪽은 들어갈 공간이 있는 것으로 봅니다.
                                leftPass = (itemList[i].GetLocationX() - finderList[j].startX) >= wantSizeX;
                                
                                //범위의 끝위치 - 아이템의 끝위치 가 넣는 크기보다 크면
                                //오른쪽에 들어갈 공간이 있는 것으로 봅니다.
                                rightPass = (finderList[j].lastX - (itemList[i].GetLocationX() + itemList[i].GetSizeX())) >= wantSizeX;

                                //오른쪽에 공간이 있는 경우
                                if (rightPass)
                                {
                                    //오른쪽도 있고 왼쪽도 공간이 있으면, 아이템이 가운데에 있는 것으로 보고
                                    //오른쪽에 해당하는 부분을 범위에 추가하고
                                    //현재 범위를 충돌한 아이템 위치까지로 줄입니다.
                                    if (leftPass)
                                    {
                                        finderList.Insert(j + 1, new XFinder(itemList[i].GetLocationX() + itemList[i].GetSizeX(), finderList[j].lastX));
                                        finderList[j].lastX = itemList[i].GetLocationX();
                                    }
                                    //왼쪽에는 공간이 없으면 현재 범위의 시작점을 충돌한 아이템 끝에 맞춥니다.
                                    else
                                    {
                                        finderList[j].startX = itemList[i].GetLocationX() + itemList[i].GetSizeX();
                                    };
                                }
                                //오른쪽에 공간이 없는 경우
                                else
                                {
                                    //왼쪽에는 공간이 있으면 현재 범위의 끝부분을
                                    //해당 아이템의 시작부분으로 맞춥니다.
                                    if (leftPass)
                                    {
                                        finderList[j].lastX = itemList[i].GetLocationX();
                                    }
                                    //양쪽 다 공간이 없으면 해당 범위를 삭제합니다.
                                    else
                                    {
                                        finderList.RemoveAt(j);
                                    };
                                };
                            };
                        };
                    };
                }
                //해당 아이템의 높이가 (넣는 높이 + 넣는 크기)보다 크면
                //더 이상 확인할 필요가 없는 것으로 치고 반복을 종료합니다.
                else
                {
                    break;
                };
            };

            //만약 위의 과정을 거친 후에 남은 범위가 있는 경우
            //해당 y축과 첫 번째 범위의 시작점에는 아이템을 넣을 수 있는 것으로 판단하여
            //그 위치에 아이템 넣기를 시도하라고 알립니다.
            if (finderList.Count > 0)
            {
                returnLocationX = finderList[0].startX;
                returnLocationY = heightList[heightIndex];
                return true;
            };
        };
    };
    
    //중간에 아이템을 넣을 수 없었다면, 실패했다고 알립니다.
    returnLocationX = 0;
    returnLocationY = 0;
    return false;
}

 

아이템을 획득했을 때, 빈 공간을 찾아서 아이템을 넣어주는 함수입니다!

 

아주 쬐끔 길다고 느껴지실 수도 있을 것 같은데, 크게 한 번 살펴보면 쉽습니다~

 

 

우선 아이템이 없으면 그냥 (0,0)에 넣습니다! 아주 간단하죠.

 

그 다음은 인벤토리의 높이를 넘지 않을 때까지 반복해서 원하는 높이에 있는 아이템을 확인하는 작업인데요!

 

내용은 아주 간단합니다!

 

인벤토리와 넣을 아이템

이렇게 생긴 인벤토리와 아이템이 있다고 합시다.

 

일단 제일 위에 들어갈 자리가 있는지 확인하는 것이 좋겠죠!

 

해당 라인에 있는 아이템에 색을 칠해둡니다.

 

현재 확인 중인 높이의 아이템들

이제 이 아이템들의 높이를 저장해둡시다.

 

그리고, 다음 높이에 있는 아이템들도 대기열에 추가해주어야 겠죠!

아이템들의 높이를 다음에 확인할 높이로 저장

높이를 저장해둔 후에는, 진짜 계산을 시작합니다.

 

지금 선택된 아이템들 사이의 간격을 생각해보죠!

현재 높이에 있는 아이템 간격을 확인합니다.

뒤 아이템에서 앞 아이템 위치를 빼서 생각해보면, 아이템을 넣을 수 있는 공간이 있는지 확인하는 것은

 

아주 식은 죽 먹기입니다~

 

그냥 크기를 비교만 하면 되겠지요!

 

해당 범위에 충돌하는지 확인

 

그러면 이 범위에, 넣을 아이템과 같은 높이의 상자를 놓는다고 합시다.

 

두 개의 충돌하는 아이템이 발견되었네요!

 

이 아이템에 따라 범위를 조정해봅시다.

 

충돌한 아이템에 따라 범위를 조정한 모습

이런..

 

모든 범위가 넣을 아이템의 크기보다 작게 되었네요!

 

이렇게 되면, 이 높이에는 아이템을 놓을 자리가 없는 것으로 보면 되겠군요~

 

그럼 다음 대기중인 높이를 계속 확인하면서 답을 찾아봅시다.

 

모든 확인 후에도 넣을 수 있는 범위가 남은 상태

세 번째로 저장된 높이를 확인했더니

 

모든 충돌을 감안해도 두 개의 범위가 남았군요!

 

그렇다면, 망설일 것 없이 첫 번째 범위의 왼쪽 끝에 아이템을 놓고

 

높이는 확인하던 높이에 아이템을 올려주면

 

아이템을 해당 위치에 올려놓은 모습

짜잔!

 

여러 과정을 거친 끝에 원하는 아이템을 넣을 수 있는 가장 위, 가장 왼쪽에 넣는 데에 성공했습니다!

 

위에 있는 코드는 이런 과정을 써 놓은 것이니, 잘 확인해주시면 좋겠습니다!

 

각주와 그림을 잘 참고하면 어렵지 않게 이해하실 수 있을 거에요~

 

 

 

 

이제 인벤토리의 베이스는 모두 끝났습니다!

 

이제 유저와 직접적으로 대면할 인벤토리를 만들건데요!

 

기본적으로는 [베이스에서 만든 내용을 쓰기 때문]에 여기서부터는 쉬어간다는 느낌으로

 

가볍게 만들어보도록 하겠습니다~

 

public class Inventory
{
    public InventoryBase currentBase = new InventoryBase();
    public ItemContain itemGrab;
}

참으로 간단하네요!

 

제 인벤토리는 인벤토리 베이스와 더불어서 마우스에 들고 있는 아이템 하나로 만들었습니다.

 

혹시나 추가할 보조 수단이 있거나, 인벤토리를 여러개 쓰고싶으시면 추가하셔도 좋습니다!

 

public int ItemGet(ItemInfo wantItem, int wantStack)
{
  int itemLeft = wantStack;

  if (wantItem.stackMax > 1)
  {
  	itemLeft = currentBase.ItemStack(wantItem, wantStack);
  };

  int targetLocationX;
  int targetLocationY;

  while(itemLeft > 0)
  {
    if (currentBase.ItemPlacementFinder(wantItem.sizeX, wantItem.sizeY, out targetLocationX, out targetLocationY))
    {
    	currentBase.ItemPlacement(new ItemContain(Math.Min(itemLeft, wantItem.stackMax), wantItem), targetLocationX, targetLocationY);
    	itemLeft -= wantItem.stackMax;
    }
    else if(itemGrab == null)
    {
    	itemGrab = new ItemContain(0, wantItem);
    	itemLeft = itemGrab.ItemStack(itemLeft);
    }
    else
    {
    	return itemLeft;
    };
  };

  return 0;
}

일단 방금 아이템을 넣을 방법을 만들었으니

 

아이템을 획득하는 기능을 추가해보도록 하죠~

 

우선 아이템 종류와, 아이템 개수를 받아오도록 합시다.

 

이제 아이템이 남지 않을 때까지 계속 아이템을 인벤토리에 넣을 건데요!

 

아이템을 새로 만들기 전에, 이미 있는 아이템을 돌면서 빈 공간을 채워줍시다~

 

다 채워봤는데 아이템이 남는다!

 

그러면 이제 인벤토리에 새로 아이템 칸을 만들어 넣습니다~

 

넣다 보니 도저히 못 넣는데, 손이 비면 손에다가도 쥐어주고

 

그래도 남으면 남은 개수를 반환해주도록 합시다!

 

간단하네요~

 

 

public void ItemRemove(ItemInfo wantItem, int wantStack)
{
  int stackLeft = wantStack;

  if (itemGrab.ItemCheck(wantItem))
  {
    itemGrab.ItemDestack(stackLeft);

    if(itemGrab.GetItemStack() < 0)
    {
    	stackLeft = Math.Abs(itemGrab.GetItemStack());
        itemGrab = null;
    }
    else
    {
    	return;
    };
  };

  currentBase.ItemDestack(wantItem, stackLeft);
}

아이템을 얻을 수 있다면 아이템을 없애기도 할 수 있어야겠죠!

 

우선 손에 들고 있는 것이 해당 아이템인지 확인합니다.

 

맞으면, 손에 있는 것을 빼앗습니다!

 

그래도 부족하면 인벤토리에서 빼가는걸로 하죠~

 

public void ItemRemove(ItemInfo wantItem)
{
  if (itemGrab.ItemCheck(wantItem))
  {
  	itemGrab = null;
  };

  currentBase.ItemRemoveList(wantItem);
}

뭐, 모든 아이템을 없앨 때는 무자비하게 다 빼앗아갑니다.

 

그냥 종류만 맞으면 다 가져가도록 합시다!

 

public int ItemStackCheck(ItemInfo wantItem)
{
  int returnValue = currentBase.ItemStackCheck(wantItem);

  if (itemGrab.ItemCheck(wantItem))
  {
  	returnValue += itemGrab.GetItemStack();
  };

  return returnValue;
}

상황에 따라 아이템의 개수를 파악해야할 때가 있습니다!

 

퀘스트의 진척도를 파악하거나, 어떤 요구사항을 확인하거나, 아이템 몇 개를 한 번에 사용하거나 할 때 말이죠!

 

그럴 때에는, 인벤토리베이스에 있는 해당 아이템의 개수를 구한 다음

 

손에도 들고 있으면 그것도 더해주는 것으로 합시다~

 

 

public ItemContain ItemThrow()
{
  ItemContain returnItem = itemGrab;
  itemGrab = null;
  return returnItem;
}

아이템 던지기!

 

네 필요하면 손에 들고 있는 아이템을 버려줘야겠죠

 

버릴 때 그냥 버리지 말고, 들고 있던 값을 반환해서 나중에 쓰도록 합시다!

 

 

public void ItemUngrab()
{
  if (itemGrab != null && currentBase.ItemPlacement(itemGrab, itemGrab.GetLocationX(), itemGrab.GetLocationY()))
  {
  	itemGrab = null;
  };
}

아이템 다시 놓기도 만들어둡시다~

 

만약 아이템을 들고 오긴 했는데, 그냥 취소하고 다시 원래 자리에 놓고 싶다

 

아이템을 가져올 때 위치 정보는 바꾸지 않을 것이기 때문에

 

그냥 그 위치에 넣기를 시도합니다.

 

성공하면 손을 비워줍시다!

 

public ItemContain ItemInfo(int wantX, int wantY)
{
	return currentBase.ItemFind(wantX, wantY);
}

뭐... 해당 아이템 정보정도는 가져올 필요도 있죠

 

원하는 위치에 있는 정보를 가져오는 함수입니다.

 

 

public void ItemClickLeft(int wantX, int wantY)
{
  //손에 아이템이 있는 경우
  if (itemGrab != null)
  {
    //아이템을 내려 놓고, 성공하면 손을 비웁니다.
    if (currentBase.ItemPlacement(itemGrab, wantX, wantY))
    {
      itemGrab = null;
    }
    //아이템을 내려놓지 못하는 상태일 때
    else
    {
      //겹친 아이템을 저장합니다.
      int touchItemIndex;
      
      //겹친 아이템이 1이하인 경우에만 상호작용을 시도합니다.
      if (currentBase.ItemCollision(wantX, wantY, itemGrab.GetSizeX(), itemGrab.GetSizeY(), out touchItemIndex) <= 1)
      {
        //겹친 아이템이 손에 들린 것과 같으면
        if (currentBase.itemList[touchItemIndex].ItemCheck(itemGrab.GetItemInfo()))
        {
          //해당 아이템에 쌓을 수 없는 상태인 경우
          if(currentBase.itemList[touchItemIndex].GetItemStack() >= itemGrab.GetItemInfo().stackMax)
          {
            //일단 해당 아이템을 빼놓습니다.
            ItemContain targetItem = currentBase.ItemRemoveList(touchItemIndex);

            //손에 들고 있던 것을 아이템 창에 놓습니다.
            if (currentBase.ItemPlacement(itemGrab, wantX, wantY))
            {
              //성공하면 빼 놓은 아이템을 손에 듭니다.
              itemGrab = targetItem;
            }
            //실패하면 빼 놓은 아이템을 원래 자리에 둡니다.
            else
            {
              currentBase.ItemInsertList(targetItem, targetItem.GetLocationX(), targetItem.GetLocationY());
            };
          }
          //해당 아이템을 쌓을 수 있으면 쌓습니다.
          else
          {
              itemGrab.SetItemStack(currentBase.itemList[touchItemIndex].ItemStack(itemGrab.GetItemStack()));
          };
        }
        //겹친 아이템이 손에 들린 것과 다르면
        else
        {
          //겹친 아이템을 빼둡니다.
          ItemContain targetItem = currentBase.ItemRemoveList(touchItemIndex);

          //들고 있던 아이템을 놓습니다.
          if (currentBase.ItemPlacement(itemGrab, wantX, wantY))
          {
            //성공하면 빼 놓은 아이템을 손에 듭니다.
            itemGrab = targetItem;
          }
          //실패하면 빼 놓은 아이템을 다시 원래 자리에 둡니다.
          else
          {
            currentBase.ItemInsertList(targetItem, targetItem.GetLocationX(), targetItem.GetLocationY());
          };
        };
      };
    };
  }
  //손에 아이템이 없는 경우
  else
  {
    //클릭한 위치에서 아이템을 찾습니다.
    int targetIndex;
    ItemContain targetContain = currentBase.ItemFind(wantX, wantY, out targetIndex);

    //클릭한 위치에 아이템이 있는 경우
    if (targetContain != null)
    {
      //해당 아이템을 손에 들고
      itemGrab = targetContain;
      //인벤토리에서 해당 아이템을 지웁니다.
      currentBase.ItemRemoveList(targetIndex);
    };
  };
}

쉬어가려고 했는데 급발진을 해버린 감이 있네요!

 

아이템을 좌클릭한 경우를 고려해서 상황을 만들어봤습니다!

 

이것만 있으면 다른 곳에서 위치만 줘도 잘 반응하는 인벤토리가 됩니다~

 

크게 "손에 아이템이 있는 경우""손에 아이템이 없는 경우"로 나누어서 진행해보도록 하죠

 

 

[손에 아이템이 있는 경우]

아이템을 놓아봅니다.
놓는 데에 실패하면 쌓아봅니다.
쌓는 데에 실패하면 해당 아이템과 위치를 바꿉니다.
위치를 바꾸는 데 실패하면 포기합니다.

 

[손에 아이템이 없는 경우]

아이템이 있는지 확인합니다.
아이템이 있으면 들어올립니다.

 

이렇게만 해 둬도, 좌클릭으로 물건을 옮기는 것이 가능할 것 같네요~

 

들어서 옮기고, 쌓고, 바꿔치고 하는 것이 쉽습니다!

 

물론 위치를 마우스가 클릭한 곳으로 지정했기 때문에, 아이템이 치우치는 경향이 있는데요!

 

아이템 삽입을 할 때에, 원하는 만큼 잘 조정을 해서 넣어주시면 편의성이 증가하겠죠!

 

이것은 제작자 나름이기 때문에 따로 넣지 않겠습니다~

 

 

 

public void ItemClickRight(int wantX, int wantY)
{
  //손에 아이템이 있는 경우
  if(itemGrab != null)
  {
    //손에 있는 아이템을 하나 빼서 인벤토리에 넣습니다.
    if (currentBase.ItemPlacement(itemGrab.ItemSplit(1), wantX, wantY)) 
    {
      //만약 방금 뺀 걸로 남은 아이템이 없는 경우 손을 비웁니다.
      if (itemGrab.GetItemStack() <= 0)
      {
        itemGrab = null;
      };
    }
    //인벤토리에 넣지 못 한 경우, 한 개를 돌려받습니다.
    else
    {
      itemGrab.ItemStack(1);
    };
  }
  //손에 아이템이 없는 경우
  else
  {
    //해당 위치의 아이템 정보를 받아옵니다.
    int targetIndex;
    ItemContain targetContain = currentBase.ItemFind(wantX, wantY, out targetIndex);

    //받아온 아이템이 있는 경우
    if (targetContain != null)
    {
      //해당 아이템의 남은 개수가 2개 이상일 때
      if(targetContain.GetItemStack() > 1)
      {
        //해당 아이템을 반으로 나누고, 소숫점을 버린만큼 떼어옵니다.
        itemGrab = targetContain.ItemSplit((int)Math.Floor(targetContain.GetItemStack() / 2.0f));
      }
      //해당 아이템의 남은 개수가 하나일 때
      else
      {
        //인벤토리에서 빼서 손으로 가져옵니다.
        itemGrab = targetContain;
        currentBase.ItemRemoveList(targetIndex);
      };
    };
  };
}

좌클릭을 만들었으니, 우클릭을 한 경우도 만들어주면 좋겠죠!

 

마인크래프트의 인벤토리를 참고하여 만들어보았습니다~

 

[손에 아이템이 있는 경우]

들고 있는 아이템에서 하나를 떼어 인벤토리에 넣어봅니다.
넣는 데에 성공하고, 남은 아이템이 없으면 손을 비웁니다.
넣는 데에 실패하면, 떼어낸 아이템을 다시 손으로 가져옵니다.

 

[손에 아이템이 없는 경우]

해당 위치에 아이템이 있는지 확인합니다.
해당 아이템이 2개 이상 쌓인 상태이면, 절반을 손으로 가져옵니다 (소수점 버림)
해당 아이템이 1개인 경우 그 아이템을 손으로 가져옵니다.

 

이것도 물론 만들기 나름이지만, 비교적 만들기 쉬웠던 마인크래프트로 만들어봤습니다~

 

우클릭으로 정보를 나타나게 하셔도 좋고, 바로 버리게 하는 것도 좋으니

 

한 번 본인만의 기능을 만들어보는 것도 좋겠네요~

 

 

 

여기까지가 인벤토리의 구현부분이었습니다!

 

 

<마치며>

 

배열을 쓰지 않고 인벤토리를 만들려다 보니

 

여러가지 방법이 떠올라서 실험을 막 해보았는데요!

 

리스트형식이 제일 만들기 편하고 괜찮았던 것 같네요~

 

 

디아블로 방식의 인벤토리가 쬐끔 불편해서 많이 적용이 안되고 있는 것 같긴 하지만!

 

저는 좋아하므로..ㅎㅎ

 

좀 더 여러 곳에서 이런 인벤토리가 보였으면 좋겠네요!

 

 

 

그리고 읽으시면서 눈치채셨을 부분이 있는데요~

 

이 인벤토리를 그리시거나 상호작용 하시려면, 아이템의 Y위치를 신경 써주셔야 합니다!

 

아이템 위치 좌표

좌표가 이런 식으로 되어있기 때문이죠!

 

이것만 유의해주시고 본인의 환경에 맞춰서 만들어주시면 되겠습니다!

 

 

그럼 다음 포스팅에서 뵙겠습니다!

 

읽어주셔서 감사합니다!

 

 

[C#] Int를 비트단위로 저장해주는 클래스

안녕하십니까 밤말팅입니다. 블로그를 열고 첫 포스팅인데요, 앞으로 게임 개발에 도움이 될 여러 가지 클래스를 구상하고 제작하는 대로 포스팅 할 예정입니다. 내용이 괜찮다고 생각하시면

game-part-factory.tistory.com

 

이 글은 위에서 설명하는 클래스의 전체 코드입니다!

 

내용이 궁금하시면 해당 포스팅을 참고해주시면 감사드리겠습니다~

 

public enum Bit_Type
{
	FLOAT,
	BOOL
}

public class Bit_Builder
{
  private Bit_Type[] containTypeArray;
  private byte[] containByteArray;
  private int[] locationBitArray;

  public int Length { get { return containTypeArray.Length; } }

  public object this[int targetIndex]
  {
    get
    {
      if(targetIndex > containTypeArray.Length)
      {
          return null;
      };

      if (containTypeArray[targetIndex] == Bit_Type.BOOL)
      {
          return GetBool(targetIndex);
      }
      else if (containTypeArray[targetIndex] == Bit_Type.FLOAT)
      {
          return BitConverter.ToSingle(GetByte(targetIndex), 0);
      }
      else
      {
          return BitConverter.ToInt32(GetByte(targetIndex), 0);
      };
    }

    set
    {
      if(targetIndex > containTypeArray.Length)
      {
          return;
      };

      if (containTypeArray[targetIndex] == Bit_Type.BOOL)
      {
          SetBool(targetIndex, (bool)value);
      }
      else
      {
        Type valueType = value.GetType();
        byte[] wantByte = new byte[0];

        if (valueType == typeof(float))
        {
          wantByte = BitConverter.GetBytes((float)value);
        }
        else
        {
          wantByte = BitConverter.GetBytes((int)value);
        };

        if(wantByte.Length > 0)
        {
          SetByte(targetIndex, wantByte);
        };
      };
    }
  }

  public Bit_Builder(Bit_Type[] wantTypes)
  {
    int bitLength = 0;

    containTypeArray = wantTypes;
    locationBitArray = new int[wantTypes.Length];

    for (int i = 0; i < containTypeArray.Length; ++i)
    {
      if (containTypeArray[i] > (Bit_Type)32)
      {
      	containTypeArray[i] = (Bit_Type)32;
      };

      locationBitArray[i] = bitLength;
      bitLength += TypeLength(containTypeArray[i]);
    };
    containByteArray = new byte[(int)Math.Ceiling(bitLength / 8.0d)];
  }

  private static int TypeLength(Bit_Type wantType)
  {
    if(wantType == Bit_Type.FLOAT)
    {
    	return 32;
    }
    else
    {
    	return (int)wantType;
    };
  }

  private void SetBool(int targetIndex, bool value)
  {
    int bitLocation = locationBitArray[targetIndex] % 8;
    int byteLocation = locationBitArray[targetIndex] >> 3;

    if (value)
    {
    	containByteArray[byteLocation] |= (byte)(0x80 >> bitLocation);
    }
    else
    {
    	containByteArray[byteLocation] &= (byte)~(0x80 >> bitLocation);
    };
  }

  private bool GetBool(int targetIndex)
  {
    int bitLocation = locationBitArray[targetIndex] % 8;
    byte targetByte = containByteArray[locationBitArray[targetIndex] >> 3];

    return (targetByte & (0x80 >> bitLocation)) > 0;
  }

  private void SetByte(int targetIndex, byte[] wantByte)
  {
    int bitLocation = locationBitArray[targetIndex] % 8;
    int byteLocation = locationBitArray[targetIndex] >> 3;
    int bitEnd = bitLocation + TypeLength(containTypeArray[targetIndex]);
    int byteEnd = (locationBitArray[targetIndex] + TypeLength(containTypeArray[targetIndex])) >> 3;
    int wholeByte = TypeLength(containTypeArray[targetIndex]) >> 3;
    int leftBit = TypeLength(containTypeArray[targetIndex]) % 8;
    int lastBit = leftBit + bitLocation;

    if (byteEnd > containByteArray.Length)
    {
    	return;
    };

    if (1 < TypeLength(containTypeArray[targetIndex]) && TypeLength(containTypeArray[targetIndex]) < 8)
    {
      if (bitEnd < 9)
      {
        int bitMask = (0xff >> bitLocation) ^ (0xff >> bitEnd);

        containByteArray[byteLocation] &= (byte)~bitMask;
        containByteArray[byteLocation] |= (byte)(bitMask & (wantByte[0] << (8 - bitEnd)));
        return;
      }
      else
      {
        int bitMask = (0xff >> bitLocation);

        containByteArray[byteLocation] &= (byte)~bitMask;
        containByteArray[byteLocation] |= (byte)(bitMask & wantByte[0]);

        bitMask = ~(0xff >> (bitEnd - 8));

        containByteArray[byteLocation + 1] &= (byte)~bitMask;
        containByteArray[byteLocation + 1] |= (byte)(bitMask & (wantByte[0] << (8 - TypeLength(containTypeArray[targetIndex]))));
      };
    }
    else
    {
      if (bitLocation == 0)
      {
        for (int i = 0; i < wholeByte; ++i)
        {
          containByteArray[byteLocation + i] = wantByte[i];
        };

        if(leftBit > 0)
        {
          containByteArray[byteLocation + wholeByte] &= (byte)~(0xff >> bitEnd % 8);
          containByteArray[byteLocation + wholeByte] |= (byte)(wantByte[wholeByte] << (8 - leftBit));
        };

      return;
      }
      else
      {
        int bitMask = 0xFF >> bitLocation;

        byte firstByte = (byte)(wantByte[0] & bitMask);
        byte lastByte = (byte)(wantByte[0] & ~bitMask);

        for (int i = 1; i < wholeByte; ++i)
        {
        	containByteArray[byteLocation + i] = wantByte[i];
        };

        containByteArray[byteLocation] &= (byte)~bitMask;
        containByteArray[byteLocation] |= firstByte;

        containByteArray[byteLocation + wholeByte] &= (byte)bitMask;
        containByteArray[byteLocation + wholeByte] |= lastByte;

        if(wholeByte < 4)
        {
          if (lastBit > 8)
          {
            containByteArray[byteLocation + wholeByte] &= (byte)~bitMask;
            containByteArray[byteLocation + wholeByte] |= (byte)(wantByte[wholeByte] & bitMask);

            bitMask = ~(0xff >> lastBit % 8);
            containByteArray[byteLocation + wholeByte + 1] &= (byte)~bitMask;
            containByteArray[byteLocation + wholeByte + 1] |= (byte)((wantByte[wholeByte] << (8 - leftBit)) & bitMask);
          }
          else if (leftBit > 0)
          {
            bitMask = ~(~bitMask | (0xff >> bitLocation + leftBit));

            containByteArray[byteLocation + wholeByte] &= (byte)~bitMask;
            containByteArray[byteLocation + wholeByte] |= (byte)((wantByte[wholeByte] << (8 - lastBit)) & bitMask);
          };
      	};

      return;
      };
    };
  }

  private byte[] GetByte(int targetIndex)
  {
    int bitLocation = locationBitArray[targetIndex] % 8;
    int byteLocation = locationBitArray[targetIndex] >> 3;
    int bitEnd = bitLocation + TypeLength(containTypeArray[targetIndex]);
    int byteEnd = (locationBitArray[targetIndex] + TypeLength(containTypeArray[targetIndex])) >> 3;
    int wholeByte = TypeLength(containTypeArray[targetIndex]) >> 3;
    int leftBit = TypeLength(containTypeArray[targetIndex]) % 8;
    int lastBit = leftBit + bitLocation;

    byte[] result = new byte[4];

    if (byteEnd > containByteArray.Length)
    {
    	return result;
    };

    if (1 < TypeLength(containTypeArray[targetIndex]) && TypeLength(containTypeArray[targetIndex]) < 8)
    {
      if (bitEnd < 9)
      {
        int bitMask = (0xff >> bitLocation) ^ (0xff >> bitEnd);

        result[0] = (byte)((containByteArray[byteLocation] & bitMask) >> (8 - bitEnd));
      }
      else
      {
        int bitMask = (0xff >> bitLocation);

        result[0] = (byte)(containByteArray[byteLocation] & bitMask);

        bitMask = ~(0xff >> (bitEnd - 8));

        result[0] |= (byte)((containByteArray[byteLocation + 1] & bitMask) >> (8 - TypeLength(containTypeArray[targetIndex])));
      };
    }
    else
    {
      if (bitLocation == 0)
      {
        for (int i = 0; i < wholeByte; ++i)
        {
          result[i] = containByteArray[byteLocation + i];
        };

        if (leftBit > 0)
        {
          result[wholeByte] = (byte)(containByteArray[byteLocation + wholeByte] & ~(0xff >> bitEnd % 8));
          result[wholeByte] = (byte)(result[wholeByte] >> (8 - leftBit));
        };
      }
      else
      {
        int bitMask = 0xFF >> bitLocation;

        result[0] = (byte)(containByteArray[byteLocation] & bitMask);
        result[0] |= (byte)(containByteArray[byteLocation + wholeByte] & ~bitMask);

        for (int i = 1; i < wholeByte; ++i)
        {
          result[i] = containByteArray[byteLocation + i];
        };
        if(wholeByte != 4)
        {
          if (lastBit > 8)
          {
            result[wholeByte] = (byte)(containByteArray[byteLocation + wholeByte] & bitMask);

            bitMask = ~(0xff >> lastBit % 8);
            result[wholeByte] |= (byte)((containByteArray[byteLocation + wholeByte + 1] & bitMask) >> (8 - leftBit));
          }
          else if(leftBit > 0)
          {
            bitMask = ~(~bitMask | (0xff >> bitLocation + leftBit));
            result[wholeByte] = (byte)((containByteArray[byteLocation + wholeByte] & bitMask) >> (8 - lastBit));
          };
        };				
      };
    };

    if(TypeLength(containTypeArray[targetIndex]) % 8 == 0)
    {
      if (wholeByte <= result.Length && (result[wholeByte - 1] & (0x80)) > 0)
      {
        for (int i = 3; i>=wholeByte; --i)
        {
        	result[i] = 0xff;
        };
      };
    }
    else
    {
      if (wholeByte < result.Length && (result[wholeByte] & (0x01 << leftBit - 1)) > 0)
      {
        for (int i = 3; i > wholeByte; --i)
        {
        	result[i] = 0xff;
        };
        result[wholeByte] |= (byte)(0xff << leftBit);
      };
    };

    return result;
  }

  public bool SetByteArray(byte[] wantArray)
  {
    if (wantArray.Length == containByteArray.Length)
    {
    	containByteArray = wantArray;
    	return true;
    }
    else
    {
    	return false;
    };
  }

  public byte[] GetByteArray()
  {
  	return containByteArray;
  }
}

'부품 설계도' 카테고리의 다른 글

[C#] 대화창 전체 코드  (0) 2021.01.30
[C#] 디아블로 스타일 인벤토리 전체 코드  (0) 2020.12.27

Int를 비트단위로 저장해주는 클래스

안녕하십니까 밤말팅입니다.

블로그를 열고 첫 포스팅인데요, 앞으로 게임 개발에 도움이 될 여러 가지 클래스를 구상하고 제작하는 대로 포스팅 할 

예정입니다. 내용이 괜찮다고 생각하시면 자주 들러주시고 피드백도 주시면 감사드리겠습니다!

 

그리고 첫 번째 포스팅 내용은 [Int를 비트단위로 저장해주는 클래스]입니다!

네트워크 전송을 하려고 할 때에는 변수 하나하나의 크기가 너무 아까울 때가 있습니다.

"이 변수는 127까지만 표현하면 되는데"

와 같은 고민을 하게 될 때가 있는데요, 4명이 한 방에 들어가는 2D 횡스크롤인 경우 극단적으로 이렇게도 할 수 있겠죠.

 

1바이트에 4가지 정보를 담다

[유저 번호, 오른쪽을 보고 있는지, 이동 중인지, 남은 체력은 0~15]

 

물론 하드코딩으로도 할 수 있을 겁니다. 하지만 넣을 정보가 바뀔 때마다 다시 하드코딩을 해주어야 하고,

만들 때마다 계속해서 변수를 짜깁기하는 데 시간과 노력이 많이 들겠죠, 변수가 byte 사이에 걸쳐도 문제구요.

그냥 길이만 쫙 등록해주고 배열에다가 순차적으로 넣어주기만 하면 얼마나 편할까요, 알아서 bit를 넣을

공간을 마련해주고, 들어온 정보를 스스로 쪼개서 공간에 맞춰 넣어주는 클래스가 있으면요?

 

이런 것들을 처리해줄 만한 클래스를 만들어보도록 합시다!

 

전체 코드를 보고싶으신 분은 아래 글을 참조해주시면 되겠습니다~

 

 

[C#]Bit_Builder 전체 코드

[C#] Int를 비트단위로 저장해주는 클래스 안녕하십니까 밤말팅입니다. 블로그를 열고 첫 포스팅인데요, 앞으로 게임 개발에 도움이 될 여러 가지 클래스를 구상하고 제작하는 대로 포스팅 할 예

game-part-factory.tistory.com

[이 클래스는 아래와 같은 네임스페이스 참조가 필요합니다]

using <system>

 

<목표>

//Bit_Bulder를 생성합니다. bool,bool,6,8,16,22,10,15,bool,32,float이 들어있습니다.
//이름은 Build_Test로 하겠습니다.
Bit_Builder Build_Test = new Bit_Builder
(new Bit_Type[11] { Bit_Type.BOOL, Bit_Type.BOOL, (Bit_Type)6, (Bit_Type)8, (Bit_Type)16, 
(Bit_Type)22, (Bit_Type)10, (Bit_Type)15, Bit_Type.BOOL, (Bit_Type)32, Bit_Type.FLOAT});

//각 위치에 원하는 값을 넣어줍니다.
Build_Test[0] = true;
Build_Test[1] = false;
Build_Test[2] = 30;
Build_Test[3] = -116;
Build_Test[4] = 29533;
Build_Test[5] = 1934581;
Build_Test[6] = -506;
Build_Test[7] = 13152;
Build_Test[8] = true;
Build_Test[9] = -205578954;
Build_Test[10] = 124782.3f;

//각 위치에 있는 값을 표시합니다.
for (int i = 0; i < Build_Test.Length; ++i)
{
Console.WriteLine("Build_Test[" + i + "] : " + Build_Test[i]);
};

//Bit_Test에 들어있는 바이트 배열의 길이를 표시합니다.
Console.WriteLine("\nTotal Byte : " + Build_Test.GetByteArray().Length);

결과값

위 코드와 같이 bit 타입과 길이를 넣은 클래스를 선언해준 후,  각 위치에 값을 넣습니다.

마찬가지로 각 위치에 접근하면 원하는 값을 다시 꺼내 주는 것까지가 이번 클래스의 목표가 되겠습니다.

물론 그냥 넣는 것이 아니라, bit에 맞게 위치를 잘 잡아주는 것이 중요하겠죠?

 

 

<구상>

구상에 앞서 저장 방식이 리틀 엔디언으로 이뤄졌다는 것을 전제로 합니다.
빅 엔디언의 경우 각 byte 순서를 뒤집어서 계산해야 합니다.

위쪽이 원본 값, 아래쪽이 넣어야할 위치를 표시합니다

 

위쪽은 평범한 4byte 값입니다. 이걸 아래 위치에 넣기만 하면 되겠습니다.

첫 번째 바이트를 2개로 나눕니다.

원본 첫 번째 byte를 비트 위치에 맞게 두 개로 쪼갭니다.

두 개로 쪼갠 뒤에, 뒷부분은 목표 앞 byte에, 앞부분은 목표 뒷 byte에 넣습니다.

 

각 부분이 자리를 찾아 들어간 예

목표 byte에 첫 번째 byte를 넣은 후에는 뒷 byte를 목표 위치까지 시프트 한 후에 대입하면

짜잔! 4byte를 11bit로 끼워 넣는 데에 성공했습니다!

 

정보를 가져올 때에는 역순으로 진행하면 되겠습니다.

 

비트의 길이가 더 늘어난 경우

bit가 더 늘어난 경우는 별로 신경 안 쓰셔도 됩니다. 중간 값은 그대로 넣어도 무방하니까요.

대신 아래 경우만 한 번 더 따져봅시다.

 

앞에 있는 비트가 더 늘어난 경우

앞에 공간이 부족해서 맨 뒤 byte가 들어갈 공간이 없는 상황입니다.

이때에는 맨 뒤 byte를 맨 앞 byte와 같이 한 번 잘라줍니다.

byte 하나를 더 쓰게 되었지만, 그 뒤에도 새로운 정보가 들어갈 것이니 괜찮습니다.

혹여나 이게 마지막 정보라면, 정보량을 제한하거나, 비트를 여유롭게 조절하면 효율적으로 쓸 수 있습니다.

 

이제 bit를 구겨 넣을 방법을 생각했으니 실제 클래스를 만들어보도록 합시다.

 

<구현>

public class Bit_Builder
{
    //각 위치에 들어갈 타입(길이)을 명시한 배열
    private Bit_Type[] containTypeArray;

    //실제 결과물이 들어가는 배열
    private byte[] containByteArray;

    //각 변수가 들어갈 위치를 넣는 배열
    private int[] locationByteArray;

    //타입이 몇 개 들어있는지 반환
    public int Length { get { return containTypeArray.Length; } }
}

기본이 될 클래스입니다.

각 변수의 타입을 모아놓은 배열을 일단 만들었습니다. 이걸로 각 변수를 어떻게 읽어올지 결정합니다.

 

실제 데이터가 들어간 byte 배열, 각 변수의 시작 bit를 보여주는 배열도 같이 만들어줍니다.

 

몇 개의 변수가 현재 클래스에 들어있는지 보여주는 Length도 만들어 놓겠습니다.

 

public enum Bit_Type
{
    FLOAT,
    BOOL,
}

위에서 Bit_Type이라는 것이 나왔는데요, 간단하게 float과 bool로만 이루어져 있습니다.

 

이 두 개를 쓰시지 않으시려면, 위에 Bit_Type []를 int []로 바꾸시면 됩니다~

 

숫자를 건드리지 않아서 float는 0, bool은 1로 보시면 되겠죠!

 

만약 본인이 원하는 변수가 있다면, 추가해도 무관합니다!

 

public Bit_Builder(Bit_Type[] wantTypes)
{
    //비트 총길이 계산
    int bitLength = 0;

    //타입 배열에 받아온 타입을 넣습니다.
    containTypeArray = wantTypes;

    //타입마다 위치를 정해주기 위해 타입 배열의 길이만큼 위치 배열도 만듭니다.
    locationBitArray = new int[wantTypes.Length];

    //각 타입을 돌면서 비트 시작 위치를 잡아주는 반복문입니다.
    for (int i = 0; i < containTypeArray.Length; ++i)
    {
        //int를 쓰기 때문에 32비트보다 큰 값은 자릅니다.
        if (containTypeArray[i] > (Bit_Type)32)
        {
        containTypeArray[i] = (Bit_Type)32;
        };

        //지금까지 더해진 길이를 현재 위치에 넣습니다.
        locationBitArray[i] = bitLength;

        //비트 총길이에 현재 타입의 길이를 더합니다.
        bitLength += TypeLength(containTypeArray[i]);

        //반복..
    };

    //비트 총길이를 8로 나눈 후에, 소숫점 올림하여(마지막 남는 비트를 위함)
    //결과물 바이트 배열을 만듭니다.
    containByteArray = new byte[(int)Math.Ceiling(bitLength / 8.0d)];
}

우선 첫 번째로 생성자를 한 번 보도록 합시다.

 

모든 타입을 더해서 기록할 변수를 하나 마련한 뒤에

받아온 타입을 타입 배열에 넣고, 그 길이만큼 위치 배열도 만듭니다.

 

이후에는 타입 배열을 전부 돌면서 위치 배열을 채웁니다.

첫 번째 타입의 길이가 6이었으면, 두 번째 타입은 6부터 시작,
두 번째 타입의 길이가 7이므로, 세 번째 타입은 6+7 = 13부터 시작

과 같은 방식으로 위치를 채워나가는 것이 되겠죠!

모든 길이를 다 더한 값이 나오면, 8로 나눈 뒤, 소수점 올림 하여 byte 배열을 만듭니다.

만약 bit의 개수가 9개라면, 바이트가 2개 필요하겠죠?

그래서 소수점 올림으로 계산하도록 하겠습니다.

 

 

private static int TypeLength(Bit_Type wantType)
{
    if(wantType == Bit_Type.FLOAT)
    {
    	return 32;
    }
    else
    {
    	return (int)wantType;
    };
}

생성자에서 길이를 더할 때, 타입을 그대로 넣지 않고 TypeLength()를 사용했습니다.

이건 제가 float의 길이를 직접 넣어주기 위해서 작게 만들었습니다.

만약 다른 변수형을 Bit_Type에 추가하신다면, 여기서 길이를 정해주실 수 있습니다.

 

여기까지 하면 대충 필요한 것은 다 적은 듯하고, 이제 본격적으로 내용을 집어넣어 보도록 하겠습니다.

집어넣는 Set계열 함수는 공정이 더 많이 필요하기 때문에 먼저 설명하고,

Set에 따라가는 Get함수는 바로 이어서 설명하도록 하겠습니다.

 

private void SetBool(int targetIndex, bool value)
{
    int bitLocation = locationBitArray[targetIndex] % 8;
    int byteLocation = locationBitArray[targetIndex] >> 3;

    if (value)
    {
    	containByteArray[byteLocation] |= (byte)(0x80 >> bitLocation);
    }
    else
    {
    	containByteArray[byteLocation] &= (byte)~(0x80 >> bitLocation);
    };
}

시작은 가볍게 bool 변수를 넣는 작업입니다. 원래는 그냥 길이가 1인 값을 넣으면 되긴 하겠지만,

굳이 다른 작업을 할 필요가 없기 때문에 따로 함수를 만들도록 하겠습니다.

 

bitLocation은 bit 위치를 8로 나눈 나머지입니다. 만약 10이었으면, 2만 남겠죠.

0x80을 2만큼 오른쪽 시프트

이걸 활용해서 0x80 >> bitLocation을 하면, 원하는 위치에 접근할 수 있게 됩니다.

 

그리고 밑에 byteLocation은 3번 오른쪽 시프트를 하는데요, 이러면 2의 3 제곱인 8로 나눈 후 소수점을 버린 것과 같죠.

시작 비트가 3이면, 바이트[0]에 접근
시작 비트가 20이면, 바이트[2]에 접근

위과 같이 접근할 byte를 고를 수 있게 미리 잡아둘 수 있겠죠.

이걸 가지고 접근을 시도해봅시다.

 

들어온 값에 따라 다른 처리

만약 들어온 값이 참이면, 방금 만든 비트 마스크와 원래 데이터를 Or 연산합니다.

그렇게 되면 해당 위치에 1이 들어가게 됩니다.

 

반대로 거짓을 받는다면, 비트 마스크를 뒤집습니다.

뒤집은 상태로 데이터와 And 연산을 하게 되면, 원하는 위치 빼고는 원래 데이터를 유지하여

해당 위치를 무조건 0으로 바꿀 수 있게 됩니다.

 

그러면 벌써 bool값을 넣는 함수가 끝났네요!

 

가져와보기만 하면 되겠죠?

 

private bool GetBool(int targetIndex)
{
    int bitLocation = locationBitArray[targetIndex] % 8;
    byte targetByte = containByteArray[locationBitArray[targetIndex] >> 3];

    return (targetByte & (0x80 >> bitLocation)) > 0;
}

가져오는 함수는 굉장히 굉장히 간단합니다. 사실 한 줄로도 줄일 수 있죠.

bitLocation은 그대로 bit 위치입니다.

 

targetByte는 byte 위치를 구한 뒤에, 실제로 그 byte를 가져온 값입니다.

 

비트 마스크와 And 연산을 하게 되면

바이트에 비트마스크를 And연산

짠! 아주 간단하게 해당 위치의 값을 알 수 있게 되겠죠.

 

이 값이 0보다 큰지 비교를 해서 리턴을 하게 해 놓으면,

 

해당 위치에 1이 있을 때, True라고 반환을 해주고, 0이 있으면 False라고 리턴을 해줄 겁니다.

 

bool까지는 굉장히 쉽군요!

 

하지만 아직 몸체인 SetByte와 GetByte를 안 들어갔다는 점!

 

이제부터 진짜 내용을 시작해 보도록 하겠습니다~

 

조금 길기 때문에 조금씩 나눠서 설명을 해드릴게요!

 

<SetByte함수의 분기점>

○ 넣으려는 변수의 길이가 8 이하
    > 바이트 하나에 넣을 수 있음
    > 바이트 두 개에 넣을 수 있음
    
○ 넣으려는 변수의 길이가 8 이상
    > 0번 비트부터 쓸 수 있음
    		- 마지막 여분의 비트가 남음
        
    > 0번 비트를 쓸 수 없음
    		- 마지막 비트를 바이트 하나에 넣을 수 있음
        	- 마지막 비트를 바이트 두 개에 넣을 수 있음

SetByte에서 확인해야되는 분기점들입니다.

 

해당 분기점들을 하나하나 따라가면서 만들어보도록 합시다!

 

private void SetByte(int targetIndex, byte[] wantByte)
{
    //시작 bit의 위치
    int bitLocation = locationBitArray[targetIndex] % 8;
    //시작 byte의 위치
    int byteLocation = locationBitArray[targetIndex] >> 3;
    //끝나는 bit의 위치
    int bitEnd = bitLocation + TypeLength(containTypeArray[targetIndex]);
    //끝나는 byte의 위치
    int byteEnd = (locationBitArray[targetIndex] + TypeLength(containTypeArray[targetIndex])) >> 3;
    //원래 값에서 모두 채워진 byte의 수
    int wholeByte = TypeLength(containTypeArray[targetIndex]) >> 3;
    //원래 값에서 마지막 바이트에 있는 bit 수
    int leftBit = TypeLength(containTypeArray[targetIndex]) % 8;
    //원래 값에서 마지막 바이트에 있는 bit 수 + 시작 bit의 위치
    int lastBit = leftBit + bitLocation;

    //끝나는 byte가 byte배열보다 큰 경우에는 실행하지 않고 끊습니다.
    if (byteEnd > containByteArray.Length)
    {
        return;
    };
}

SetByte의 기본 모습입니다.

대상이 되는 위치와 값을 받는 함수가 되겠습니다.

 

시작 bit와 byte를 찾기 위해 아까 SetBool에서 했던 것과 마찬가지의 과정을 진행했구요,

끝나는 위치를 알기 위해 길이를 더해서 위치를 한 번 더 찾아주었습니다.

 

그리고 중간은 그냥 채우고 넘어가기 위해서 다 채워진 byte를 구분해주었구요.

다 채워진 이후에 남는 bit수도 따로 정리해두었습니다.

 

그리고 혹시나 마지막 byte를 두 개로 쪼개야 될 것을 구분하기 위해, 마지막 bit위치와 시작 bit위치를 더합니다.

이 상황을 구분하기 위해서 lastBit를 씁니다

그리고 클래스에 있는 byte배열의 길이를 넘어버리면 배열을 넘어갈 위험이 발생할 여지가 있으므로

이 이상의 요청이 들어오는 경우 실행하지 않도록 하겠습니다.

 

○ 넣으려는 변수의 길이가 8 이하

if (1 < TypeLength(containTypeArray[targetIndex]) && TypeLength(containTypeArray[targetIndex]) < 8)
{
    //첫 번째 비트에 모두 넣을 수 있는 상황입니다.
    if (bitEnd < 9)
    {
        int bitMask = (0xff >> bitLocation) ^ (0xff >> bitEnd);

        containByteArray[byteLocation] &= (byte)~bitMask;
        containByteArray[byteLocation] |= (byte)(bitMask & (wantByte[0] << (8 - bitEnd)));
        return;
    }
    //첫 번째 비트에 모두 넣을 수 없는 상황입니다.
    else
    {
        int bitMask = (0xff >> bitLocation);

        containByteArray[byteLocation] &= (byte)~bitMask;
        containByteArray[byteLocation] |= (byte)(bitMask & wantByte[0]);

        bitMask = ~(0xff >> (bitEnd - 8));

        containByteArray[byteLocation + 1] &= (byte)~bitMask;
        containByteArray[byteLocation + 1] |= (byte)(bitMask & (wantByte[0] << (8 - TypeLength(containTypeArray[targetIndex]))));
    };
}

8이하일 때, 잘라서 넣어야하는지 아닌지를 확인하기 위해 마지막 bit위치를 확인합니다.

마지막 bit위치는 (시작 위치 + 길이)로 되어있어서 9를 넘어가면 다음 byte에 걸쳤다는 의미가 되겠죠.

 

먼저 9를 넘지 않았을 때를 살펴봅시다.

2에서 시작해서 길이가 5인 비트의 비트마스크

임의의 숫자를 넣은 비트마스크입니다. 시작 위치만큼 11111111을 오른쪽 시프트해서

왼쪽에 시작 위치 수만큼 0을 넣습니다.

 

두 번째 것은 시작 위치 + 길이만큼 오른쪽 시프트한 값을 넣습니다.

 

두 개를 xor하게 되면 가운데 서로 다른 부분만 마스크가 떠지게 되어 원하는 위치를 받아올 수 있게 되겠죠.

 

여기에 첫 번째 byte를 밀어넣으면 되겠습니다.

 

목표로 하는 byte에서 방금 얻은 비트마스크를 역전시킨 후 And연산을 하게 되면, 넣을 위치만 0으로

초기화시켜줄 수 있습니다.

 

넣을 비트를 위치시키는 과정

0으로 초기화시킨 부분에 위와 같은 과정을 거친 값을 Or연산 해주면

다른 bit는 건들지 않고 접근하려고 한 bit에만 원하는 값을 넣어줄 수 있겠습니다~

 

이제 두 바이트에 나눠서 담아야 할 경우를 살펴봐야 하겠죠?

바이트 쪼개기

원래 이렇게 설명을 드렸었는데요, 지금은 8bit 이내이기 때문에 초록색 부분(○)을 빼고 생각하면 되겠습니다~

 

일단 비트마스크를 (0xff >> bitLocation)로 설정해서 하늘색 부분(□)을 가져옵니다.

가져온 대로 그대로 넣어주면 되겠죠?

 

하지만 빨간색 부분(△)이 문제입니다. 8bit이내라는 말은, 맨 앞에 쓸데없는 부분이 있을 수 있다는 거겠죠~

(8-길이)는 비어있는 값의 크기입니다.

그래서 (8-길이)만큼 왼쪽 시프트해주어 bit를 왼쪽에 정렬시킨 후에 빨간색 부분(△)을 넣도록 하겠습니다!

 

뒷 byte에 들어갈 bit수는 (마지막 위치 - 8)이 되겠죠,

이걸로 비트마스크를 만든 뒤에, 정렬시킨 값과 Or연산 해주면 값이 들어가게 됩니다~

 

읽을 때에는 이 두 개를 잘 붙혀주기만 하면 됩니다!

 

이걸로 8it 이하는 끝났군요

 

8bit를 넘어간 것만 처리해주면 값을 넣는 것은 얼추 끝나게 됩니다!

 

if (bitLocation == 0)
{
    for (int i = 0; i < wholeByte; ++i)
    {
        containByteArray[byteLocation + i] = wantByte[i];
    };

    if(leftBit > 0)
    {
        containByteArray[byteLocation + wholeByte] &= (byte)~(0xff >> bitEnd % 8);
        containByteArray[byteLocation + wholeByte] |= (byte)(wantByte[wholeByte] << (8 - leftBit));
    };

    return;
}

가볍게 앞에 bit가 없는 상황을 가정해봅시다.

0번 byte부터 모두 차 있는 byte까지 돌려가면서 목표 위치에 넣어줍니다.

 

그래도 남는 bit가 있을 때에는

왼쪽 정렬 재사용!

이걸 다시 사용하면 되겠죠, 마지막 byte를 왼쪽 정렬해준 다음에 그 길이만큼 넣어주도록 합시다. 쉽네요!

 

int bitMask = 0xFF >> bitLocation;
//첫 번째 byte를 두 개로 나누기
byte firstByte = (byte)(wantByte[0] & bitMask);
byte lastByte = (byte)(wantByte[0] & ~bitMask);
//중간은 그냥 넣습니다.
for (int i = 1; i < wholeByte; ++i)
{
    containByteArray[byteLocation + i] = wantByte[i];
};

containByteArray[byteLocation] &= (byte)~bitMask;
containByteArray[byteLocation] |= firstByte;

containByteArray[byteLocation + wholeByte] &= (byte)bitMask;
containByteArray[byteLocation + wholeByte] |= lastByte;

만약 앞에 bit가 있는 경우, 첫 번째 byte를 두 개로 쪼개줍니다.

 

그리고 중간에 모두 다 찬 바이트를 돌려주면서 그대로 넣어준 뒤에

 

쪼개둔 byte를 맨 앞과 맨 뒤에 넣어주면 아주 좋게 잘 되겠죠..?

 

if(wholeByte < 4)
{
    if (lastBit > 8)
    {
        containByteArray[byteLocation + wholeByte] &= (byte)~bitMask;
        containByteArray[byteLocation + wholeByte] |= (byte)(wantByte[wholeByte] & bitMask);

        bitMask = ~(0xff >> lastBit % 8);
        containByteArray[byteLocation + wholeByte + 1] &= (byte)~bitMask;
        containByteArray[byteLocation + wholeByte + 1] |= (byte)((wantByte[wholeByte] << (8 - leftBit)) & bitMask);
    }
    else if (leftBit > 0)
    {
        bitMask = ~(~bitMask | (0xff >> bitLocation + leftBit));

        containByteArray[byteLocation + wholeByte] &= (byte)~bitMask;
        containByteArray[byteLocation + wholeByte] |= (byte)((wantByte[wholeByte] << (8 - lastBit)) & bitMask);
    };
};

잘 되었으면 좋겠지만, 마지막 byte를 넣는 과정을 아직 안 안 넣었네요!

 

wholeByte가 4라면, 4개가 다 찬 상태이기 때문에, 실행을 시키지 않도록 하겠습니다.

 

마지막 bit위치를 비교해서 8보다 크다면, 두 개의 byte를 써야하는 것으로 보고 진행합니다.

 

과정은 SetBool과 똑같이 진행해주시면 됩니다~

 

GetByte는 SetByte의 역순입니다.

SetByte에서 썼던 비트마스크나 조건을 그대로 쓰는 대신

값을 짜깁기해서 완성시켜주는 것이 전부이죠.

 

간단하게 코드만 보여드리도록 하겠습니다.

 

private byte[] GetByte(int targetIndex)
{
    int bitLocation = locationBitArray[targetIndex] % 8;
    int byteLocation = locationBitArray[targetIndex] >> 3;
    int bitEnd = bitLocation + TypeLength(containTypeArray[targetIndex]);
    int byteEnd = (locationBitArray[targetIndex] + TypeLength(containTypeArray[targetIndex])) >> 3;
    int wholeByte = TypeLength(containTypeArray[targetIndex]) >> 3;
    int leftBit = TypeLength(containTypeArray[targetIndex]) % 8;
    int lastBit = leftBit + bitLocation;

    byte[] result = new byte[4];

    if (byteEnd > containByteArray.Length)
    {
        return result;
    };

    if (1 < TypeLength(containTypeArray[targetIndex]) && TypeLength(containTypeArray[targetIndex]) < 8)
    {
        if (bitEnd < 9)
        {
            int bitMask = (0xff >> bitLocation) ^ (0xff >> bitEnd);

            result[0] = (byte)((containByteArray[byteLocation] & bitMask) >> (8 - bitEnd));
        }
        else
        {
            int bitMask = (0xff >> bitLocation);

            result[0] = (byte)(containByteArray[byteLocation] & bitMask);

            bitMask = ~(0xff >> (bitEnd - 8));

            result[0] |= (byte)((containByteArray[byteLocation + 1] & bitMask) >> (8 - TypeLength(containTypeArray[targetIndex])));
        };
    }
    else
    {
        if (bitLocation == 0)
        {
            for (int i = 0; i < wholeByte; ++i)
            {
                result[i] = containByteArray[byteLocation + i];
            };

            if (leftBit > 0)
            {
                result[wholeByte] = (byte)(containByteArray[byteLocation + wholeByte] & ~(0xff >> bitEnd % 8));
                result[wholeByte] = (byte)(result[wholeByte] >> (8 - leftBit));
            };
        }
        else
        {
            int bitMask = 0xFF >> bitLocation;

            result[0] = (byte)(containByteArray[byteLocation] & bitMask);
            result[0] |= (byte)(containByteArray[byteLocation + wholeByte] & ~bitMask);

            for (int i = 1; i < wholeByte; ++i)
            {
                result[i] = containByteArray[byteLocation + i];
            };
            if(wholeByte != 4)
            {
                if (lastBit > 8)
                {
                    result[wholeByte] = (byte)(containByteArray[byteLocation + wholeByte] & bitMask);

                    bitMask = ~(0xff >> lastBit % 8);
                    result[wholeByte] |= (byte)((containByteArray[byteLocation + wholeByte + 1] & bitMask) >> (8 - leftBit));
                }
                else if(leftBit > 0)
                {
                    bitMask = ~(~bitMask | (0xff >> bitLocation + leftBit));
                    result[wholeByte] = (byte)((containByteArray[byteLocation + wholeByte] & bitMask) >> (8 - lastBit));
                };
            };				
        };
    };

    if(TypeLength(containTypeArray[targetIndex]) % 8 == 0)
    {
        if (wholeByte <= result.Length && (result[wholeByte - 1] & (0x80)) > 0)
        {
            for (int i = 3; i>=wholeByte; --i)
            {
                result[i] = 0xff;
            };
        };
    }
    else
    {
        if (wholeByte < result.Length && (result[wholeByte] & (0x01 << leftBit - 1)) > 0)
        {
            for (int i = 3; i > wholeByte; --i)
            {
                result[i] = 0xff;
            };
            result[wholeByte] |= (byte)(0xff << leftBit);
        };
    };

    return result;
}

조금 길긴 하지만, SetByte의 역이라고 생각해주시면 됩니다.

SetByte의 분기점을 그대로 따라가면 금방 해당하는 부분을 찾으실 수 있습니다~

 

대신 여기서 눈여겨 볼 부분이 있는데요,

 

if(TypeLength(containTypeArray[targetIndex]) % 8 == 0)
{
    if (wholeByte <= result.Length && (result[wholeByte - 1] & (0x80)) > 0)
    {
        for (int i = 3; i>=wholeByte; --i)
        {
            result[i] = 0xff;
        };
    };
}
else
{
    if (wholeByte < result.Length && (result[wholeByte] & (0x01 << leftBit - 1)) > 0)
    {
        for (int i = 3; i > wholeByte; --i)
        {
            result[i] = 0xff;
        };
        result[wholeByte] |= (byte)(0xff << leftBit);
    };
};

맨 마지막에 나오는 이 부분입니다!

 

SetBool에서는 없었던 부분인데요,

 

그냥 맨 뒤에 있는 정보만 가지고 가면 음수와 양수의 구분이 되지 않습니다!

 

지금까지 만든 것은 uint를 다루는 클래스였던 것이죠!

 

하지만 이 부분을 추가하면서 int로 바꿔줄 수 있게 됩니다.

맨 첫 번째 bit를 가져오는 방법

GetBool에서 봤었던 그림입니다. 첫 번째 bit를 가져오는 것이었죠~

 

이걸 이용해서 첫 번째 bit가 1이면 음수, 0이면 양수로 출력해줍시다!

 

26은  00000000 00000000 00000000 00011010
-26은 11111111 11111111 11111111 11100110

26을 표현하기 위해선 -32~31까지 표현가능한 6비트가 되겠네요!

마침 뒤에서 6번째 bit는 음수와 양수의 차이를 나타내주기 좋게 따라가고 있습니다~

 

위에서 첫 번째 bit를 가져왔으므로, 결과 byte에서 비어있는 bit만 부호에 맞게 채워주면 되겠습니다!

 

만약 bit수가 다행스럽게 8로 떨어지는 경우에는 반복문으로 앞에 있는 byte를 0xFF (1111 1111)로 채웁니다.

 

그러나 8로 떨어지지 않는 경우에는, 일단 비어있는 byte에는 0xFF를 채운 뒤에

 

마지막 바이트에 1 채워넣기

값이 있는 마지막 바이트로 이동해서 앞부분을 마저 1로 채워줍니다.

 

어차피 저장을 할 때 앞에 필요없는 부분만 떼고 저장을 했기 때문에,

 

그 값은 양수일 때 0, 음수일 때 1이었을 겁니다.

 

그래서 GetByte에만 이 부분을 넣어주면 음수와 양수를 깔끔하게 돌려받을 수 있는 것이죠!

 

만약, 이 클래스를 uint로만 쓰고 싶다면 빼시면 되고,

 

아니면 클래스에 Unsigned 체크를 따로 선언해서 On/Off형식으로 사용하셔도 무방하겠습니다~

 

이제 기본적인 입/출력은 완성이 되었고

 

public bool SetByteArray(byte[] wantArray)
{
    if (wantArray.Length == containByteArray.Length)
    {
        containByteArray = wantArray;
        return true;
    }
    else
    {
        return false;
    };
}

public byte[] GetByteArray()
{
    return containByteArray;
}

저장하고 있는 값을 가져오거나, 다른 데에서 가져온 값을 넣는 함수를 조금 추가해보았습니다.

 

요긴하게 쓸 수 있겠죠?

 

근데 이렇게 해놓으면 기능은 충분하겠습니다만, 매번 함수를 써야하므로 좀 더 보기좋게 해봅시다.

 

이미 눈치채셨겠지만, 타이틀 이미지에 보여드린 배열은 실제 배열이 아닙니다.

 

여기 들어있는 배열은 단순한 byte 배열밖에 없죠.

 

이 클래스 자체를 배열처럼 쓸 수 있게 해봅시다!

 

public object this[int targetIndex]
{
    get
    {
    	//가져오는 내용
    }
    
    set
    {
    	//저장하는 내용
    }
}

이런식으로 클래스 내부에 선언을 해주도록 합시다~

 

이러면 배열과 같은 모습으로 접근이 가능하겠죠~

 

이제 내용을 채워봅시다.

 

get
{
    if(targetIndex > containTypeArray.Length)
    {
        return null;
    };

    if (containTypeArray[targetIndex] == Bit_Type.BOOL)
    {
        return GetBool(targetIndex);
    }
    else if (containTypeArray[targetIndex] == Bit_Type.FLOAT)
    {
    	return BitConverter.ToSingle(GetByte(targetIndex), 0);
    }
    else
    {
    	return BitConverter.ToInt32(GetByte(targetIndex), 0);
    };
}

먼저 접근하는 위치가 지원하는 타입 수보다 크면 안되기 때문에, 걸러주었습니다.

 

이후로는 타입에 따라 움직이게 되는데요,

 

bool인 경우에는 그대로 전달,

 

float인 경우에는 byte를 float으로 변환해주는 함수를 쓰고,

 

int인 경우에는 byte를 int로 변환해주는 함수를 따로 쓸 수 있습니다.

 

이런 식으로 새로 추가하는 자료형이 있다면, 해석하는 것도 따로 추가해주시면 되겠습니다.

 

set
{
    if(targetIndex > containTypeArray.Length)
    {
    	return;
    };

    if (containTypeArray[targetIndex] == Bit_Type.BOOL)
    {
    	SetBool(targetIndex, (bool)value);
    }
    else
    {
    	Type valueType = value.GetType();
    	byte[] wantByte = new byte[0];

    	if (valueType == typeof(float))
    	{
    		wantByte = BitConverter.GetBytes((float)value);
    	}
    	else
    	{
    		wantByte = BitConverter.GetBytes((int)value);
    	};

    	if(wantByte.Length > 0)
    	{
    		SetByte(targetIndex, wantByte);
    	};
    };
}

Set도 마찬가지로 배열 크기를 넘지 못하게 막아두었습니다.

 

Boolean은 그대로 넣게 되어있고,

 

나머지는 들어온 값의 타입을 식별하도록 해놓았습니다.

 

식별한 타입에 따라 byte를 읽어오도록 설정을 해놓은 뒤에

 

받은 값의 길이가 0이상인 경우, 값을 제대로 받아왔다고 판단하여 해당 byte 배열을 위치에 넣습니다.

 

여기까지하면 타이틀에 나온대로 배열에 접근하듯 가볍게 접근할 수 있겠죠~

 

 

<마치며>

 

 

그렇게 길지 않은 코드였는데 생각보다 포스팅이 길어졌네요.

 

조금 편리하게 불필요한 데이터량을 줄이려고 만들어 본 것을 올려보는 시간이었는데요,

 

제 코드를 다시 읽어보면서 생각하는 시간도 되는 것 같네요!

 

다른 분들께도 도움이 되는 자료였으면 좋겠구요,

 

다음에 또 괜찮은 것이 생각나면 돌아오도록 하겠습니다!

 

여기까지 읽어주신 모든 분들께 감사드립니다!

+ Recent posts