해당 포트폴리오는 SBS 게임 아카데미를 다니면서 제작하고 있는 포트폴리오
게임 기획안(미완성)
https://docs.google.com/presentation/d/11OoqAcT9QXyRMNp8z9JR2jpg0dmHnSD4ij8N11RYkVE/edit?usp=sharing
박상운 - [Template] 수업용_게임기획서샘플_2D.pptx의 사본
We Mustn’t Die Here 게임 기획서 작성자 : 박상운
docs.google.com
현재 구현한 시스템은 베이스 캠프 화면 일부와 탐험 맵 화면 일부
사용한 에셋과 인벤토리 코드 참조
유니티 (Unity) - 처음 만들어 보는 인벤토리 이해하기 (Inventory)
인벤토리를 만들기 위한 기본 구조를 살펴보겠습니다. 인벤토리가 어떻게 작동하는지 알아보기 위한 아주 간단한 프로그램입니다. 사용된 아이콘 및 슬롯 이미지 https://assetstore.unity.com/packages/2d
geojun.tistory.com
https://assetstore.unity.com/packages/2d/gui/gui-pro-kit-sci-fi-194741
GUI PRO Kit - Sci-Fi | 2D GUI | Unity Asset Store
Elevate your workflow with the GUI PRO Kit - Sci-Fi asset from Layer Lab. Find this & more GUI on the Unity Asset Store.
assetstore.unity.com
https://assetstore.unity.com/packages/2d/gui/icons/rpg-inventory-icons-56687
RPG inventory icons | 2D 아이콘 | Unity Asset Store
Elevate your workflow with the RPG inventory icons asset from REXARD. Browse more 2D GUI on the Unity Asset Store.
assetstore.unity.com
베이스 캠프 화면 구현 상태
- 구현된 메인 시스템
> 인벤토리 시스템
탐험 시스템(맵)
- 구현된 메인 시스템
> 랜덤 방 생성
> 방의 갯수는 변수로 조절
> 방은 가로 세로로 인접한 칸이 있어야 생성됨
> 방 이동이 구현되어 있으며 현재 위치와 인접해 있어야함
이 외에도 광부와 전투할 동물 몬스터에 대한 기초 스크립트 작성도 하였지만 나중에 다루겠음
아래는 관련 코드들이다
Inventory 관련 코드
//InventoryMng.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using TMPro;
public class InventoryMng : MonoBehaviour
{
static InventoryMng instance;
public static InventoryMng ins
{
get
{
if (instance == null)
{
instance = FindObjectOfType<InventoryMng>();
if (instance == null)
{
GameObject obj = new GameObject();
instance = obj.AddComponent<InventoryMng>();
obj.name = typeof(InventoryMng).Name;
}
}
return instance;
}
}
[SerializeField]
private Dictionary<Item, int> items = new Dictionary<Item, int>();
[SerializeField]
private Transform slotParent;
[SerializeField]
private List<Slot> slots;
[SerializeField]
private Item clickItem;
[SerializeField]
private GameObject itemInfo;
[SerializeField]
private TMP_Text itemInfoName;
[SerializeField]
private Image itemInfoImage;
[SerializeField]
private TMP_Text itemInfoDetail;
[SerializeField]
Canvas canvas;
GraphicRaycaster graphicRaycaster;
PointerEventData pointerEvent;
void Awake()
{
FreshSlot();
itemInfo.SetActive(false);
graphicRaycaster = canvas.GetComponent<GraphicRaycaster>();
pointerEvent = new PointerEventData(null);
}
private void Update()
{
ItemDetail();
}
public void ItemDetail()
{
//마우스가 눌렸을때 UI에 대한 RayCast를 실행해서 RayCast대상이 있고 해당 오브젝트의 Component가 Slot이라면 아이템의 정보를 띄워준다
if(Input.GetMouseButtonDown(0))
{
pointerEvent.position = Input.mousePosition;
List<RaycastResult> results = new List<RaycastResult>();
graphicRaycaster.Raycast(pointerEvent, results);
if (results.Count > 0 && results[0].gameObject.TryGetComponent(out Slot slot))
{
int i = 0;
Item target = null;
foreach(Item j in items.Keys)
{
if(i >= slots.IndexOf(results[0].gameObject.GetComponent<Slot>()))
{
target = j;
break;
}
i++;
}
if(target != null)
{
clickItem = target;
itemInfo.SetActive(true);
itemInfo.transform.position = pointerEvent.position;
itemInfoName.text = clickItem.ItemName;
itemInfoImage.sprite = clickItem.ItemImage;
itemInfoDetail.text = clickItem.ItemExplain;
}
else
{
clickItem = null;
itemInfo.SetActive(false);
}
}
}
}
public void FreshSlot()
{
int i = 0;
for (; i < items.Keys.Count && i < slots.Count; i++)
{
int k = 0;
Item target = null;
foreach(Item j in items.Keys)
{
if (k >= i)
{
target = j;
break;
}
k++;
}
slots[i].item = target;
slots[i].amountText.text = items[target].ToString();
slots[i].amountText.gameObject.SetActive(true);
}
for (; i < slots.Count; i++)
{
slots[i].item = null;
slots[i].amountText.gameObject.SetActive(false);
}
}
public void AddItem(Item _item, int amount)
{
if(items.ContainsKey(_item))
{
int i = 0;
foreach(Item j in items.Keys)
{
if(j == _item)
break;
i++;
}
items[_item] += amount;
FreshSlot();
}
else
{
if (items.Keys.Count < slots.Count)
{
items.Add(_item, amount);
int i = 0;
foreach (Item j in items.Keys)
{
if (j == _item)
break;
i++;
}
FreshSlot();
}
else
Debug.Log("슬롯이 가득참");
}
}
public void RemoveItem(Item _item)
{
int i = 0;
foreach (Item j in items.Keys)
{
if(j == _item)
break;
i++;
}
items.Remove(_item);
FreshSlot();
}
}
//ItemMng.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 모든 아이템 속성을 가지고 있는 ItemList로 드랍되거나 얻은 아이템을 추가해주는 스크립트
/// 사용법 InventoryMng.ins.AddItem(ItemList[0]);
/// </summary>
public class ItemMng : MonoBehaviour
{
[SerializeField] List<Item> ItemList;
private void Start()
{
InventoryMng.ins.AddItem(ItemList[0], 1);
}
}
//Slot.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class Slot : MonoBehaviour
{
[SerializeField] Image image;
public TMP_Text amountText;
private void Awake()
{
amountText.gameObject.SetActive(false);
}
private Item _item;
public Item item
{
get
{
return _item;
}
set
{
_item = value;
if (_item != null)
{
image.sprite = item.ItemImage;
image.color = new Color(1, 1, 1, 1);
}
else
image.color = new Color(1, 1, 1, 0);
}
}
}
//Item.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu]
public class Item : ScriptableObject
{
public string ItemName;
public Sprite ItemImage;
public string ItemExplain;
}
맵 관련 코드
//ExploreMapMng.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class ExploreMapMng : MonoBehaviour
{
public enum RoomKind
{
//식수를 얻거나 낚시를 할 수 있는 동굴 호수 방
lake,
//동물과의 전투를 벌이는 방
battle,
//버려진 다른 베이스 캠프가 있는 방
baseCamp,
//광물지대 방
mine,
//함정 방
trap,
//버려진 아이템이 있는 방
item,
//고립된 다른 광부가 있는 방
miner,
}
static ExploreMapMng instance;
public static ExploreMapMng ins
{
get
{
if (instance == null)
{
instance = FindObjectOfType<ExploreMapMng>();
if (instance == null)
{
GameObject obj = new GameObject();
instance = obj.AddComponent<ExploreMapMng>();
obj.name = typeof(ExploreMapMng).Name;
}
}
return instance;
}
}
[SerializeField]
Vector2[,] loca;
[SerializeField]
bool[,] locaFlag;
Vector2 startPos;
[SerializeField]
GameObject startPoint;
[SerializeField]
GameObject roomPrefab;
[SerializeField]
GameObject mapCanvas;
[SerializeField]
GameObject cursorPrefab;
[SerializeField]
GameObject cursor;
int mapSize = 0;
[SerializeField]
int maxMapSize;
int nowX;
int nowY;
void GetSize()
{
Vector2 _leftBottom = CameraMng.ins.cam.ViewportToWorldPoint(new Vector2(0, 0));
Vector2 _rightUp = CameraMng.ins.cam.ViewportToWorldPoint(new Vector2(1, 1));
int _x = (int)Mathf.Round(_rightUp.x - _leftBottom.x);
int _y = (int)Mathf.Round(_rightUp.y - _leftBottom.y);
loca = new Vector2[_x,_y];
locaFlag = new bool[_x, _y];
for (int i = 0; i < loca.GetLength(0); i++)
{
for (int j = 0; j < loca.GetLength(1); j++)
{
loca[i, j] = new Vector2(_leftBottom.x + i, _leftBottom.y + j);
}
}
}
void SetStartPoint()
{
startPos = CameraMng.ins.cam.WorldToScreenPoint(loca[1, 5]);
startPoint = Instantiate(roomPrefab);
startPoint.name = "StartPoint";
startPoint.GetComponent<Room>().StartRoomSet(loca[1, 5], startPos, new int[] {1, 5});
startPoint.transform.SetParent(mapCanvas.transform);
nowX = 1;
nowY = 5;
locaFlag[nowX, nowY] = true;
startPoint.transform.position = startPos;
}
public void UpdateNowXY(int _x, int _y)
{
nowX = _x;
nowY = _y;
}
void SetCursor()
{
cursor = Instantiate(cursorPrefab);
cursor.transform.SetParent(mapCanvas.transform);
cursor.transform.position = new Vector2(startPos.x + 50f, startPos.y + 50f);
}
void CursorUpdate()
{
Vector2 _cursorPos = CameraMng.ins.cam.WorldToScreenPoint(loca[nowX, nowY]);
cursor.transform.position = new Vector2(_cursorPos.x + 50f, _cursorPos.y + 50f);
}
void MapMaking(int _xPos, int _yPos)
{
int _x = _xPos;
int _y = _yPos;
//Debug.Log(mapSize);
if (_x > loca.GetLength(0) || _y > loca.GetLength(1))
return;
for (int j = _y - 1; j <= _y + 1; j++)
{
for (int i = _x - 1; i <= _x + 1; i++)
{
//배열 범위 밖인지 판단
if (IsOutOfRange(i, j))
continue;
//기준 위치와 같은 위치인 경우
if (i == _x || i == _y)
continue;
//현재 생성위치에 이미 방이 있는 경우
if (locaFlag[i, j])
continue;
int _cnt = 0;
//배열 범위에서 상하좌우에 방이 있는 경우(인접한 방이 있는경우)인지 판단
//방이 비어있지 않다면 cnt++
if (!IsOutOfRange(i - 1, j))
if (locaFlag[i - 1, j])
_cnt++;
if (!IsOutOfRange(i + 1, j))
if (locaFlag[i + 1, j])
_cnt++;
if (!IsOutOfRange(i, j - 1))
if (locaFlag[i, j - 1])
_cnt++;
if (!IsOutOfRange(i, j + 1))
if (locaFlag[i, j + 1])
_cnt++;
//Debug.Log(_cnt);
//cnt가 0인 경우, 즉 인접한 방이 없는 경우 continue
if (_cnt == 0)
continue;
//방이 없다면 50% 확률로 방을 생성
bool _dice = Random.value > .5;
if (_dice)
{
GameObject _target = Instantiate(roomPrefab);
_target.transform.SetParent(mapCanvas.transform);
Room _targetRoom = _target.GetComponent<Room>();
Vector2 _targetPos = CameraMng.ins.cam.WorldToScreenPoint(loca[i, j]);
_targetRoom.RoomInstiate(loca[i, j], _targetPos, new int[2] { i, j });
locaFlag[i, j] = true;
mapSize++;
}
if(mapSize >= maxMapSize)
return;
else
MapMaking(i, j);
}
}
}
private bool IsOutOfRange(int _x, int _y)
{
//x값 배열 범위 밖
if (loca.GetLength(0) - 1 <= _x || _x < 1)
return true;
//y값 배열 범위 밖
if (loca.GetLength(1) - 1 <= _y || _y < 1)
return true;
return false;
}
/// <summary>
/// 현재 위치 기준으로 _roomNum에 해당하는 좌표의 방이 인접했는지를 bool로 반환
/// </summary>
/// <param name="_roomNum">방 좌표 loca값</param>
/// <returns></returns>
public bool IsNear(int[] _roomNum)
{
int x = _roomNum[0];
int y = _roomNum[1];
if ((x - 1 == nowX || x + 1 == nowX) && y == nowY)
return true;
if ((y - 1 == nowY || y + 1 == nowY) && x == nowX)
return true;
//for (int i = x - 1; i <= x + 1; i++)
//{
// for (int j = y - 1; j <= y + 1; j++)
// {
// if(nowX == i && nowY == j)
// {
// return true;
// }
// }
//}
return false;
}
// Start is called before the first frame update
void Start()
{
GetSize();
SetStartPoint();
MapMaking(nowX, nowY);
SetCursor();
}
// Update is called once per frame
void Update()
{
CursorUpdate();
}
}
//Room.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Room : MonoBehaviour
{
[SerializeField]
GameObject unrevealed;
[SerializeField]
GameObject revealed;
[SerializeField]
Image revealedSprite;
[SerializeField]
int roomType;
[SerializeField]
Sprite[] roomTypeSprite;
[SerializeField]
Vector2 roomPos;
[SerializeField]
int[] roomNum;
/// <summary>
/// 방이 근처에 있다면 랜덤으로 방의 종류를 결정해주는 함수
/// </summary>
public void RandomRoomClicked()
{
if (ExploreMapMng.ins.IsNear(roomNum))
{
int _dice = Random.Range(0, 7);
roomType = (int)(ExploreMapMng.RoomKind)_dice;
ExploreMapMng.ins.UpdateNowXY(roomNum[0],roomNum[1]);
//revealedSprite.sprite = roomTypeSprite[roomType];
unrevealed.SetActive(false);
revealed.SetActive(true);
}
}
public void JustMoveRoom()
{
if(ExploreMapMng.ins.IsNear(roomNum))
{
ExploreMapMng.ins.UpdateNowXY(roomNum[0], roomNum[1]);
}
}
public void RoomInstiate(Vector2 _loca, Vector2 _pos, int[] _roomNum)
{
roomPos = _loca;
transform.position = _pos;
roomNum = _roomNum;
revealed.SetActive(false);
}
public void StartRoomSet(Vector2 _loca, Vector2 _pos, int[] _roomNum)
{
RoomInstiate(_loca, _pos, _roomNum);
revealed.SetActive(true);
unrevealed.SetActive(false);
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
'Unity' 카테고리의 다른 글
[턴제 생존 게임] 유닛 정보 UI (0) | 2023.05.19 |
---|---|
[TowerDefence] 타워 디펜스 게임 마무리 및 코드 정리 (0) | 2023.04.17 |
[타워디펜스] 타워 철거기능 구현 (0) | 2023.02.03 |
[타워디펜스] 몹 스포너(Spawner)와 웨이브(Wave) (0) | 2023.01.16 |
[타워디펜스] 메뉴UI와 자기장 (0) | 2023.01.13 |