게임 UI ( user interface )
게임과 유저 사이의 의사소통을 위해서 물리적 가상적 매개체로 가시적으로 보이는 화면 ( 버튼, 스크룰 )과 터치, 드래그 와 같은 조작 방법을 이야기 한다.
1.
풀 화면
: 하나의 장면이 스크린에 꽉 채워지는 형태를 말한다.
[ 브롤스타즈 풀 화면 ]
2. 팝업 화면 ( 팝업 : 갑자기 툭 튀어 나오는 이라는 뜻을 가짐 )
: 하나의 장면이 기존 화면 위에 팝업으로 뜨는 형태를 말한다.
[ 브롤스타즈 팝업 화면 ]
게임에서는 화면 단위로 UI를
제공하고 컨트롤 함으로써 유저와의 상호작용이 일어난다.
: 타이틀 화면에서 게임 스타트 버튼을 눌러서 게임에 들어가는 것, 로비
화면에서 버튼을 눌러서 게임을 시작하는 것 등 게임의 시작부터 끝까지 모두 화면으로 시작해서 화면으로 끝나는 것을 알 수 있다.
어떻게 게임에서는 이런 화면 단위를 관리해야하는가?
:
Android에서 Activity를
관리하는 방식을 생각한다면 쉽게 UI를 어떻게 관리할지 알 수 있다.
: Android에서는 Activity ( = 하나의 화면이라고 할 수 있는데) 를 관리할 때에 Stack 구조를 사용하여 관리한다.
● Stack 이란?
:
Last-In-First-Out 형태의 데이터 구조
: 위의 그림에서 볼 수 있듯이 Activity 1이
있는 상태에서 Activity 2가 들어오면
Activity 1 위에 쌓이고, Activity 3이
들어오면 Acivitiy 2 위에 쌓이는 형태에서, Back 버튼을 누르면 가장 위에
있는 Activity3 이 없어지는 것을 볼 수 있다.
● 게임 UI에 Stack 구조를 적용 한다면 어떤 모습이 되는가.
: 예를 들어 킹오파 익스트림 매치의 플로우를 본다면
● 게임 UI에 Stack 구조를 적용 한다면 어떤 모습이 되는가.
: 예를 들어 킹오파 익스트림 매치의 플로우를 본다면
1. 로비 화면 ( 풀 화면 )
2. 로비에서 스테이지 버튼 클릭 -> 스테이지 화면 ( 풀 화면 )
3. 스테이지 화면에서 스테이지 선택 -> 스테이지 정보 화면 ( 팝업 화면 )
4. 스테이지 정보 화면에서 순위 버튼 선택 -> 순위 화면 ( 팝업 화면 )
5. 순위 화면을 닫은 경우 ( 3번 화면으로 이동 )
6. 스테이지 정보 화면을 닫은 경우 ( 2번 화면으로 이동 )
1.
스테이지 화면을 닫은 경우 ( 1번 화면으로 이동 )
: 위의 플로우를 도식화 한다면 다음과 같다.
: 유저의 상호작용에 의해 변화되는 화면이 안드로이드의 Activity 관리 ( Stack )와
동일함을 알 수 있다.
혹여 같은 프레임 안에서 동시에 UI가 실행될수 있기 때문에 스택에 담기전에 메세지큐를 통해서 한번더 받아두고 한프레임단위로 UI를 화면에 띄어준다.
void UIManager::Update()
{
std::stack<UIBase*> uiBases = mUIBases;
while (!uiBases.empty())
{
UIBase* uiBase = uiBases.top();
if (uiBase)
{
uiBase->Update();
uiBases.pop();
}
}
if (mRequestUiQueue.size() > 0)
{
eUIType requestUI = mRequestUiQueue.front();
mRequestUiQueue.pop();
OnLoad(requestUI);
}
}
void UIManager::OnLoad(eUIType type)
{
std::unordered_map<eUIType, UIBase*>::iterator iter
= mUIs.find(type);
if (iter == mUIs.end())
{
OnFail();
return;
}
OnComplete(iter->second);
}
C++
복사
● 모든
화면을 Stack으로만 관리한다면 UI를 쉽게 관리할 수 있을까.
Stack만으로는
게임의 로직에 맞는 화면 구성을 할 수 없다.
위에서 정의한 풀 화면 또는 팝업 화면에 따라 관리하는
방식에 약간의 차이가 있기 때문에 해당 부분을 처리해 주어야 한다.
: 풀 화면이
2개 이상 쌓여있는 경우, 가장
최상단의 풀 화면만을 볼 수 있기 때문에 하위의 풀화면은 최적화를 위해 숨김 처리를 해주어야 한다.
: 팝업 화면이
2개 이상 쌓여있는 경우도 팝업 화면의 집중도를 위해
최상단의 팝업 화면을 제외한 하위의 팝업 화면은 최적화를 위해 숨김 처리를 해주어야 한다.
: 킹오파 익스트림 매치, 브롤스타즈 등의 게임에서 화면이
어떻게 처리되고 있는지를 보면 위와 같이 처리됨을 알 수 있다.
void UIManager::OnComplete(UIBase* addUI)
{
if (addUI == nullptr)
return;
addUI->Initialize();
addUI->Active();
addUI->Update();
// 만약에 현재 추가된 ui가 전체화면이라면
// 전체화면인 ui 말고 나머지를 전부 비활성화
if (addUI->IsFullScreen())
{
std::stack<UIBase*> uiBases = mUIBases;
while (!uiBases.empty())
{
UIBase* uiBase = uiBases.top();
uiBases.pop();
if (uiBase)
{
uiBase->InActive();
}
}
}
mUIBases.push(addUI);
mActiveUI = nullptr;
}
C++
복사
마찬 가지로 UI를 POP해줄떄에도 이전에 숨김처리 해둔 UI들을 전부 활성화시켜준다.
void UIManager::Pop(eUIType type)
{
if (mUIBases.size() <= 0)
return;
// 해당 ui 한개만 스택에서 뺴줘야한다.
std::stack<UIBase*> tempStack;
UIBase* uibase = nullptr;
while (mUIBases.size() > 0)
{
uibase = mUIBases.top();
mUIBases.pop();
if (uibase->GetType() != type)
{
tempStack.push(uibase);
continue;
}
if (uibase->IsFullScreen())
{
std::stack<UIBase*> uiBases = mUIBases;
while (!uiBases.empty())
{
UIBase* uiBase = uiBases.top();
uiBases.pop();
if (uiBase)
{
uiBase->Active();
break;
}
}
}
uibase->UIClear();
}
while (tempStack.size() > 0)
{
uibase = tempStack.top();
tempStack.pop();
mUIBases.push(uibase);
}
}
C++
복사