Skip to content

Commit 97be2e8

Browse files
authored
Merge pull request #84 from fkdl0048/79-task-unity-core-system-설계
Task: 코어 시스템 설계
2 parents 24e6a59 + 48426ff commit 97be2e8

File tree

5 files changed

+182
-4
lines changed

5 files changed

+182
-4
lines changed

Architecture/README.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,4 @@
44

55
- [MVP](./MVP/README.md)
66
- [SOLID](./SOLID/README.md)
7-
- [아키텍처에 관한 나의 생각 정리](./ArchitectureThoughts.md)
8-
- GAS
7+
- [아키텍처에 관한 나의 생각 정리](./ArchitectureThoughts.md)

GameEngine/Unity/CoreSystem/README.md

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# CoreSystem
2+
3+
기본 프로젝트를 시작할 때 대부분 기반작업 또는 기본적인 게임 시스템을 구현하는 경우 코어 시스템을 설계한다고 한다.
4+
5+
최근 진행한 프로젝트인 [merchants-journey](https://github.com/fkdl0048/merchants-journey)에서 제작한 코어 시스템에 대한 리뷰와 설계 방법에 대하여 정리한다.
6+
7+
## 도메인 모델링
8+
9+
우선 Unity로 PC게임을 제작하며, 동아리에서 진행한 프로젝트라 기획이 명확하게 정해진 상태에서 시작한 프로젝트가 아니다.
10+
11+
따라서 기획이 불안정하거나 변경이 잦은 경우에 대비하여 도메인 모델링을 통해 설계를 진행하였다. 과거 객체지향 설계 관련 책을 읽고 실습을 해본 경험이 있어서 이를 토대로 설계해봤다.
12+
13+
- [해당 이슈](https://github.com/BRIDGE-DEV/BRIDGE_BookClub/issues/129)
14+
15+
이번 게임은 유니티라는 게임엔진 특성에 맞게 설계하였다. 기존에 다른 프로그램을 설계할 때 도움 받았던 다음의 룰을 적용하여 생각한다.
16+
17+
- 나는 내가 이해할 수 있는 언어로 쉽게 풀어쓰고, 협력 과정을 의사코드로 나타냈다.
18+
- 가장 먼저 도메인을 크게 역할에 맞게 덩어리로 잡는다.
19+
- 과정을 요약하여 글로 적는다.
20+
- 각 역할에 대한 책임을 구체화하여 적는다.
21+
- 코드로 나타내본다. (행위 중심으로)
22+
23+
*기본적으로 게임의 기획이 자세하게 나오지 않아, 변경 가능성을 생각하여 기본적인 게임 구조를 설정함*
24+
25+
---
26+
27+
- 게임은 기본적으로 다른 PC게임과 비슷한 형태로 진행된다.
28+
- 즉, 타이틀, 인게임, 크레딧 등의 상태를 가진다.
29+
- 인게임에서는 유닛 배치, 웨이브, 스테이지 클리어, 스테이지 실패 등의 상태를 가지지만 이는 추후 변경될 수 있다. (변경 가능성이 큼)
30+
- 게임에서 사용되는 유틸적인 부분과 프레젠테이션, 비즈니스 로직을 분리하여 설계한다.
31+
32+
이에 맞게 구체화를 진행
33+
34+
- 게임 상태를 나타내는 GameState와 InGameState로 구분한다.
35+
- GameState는 전역적으로 관리가 되어야 함 -> GameManager 각각 덩어리가 크기 때문에 씬으로 관리하고 이를 전역적으로 관리하는 GameManager를 두어 관리함
36+
- GameManager는 싱글톤의 형태를 가진다. 유니티 특성 상 오브젝트 마다 독립적인 라이프 사이클, 객체지향의 성격을 가져가기 어렵기 때문에 싱글톤으로 관리하게 됨
37+
- 싱글톤은 데이터를 사용하지 않음 (들고 있으면 안됨) 불변성을 보장하고 매니저에 맞는 책임만 할당할 것 -> 이 구조가 깨지는 순간 스파게티 코드가 됨
38+
- InGameState는 씬에서 관리되어야 함 -> InGameSceneController로 관리하고 이를 통해 상태를 변경함
39+
- 각각 상태는 모듈화를 위해 State패턴을 사용
40+
- 앞서 말한 상태가 추가되고 삭제될 수 있는 부분이 대부분 인게임에서 일어나기 때문이 이 부분을 가장 구체화하여 설계함
41+
- 게임에서 사용되는 유틸적인 부분은 Util로 관리함
42+
- Enum, Struct, Class 등의 데이터를 관리함
43+
- Manager도 여기에 해당함 (Core)
44+
45+
## 현재 게임 구조
46+
47+
크게 GameState와 InGameState로 구분됨 두 상태 모두 State패턴으로 구현되어 있으며, 데이터는 [Util](https://github.com/fkdl0048/merchants-journey/blob/main/Assets/2.%20Scripts/Utils/Enums.cs)에 정리되어 있음 *같이 사용해야 하는 Enum데이터는 여기에 작성 부탁*
48+
49+
GameState란 크게 게임을 관장하는 상태를 말함 Boot, Title, InGame, Credits 등 큰 단위를 다룸 따라서 씬으로 분리함 이에 따라 상태를 변경하는 매니저를 [GameManager](https://github.com/fkdl0048/merchants-journey/blob/main/Assets/2.%20Scripts/Core/Manager/GameManager.cs)가 관장함 최대한 GamaManager는 가벼워야 함 불변속성을 보장해야 하며, 가능하다면 분리할 것 -> 다른 매니저나 SO로 뺄 것
50+
51+
InGame은 GameState가 InGame일 때 관리하게 되는 상태를 말하며, 이 상태는 local(Ingame씬)에서 관리됨, 이는 [InGameSceneController](https://github.com/fkdl0048/merchants-journey/blob/main/Assets/2.%20Scripts/Controller/InGameSceneController.cs)로 관리함
52+
53+
현재 게임 구조의 위계는 Manager, Controller, System의 접미어를 순서로 가짐 즉, Manager가 붙은 클래스가 가장 높은 위치 (Global에 위치)하고 이는 Scripts/Core에 정리해둠, [Controller](https://github.com/fkdl0048/merchants-journey/tree/main/Assets/2.%20Scripts/Controller)는 씬 마다 각각 붙으며 Boot, MainMenu, InGame 각각 씬에 컴포넌트로 부착되어 있음
54+
55+
System은 Controller에서 각각 필요한, 모듈화된 State나 시스템에 DI(dependency injection)하기 위해 구분함
56+
57+
### InGameState
58+
59+
좀 더 활용적인 예시를 말한다면 InGameState와 System, Controller의 관계에 대해서 설명함
60+
61+
```cs
62+
// 중략..
63+
public class InGameSceneController : MonoBehaviour
64+
{
65+
[Header("Scene References")]
66+
[SerializeField] private GameUI gameUI;
67+
[SerializeField] private UnitSystem unitSystem;
68+
[SerializeField] private StageSystem stageController;
69+
[SerializeField] private AudioClip gameBGM;
70+
71+
private Dictionary<InGameState, IInGameState> states;
72+
private IInGameState currentState;
73+
74+
private void Awake()
75+
{
76+
InitializeStates();
77+
}
78+
79+
private void Start()
80+
{
81+
InitializeGame();
82+
}
83+
84+
private void InitializeStates()
85+
{
86+
states = new Dictionary<InGameState, IInGameState>
87+
{
88+
{
89+
InGameState.UnitPlacement,
90+
new UnitPlacementState(this, gameUI, unitSystem, stageController)
91+
},
92+
{
93+
InGameState.Wave,
94+
new BattleState(this, gameUI, stageController)
95+
},
96+
{
97+
InGameState.StageClear,
98+
new StageClearState(this, gameUI)
99+
},
100+
{
101+
InGameState.StageOver,
102+
new StageOverState(this, gameUI)
103+
},
104+
// stage fail state 추가..예정
105+
};
106+
}
107+
108+
// 중략..
109+
public void ChangeInGameState(InGameState newState)
110+
{
111+
currentState?.Exit();
112+
113+
currentState = states[newState];
114+
currentState.Enter();
115+
116+
Debug.Log($"Game State Changed to: {newState}");
117+
}
118+
119+
private void Update()
120+
{
121+
currentState?.Update();
122+
}
123+
124+
private void OnDestroy()
125+
{
126+
if (this.gameObject.scene.isLoaded)
127+
{
128+
currentState?.Exit();
129+
}
130+
}
131+
132+
}
133+
}
134+
```
135+
136+
IInGameState라는 인터페이스를 활용하여 현재 InGame상태를 관리함 이는 현재 기획이 불안정하고 각각 상태가 독립적으로 관리되어야 추가 및 수정이 용이하기에 해당 구조를 채택함
137+
138+
각각 상태에 대한 정의는 [InGame/State](https://github.com/fkdl0048/merchants-journey/tree/main/Assets/2.%20Scripts/InGame/State)폴더에 모아둠
139+
140+
각각의 상태는 Mono가 아닌 CS클래스이기에 유니티 GUI로 보이지 않음, 즉 인게임에 존재하는 데이터를 사용하기 위해선 인젝션 받아야 함 -> 이를 관장하는 것이 Controller임 위를 보면 알 수 있지만 Controller는 독립적으로 구성된 UnitSystem(유닛 관리 관련 System, StateSystem 스테이지 관리 시스템) 이를 직렬화를 통해 씬에서 직접 연결함 Controller가 System에 의존하게 됨 (System은 뒤에서 설명하지만 독립적으로 구성되어야 함)
141+
142+
```cs
143+
{
144+
InGameState.UnitPlacement,
145+
new UnitPlacementState(this, gameUI, unitSystem, stageController)
146+
}
147+
```
148+
149+
*stageController -> stageSystem으로 수정해야 하겠네요 수정하겠읍니다..*
150+
151+
이렇게 InGame에서 State를 동적 생성하여 책임을 분리하며 독립적으로 동작하는 System을 받아와서 해당 상태에서 활용하여 게임이 동작함, 중요한 점은 State로 관리되기에 수정이나 제거가 용이하다는 점 즉, 이런 구조를 유지하기 위해선 위에서 말한 Controller, Manager, System의 관계를 이해해야 함
152+
153+
System의 경우 State에서 직접 Manager을 사용하여 데이터를 받아오는 경우를 최대한 줄이고, State나 Controller가 방대해지는 것을 막기 위함임
154+
155+
예를 들어 InGameController가 모든 행위 (유닛배치, 업그레이드, 웨이브 관리 등) 모든 행위를 담당한다면 코드는 커지고 엉키게 되며 객체지향이 깨짐 -> 각각 컴포넌트, cs에 맞는 역할만 수행할 수 있도록 분리하는것이 핵심임,
156+
157+
## 유의사항
158+
159+
1. Manager는 싱글톤이기 때문에, 비지니스 로직과 인게임 로직을 명확하게 분리해야 함 즉, 인게임 로직이 싱글톤에 결합되면 안됨
160+
2. State가 커진다면 System으로 분리할 것 마찬가지로 System도 덩치가 커진다면 분리할 것
161+
3. Controller는 각 시스템에 분배, DI 하는 역할이지 다른 역할을 수행하면 안됨
162+
4. 대부분의 이벤트는 코드로 작성하고 State나 씬이 변경될 때 Free할 것

GameEngine/Unity/README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
- [Addressables](./Addressables/README.md)
66
- [InputSystem](./InputSystem/README.md)
77
- [Localization](./Localization/README.md)
8-
- [Unity-PlayFab](./UnityPlayFab/README.md)
8+
- [Unity-PlayFab](./UnityPlayFab/README.md)
9+
- [CoreSystem](./CoreSystem/README.md)
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# merchants-journey 프로젝트
2+
3+
> GameMakers 9기 프로젝트
4+
5+
- [레포 주소](https://github.com/fkdl0048/merchants-journey)
6+
7+
## 프로젝트 개요
8+
9+
- 2D 탑다운 전략 게임
10+
- Unity 3D 쿼터뷰로 제작
11+
12+
## 프로젝트 스케줄링
13+
14+
- [알파 개발](https://github.com/fkdl0048/merchants-journey/issues/1)
15+
- [베타 개발](https://github.com/fkdl0048/merchants-journey/issues/36)

Project/README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@
88
- [Cherry](./CherryProjcet/README.md)
99
- [Gameover](./GameOverProject/README.md)
1010
- [Planeta](./PlanetaProject/README.md)
11-
- [AntCompany](./AntCompanyProject/README.md)
11+
- [AntCompany](./AntCompanyProject/README.md)
12+
- [merchants-journey](./MerchantsJourneyProject/README.md)

0 commit comments

Comments
 (0)