0. 파일 구조
RPGGame.cpp
⊢ Enums.h : 아이템 희귀도와 타입을 열거형으로 저장
⊢ Inventory.cpp, Inventory.h : 인벤토리(컨테이너가 아닌 배열로 구현됨)
⨽ Item.cpp, Item.h : 아이템
1. 코드
(1) Inventory.h
#pragma once
#include "Item.h"
enum
{
MAX_SLOT = 100
};
class Inventory
{
public:
Inventory();
~Inventory();
bool AddItem(Item* item);
bool RemoveItem(Item* item);
Item* GetItemAtSlot(int slot);
void Clear();
static Inventory* GetInstance();
private:
int FindEmptySlot();
int FindItemSlot(Item* item);
static Inventory* s_instance;
private:
// 포인터 연산자가 있는것과 없는건 달라
// 메모리를 실제로 점유하는 것과
// 4바이트만 가지고 있는 것
Item* _items[MAX_SLOT];
int _itemCount = 0;
};
(2) Inventory.cpp
#include "Inventory.h"
Inventory* Inventory::s_instance = nullptr;
Inventory::Inventory()
{
for (int i = 0; i < MAX_SLOT; i++)
{
_items[i] = nullptr;
}
}
Inventory::~Inventory()
{
}
// 아이템을 넣을 수 있는가?
bool Inventory::AddItem(Item* item)
{
if (item == nullptr)
return false;
int emptySlot = FindEmptySlot();
// 인벤토리가 꽉 차있음
if (emptySlot < 0)
return false;
_items[emptySlot] = item;
_itemCount++;
return true;
}
bool Inventory::RemoveItem(Item* item)
{
if (item == nullptr)
return false;
int slot = FindItemSlot(item);
if (slot < 0)
return false;
_items[slot] = nullptr;
_itemCount--;
// delete this;
}
Item* Inventory::GetItemAtSlot(int slot)
{
if (slot < 0 || slot >= MAX_SLOT)
return nullptr;
return _items[slot];
}
void Inventory::Clear()
{
for (int i = 0; i < MAX_SLOT; i++)
{
if (_items[i])
{
delete _items[i];
_items[i] = nullptr;
}
}
}
Inventory* Inventory::GetInstance()
{
if (s_instance == nullptr)
s_instance = new Inventory();
return s_instance;
}
int Inventory::FindEmptySlot()
{
for (int i = 0; i < MAX_SLOT; i++)
if (_items[i] == nullptr)
return i;
return -1;
}
int Inventory::FindItemSlot(Item* item)
{
for (int i = 0; i < MAX_SLOT; i++)
if (_items[i] == item)
return i;
return -1;
}
2. 코드 분석
(1) 인벤토리의 원리
enum
{ MAX_SLOT = 100 };
여기서 구현된 인벤토리는 최대 100개의 아이템을 저장할 수 있으며, map과 vector같은 컨테이너를 사용하지 않은 배열로 구현되었다.
인벤토리의 기본적인 원리는 100개의 나열된 슬롯이 있고(0으로 초기화되어 있음) 앞에서부터 비어있는(0인) 슬롯에 아이템을 집어넣는 방식으로 되어있다.
이 원리를 이용하기 위해 FindEmptySlot 함수와 FindItemSlot 함수를 사용한다. 이 함수들은 외부에서 접근할 수 없도록 private으로 지정되어있다.
인벤토리 안에는 MAX_SLOT 만큼의 아이템이 들어갈 것(_items ; 100개 원소를 가지는 Item 타입의 포인터 배열)이며, 들어간 아이템의 갯수를 파악하기 위해 _itemCount 변수를 사용한다.
(2) Inventory 구현부
(2-1) 생성자
Inventory::Inventory()
{
for (int i = 0; i < MAX_SLOT; i++)
{
_items[i] = nullptr;
}
}
MAX_SLOT개 만큼의 아이템이 저장되는 각 슬롯을 0(nullptr)로 초기화한다.
(2-2) 인벤토리 구현을 위한 두 함수
int Inventory::FindEmptySlot()
{
for (int i = 0; i < MAX_SLOT; i++)
if (_items[i] == nullptr)
return i;
return -1;
}
int Inventory::FindItemSlot(Item* item)
{
for (int i = 0; i < MAX_SLOT; i++)
if (_items[i] == item)
return i;
return -1;
}
빈 슬롯을 찾아 해당 인덱스를 반환하는 int 타입의 FindEmptySlot 함수와 Item 포인터 타입을 받아 해당 아이템이 존재하는 인덱스를 반환하는 FindItemSlot(Item* item) 함수를 작성한다.
(2-3) GetItemAtSlot ; 특정 슬롯의 아이템에 접근하는 함수
Item* Inventory::GetItemAtSlot(int slot)
{
if (slot < 0 || slot >= MAX_SLOT)
return nullptr;
return _items[slot];
}
인덱스를 대입하여 _items 배열에 직접 접근할 수 있는 함수이다.
예외 사항을 방지하기 위해 음수 또는 MAX_SLOT을 초과하는 수를 넣으면 nullptr을 반환하도록 한다.
(2-4) AddItem ; 아이템 저장 판단
bool Inventory::AddItem(Item* item)
{
if (item == nullptr)
return false;
int emptySlot = FindEmptySlot();
// 인벤토리가 꽉 차있음
if (emptySlot < 0)
return false;
_items[emptySlot] = item;
_itemCount++;
return true;
}
예외 사항으로 아이템 자체가 존재하지 않으면 false를 반환한다.
이후에 슬롯이 꽉 차 있는지 판단하고 아니라면 찾은 빈 슬롯에 해당 아이템을 저장하고 카운트를 1 증가시킨다.
그리고 true를 리턴한다.
(2-5) RemoveItem ; 아이템 제거 판단
bool Inventory::RemoveItem(Item* item)
{
if (item == nullptr)
return false;
int slot = FindItemSlot(item);
if (slot < 0)
return false;
_items[slot] = nullptr;
_itemCount--;
return true;
// delete this;
}
위와 동일하게 예외 사항을 설정한다. 다른점은 빈 슬롯을 찾는 것이 아니라, item이 있는 슬롯을 찾는 것이다.
해당 슬롯을 찾았다면 nullptr로 바꿔주고 카운트를 1 감소시킨다.
여기에서 동적할당된 포인터를 제거해야 하는지 판단해야 하는데, 여기에서는 delete 하지 않도록 하겠다.
(2-6) Clear ; 인벤토리 비우기
void Inventory::Clear()
{
for (int i = 0; i < MAX_SLOT; i++)
{
if (_items[i])
{
delete _items[i];
_items[i] = nullptr;
}
}
}
동적 할당된 모든 슬롯을 삭제하고 nullptr로 초기화한다.
(3) GetInstance, s_instance ; 싱글톤
//-----------Inventory.h------------------
class Inventory
{
public:
// 생략
static Inventory* GetInstance();
private:
// 생략
static Inventory* s_instance;
// 생략
//-----------Inventory.cpp----------------
Inventory* Inventory::s_instance = nullptr;
Inventory* Inventory::GetInstance()
{
if (s_instance == nullptr)
s_instance = new Inventory();
return s_instance;
}
Inventory 함수 내에 private으로 지정된 함수들을 전역적으로 접근하고 제어하기 위해 싱글톤 패턴을 사용한다.
GetInstance 함수는 static으로 생성된 s_instance(인벤토리가 동적 할당된 상태)를 반환한다.
이렇게 생성된 싱글톤을 이용하여 Main에서 활용할 수 있다.
(4) Main (RPGGame.cpp)
#include <iostream>
#include "Item.h"
#include "Inventory.h"
using namespace std;
// 아이템은 아이템 타입으로 반환
Item* DropItem()
{
if (rand() % 2 == 0)
{
Weapon* weapon = new Weapon();
return weapon;
}
else
{
Armor* armor = new Armor();
return armor;
}
}
int main()
{
srand((unsigned)time(0));
for (int i = 0; i < 100; i++)
{
Item* item = DropItem();
item->PrintInfo();
if (Inventory::GetInstance()->AddItem(item))
{
cout << "Added Item to inven" << endl;
}
else
{
cout << "Failed Item to inven" << endl;
delete item;
}
}
여기에서는 GetInstance 함수를 사용해 bool 타입인 AddItem 함수를 판단하여 올바르게 아이템이 추가되었는지 추적한다.
만약에 어떤 이유로 아이템이 추가되지 못한다면 메모리 누수를 방지하기 위해 delete 한다.
(5) 랜덤으로 몇 개의 아이템이 제거되는 상황
// PK로 인해 랜덤으로 일부 아이템 드랍
for (int i = 0; i < 20; i++)
{
int randIndex = rand() % MAX_SLOT;
Item* item = Inventory::GetInstance()->GetItemAtSlot(randIndex);
if (item && Inventory::GetInstance()->RemoveItem(item))
{
cout << "Item Removed" << endl;
delete item;
}
}
}
Hardcore RPG 게임에서 강제 PK로 인해 아이템을 떨어트리는 상황을 구현한다.
20개의 랜덤 아이템이 제거되는데, 일반적인 상황에서는 특정 아이템 슬롯에 접근하는 GetItemAtSlot 함수에 접근할 수 없다(접근 지정자가 private으로 지정되어 있기 때문에).
따라서 싱글톤 패턴을 이용하여 GetInstance로 접근한다.
아이템이 존재하는 슬롯을 찾아 item 포인터에 지정하고
이 포인터가 nullptr이 아니며 아이템을 제거할 수 있는 상황이라면 아이템을 삭제하고 로그를 띄우도록 한다.