게임에서 상자 시스템은 보상을 통해 유저들에게 재미와 긴장감을 제공하며, 게임 내 아이템을 무작위로 획득하는 기회를 제공합니다. 또한 유저는 상자에 아이템을 보관하는 기능을 이용할 수 있습니다. 상자 시스템을 구현하고 이를 정리하였습니다.
📺 미리보기
💬 목차
- 총 두 개의 목차로 구성되어 있습니다.
- 글 별로 아직 언급되지 않은 클래스의 호출이 있을 수 있습니다. 이 사항은 모든 글을 참조하면 충분히 이해할 수 있습니다.
[📌현재 글] 1. [유니티] 상자 시스템(1) - 상자 데이터
📖 구현 내용
- 월드에 배치되어있는 상자를 바라보고 키를 누르면 상자가 열립니다.
- 플레이어에게 아이템을 지급하기위한 목적으로 상자를 처음 열을경우 설정에 따라 아이템이 무작위로 생성됩니다.
- 아이템이 생성될 때 아이템이 생성되는 위치 또한 무작위로 설정됩니다.
- 처음으로 상자를 여는것이 아닌경우 저장된 아이템이 유지되며 해당 아이템을 꺼낼 수 있습니다.
- 플레이어의 아이템을 상자에 집어넣어 보관할 수 있습니다.
- 상자의 데이터가 저장되어 게임을 세이브 및 로드를 할 때 해당 사항이 저장됩니다.
- 상자를 열 때 UI의 영역의 크기를 다르게 설정할 수 있습니다. 예) 2x3 크기, 5x5크기 등...
✅ 구현
- 이번 글에서는 월드에 배치할 상자에 대해 아이템을 보관하고, 상자 내부의 아이템을 초기화하기위한 기능을 구현합니다.
- 세이브 및 로드 기능도 포함되어있지만, 이 글에서는 다루지 않겠습니다.
· ChestController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
namespace ChestSystem
{
[System.Serializable]
public struct ChestSlotItem
{
[Header("아이템 코드")]
[SerializeField] public EntityCode itemCode;
[HideInInspector] public int itemCount;
[HideInInspector] public int itemPositionIndex; // 아이템의 슬롯 위치
}
[System.Serializable]
public struct ChestItemSpawnConfig
{
[Header("\n초기화 시 아이템 스폰 설정")]
[Header("스폰을 시도할 아이템의 코드")]
[SerializeField] public EntityCode itemCode;
[Header("아이템의 스폰율 (0~1)")]
[Range(0.0f, 1.0f)][SerializeField] public float spawnRate;
[Header("아이템의 최소 스폰 개수")]
[SerializeField] public int minItemCount;
[Header("아이템의 최대 스폰 개수")]
[SerializeField] public int maxItemCount;
}
[System.Serializable]
public class ChestInfo
{
[Header("상자의 고유 ID")]
[SerializeField] public int chestUniqueId;
[Header("상자 인벤토리의 가로 셀 개수")]
[Range(1, 10)][SerializeField] public int row;
[Header("상자 인벤토리의 세로 셀 개수")]
[Range(1, 10)][SerializeField] public int col;
[Header("초기화 시 상자를 초기화 할 설정값")]
[SerializeField] public ChestItemSpawnConfig[] chestItemSpawnConfig;
// 상자에 있는 아이템의 정보
[HideInInspector] public List<ChestSlotItem> chestSlotItems;
}
public class ChestController : MonoBehaviour
{
[Header("초기화 시 상자의 정보")]
[SerializeField] public ChestInfo ChestInfo;
[field: Header("상자의 타입")]
[field: SerializeField] public ChestType ChestType { private set; get; } = ChestType.HEAVY_WOOD;
private Animator mChestAnimator; // 상자의 애니메이션 컨트롤러
private bool mIsInitReady = true; // 현재 초기화 대기상태인가?
public bool IsInitReady
{
get
{
return mIsInitReady;
}
}
public int ChestUniqueId
{
get
{
return ChestInfo.chestUniqueId;
}
}
private void Awake()
{
mChestAnimator = GetComponentInChildren<Animator>();
}
public void TryOpenDialog()
{
// 만약 초기화 대기 상태라면?
if (mIsInitReady == true)
Init();
// 상자 다이얼로그 열기
ChestDialogManager.Instance.TryOpenDialog(this);
}
// 최초로 해당 상자를 여는경우 무작위 초기화 설정
private void Init()
{
// 최초 1회 상자를 열었음을 설정
mIsInitReady = false;
// 무작위 unique 값을 리턴하기위한 리스트
List<int> randPosGen = new List<int>();
for (int i = 0; i < ChestInfo.row * ChestInfo.col; ++i)
randPosGen.Add(i);
// 상자 아이템 리스트 생성
ChestInfo.chestSlotItems = new List<ChestSlotItem>();
for (int i = 0; i < ChestInfo.chestItemSpawnConfig.Length; ++i)
{
// 확률을 계산하여 확률에 미치지 못하는경우 리턴
if (Random.value > ChestInfo.chestItemSpawnConfig[i].spawnRate)
continue;
// 아이템 한개 생성
ChestSlotItem slotItem = new ChestSlotItem();
{
// 해당 아이템의 개수를 범위 내 랜덤으로 지정
slotItem.itemCount = Random.Range(ChestInfo.chestItemSpawnConfig[i].minItemCount, ChestInfo.chestItemSpawnConfig[i].maxItemCount + 1);
// 무작위 위치를 지정
int randIndex = Random.Range(0, randPosGen.Count);
// 고유한 무작위 값 가져오기
int randPos = randPosGen[randIndex];
// 해당 위치의 값 요소 제거 (고유성 보장)
randPosGen.RemoveAt(randIndex);
// 값 지정
slotItem.itemPositionIndex = randPos;
// 아이템코드 가져오기
slotItem.itemCode = ChestInfo.chestItemSpawnConfig[i].itemCode;
// 아이템을 리스트에 삽입
ChestInfo.chestSlotItems.Add(slotItem);
}
}
}
public void PlayChestAnim(bool isOpen)
{
switch (this.ChestType)
{
case ChestType.HEAVY_WOOD:
SoundManager.Instance.PlaySound2D($"{(isOpen ? "Open" : "Shut")}_HeavyWood_" + SoundManager.Range(1, 3));
break;
case ChestType.LIGHT_WOOD:
SoundManager.Instance.PlaySound2D($"{(isOpen ? "Open" : "Shut")}_LightWood_" + SoundManager.Range(1, 3));
break;
case ChestType.METAL:
SoundManager.Instance.PlaySound2D($"{(isOpen ? "Open" : "Shut")}_Metal_" + SoundManager.Range(1, 3));
break;
}
if(isOpen)
mChestAnimator.SetTrigger("openLid");
else
mChestAnimator.SetTrigger("closeLid");
}
#region Save & Load
public void LoadFromData(GameSave.SceneChestInfo chestInfo)
{
// 로드 된 경우 상자를 열었음을 설정
mIsInitReady = false;
// 기존 상자의 슬롯 정보는 제거
ChestInfo.chestItemSpawnConfig = null;
// 새로운 슬롯들을 사용하기위해 리스트 대치
ChestInfo.chestSlotItems = chestInfo.chestSlotItems.ToList();
}
public GameSave.SceneChestInfo GetData()
{
GameSave.SceneChestInfo sceneChestInfo = new GameSave.SceneChestInfo();
sceneChestInfo.chestUniqueId = ChestInfo.chestUniqueId;
sceneChestInfo.chestSlotItems = ChestInfo.chestSlotItems.ToArray();
return sceneChestInfo;
}
#endregion
}
}
[Header("초기화 시 상자의 정보")]
[SerializeField] public ChestInfo ChestInfo;
- 상자를 처음 열을경우 해당 상자에 아이템을 초기화하기위한 정보를 정의합니다.
[field: Header("상자의 타입")]
[field: SerializeField] public ChestType ChestType { private set; get; } = ChestType.HEAVY_WOOD;
- 상자의 타입입니다.
- 큰 기능이 아닌 소리를 재생하기위한 타입을 정의합니다.
- 예를들어, HEAVY_WOOD인경우에는 무거운 나무소리, METAL인경우에는 철제 상자 소리가 들리게합니다.
private bool mIsInitReady = true; // 현재 초기화 대기상태인가?
public bool IsInitReady
{
get
{
return mIsInitReady;
}
}
- 해당 상자가 한번도 열리지 않아 초기화 대상인지 확인하기위한 변수입니다.
public int ChestUniqueId
{
get
{
return ChestInfo.chestUniqueId;
}
}
- 세이브 및 로드 기능을 이용할 때 상자를 식별하기위한 ID입니다.
public void TryOpenDialog()
{
// 만약 초기화 대기 상태라면?
if (mIsInitReady == true)
Init();
// 상자 다이얼로그 열기
ChestDialogManager.Instance.TryOpenDialog(this);
}
- 해당 상자에 접근하여 상자 열기를 시도합니다.
- ChestDialogManager에 상자의 정보를 전달하여 상자 내부의 아이템을 다이얼로그 창을 통해 보여줍니다.
- ChestDialogManager는 다음 글에서 다루겠습니다.
// 최초로 해당 상자를 여는경우 무작위 초기화 설정
private void Init()
{
// 최초 1회 상자를 열었음을 설정
mIsInitReady = false;
// 무작위 unique 값을 리턴하기위한 리스트
List<int> randPosGen = new List<int>();
for (int i = 0; i < ChestInfo.row * ChestInfo.col; ++i)
randPosGen.Add(i);
// 상자 아이템 리스트 생성
ChestInfo.chestSlotItems = new List<ChestSlotItem>();
for (int i = 0; i < ChestInfo.chestItemSpawnConfig.Length; ++i)
{
// 확률을 계산하여 확률에 미치지 못하는경우 리턴
if (Random.value > ChestInfo.chestItemSpawnConfig[i].spawnRate)
continue;
// 아이템 한개 생성
ChestSlotItem slotItem = new ChestSlotItem();
{
// 해당 아이템의 개수를 범위 내 랜덤으로 지정
slotItem.itemCount = Random.Range(ChestInfo.chestItemSpawnConfig[i].minItemCount, ChestInfo.chestItemSpawnConfig[i].maxItemCount + 1);
// 무작위 위치를 지정
int randIndex = Random.Range(0, randPosGen.Count);
// 고유한 무작위 값 가져오기
int randPos = randPosGen[randIndex];
// 해당 위치의 값 요소 제거 (고유성 보장)
randPosGen.RemoveAt(randIndex);
// 값 지정
slotItem.itemPositionIndex = randPos;
// 아이템코드 가져오기
slotItem.itemCode = ChestInfo.chestItemSpawnConfig[i].itemCode;
// 아이템을 리스트에 삽입
ChestInfo.chestSlotItems.Add(slotItem);
}
}
}
- 상자를 처음 여는경우 아이템을 지급할 목적으로 초기화합니다.
- 상자 내부에 존재하는 아이템을 무작위로 설정하여 게임마다 지급하는 아이템을 다르게 설정할 수 있습니다.
- 인덱스 번호를 이용하여 슬롯 내의 위치를 지정하여 아이템의 위치 또한 변경할 수 있습니다.
[System.Serializable]
public struct ChestItemSpawnConfig
{
[Header("\n초기화 시 아이템 스폰 설정")]
[Header("스폰을 시도할 아이템의 코드")]
[SerializeField] public EntityCode itemCode;
[Header("아이템의 스폰율 (0~1)")]
[Range(0.0f, 1.0f)][SerializeField] public float spawnRate;
[Header("아이템의 최소 스폰 개수")]
[SerializeField] public int minItemCount;
[Header("아이템의 최대 스폰 개수")]
[SerializeField] public int maxItemCount;
}
- 상자를 처음 열어서 초기화를 해야하는경우 스폰할 아이템에 대한 정보를 정의하는 구조체입니다.
[System.Serializable]
public struct ChestSlotItem
{
[Header("아이템 코드")]
[SerializeField] public EntityCode itemCode;
[HideInInspector] public int itemCount;
[HideInInspector] public int itemPositionIndex; // 아이템의 슬롯 위치
}
- 실제로 상자 내에 보관중인 아이템을 담는 정보입니다.
[System.Serializable]
public class ChestInfo
{
[Header("상자의 고유 ID")]
[SerializeField] public int chestUniqueId;
[Header("상자 인벤토리의 가로 셀 개수")]
[Range(1, 10)][SerializeField] public int row;
[Header("상자 인벤토리의 세로 셀 개수")]
[Range(1, 10)][SerializeField] public int col;
[Header("초기화 시 상자를 초기화 할 설정값")]
[SerializeField] public ChestItemSpawnConfig[] chestItemSpawnConfig;
// 상자에 있는 아이템의 정보
[HideInInspector] public List<ChestSlotItem> chestSlotItems;
}
- 상자의 정보를 나타내는 구조체입니다.
✅ 사용
- 위 이미지와 같이 상자 오브젝트에서 상자 시스템을 사용합니다.
- 고유 식별 아이디를 설정하여 해당 상자를 식별할 수 있도록 합니다.
- Row, Col을 설정하여 열리는 상자의 크기를 설정할 수 있습니다.
- Chest Item Spawn Config에서 처음으로 상자를 열 경우 어떤 아이템들을 스폰할지 설정할 수 있습니다.
// 상자 다이얼로그 열기
ChestDialogManager.Instance.TryOpenDialog(this);
- 상자를 엽니다.
- 상자 다이얼로그 매니저에게 자신의 정보를 전달하여 호출합니다.
- 상자 다이얼로그 매니저는 다음 글에서 다루겠습니다.
'unity game modules' 카테고리의 다른 글
[유니티] 게임 저장 시스템(1) - 슬롯 구성 (0) | 2023.04.02 |
---|---|
[유니티] 상자 시스템(2) - 상자 다이얼로그 (0) | 2023.04.02 |
[유니티] 제작 시스템(4) - 매니저 (0) | 2023.04.02 |
[유니티] 제작 시스템(3) - 제작소 (0) | 2023.04.02 |
[유니티] 제작 시스템(2) - 제작 슬롯 (0) | 2023.04.02 |