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이 아니며 아이템을 제거할 수 있는 상황이라면 아이템을 삭제하고 로그를 띄우도록 한다.

'포트폴리오 > 기능 구현 연습' 카테고리의 다른 글

1. 아이템 드랍  (0) 2023.09.22