Unity

[턴제 생존 게임] We Mustn't Die Here

wny0320 2023. 4. 14. 12:01

해당 포트폴리오는 SBS 게임 아카데미를 다니면서 제작하고 있는 포트폴리오

 

게임 기획안(미완성)

https://docs.google.com/presentation/d/11OoqAcT9QXyRMNp8z9JR2jpg0dmHnSD4ij8N11RYkVE/edit?usp=sharing 

 

박상운 - [Template] 수업용_게임기획서샘플_2D.pptx의 사본

We Mustn’t Die Here 게임 기획서 작성자 : 박상운

docs.google.com


현재 구현한 시스템은 베이스 캠프 화면 일부와 탐험 맵 화면 일부

 

 

사용한 에셋과 인벤토리 코드 참조

https://geojun.tistory.com/62

 

유니티 (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()
    {
        
    }
}