Ace Combat Zero: 유니티로 구현하기 #25 : 빌드

Lunetis·2021년 8월 16일
2

Ace Combat Zero

목록 보기
26/27

실제 플레이 영상입니다.



마침내 게임을 플레이하고, 결과를 보고, 종료할 수 있을 정도로 구현을 했습니다.


그런데 누가 "그 게임 해볼래요!" 라고 할 때, 유니티 에디터를 켜서 시켜줄 수는 없죠.

개발자가 아닌 사람들이 게임을 플레이할 수 있도록 빌드를 해봅시다.


유니티 엔진은 매우 다양한 플랫폼으로의 빌드를 지원합니다.
물론 여기 있는 거 다 하지는 않고, 충분한 접근성을 확보할 수 있을 정도로만 해보려고 합니다.

  1. 프로그램(애플리케이션)으로 빌드해서 파일을 배포하기
  2. WebGL로 빌드해서 웹 환경에서 바로 플레이하기

1번은 Windows 스탠드얼론 빌드로 Windows를 사용하는 사람들을 타겟으로 잡고,
2번은 Windows 외 플랫폼과, 굳이 파일을 다운로드받고 싶지 않은 사람들을 타겟으로 잡습니다.

아마 모바일 기기에서도 키보드나 컨트롤러를 페어링한다면 웹으로도 플레이할 수 있을 겁니다.


컨트롤러나 키보드를 사용하는 것을 전제로 하는 게임이기 때문에, 안드로이드/iOS 등 모바일 애플리케이션 빌드는 하지 않을 예정입니다.



빌드 하기 전에 할 일

해상도 조절

기준 해상도는 1920 x 1080 입니다.
그런데 그 해상도보다 낮은 해상도로 실행될 경우 UI가 겹쳐질 가능성이 있고, 높은 해상도에서 실행될 경우 UI가 너무 작아져서 보기 힘들 수도 있습니다.

메인 화면을 예로 들어봅시다. 기준 해상도인 1920 x 1080에서는 이렇게 보입니다.

하지만 이보다 낮은 해상도에, 16:9가 아닌 16:10 해상도면,

화면이 잘리죠.

작은 해상도나 16:9 비율이 아닌 해상도에서도 정상적으로 보일 수 있게끔 수정해야 합니다.

Canvas를 선택하면 하단에 Canvas Scaler 항목이 있습니다.

UI Scale Mode를 이용해서 해상도 대응을 해줄 수 있습니다.

프로퍼티기능
Constant Pixel SizeUI 요소가 화면 크기에 관계없이 동일한 픽셀 크기로 유지됩니다.
Scale With Screen Size화면이 커질수록 UI 요소도 커집니다.
Constant Physical Size화면 크기와 해상도에 관계없이 UI 요소가 동일한 물리적인 크기로 유지됩니다.

저는 어느 해상도에서나 비슷한 크기로 보였으면 합니다. 동일한 물리적인 크기가 아니라요.

UI Scale Mode를 Scale With Screen Size로 바꾸고, 기준 해상도를 FHD (1920, 1080)으로 바꿔주면,

이렇게 나오게 됩니다. 화면에 렌더링되지 않는 검은 부분이 추가되었습니다.
흔히 레터 박스라고 부르기도 하죠.


Scene 뷰에서 화면에 그려지는 범위를 볼 수 있습니다.
배경화면의 비율이 16:9라서, 16:10 화면에서는 위 아래로 검은 부분이 보이게 되는 것 같네요.

그러면 16:9가 아닌 18:9 (2:1)에서는 어떤지 봅시다.

가로로 넓은 상황에서는 검은 부분이 보이지 않습니다.

Scene 뷰에서 보면 이렇게 위 아래 배경화면이 잘린 상태가 됩니다.

16:9 비율의 가로를 기준으로 화면을 그려주고 있습니다.

가로/세로 둘 중 하나로 맞추기 위해서는 Match 값을 조절해주면 됩니다.


여기서 가장 이상적으로 만들자면, 글씨는 어느 해상도에서도 보이게끔 하되,
배경화면은 16:10처럼 세로가 길어도, 18:9처럼 가로가 길어도 잘리지 않는 환경이겠죠.

Screen Match Mode를 설정하는 방법으로도 대응해줄 수 있습니다.

프로퍼티기능
Match Width or Height캔버스 영역의 너비 또는 높이를 레퍼런스로 사용하여 스케일하거나 그 사이로 스케일합니다.
Expand캔버스 크기가 레퍼런스보다 더 작아지지 않도록 캔버스 영역을 수평 또는 수직으로 확
Shrink캔버스 크기가 레퍼런스보다 커지지 않도록 캔버스를 수평 또는 수직으로 자릅니다.

Screen Match Mode를 Expand로 바꾸면, 16:9가 아닌 비율이면 검은 부분이 항상 보이게 됩니다.
Screen Match Mode를 Shrink로 바꾸면, 검은 부분이 보이지 않고 꽉 차게 됩니다.

Shrink로 바꿔봅시다.


각 비율에 대해서 확인해봅니다. 문제는 없어 보입니다.


로딩 화면에 대해서도 확인해보고,

게임 화면도 확인해봅니다.

960 x 480 (2:1) 화면에서 UI 일부분이 잘리는 문제가 있는 것을 확인할 수 있습니다.

*이 씬은 조작을 테스트하기 위해 만든 FREE FLIGHT 씬입니다.


여기는 Expand를 설정해야 할 것 같네요.

이제 잘리는 UI 없이 정상적으로 출력되고 있습니다.


그리고 그 해상도로 게임을 플레이해서 정말 해상도가 문제가 없는지 확인을 해야 합니다.


특히 이 게임에서의 타겟 UI처럼, 게임 내 오브젝트를 스크린 좌표계로 전환해서 그려져야 하는 오브젝트의 경우는 해상도를 바꿨을 때 제대로 맞지 않을 수 있습니다.

이미 코드 수정을 끝내서 스크린샷에서는 정확히 나오고 있지만, 수정 이전에는 완전히 다른 곳을 가리키고 있었습니다.


protected Vector2 screenSize;
protected float screenAdjustFactor;

void Start()
{
    ...
    screenSize = new Vector2(Screen.width, Screen.height);
    screenAdjustFactor = Mathf.Max((1920.0f / Screen.width), (1080.0f / Screen.height));
}

// Update is called once per frame
void Update()
{
    ...
    // if screenPosition.z < 0, the object is behind camera
    if(screenPosition.z > 0)
    {
        ...
        Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(activeCamera, targetObject.transform.position);
        Vector2 position = screenPoint - screenSize * 0.5f;
        position *= screenAdjustFactor;
        rectTransform.anchoredPosition = position;
    }
}

제 경우에는 스크린 좌표계로 보여주는 스크립트를 모두 수정해야 했습니다.

기존에는 이 오브젝트가 속해 있는 Canvas의 너비와 높이를 기반으로 계산했지만, 지금은 실제로 그려지고 있는 화면의 해상도를 기반으로 계산하고 있습니다.


이렇게 대응하려고 하는 해상도 비율들을 모두 확인하면, 해상도 문제는 어느 정도 해결됐다고 할 수 있습니다.


그나저나, 16:9 비율을 두 개 합쳐놓은 32:9 비율은 과연 어떻게 보일까요?


이렇게 보이는군요.

지금 설정으로는 16:9 비율을 기준으로 화면의 크기만 조정하기 때문에, 화면에 끝에 있었던 UI도 가로 비율이 넓어질수록 가장자리에서 멀어지고 있습니다.

울트라와이드 해상도에서도 가장자리로 두기 위해서는 코드로 직접 제어해야 합니다.

미니맵이나 상태 UI들을 모두 화면 가장자리로 보내버리는 게 "기술적으로는" 좋지만, 오히려 플레이어의 시선이 엄청나게 좌우로 왔다갔다 하게 되어 눈이 피로해질 수 있습니다.
그냥 가운데에 둘 지, 맨 끝으로 보낼 지 생각해봐야 합니다.

지금은 화면 가장자리로 UI를 보내는 것보다 더 시급한 문제들이 많기 때문에 후순위로 미루겠습니다.

사람마다 취향이 갈린다면 "넓은 화면에서 UI 가장자리로 보내기" 옵션을 넣어볼 수는 있겠군요.


https://youtu.be/dlvhWzNdTUc

여담으로, 에이스 컴뱃 7은 16:9보다 가로가 긴 비율을 제대로 지원해주지 않는 이슈가 있었습니다.

위에서 말한 가장자리에 글씨 제대로 나오느냐 마느냐 문제 수준이 아니라, UI가 한쪽으로 쏠리고 초상화는 무슨 이유에선지 하얗게 보였죠.


미리 최소한의 대응은 하고 갑시다.

하지만... 테스트에서는 멀쩡한데 실제 기기에서만 이슈가 발생할 때가 있다는 게 문제죠.


"아니, 내 컴퓨터에서는 잘 돌아갔다니까?"



Addressable 빌드

자막 포스트에서 오디오를 동적으로 불러오기 위해 Addressable이라는 걸 활용했었습니다.
테스트할 때는 그냥 아무 생각 없이 사용해도 잘 됐지만, 빌드할 때는 Addressable 전용 빌드를 한 번 돌려줘야 합니다.

빌드를 하지 않으면 에셋을 로드하는 시점에서 로드가 되지 않아 에러가 발생하게 됩니다.
경우에 따라서 아예 게임 진행이 막혀버릴 수도 있죠.

Window - Asset Management - Addressables - Groups에 들어가면,

Addressable Groups라는 창을 띄울 수 있는데,
위쪽에 Play Mode Script라는 항목이 있습니다.

Use Existing Build로 바꿔주고,

바로 옆의 Build 버튼을 눌러서 New Build - Default Build Script를 눌러줍니다.

이제 실제 빌드에서도 Addressable로 지정된 에셋을 불러올 수 있습니다.



최적화

에디터보다는 실제 빌드에서 실행하는 게 프레임이 잘 나오긴 할 겁니다.

하지만 중/고사양의 컴퓨터는 무리없이 돌아갈 지 몰라도, 저전력 CPU + 내장 그래픽처럼 게임용이 아닌 사무용으로 돌아가는 컴퓨터에서도 '적당히' 돌아가게 만들려면 최소한의 최적화는 거쳐야 합니다.

이 포스트는 가능한 최적화 작업 중 매우 일부만 설명 및 실천하고 있으므로,
유니티 최적화 방법을 배우고 싶다면 하단 링크를 참고하시는 것을 추천합니다.
https://learn.unity.com/tutorial/fixing-performance-problems



프로파일러 보기

Ctrl+7 또는 Window - Analysis - Profiler로 프로파일러를 열 수 있습니다.
도대체 어디서 시간을 잡아먹는지 알 수 있죠.

Others와 Rendering에서 엄청나게 잡아먹고 있는 모습인데요,

보통 Others는 Editor Loop, 에디터로 실행하느라 발생하는 부하들이 대부분을 차지합니다.

빌드해서 플레이할 때는 적용되지 않는 부분이므로 신경쓰지 않아도 됩니다.


우선 Rendering 부하를 차지하는 그래픽 부분을 봅시다.



몇 가지 그래픽 최적화

과부하를 주는 오브젝트 삭제

게임 뷰에서 Stats를 활성화한 후, Tris, Verts, Batches 값을 유심히 봅시다.

지면을 바라보고 있는데 Batches 값이 굉장히 크고,

댐이 보이는 쪽으로 가보니 Tris, Verts, Batches가 엄청나게 증가합니다.

비슷한 각도에서 댐을 비활성화시키면,

Tris : 3,700,000 -> 151,200
Verts : 2,900,000 -> 104,700
Batches : 460+ -> 300+

퍼포먼스에 엄청나게 영향을 주는 만악의 근원을 찾았습니다.

로우 폴리 모델을 찾기 전까지 댐 모델링은 안 쓰는 것이 낫겠군요.
어차피 이 미션에서의 비중은 공기나 다름없습니다.


지형 최적화

이 게임의 퍼포먼스에 영향을 끼치는 만악의 근원 중 하나는 미친듯이 넓은 맵입니다.

지형의 디테일을 낮춰서 퍼포먼스 향상을 노려볼 수 있을 겁니다.
어차피 너무나도 넓은 맵인데다가 공중에서 머무르는 시간이 길기 때문에 디테일이 떨어져도 눈치채기 힘들 겁니다.

여기서 하나씩 값을 건드려보죠.

Pixel Error : 5 -> 15

값을 높이면 렌더링 결과물의 정확도가 떨어지지만 부하를 낮출 수 있습니다.
멀리 있는 지형은 원래 디자인했던 지형과 더욱 딴판으로 보일 수 있으며,
가까이 가면 지형이 갑자기 변하는 것처럼 보일 수도 있습니다.

Tree & Detail Objects - Draw : 체크 해제

그릴 나무나 지형에 붙은 오브젝트가 없으므로 그리지 않습니다.

Texture Resolutions

  • Heightmap Resolution : 513 -> 129
  • Control Texture Resolution : 512 -> 128
  • Base Texture Resolution : 1024 -> 256

해상도를 1/4 수준으로 낮춥니다.


멀리서 보면 지하철 노선도마냥 아주 막 만든 것처럼 보이지만,

가까이 가면 그래도 봐줄 만한(?) 정도로 렌더링됩니다.


배칭

움직이지 않는 오브젝트(맵 등)들은 Static으로 설정해서 배치 카운트를 줄일 수 있습니다.



스프라이트 아틀라스

Assets - Create - 2D - Sprite Atlas를 선택하고,

적당한 이름을 붙여준 후,

이름에 맞는 오브젝트들을 Objects for Packing으로 끌어다놓습니다.

폴더째로 넣어도 작동합니다.

Addressable로 지정한 스프라이트는 아틀라스에 들어가지 않도록 주의해야 합니다.



라이트매핑

빛을 실시간으로 연산하지 않아도 되는 부분이 있습니다. 대표적으로 움직이지 않는 지형 등이 있죠.

이 부분의 연산을 줄이도록 라이트맵을 미리 베이크하겠습니다.


Window - Rendering - Lighting으로 들어갑니다.

일단 라이트 세팅이 들어가있는 상태입니다.


라이트 맵을 베이크하기 전에, 지형이나 열리지 않는 미사일 해치 등 정적인 오브젝트를 클릭해서,

설정이나 MeshRenderer의 Lighting - Contribute Global Illumination이 체크된 상태인지 확인합니다. 이 속성이 체크되면 라이트매핑의 대상이 됩니다.

컷씬에 쓰이는 로켓과 미사일 해치는 애니메이션이 있기 때문에 라이트매핑에서 제외되어야 합니다.
대개는 설정하지 않아도 이렇게 알아서 체크 해제가 되어 있는 상태지만, 한 번 쯤은 확인해봅시다.


다시 Light Settings로 돌아가서, 제일 아래에 있는 Generate Lighting을 클릭합니다.

하단에 라이트 맵을 만들고 있다는 표시가 뜨고,

에디터는 미친듯이 돌아가고,

매핑은 언제 끝날 지 모르죠.

정적 라이팅은 하나밖에 없지만, 지형이 굉장히 넓어서 (약 12,000 x 12,000) 오래 걸리나 봅니다.

음... CPU 라이트매핑을 하지 말 걸 그랬나요?

취소하고 GPU로 해보겠습니다.















아...







장비 업그레이드를 하고 싶지만, 이 포스트를 작성하는 2021년 8월에는 아직 그래픽카드 값이 비싼 편입니다.
MSRP $399인 RTX 3060 Ti가 70 ~ 80만원대에 팔리고 있습니다.

정점을 찍었던 5월보단 낫습니다만...



라이트 맵 베이크가 완료된 모습입니다. 겉보기에는 아무것도 바뀌지 않은 것처럼 보입니다.

오른쪽의 미사일 해치 3개는 움직이지 않는 해치들이며 라이트 맵의 영향을 받고,
왼쪽의 미사일 해치 1개는 컷씬에 사용되는 해치이며 라이트 맵의 영향을 받지 않습니다.

양쪽이 모두 보이는 상태에서 씬 내부의 Directional Light를 선택하고 비활성화를 시켜보면,
왼쪽의 미사일 해치는 빛을 받지 않아 어두워지지만 오른쪽의 해치들과 지형은 그대로인 것을 확인할 수 있습니다.



다시 프로파일러 보기

어느 정도 그래픽 최적화를 거친 다음 프로파일러를 켜봅니다.
이번에는 Deep Profile을 켜서 좀 더 정밀한 범인 찾기를 해봅시다.

Others 항목은 에디터로 인한 부하가 대부분이니, 클릭해서 꺼두죠.


Rendering 영역은 그래픽 최적화에서 적당히 조율했으니, 스크립트로 인한 부하를 찾아봅니다.
Total을 기준으로 내림차순으로 정렬해서 도대체 어떤 스크립트가 이렇게 부하를 주는지 확인해보죠.


InputManager가 엄청나게 잡아먹고 있습니다.

근데 이건 없애버릴 수도 없고, 코드로 뭔가를 건드리기도 힘든 영역입니다.


그 외 1%에 육박하는 점유율을 보이는 함수들이 보입니다. SetUI(), SetTime() 등이 있군요.

거기에 들어있는 String.Format이 퍼포먼스를 잡아먹고 있었습니다.

InputSystemUIInputModule...에서 가끔씩 스파이크가 일어나는데요,

그 클래스를 가지고 있는 건 이 오브젝트밖에 없는데, 저는 이 오브젝트를 안 쓰고 있습니다.
모든 Input 처리는 InputManager에 있는 Player Input에서 처리하도록 구현되어 있습니다.

필요없으니 비활성화해주고, 프로파일러에서 이 부분이 다시 나타나는지 확인해봅시다.

Behaviour에서 알파벳 내림차순으로 정렬해도 아까와 같은 이벤트는 더 이상 보이지 않습니다.

그 외에는 카메라의 UI 렌더링 과정에서 일어나는 코드들이 대부분을 차지하고 있습니다.

적어도 뭔가 매 프레임마다 필요없는 레이캐스팅이나 대규모로 루프문을 돌아서 렉이 걸리는 것 같지는 않군요.


그리고 컷씬에서 굉장한 프레임 드랍이 있길래 확인해봤는데,

PlayableDirector, Cinemachine과 관련된 코드들이 대부분을 차지하고 있습니다.
컷씬과 관련된 부분의 최적화를 수행해야 할 것 같습니다.

활성화된 가상 카메라들이 리소스를 잡아먹고 있는 것 같으니, 사용하지 않는 가상 카메라는 그때그때 비활성화하도록 바꿔야 할 것 같습니다.

다 꺼놓으니 8%대에서 4%대로 낮춰졌습니다.
그래도 카메라 렌더링 부분은 여전히 답이 없군요.


활성화/비활성화로 해결할 수 있는 선에서는 적당히 정리되었습니다.

이 상태에서 빌드를 돌리면 얼마나 프레임이 잘 나오는지, 프레임 드랍이 많이 발생하는지 보겠습니다.



Windows 빌드

Scenes In Build에 빌드할 씬들을 모두 넣어놓고 빌드를 돌려봅시다.

이 정도 프로젝트는 어지간하면 Windows 빌드는 딱히 손 댈 게 없을 것으로 보이는데,
일단 해봅시다.


Build를 눌러서 빌드하면 이렇게 exe 파일과 기타 데이터 파일들이 생성됩니다.

최적화하기 전에 빌드했던 결과물과 한 번 프레임을 비교해보겠습니다.

https://kr.msi.com/Landing/afterburner
프레임 표시는 MSI 애프터버너에 같이 따라오는 프레임 표시 유틸리티를 사용하겠습니다.

컴퓨터 사양:
AMD RYZEN 5 1600
NVIDIA GeForce GTX 1060 3GB
16GB RAM
*모든 부품 오버클럭 X

초반에 픽시가 레이저를 날리고, 플레이어가 미사일을 발사할 때의 FPS는 약 400 초~중반대였으나 400 중~후반대로 올라간 모습입니다.

이 상황에서의 컷씬 프레임은...

이전 210프레임,

이후 320프레임 가량이 나옵니다.

두 번째 컷씬은 큰 개선이 없네요. (254 : 255)


Windows 스탠드얼론 빌드는 이 쯤에서 마무리하면 될 것 같습니다.



WebGL 빌드

빌드 세팅

WebGL 환경에서는 윈도우 빌드에서 네이티브로 게임을 돌리는 것보다 퍼포먼스가 낮습니다.
프레임 저하는 물론이고, 어떤 쉐이더는 웹 환경에서 작동하지 않을 수도 있습니다.

일단 WebGL을 선택하고 Switch Platform을 눌러줍니다.


그리고 Build를 눌러서 기다려보죠.


WebGL로 빌드하면 index.html과 함께 데이터를 담은 폴더들이 생성됩니다.

그런데 html 파일을 실행하면 로딩이 안 됩니다.
로컬 파일과 관련된 보안 정책으로 인해 경우에 따라 로딩이 안 될 수 있습니다.


WebGL 플레이어를 확실하게 가동시키기 위해 Build가 아닌 Build and Run을 클릭합니다.
이 때는 유니티가 로컬 웹 서버를 가동시켜서 빌드를 실행합니다.

그러면 로딩이 되는데...

엔진 아지랑이를 표현하는 쉐이더 쪽에 문제가 있는 모습입니다.

그 외에도 컷씬을 보는 과정에서 초반에 상당한 프레임 드랍이 관찰되기도 하고,
이펙트가 제대로 출력되지 않기도 합니다.

몇몇 효과음이 왜곡되는 문제도 있고요.



WebGL 문제 수정

WebGL 플랫폼으로 빌드 타겟을 설정한 상태에서 Asset Bundle과 관련된 기능들은 에디터에서는 작동하지 않을 수 있다고 합니다.
이 프로젝트에서는 대사 오디오 파일들이 여기에 해당되기 때문에, 에디터 상에서 대사 오디오가 출력되지 않고 있습니다.

그래도 대사들이 제대로 넘어가지긴 합니다.



쉐이더 문제 수정

WebGL에서는 이상하게 보이는 아지랑이 쉐이더가 에디터에서는 멀쩡하게 보이죠.

전형적인 "빌드에서만 문제 있음"의 예시입니다.

https://docs.unity3d.com/Manual/webgl-graphics.html

WebGL에서는 쉐이더에 몇 가지 제약사항이 있습니다. 유니티 내장 쉐이더는 큰 문제가 없을지라도 직접 작성하는 커스텀 쉐이더의 경우에는 에디터에서와 다르게 보이는 경우가 생길 수 있습니다.


...문제는 이 포스트 작성자는 쉐이더를 거의 모른다는 겁니다.


거슬리지 않을 정도로 쉐이더 왜곡 강도를 낮췄습니다.

언젠가 시간을 좀 들여서 쉐이더를 배워야 하는데...



오디오 문제 수정 1: 짧은 길이의 오디오 깨짐

검색을 해봤는데 0.5초 미만의 매우 짧은 오디오를 재생할 때 소리가 왜곡된다는 글이 있었습니다.
오디오가 깨지는 부분은 다른 형식이나 포맷으로 오디오를 변환해서 등록시키는 방법을 사용해볼 수 있습니다.


.ogg로 바꿔보니 여전히 약간 왜곡되는 것 같긴 하지만 깨지는 수준까지는 가지 않습니다.



오디오 문제 수정 2: 배경음이 반복되지 않음

반복되는 부분의 음악은 PlayScheduled()로 재생을 미리 예약해놓는데,
WebGL 환경에서는 PlayScheduled()로 재생된 오디오는 AudioSourceloop가 체크된 상태여도 배경음이 반복되지 않는 이슈가 있습니다.


2020.2.X에 해결되었다고는 하나...

제 프로젝트의 버전은 2020.2.3f인데 말이죠.

댓글을 보니 역시 아닌 것 같습니다.

거진 5년째 고쳐지지 않고 있는 문제인데 아직도 안 고쳐졌다니까요.


AudioController.cs

void Start()
{
    ...
    
    #if UNITY_WEBGL
    bgmLoopAudioSource.loop = false;
    #else
    bgmLoopAudioSource.loop = true;
    #endif
}

void CheckWebGLAudioLoop()
{
    if(bgmLoopAudioSource.isPlaying == false)
    {
        bgmLoopAudioSource.Play();
    }
}

// Update is called once per frame
void Update()
{
    ...

    // WebGL
    // Issue: When playing audio with PlayScheduled(), it doesn't loop whether the loop property is checked or not.
    #if UNITY_WEBGL
    CheckWebGLAudioLoop();
    #endif
}

어쩔 수 없죠. 재생되고 있는지 매 프레임마다 확인해서 아니면 재생시키라고 하는 수밖에요.



UI 문제 (?) 수정

에디터에서는 괜찮은데 왜 빌드에서만 UI 좌표가 매 프레임마다 바뀌면서 깜빡이는지 모르겠네요.


며칠동안 찾아봤지만 도저히 원인을 알아내지 못한 관계로 임시 땜빵용 코드를 작성하겠습니다.


FollowTransformUI.cs

[SerializeField]
public bool preventBlinkError = false;
[SerializeField]
[Range(0.01f, 1)]
float errorRange = 0.1f;

Vector2 initPos;
Vector2 prevPos;
float errorDistanceThreshold;

protected virtual void Start()
{
    ...
    
    // Error Check
    initPos = rectTransform.anchoredPosition;
    errorDistanceThreshold = screenSize.x * errorRange;
    prevPos = Vector2.zero;
}

void CheckError(Vector2 pos)
{
    // Was it close enough to intended position?;
    if(prevPos == Vector2.zero)
    {
        if((initPos - pos).magnitude < errorDistanceThreshold)
        {
            rectTransform.anchoredPosition = pos;
            prevPos = pos;
        }
    }
    else
    {
        if((prevPos - pos).magnitude < errorDistanceThreshold * 0.5f)
        {
            rectTransform.anchoredPosition = pos;
            prevPos = pos;
        }
        else
        {
            prevPos = Vector2.zero;
        }
    }
}

// Update is called once per frame
protected virtual void Update()
{
    ...

    // if screenPosition.z < 0, the object is behind camera
    if(screenPosition.z > 0)
    {
        ...

        if(preventBlinkError == true)
        {
            CheckError(position);
        }
        else
        {
            rectTransform.anchoredPosition = position;
        }
    }
}

깜빡이는 현상을 방지할 에러 방지용 코드를 추가합니다.

원래 표시되어야 할 좌표를 에디터에서 대충 지정해준 다음에, 그 지점 주변에 UI가 표시될 때만 UI 위치를 갱신해줍니다.
한 번 갱신된 이후에는 이전 프레임의 좌표 주변에 UI가 표시될 때만 UI 위치를 갱신합니다.

깜빡이는 현상은 원래 있어야 할 좌표 주변이 아닌 곳에 간헐적으로 표시되어서 나타나는 현상이므로, 표시되어야 하는 위치를 어느 정도 강제하도록 코드를 작성했습니다.


이 에러 방지용 코드는 bool preventBlinkError 값으로 키거나 끌 수 있습니다.
기본값은 꺼져있고, 문제가 발생하는 컷씬에서의 UI에서만 이 기능을 켜주려고 합니다.

움짤에서 볼 수 있듯 약간의 카메라 흔들림만 있는 컷이기 때문에, 한 프레임만에 가로 픽셀의 10%만큼 이동할 일은 거의 없을 것입니다.



용량 줄이기

현재 WebGL 빌드의 크기는 113MB입니다.
맵 크기, 모델링, 쓰인 음성 파일 개수를 생각하면 용량이 작다고 생각하는 사람도 있을 수 있지만,
이게 웹에 올라가서 로딩된다고 생각하면... 여전히 큰 것 같습니다.

제가 웹 빌드를 만드는 목적은 더 높은 접근성을 얻기 위해서입니다.
퀄리티를 희생해서라도, 용량을 조금 더 줄여보겠습니다.



빌드 로그 보기

빌드가 끝난 후 Console 창의 오른쪽 끝의 (...) 버튼을 누르고, Open Editor Log를 클릭합니다.

맨 위로 올린 다음 "build report"를 검색합니다.

압축되지 않은 상태의 에셋 용량들과 용량이 큰 파일들의 랭킹을 보여주고 있는데요,

텍스처가 용량을 무지막지하게 먹고 있다는 것을 확인할 수 있습니다.
1순위는 텍스처의 용량을 줄이는 작업이 되어야 하겠네요.

텍스처를 설정하면서도 따로 용량을 줄일 수 있는 기능을 찾아봅시다.



유니티 매뉴얼 따라하기

http://docs.unity3d.com/Manual/webgl-building.html

WebGL 빌드 매뉴얼에는 몇 가지 용량 줄이는 팁이 소개되어 있습니다.

Publishing Settings - Enable Exception을 None으로 두기,

Compression Format을 정하기,
(매뉴얼에 따르면 Gzip은 높은 호환성과 빠른 빌드, Brotli는 높은 압축률을 가진다고 합니다.)

Other Settings - Strip Engine Code를 체크하기, (기본적으로 체크됨)

그리고 용량의 원흉인 Texture들의 압축 포맷 조정하기.

WebGL 탭에서만 Max Size를 작게 조정합니다.
매뉴얼에 따르면 "인게임에서 보기 싫게 되기 전까지" 줄이라고 합니다.

특히 빌드 로그에서 용량 랭킹 상위권에 위치해있었던 텍스쳐 파일들은 확실하게 줄여줍시다.

이런 친구들이요.


몇 가지 설정을 거치고 다시 빌드를 돌려보죠. 113MB에서 얼마나 줄어들게 될지 봅시다.







113MB -> 58MB

거의 절반 정도로 줄어들었습니다.

압축 전 텍스처 크기가 192.4MB였지만 이제는 86MB입니다.
라이트맵 텍스처들이 슬슬 랭킹 상위권을 차지하려고 하고 있습니다.

라이트맵은 그래픽 부하를 줄이기 위해 만들어졌지만, 대신 용량이 커지는 효과를 낳았죠.
용량을 줄일 것이냐, 부하를 줄일 것이냐, 둘 다 최대한 조율할 것이냐는 개발자의 선택입니다.


아직은 그래픽이 나쁘지 않아보입니다.

그래픽을 더욱 희생해서 용량을 더 줄일수도 있겠지만, 일단은 이 정도를 가지고 업로드를 해봅시다.



WebGL 게임 업로드: itch.io

게임을 웹에 업로드하는 플랫폼은 여러 곳이 있지만, 가장 규모가 큰 곳에 올리는 게 낫겠죠.

https://itch.io/

인디게임을 업로드하고 유통하는 사이트입니다. 찾아보면 굉장히 익숙한 게임들도 볼 수 있습니다.

Among Us도 있고,


A Dance of Fire and Ice도 있고, (웹브라우저에서 무료로 데모 버전을 플레이할 수 있습니다.)


어... 설명은 생략하겠습니다.


아무튼 이 플랫폼에 방금 빌드한 WebGL 게임을 업로드해봅시다.


계정을 만들고, Dashboard 탭에 들어가서 Create new project 버튼을 누릅니다.

제목, URL, 짧은 설명 등을 작성합니다.

"Short description or tagline" 은 제목과 태그 밑에 있는 부분에 적히는 글입니다.


오른쪽에는 커버 이미지, 스크린샷을 첨부하는 기능이 있습니다.
스크린샷은 게임 플레이 영상 일부를 찍어서 업로드한다 치고, 커버는 새로 만들어야겠네요.

로딩 전에 이렇게 짤막한 미션 개요가 나오는 장면이 있습니다.

원작을 존중한다는 의미에서 이 디자인을 조금 살려서 만들어보겠습니다...





...

전 디자인을 전공하지 않았습니다. 컴퓨터공학과 전공이라서... 흠흠...




Kind of project에서 HTML을 선택합니다.
WebGL 게임을 브라우저에서 바로 실행할 수 있도록 하려면 HTML을 선택해야 합니다.

WebGL 게임을 업로드하고 윈도우 스탠드얼론 빌드 파일도 따로 업로드할 수 있습니다.


릴리즈 상태는 "개발중"으로 두겠습니다. 게임이 가능하긴 하지만 아직 개선할 사항이 많습니다.

가격은 무료 또는 기부 / 유료 / 무료 중 하나를 선택할 수 있습니다.

첫 번째 방식을 선택할 경우, 게임을 무료로 다운로드할 수 있지만 다운받는 사람이 원하는 경우 개발자에게 돈을 원하는 만큼 기부할 수 있습니다.

하지만 이 프로젝트는 비영리이므로 기부조차도 원천 봉쇄하는 No payments를 선택하겠습니다.

여기서 WebGL 게임을 업로드할 수 있습니다. ZIP 형식이어야 하고, 내부에는 index.html 파일이 있어야 합니다.

유니티 WebGL은 알아서 index.html로 파일 이름을 지어주기 때문에, 이걸 그대로 ZIP 파일로 압축해서 넣어주면 됩니다.

...근데 그 전에,

index.html 파일을 열어서,

전체화면과 관련된 코드를 모두 삭제해서 아예 버튼이 보이지 않도록 만들겠습니다.
지금 전체화면에서 작동이 제대로 되지 않는 부분이 있어서, 해결하기 전까지는 제거하겠습니다.


수정이 끝나면 ZIP 파일로 압축한 후 등록해줍니다.
그리고 "This file will be played in the browser"를 체크합니다.

WebGL 게임의 너비와 높이를 지정하는 부분인데요,

Player Settings에서 지정한 해상도보다 약간 더 높게 지정합니다.
Default 템플릿에는 하단에 내용이 조금 있거든요.

만약 하단에 아무것도 없는 Minimal 템플릿을 선택했다면 원래 해상도대로 써놓으면 됩니다.
Minimal을 선택하지 않은 이유는, 저건 로딩할 때 로딩 바가 나오지 않기 때문입니다.


그리고 윈도우 빌드도 만들어서...

다운로드 가능한 윈도우 스탠드얼론 빌드를 업로드합니다.


설명문에는 유의사항 및 따로 게임 내에 적어놓지 않은 조작 방식을 적어놓았습니다.

조작 방법을 게임 내에 넣어줘야 할 지 아직 정하지 않았습니다.

장르와 태그를 추가하고,

커뮤니티 속성 (비활성화, 댓글, 토론장) / 접근성을 설정합니다.
공개로 설정해도 다운로드나 구매를 막아놓거나, 검색창에서 보이지 않게 설정할 수 있습니다.

바로 Public으로 설정하기보다는, Draft로 둬서 개발자만 볼 수 있게 만든 다음 충분히 테스트를 거친 후에 Public으로 바꾸는 것이 좋겠죠.


Save를 누르면 작성한 내용이 페이지에 업로드되고 저장되지만 바로 페이지로 넘어가지는 않습니다.

View page를 누르면 페이지로 넘어갑니다.



최종 테스트

손님들을 맞을 준비를 하기 전에, 이 사이트에서 게임이 제대로 돌아가는지 확인해야 합니다.

그... 로딩이 많이 느리네요.

맨 밑에는 윈도우 스탠드얼론 빌드를 다운받을 수 있는 공간이 있습니다.

왜 안 되나 했는데 실제로 문제가 있었군요.

찾아본 바로는 압축을 하지 않아야 해결된다고 합니다.

...텍스처로 열심히 줄인 용량이 다시 늘어나겠는데요.

하, 기껏 58MB로 낮췄는데...


어쨌든, 압축을 하지 않으니 작동이 된다는 것을 확인했습니다.






게임 링크

https://lunetis.itch.io/operation-zero

로딩이 기니까 조심하세요.

개인적으로는 가능하다면 페이지 하단의 윈도우 빌드를 다운받는 것을 추천드립니다.



플레이 영상




End Of Devlog.

적당히 만들어진 게임이라고 하기에는 기체 애니메이션도 만들어야 하고, AI도 좀 더 똑똑하게 수정하고, 최적화도 더 해야 하고, 버그도 고쳐야 하는 등 해야 할 일이 많습니다.
퀄리티로 따지자면 여전히 프로토타입에서는 벗어나지 못한 상태입니다.


하지만 에이스 컴뱃 제로의 마지막 미션을 재현하겠다는 목표는 완수했다고 생각합니다.

적에게 미사일을 날리고, 상황에 따라 등장인물들이 대사를 말하고, 컷씬도 보고, 게임 오버도 하고, 클리어도 할 수 있는 상태는 맞으니까요.


앞으로도 위에 나열한 일들을 조금씩 하겠지만, 규모가 크거나 게임의 핵심 기능이라고 생각되어 개발 블로그에 적어야 할 것 같은 일은 딱히 없는 것 같습니다.

그래서, 이제 이 개발 블로그에 연재(?)하는 것은 이 쯤으로 마무리하려고 합니다.

(엄청 중요한 일이나 개발 과정이 있다면 추가 작성을 할 수도 있습니다.)







이 미친 분량과 용량의 개발 블로그를 전부 또는 일부라도 읽어주신 모든 분들께 감사드립니다.




이 프로젝트의 작업 결과물은 Github에 업로드되고 있습니다.
https://github.com/lunetis/OperationZERO

3개의 댓글

comment-user-thumbnail
2022년 3월 10일

혹시 웹GL의 해상도를 index.html 에서 정해지는 것을 동적으로 변경할수 있나요?

1개의 답글
comment-user-thumbnail
2022년 6월 19일

즐겁게 읽었습니다. 감사합니다.

답글 달기