게임 개발 포트폴리오 | 용사마을타이쿤

내일배움캠프 1기 Unity 게임 개발 과정 트랙 수료생 최종 프로젝트 '용사마을타이쿤'을 소개합니다.
Feb 26, 2024
게임 개발 포트폴리오 | 용사마을타이쿤

📽️ 게임 소개

🕹️ 주요 콘텐츠

 
notion image

🎮 게임 조작법과 가이드

notion image
notion image
notion image
 
 

 

🖥️개발 과정

🎯 개발 목표

  • 게임 컨셉
농사 및 제작 시뮬레이션 + 성장시킨 동료를 통한 전투 시뮬레이션
 
  • 아트 컨셉
Paper2D : 캐릭터, 농작물 등의 상호작용 오브젝트
Voxel : 건물과 지형지물 오브젝트
 
  • 조작 컨셉
TPS : 시점을 마우스로 돌릴 수 있도록 하여 Paper2D의 아트 컨셉 극대화
건물 : 게임 내 주요 기능들은 대부분 건물을 통하여 체험 가능, 깔끔한 정렬을 위해 셀 그리드를 사용함

🛠 사용된 기술 스택

  • Unity 2022.3.2f1
  • GitHub
  • C#

📋 작업 과정

팀 노션

피그마

 
 

🧩클라이언트 구조

💡
동적 로딩
📌 씬을 불러오면 트리거에서 시작할 때 필요한 오브젝트를 생성합니다.
  • 클래스 간 필요한 참조를 쉽고 가볍게 하기 위해 만들었습니다.
  • 오브젝트의 수정이 잦아 인스펙터에서 직렬화할 경우 생기는 연결 해제를 미연에 방지합니다.
💡
아이템 Json의 종류와 사용
 
📌아이템의 종류와 관리
아이템 구조도
아이템 구조도
 
BaseItemData
public class BaseItemData { public int Id; public string Name; public int MaxStack; public int Price; }
다른 세부 아이템 카테고리들은 전부 BaseItemData를 상속받음.
 
또한 아이템들은 ID 컨벤션에 따라서 각기 다른 고유한 정수형 ID 값을 부여받음.
Id 컨벤션
만의 자리
천의 자리
백의 자리
십의 자리
일의 자리
1이면 아이템
카테고리
티어
하위 분류 카테고리
아이템 인덱스
 
 
📌 아이템 데이터 호출
ItemData의 역직렬화와 캐싱
public Dictionary<int, BaseItemData> Items = new Dictionary<int, BaseItemData>(); ... string itemJson = Resources.Load<TextAsset>(Const.Item_Data).text; ItemData itemData = JsonConvert.DeserializeObject<ItemData>(itemJson); foreach (BaseItemData itemdata in itemData.EquipData) { Items.Add(itemdata.Id, itemdata); }
→ID값을 Key로 입력하면 아이템 세부 데이터를 Value로 반환
 
기본적으로 인 게임에서 아이템 이동은 정수형 id 값의 전달과 저장을 기반으로 하며 아이템의 사용 시 아이템의 세부 데이터를 참조하도록 하였음.
var items = Managers.Instance.DataManager.Items[id];
 
💡
MVC 패턴을 이용한 인벤토리 시스템
  • UI에서 주어진 데이터의 정보를 표시해줘야 할 뿐만 아니라, UI로부터 입력 받은 조작을 통해 데이터에 접근해서 변경해줘야 하는 상황
  • MVC 패턴을 사용해 시스템을 구현하였음.
 
📌 디자인 요소 1. Model - View - Controller 사이를 어떻게 연결할 것인가?
View → Controller → Model : View가 Controller를 참조하고 Controller가 Model을 참조하도록 하여 유저의 인풋이 있으면 Controller를 참조하여 Model을 변경하도록 메소드를 호출함.
View는 Model의 변경을 이벤트를 통해 알 수 있게 함 (관찰자 패턴).
 
📌 디자인 요소 2. 어떻게 초기화해줄 것인가?
플레이어 초기화 시 Controller 초기화 → Controller에서 Model을 생성
의존성 주입 : Controller에서 View를 초기화하고 View의 초기화 인자로 Controller를 받음.
 
의존성 주입을 통해 Controller에서 View를 초기화해주도록 하지만, View가 Controller의 참조를 가질 수 있도록 하였음.
 

notion image
💡
절제된 싱글톤을 활용한 UI 매니저
싱글톤은 장단점이 명확하고 사용 방법에 있어 많은 논쟁이 있는 패턴입니다.
그중에서도 우리 팀은 장점을 극대화하기보다는 단점을 보완하여 활용하기로 하였습니다.
📌 단점을 보완할 규칙
public class UIManager : MonoBehaviour { ... private Dictionary<Type, BaseUI> _uis; public T GetUI<T>() where T : BaseUI {...} public T OpenUI<T>() where T : BaseUI {...} public void CloseUI<T>() where T : BaseUI {...} ... }
  • 규칙 1. 데이터는 오직 UI 오브젝트만 가질 것 2. 가장 기본적인 동작만을 할 것 (열기, 닫기, 가져오기, etc.) 3. UI에 필요한 초기화는 리턴 값을 활용할 것
이 3가지 규칙을 통하여 의존성을 줄이고 디버깅의 효율을 상승시켰으며 쉽고 편리하게 UI에 전역적인 접근이 가능하게 만들었습니다.
추가적으론 리턴 값을 활용하였기에 UI의 데이터 갱신이 편리합니다.
🔨 실제 사례 UIManager um = Managers.Instance.UIManager; OptionUI ui = um.OpenUI<OptionUI>(); ui.Init(() => { um.OpenUI<TitleUI>(); }); um.CloseUI<TitleUI>();
 
💡
클린 아키텍쳐를 활용한 용사 - 토벌 데이터 교환
  • 토벌의 View에서 토벌에 대한 정보(몬스터 풀, 아이템 풀, 페이즈 수 등)도 필요하고, View에 보여줄 용사에 대한 정보도 필요한 상황
  • 클린 아키텍쳐의존성 주입을 활용해 시스템을 구현하였음.
 
📌 디자인 요소 1. 클린 아키텍쳐를 적용한 M-V-C의 관계
View → Controller → Model :
  1. Model은 MonoBehaviour 상속 없이 데이터와 그 데이터의 직접적인 처리 메서드만을 가진다.
  1. Controller는 관리할 Model의 참조를 가진다.
  1. Controller는 Model에 내장된 함수를 실행시키거나, 다른 컨트롤러가 Model의 정보를 필요로 할 때 넘겨주는 역할을 한다
  1. View는 Model의 참조를 가지는 Controller의 참조를 가진다.
  1. View는 사용자의 입력을 Controller에 전달하고, 필요한 시점에 Model을 내려받아 갱신한다.
 
📌 디자인 요소 2. 어떻게 초기화해줄 것인가?
플레이어 초기화 시 ‘Controller들을 묶어둔 클래스’ 초기화 → Controller 초기화
그 후, UIManager에 ‘Controller들을 묶어둔 클래스’를 인자로 넘겨 Controller를 필요로 하는
UI들에게 Controller들을 의존성 주입 → 하나의 View에서 여러 Controller의 참조를 가지는 게 수월해짐
UIManager의 Init 함수
UIManager의 Init 함수
동적인 데이터(랜덤한 영웅, 스테이지 데이터)의 생성을 위한 Builder
Builder는 해당 Model을 필요로하는 Controller의 참조를 가짐.
사용자의 입력으로 Model이 생성되는 경우(스테이지 데이터) View에게 Builder를 가지고 사용자의 입력 없이 Model이 생성되는 경우(랜덤 용사 생성) Controller가 직접 Builder를 가지게 함. Builder는 Model 생성에 필요한 데이터를 수집하여 필요한 시점에 Build 해주고, 동시에 Model의 참조를 Controller에게 의존성 주입함.
notion image
 
결과적으로 아래에서 위를 바라보는 구조가 됨

 
 

 

💥트러블 슈팅

💡
Dictionary를 직렬화 가능하게 만들어 스크립터블 오브젝트에 집어넣기
 
📌 문제의 알고리즘과 분석
  • 아이템에 필요한 스프라이트를 스크립터블 오브젝트에 넣어 관리하려고 하는데, <int, Sprite> Dictionary를 넣게 되면 해당 아이템의 코드를 키로 넣어 스프라이트를 받아올 수 있어 관리하기 용이함.
  • 하지만 Dictionary는 라이브러리 없이 직렬화가 되지 않기 때문에 Inspector에서 보이지 않아 에디터에서 편집 불가능한 문제가 발생
 
📌 어떻게 해결할 것인가?
다음과 같이 DictionaryISerializationCallbackReceiver 인터페이스를 상속받아 직렬화와 역직렬화 시의 처리 방법을 직접 구현
코드
public class SerializedDictionary<V> : Dictionary<int, V>, ISerializationCallbackReceiver { [Serializable] public class KeyValue { public int Key; public V Value; public KeyValue(int key, V value) { Key = key; Value = value; } } [SerializeField] List<KeyValue> KeyValues = new(); public void OnBeforeSerialize() { KeyValues.Clear(); foreach (KeyValuePair<int, V> pair in this) { KeyValues.Add(new KeyValue(pair.Key, pair.Value)); } } public void OnAfterDeserialize() { this.Clear(); for (int i = 0, icount = KeyValues.Count; i < icount; ++i) { int key = KeyValues[i].Key; while (this.ContainsKey(key)) ++key; this.Add(key, KeyValues[i].Value); } } }
 
Dictionary의 Key를 int로 한정지은 이유는, 스크립터블 오브젝트의 요소를 추가할 때 기존 요소와 같은 값이 추가되기 때문에 역직렬화가 되지 않기 때문에 int로 한정지어 Key 중복 시 다음 값의 Key를 가지도록 하였음.
 
 
📌 결과
notion image
아이템 코드와 그 순서에 상관 없이 스크립터블 오브젝트에 값을 추가하여 이미지를 ItemCode로만 접근이 가능하도록 제작하였음.
 
💡
인터페이스를 통한 건물 상호작용 규격화
 
📌 문제의 알고리즘과 분석
  • 건물마다 클릭과 상호작용 버튼을 통해 작동하는 기능 구현이 필요
  • 따로따로 구현하면 공통적인 작동 방식이 너무 많은 상황
 
📌 어떻게 해결할 것인가?
인터페이스로 클릭과 상호작용에 대한 작동방식 규격화
public interface IInteractable { public void Interact(); public string GetName(); }
 
📌 결과
상호작용이 필요한 오브젝트에 IInteractable 인터페이스를 상속받은 컴포넌트를 구현하기만 하면 상호작용이 구현됨
public void Interact() { if (PointingObject == null) return; if (!PointingObject.transform.parent.TryGetComponent(out IInteractable interactable)) return; interactable.Interact(); }
 
💡
뭉쳐있는 함수의 기능 분리하고 상황에 맞춰 사용하기
 
notion image
  • 건물의 생성은 하나의 절차로만 생성되는 것이 아닌 순서대로 절차들을 진행해야지 비로소 완성이 됩니다. 하지만 플레이어가 초기에 사용할 건물들을 미리 만들어둘 필요가 생겼고, 현재의 알고리즘으로는 불가능하였습니다.
 
📌 문제의 알고리즘과 분석
notion image
문제점 1.
마우스로 입력하지 않은 값은 건설이 불가능하다.
문제점 2.
건설 가능 여부를 확인 후 청사진은 필수적으로 설치가 된다.
초기에 만들어줄 건물은 마우스로 입력하는 게 아닌 코드로 좌표를 입력해줘야 하며, 건설이 가능한지 판단 후 청사진이 아닌 실제 건물이 생성되어야 했지만 불가능 했습니다.
 
📌 어떻게 해결할 것인가?
문제점 1 분석.
건설 위치를 선정하는 값은 마우스 입력과 상관없이 Vector3의 값만 있으면 가능하다.
문제점 2 분석.
건설을 시도하기 전에도 건설 가능 여부를 판단할 시점이 존재한다.
두 가지 문제점의 공통적인 부분은 하나의 기능이 만들어낸 값을 오로지 내부에서만 처리하고 처리하는 과정도 전혀 다른 기능이었습니다.
그리하여 값을 전달하는 방식으로 변경하고 하나의 함수의 기능을 나누기로 했습니다.
 
📌 결과
notion image
코드뿐만이 아닌 다른 방식으로도 값만 주면 건설 여부를 판단할 수 있게 되었고, 판단 여부를 콜백을 받고 어떤 행동을 할지도 자유롭게 설정 가능하게 되었습니다.
 
 

☎️팀원 구성 및 연락처


이름
담당
깃허브 주소
블로그 주소
신우석
팀장,농사,저장
https://seoksii.tistory.com/
기현빈
전투 총괄,동료 용사 관리,탐험,토벌
노재우
UI 총괄,타일맵 시스템,건설
이하택
아이템 총괄,데이터,인벤토리,상점,아이템 제작
Share article
Subscribe to our newsletter
RSSPowered by inblog