Item
아이템를 만드는 것은 쉽지 않다.
여러 과정이 필요한데 그 중 데이터 베이스 테이블 설계부터 해보자.
[Table("Item")]
public class ItemDb
{
public int ItemDbId { get; set; }
public int TemplateId { get; set; }
public int Count { get; set; }
public int Slot { get; set; }
[ForeignKey("Owner")]
public int? OwnerDbId { get; set; }
public PlayerDb Owner { get; set; }
}
add-migration을 통해 데이터베이스를 업데이트 해준다.
게임에 접속 했을 때 보유한 아이템들을 불러온다고 한다면
using (AppDbContext db = new AppDbContext())
{
List<ItemDb> items = db.Items
.Where(i => i.OwnerDbId == playerInfo.PlayerDbId)
.ToList();
foreach (var itemDb in items)
{
// 인벤토리
}
// 클라한테도 아이템 목록 전달.
}
우린 여기서 주석만 채우면 될 것이다.
그 전에 클라한테 건네줄 패킷을 설계하자.
enum ItemType{
ITEM_TYPE_NONE = 0;
ITEM_TYPE_WEAPON = 1;
ITEM_TYPE_ARMOR = 2;
ITEM_TYPE_CONSUMABLE = 3;
}
enum WeaponType{
WEAPON_TYPE_NONE = 0;
WEAPON_TYPE_SWORD = 1;
WEAPON_TYPE_BOW = 2;
}
enum ArmorType {
ARMOR_TYPE_NONE = 0;
ARMOR_TYPE_HELMET = 1;
ARMOR_TYPE_ARMOR = 2;
ARMOR_TYPE_BOOTS = 3;
}
enum ConsumableType{
CONSUMABLE_TYPE_NONE = 0;
CONSUMABLE_TYPE_POTION = 1;
}
message S_ItemList {
repeated ItemInfo items = 1;
}
message ItemInfo {
int32 itemDbId = 1;
int32 templateId =2;
int32 count = 3;
int32 slot = 4;
}
그리고 생각해보면 아이템 목록을 한번 db에서 가지고 온 후에 그 값을 계속 들고 있어야 다음에 또 접근할 필요가 없을 것이다. 그렇기 때문에 Item 클래스와 Inventory 클래스를 만들어서 게임서버에서 메모리상에 들고 있도록 하자. 이는 Player가 생성하고 가지고 있기에 Player 객체에 의존한다.
using Google.Protobuf.Protocol;
using Server.Data;
using Server.DB;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
namespace Server.Game
{
public class Item
{
public ItemInfo Info { get; } = new ItemInfo();
public int ItemDbId
{
get { return Info.ItemDbId; }
set { Info.ItemDbId = value; }
}
public int TemplateId
{
get { return Info.TemplateId; }
set { Info.TemplateId = value; }
}
public int Count
{
get { return Info.Count; }
set { Info.Count = value; }
}
public ItemType ItemType { get; private set; }
public bool Stackable { get; protected set; }
public Item(ItemType itemType)
{
ItemType = itemType;
}
public static Item MakeItem(ItemDb itemDb)
{
Item item = null;
ItemData itemData = null;
DataManager.ItemDict.TryGetValue(itemDb.TemplateId, out itemData);
if (itemData == null)
return null;
switch (itemData.itemType)
{
case ItemType.Weapon:
item = new Weapon(itemDb.TemplateId);
break;
case ItemType.Armor:
item = new Armor(itemDb.TemplateId);
break;
case ItemType.Consumable:
item = new Consumable(itemDb.TemplateId);
break;
}
if(item != null)
{
item.ItemDbId = itemDb.ItemDbId;
}
return item;
}
}
public class Weapon : Item
{
public WeaponType WeaponType { get; private set; }
public int Damage { get; private set; }
public Weapon(int templateId) : base(ItemType.Weapon)
{
Init(templateId);
}
void Init(int templateId)
{
ItemData itemData = null;
DataManager.ItemDict.TryGetValue(templateId,out itemData);
if (itemData.itemType != ItemType.Weapon)
return;
WeaponData data = (WeaponData)itemData;
{
TemplateId = data.id;
Count = 1;
WeaponType = data.weaponType;
Damage = data.damage;
Stackable = false;
}
}
}
public class Armor : Item
{
public ArmorType ArmorType { get; private set; }
public int Defence { get; private set; }
public Armor(int templateId) : base(ItemType.Armor)
{
Init(templateId);
}
void Init(int templateId)
{
ItemData itemData = null;
DataManager.ItemDict.TryGetValue(templateId, out itemData);
if (itemData.itemType != ItemType.Armor)
return;
ArmorData data = (ArmorData)itemData;
{
TemplateId = data.id;
Count = 1;
ArmorType = data.armorType;
Defence = data.defence;
Stackable = false;
}
}
}
public class Consumable : Item
{
public ConsumableType ConsumableType { get; private set; }
public int MaxCount { get; private set; }
public Consumable(int templateId) : base(ItemType.Consumable)
{
Init(templateId);
}
void Init(int templateId)
{
ItemData itemData = null;
DataManager.ItemDict.TryGetValue(templateId, out itemData);
if (itemData.itemType != ItemType.Consumable)
return;
ConsumableData data = (ConsumableData)itemData;
{
TemplateId = data.id;
Count = 1;
ConsumableType = data.consumableType;
MaxCount = data.maxCount;
Stackable = false;
}
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace Server.Game
{
public class Inventory
{
Dictionary<int, Item> _items = new Dictionary<int, Item>();
public void Add(Item item)
{
_items.Add(0, item);
}
public Item Get(int itemDbId)
{
Item item = null;
_items.TryGetValue(itemDbId, out item);
return item;
}
public Item Find(Func<Item, bool> condition)
{
foreach (Item item in _items.Values)
{
if(condition.Invoke(item))
return item;
}
return null;
}
}
}
이제 위의 주석을 채울 수 있다.
using (AppDbContext db = new AppDbContext())
{
List<ItemDb> items = db.Items
.Where(i => i.OwnerDbId == playerInfo.PlayerDbId)
.ToList();
foreach (var itemDb in items)
{
Item item = Item.MakeItem(itemDb);
if(item != null)
{
MyPlayer.Inven.Add(item);
ItemInfo info = new ItemInfo();
info.MergeFrom(item.Info);
itemListPacket.Items.Add(info);
}
}
}
Send(itemListPacket);
Client Inventory
이제 위의 아이템이 잘 작동하는지 확인해보기 위해 클라 쪽에 Inventory를 만들어보자.
RPG inventory icons | 2D Icons | Unity Asset Store
FANTASTIC UI STARTER PACK | 2D Icons | Unity Asset Store
이 에셋들을 이용해서 제작해보자!
대충 이런 느낌의 인벤토리를 만들어주고 UI_GameScene, UI_Stat, UI_Inventory, UI_Inventory_Item 스크립트를 생성한다. 그리고 이름에 맞는 스크립트를 각 프리팹에 넣어준다.
그리고 Inventory를 관리하기 위해 Manager를 하나 추가해준다.
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class UI_Inventory : UI_Base
{
public List<UI_Inventory_Item> Items { get; } = new List<UI_Inventory_Item>();
public override void Init()
{
Items.Clear();
GameObject grid = transform.Find("ItemGrid").gameObject;
foreach (Transform child in grid.transform)
Destroy(child.gameObject);
for(int i = 0; i < 20; i++)
{
GameObject go = Managers.Resource.Instantiate("UI/Scene/UI_Inventory_Item", grid.transform);
UI_Inventory_Item item = go.GetComponent<UI_Inventory_Item>();
Items.Add(item);
}
}
// 아이템 정보 갱신 함수
public void RefreshUI()
{
List<Item> items = Managers.Inven.Items.Values.ToList();
items.Sort((left, right) => { return left.Slot - right.Slot; });
foreach (var item in items)
{
if (item.Slot < 0 || item.Slot >= 20)
continue;
Items[item.Slot].SetItem(item.TemplateId, item.Count);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UI_Inventory_Item : UI_Base
{
[SerializeField]
Image _icon;
public override void Init()
{
}
public void SetItem(int templateId, int count)
{
Data.ItemData itemData = null;
Managers.Data.ItemDict.TryGetValue(templateId, out itemData);
Sprite icon = Managers.Resource.Load<Sprite>(itemData.iconPath);
_icon.sprite = icon;
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class InventoryManager : MonoBehaviour
{
public Dictionary<int, Item> Items { get; } = new Dictionary<int, Item>();
public void Add(Item item)
{
Items.Add(item.ItemDbId, item);
}
public Item Get(int itemDbId)
{
Item item = null;
Items.TryGetValue(itemDbId, out item);
return item;
}
public Item Find(Func<Item, bool> condition)
{
foreach (Item item in Items.Values)
{
if (condition.Invoke(item))
return item;
}
return null;
}
public void Clear()
{
Items.Clear();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UI_GameScene : UI_Scene
{
public UI_Stat StatUI { get; private set; }
public UI_Inventory InvenUI { get; private set; }
public override void Init()
{
base.Init();
StatUI = GetComponentInChildren<UI_Stat>();
InvenUI = GetComponentInChildren<UI_Inventory>();
StatUI.gameObject.SetActive(false);
InvenUI.gameObject.SetActive(false);
}
}
패킷이 왔을 때 다음과 같이 동작해서 인벤을 출력한다.
public static void S_ItemListHandler(PacketSession session, IMessage packet)
{
S_ItemList itemListPacket = (S_ItemList)packet;
UI_GameScene gameSceneUI = Managers.UI.SceneUI as UI_GameScene;
UI_Inventory invenUI = gameSceneUI.InvenUI;
Managers.Inven.Clear();
foreach (ItemInfo itemInfo in itemListPacket.Items)
{
Item item = Item.MakeItem(itemInfo);
Managers.Inven.Add(item);
}
// UI에서 표시
invenUI.gameObject.SetActive(true);
invenUI.RefreshUI();
}
여기서 핵심은 아이템을 불러올 때 인벤 매니저를 통해 메모리상에 아이템을 들고 있게 해 디비 접근을 최대한 줄이고 패킷의 양도 최대한 줄이는 것이 목적인 것이다.
'Unity > 온라인 RPG' 카테고리의 다른 글
[Unity 2D] 대형 구조 관리 - 서버 구조 개선 작업 (0) | 2024.04.02 |
---|---|
[Unity 2D] DB 연동 - Reward 와 아이템 착용 (0) | 2024.03.29 |
[Unity 2D] DB 연동 - Player, HP db 연동 (0) | 2024.03.26 |
[Unity 2D] 서버 구조 변경 - Command 패턴과 Job (0) | 2024.03.14 |
[Unity 2D] 서버 연동 - Hp bar와 DieEffect 그리고 몬스터 Ai (0) | 2024.03.13 |