달팽이 키우기 리팩토링 목차
Unity Refactoring - 게임의 핵심루프 설계 요약
각 스크립트의 Awake나 Start에 의존하지 않고, 게임 루프가 시작하기 전 확실하게 초기화 할 수 있도록 했고.
ActionManager의 update함수에서 루프를 관리하되, 각 기능이 호출되는 프레임을 다르게 설정해서, 항상 의도한 순서대로 함수가 실행될 수 있도록 (민감한 함수들의 실행 순서가 뒤바뀌지 않도록) 조정했다.
달팽이키우기는 관리 대상이 달팽이 딱 하나이기 때문에, 업데이트문을 한 클래스에서 집중적으로 관리하는게 좋을 것 같다고 생각했다. 그래서 ActionManager를 선언한 다음 유저 스테미나 관리, 달팽이 스테이터스 소모 등 매 틱마다 확인해야 하는 액션들을 해당 클래스 내 List<system.Action> tickActionList에 등록해서 Update()함수가 호출될 때 마다 등록된 함수들이 일괄적으로 실행되도록 했다.
이렇게 매 틱마다 갱신하는 함수를 묶어서 일괄적으로 처리하게 되면 장점이 한가지 더 생기는데. Consts내부에 Enum FramOrder에서 확인할 수 있듯, 함수들의 실행 순서를 조금 더 능동적으로 관리할 수 있다는 것이다.
달팽이키우기는 프레임에 민감한 게임이 아니고, 매 프레임마다 모든 값이 반드시 다 갱신되어야 할 필요가 없었기 때문에, 60프레임으로 고정한 뒤. 각 함수 머리에 SkipFrame(FrameOrder order)을 선언하는 것으로, 각 함수들이 자기차례가 왔을때만 실행되도록 해 두었다.
그리고, 회사 프로젝트에서 "클래스가 완전히 준비되지 않았는데 준비가 끝난 클래스에서 호출해버리는 바람에 스크립트 실행이 꼬여버리는 문제"를 해결하는게 조금 까다롭다는걸 굉장히 고통스럽게 배워버렸기 때문에.. 그런 일이 아예 일어날 수 없도록 ActionManager의 Update문은 본격적인 tickActionList를 실행하기 전, 각 클래스가 보내주는 initFlag의 값을 먼저 체크함으로써, 각 클래스가 확실하게 준비되었을 때에만 실행될 수 있도록 설계해두었다.
public class ActionManager : MonoBehaviour
{
...
public Dictionary<string, bool> initFlag =
new Dictionary<string, bool>{
{nameof(UIManager), false},
{nameof(DataManager), false},
{nameof(CreatureManager), false},
{nameof(ObjectManager), false},
};
...
void Update()
{
if(!AllClassReady)
{
// Wait for all class ready
if(AllClassReady = CheckClassReady())
Init();
}
else// AllClassReady == true
{
if(GameLoop.SkipFrame(frameOrder.refresh)) return;
// Central Game Loop
foreach (System.Action action in tickActionList)
action();
foreach (KeyCode key in keyActionDict.Keys)
if(Input.GetKeyDown(key))
foreach (System.Action action in keyActionDict[key])
action();
}
}
void Init(){
Application.targetFrameRate = 60;
foreach(var action in initActionList)
action();
ComponentUtility.Log("AllClassReady");
}
bool CheckClassReady(){
foreach(var flag in initFlag.Values)
if(!flag)
return false;
return true;
}
private void OnApplicationQuit() {
foreach (System.Action action in quitActionList)
action();
}
...
}
public class CreatureManager
{
...
void Awake()
{
dataManager = this.GetComponent<DataManager>();
actionManager = this.GetComponent<ActionManager>();
RegistInitAction();
RegistCreatureAction();
RegistEvolveAction();
actionManager.initFlag[nameof(CreatureManager)] = true;
}
void RegistInitAction(){
actionManager.RegistInitAction(()=>
LoadCreature(
dataManager.PlayerInfo.isDead,
dataManager.PlayerInfo.creatureIndex
)
);
}
...
}
namespace Consts
{
public enum frameOrder {
stat = 0,
stamina = 5,
evolve = 10,
dead = 15,
refresh
}
...
static class GameLoop
{
...
static public bool SkipFrame(frameOrder order)
{
// frame division is 20
int frameCount = Time.frameCount;
int frameRemainder = (int)(frameCount * 0.05);
if (frameCount - frameRemainder * 20 != (int)order)
return false;
return true;
}
}
}
'개발 조각글' 카테고리의 다른 글
Unity - Component / Controller / Data로 분리된 UI System. (0) | 2024.01.02 |
---|---|
Unity Refactoring - Reflection을 이용한 유닛 테스트 (0) | 2022.08.18 |
Unity Refactoring - 달팽이키우기 Action 분리하기 (0) | 2022.08.13 |
NOX - ./adb.exe logcat (0) | 2022.08.11 |
Unity - 유닛테스트를 염두한 함수 설계 (0) | 2022.08.08 |