글

{{ label!='' ? 'Label : ' : (q!='' ? '검색 : ' : '전체 게시글') }} {{ label }} {{ q }} {{ ('('+(pubs|date:'yyyy-MM')+')') }}

[게임 리뷰] The Case of the Golden Idol(황금 우상 사건) 리뷰

이미지
<The Case of the Golden Idol>   추리 게임에서 중요한 것은 플레이어가 스스로 진실에 도달했다고 느끼게 만드는 일입니다. 단서가 충분히 주어지면서도 정답이 너무 직관적이어서는 안 되고, 예상치 못한 진상을 제시하면서도 앞선 시나리오와 모순되어서는 안 되죠. 놀라운 사건을 만드는 것과, 그 추리 과정을 재미있게 만드는 것은 별개의 문제임을 명심해야 할 것입니다.   <The Case of the Golden Idol>(이하 <황금 우상 사건>)은 이 난관을 훌륭하게 헤쳐 나갑니다. 사건 현장에서 발견한 정보들이 명쾌한 서술으로 맞물리고, 그렇게 해결한 사건들이 다시 더 큰 이야기의 일부가 됩니다. 플레이하는 동안 하는 일은 비교적 단순하지만, 그 안에서 깊은 생각을 끊임없이 요구받죠.   다만 정교하게 구성된 게임인 만큼, 때로는 그 구성을 위해 사건이 만들어졌다는 인상도 받게 됩니다. 잘 맞는 퍼즐 조각들을 바라보다가, 문득 그것을 깎아놓은 개발자의 손길을 의식하게 되는 것이죠. 탐색과 추리   <황금 우상 사건>의 진행은 크게 두 가지로 나뉩니다. 첫째, 먼저 사건 현장을 살피며 단서를 수집합니다. 둘째, 이 과정에서 얻은 단어들을, 인물의 정체와 사건의 전말을 설명하는 빈칸을 채우는 데 사용합니다. 누가 누구인지, 어떤 일이 벌어졌는지를 파악하고 주어진 요약문을 완성하는 것이 기본적인 게임의 목표입니다. 포인트 앤 클릭 형식으로 사건 현장을 조사하며, 단어들을 획득합니다.   단어를 모으는 것만으로는 사건을 해결할 수 없습니다. 중요한 것은 그 단어들이 어떤 관계를 맺고 있는지 이해하는 일이죠. 처음에는 그럴듯해 보였던 설명이 다른 단서와 충돌한다는 점을 플레이어는 스스로 깨닫게 됩니다. 플레이어는 이미 살펴본 정보를 다시 검토하며, 모든 단서를 설명할 수 있는 사건의 전말을 찾아가게 됩니다.   본편에는 이러한 사건이 총 11개 마련되어 ...

[게임 리뷰] Rubinite 리뷰

이미지
<Rubinite>   보스 러시는 쉽게 만들어지기 어려운 장르입니다. 개별 보스마다 패턴과 연출, 전투 흐름을 새롭게 설계해야 하는 반면, 플레이어가 체감하는 전체 플레이타임은 짧아지기 쉽습니다. 충분한 콘텐츠를 마련하는 데 필요한 제작 비용에 비해 볼륨을 확보하기 어렵고, 전투 몇 개의 완성도가 게임 전체의 평가를 좌우한다는 부담도 크죠.   그래서인지 순수한 보스 러시 게임을 찾아보기 쉽지 않습니다. 그런 의미에서 <Rubinite>는 반가운 작품입니다. 규모가 크지는 않지만 자신이 어떤 전투를 보여주고 싶은지 분명히 알고 있으며, 그 방향을 대부분의 보스전에서 일관되게 유지합니다. 명확한 문법의 전투 집중 시스템을 이용한 패링을 주 전투 수단으로 이용합니다.   보스 러시 게임에서는 플레이어가 어떤 방식으로 적과 싸워야 하는지를 명확하게 제시하는 것이 중요합니다.   소울라이크와 같은 액션 RPG는 사정이 조금 다릅니다. RPG적인 성장 요소를 갖추고 있기 때문에 다양한 플레이 스타일을 받아들일 필요가 있죠. 원거리에서 안정적으로 피해를 누적할 수도 있고, 패링이나 긴 선딜레이를 가진 공격을 활용해 위험을 감수하면서 빠르게 전투를 끝낼 수도 있습니다. 무기와 능력치, 장비 선택을 통해 자신에게 맞는 방식을 찾는 과정 자체가 게임의 중요한 요소입니다.   반면 보스 러시는 반드시 같은 정도의 자유도를 제공할 필요가 없습니다. 오히려 게임이 하나의 명확한 전투 문법을 정하고, 보스들이 그 문법을 얼마나 다양하게 변주하는지가 더 중요할 수 있습니다.   <Titan Souls>는 모든 보스가 약점을 화살 한 발로 맞히면 쓰러진다는 단순한 규칙을 중심으로 설계되어 있습니다. <Furi> 역시 탄막을 회피하는 구간과 근접 패링을 요구하는 구간을 선명하게 구분하며 자신의 전투 방식을 구현하죠.   <Rubinite>에서는 집중 시스템...

[게임 리뷰] Void Stranger(보이드 스트레인저) 리뷰

이미지
<Void Stranger>  소코반을 기반으로 한 게임들은 계속해서 쏟아지고 있습니다. 하지만 좋은 게임들은 여전히 각자의 방식으로 개성을 증명합니다. <Void Stranger> 역시 그렇습니다. 'Void Rod'를 기반으로 한 훌륭한 퍼즐들을 선보일 뿐만 아니라, 그 뒤에 숨겨진 스토리와 방대한 지식 기반 게임 요소를 조화롭게 담아내어 더욱 빛나는 측면이 있습니다.    그러나 분명히 해야 할 것이 있겠네요.  <Void Stranger>는 분명 불편한 게임입니다. 단순히 어렵기 때문에 불편한 것이 아니라, 플레이어에게 반복, 기록, 시행착오, 불친절한 해석을 상당히 많이 요구한다는 점에서 그렇습니다. 이는 게임의 개성을 강화하는 요소가 될 수 있지만, 때로는 훌륭한 퍼즐과 서사를 덜 빛나게 만들기도 합니다. 비유와 상징만 고봉밥일 뿐 인위적인 구멍 안에는 무엇이 있나요?   주인공 Gray는 인위적으로 깎인 듯한 각진 구멍 속으로 몸을 던집니다. 이후 깊은 공간을 내려가며 여러 장면과 마주합니다. 중간중간 등장하는 나무에서 휴식을 취하면 게임이 종료되고, 다시 실행했을 때 Gray의 과거와 관련된 장면들이 이어집니다. 이는 <Void Stranger>가 서사를 전달하는 방법입니다.   퍼즐 게임에서 서사를 풀어나가는 것은 꽤 어려운 일입니다. 많은 게임은 퍼즐과 서사를 분리된 트랙처럼 다루곤 하죠. <Void Stranger>는 게임의 구조, 반복, 메타적 장치 안에 이야기를 녹이려 합니다. 게임과 아주 잘 어울리는 방식입니다. 하지만 때로는 직관적일 필요도 있는 법입니다. 몇몇 플레이어들은 중요한 정보들을 놓치고 게임의 경험을 망칠 수도 있기 때문입니다.   대표적인 예는 휴식입니다. 휴식을 하지 않아도 당장 진행이 막히지 않습니다. 플레이어는 선택할 수 있기 때문이죠. 하지만 이를 지나치면 Gray의 배경과 후반 서사를 이해하는 데 필...

[Unreal Engine] 상호작용 하이라이트 구현

이미지
상호작용 하이라이트 상호작용이 가능한 버튼이 강조되고 있습니다.    Phil in the Mirror는 1인칭 게임으로, 각종 물체와 상호작용할 수 있는데요. 플레이어가 특정 물체와 상호작용할 수 있는 상태임을 알리기 위해, 해당 물체를 바라보면 물체가 강조되는 연출이 필요했습니다. 이는 다양한 방법으로 구현될 수 있는데, 제가 구현한 방법을 말씀 드리려고 합니다. 무슨 방법으로 구현할 수 있을까요?   특히, 저는 강조될 때 각 물체가 자연스럽게 fade-in, fade-out되는 효과를 원했습니다. 여러 개가 동시에 강조될 수도 있고, 마우스가 스치는 시점에 따라서 각자가 서로 다른 주기로 fade-in, fade-out이 되었으면 했죠. 이런 효과를 구현하기 위해서는 크게 두 가지 방법으로 구현할 수 있을 것 같습니다: 마스터 머티리얼 이용하기   모든 상호작용 가능한 물체는 마스터 머티리얼을 사용하고, 해당 머티리얼에서는 머티리얼 파라미터를 통해 강조의 정도를 조절할 수 있게 만드는 것입니다. 만약 Interactable한 객체가 앞에 있다면, 해당 객체의 머티리얼 파라미터를 0에서 1로 점진적으로 조절하는 것이죠. 제가 구현한 방법은 이것이 아니기 때문에, 자세한 코드 내용을 담기는 어렵습니다만 아마 다음과 같은 형태로 이루어질 것입니다: APlayerCharacter::CheckHighlight() { if (CanInteract() == false) { return ; } AActor* InteractableActor = GetInteractableActor(); ... InteractableActorDynamicMaterial ->SetScalarParameterValue(FName( "HighlightIntensity" ), FadeValue); }   그리고 마스터 머티리얼에서는 HighlightIntensity를 가...

[Unreal Engine] AI 기반 10개국어 현지화 사례

이미지
번역을 맡길 돈이 없어요!  Phil in the Mirror는 7000+ 단어의 텍스트 분량을 가진 게임이었는데요. AI를 기반으로 한국어를 포함한 10개국어의 번역을 목표로 했습니다. 특히, 서사 기반의 퍼즐 게임이었던 만큼 번역 검수에는 신경을 많이 썼던 것 같습니다. 은유적인 표현들로 퍼즐들이 구성되기도 했기 때문에, 약간의 뉘앙스 차이만으로도 퍼즐의 난이도가 완전히 달라질 염려가 있었기 때문입니다.   언리얼 엔진에서는 프로젝트의 텍스트들을 수집하여 *.po 파일로 export하는 것을 지원합니다. po 파일의 구성은 대략적으로 다음과 같은 항목들의 나열입니다: #. Key: Content_Init_030 #. SourceLocation: /Game/StringTable/ST_Computer.ST_Computer #: /Game/StringTable/ST_Computer.ST_Computer msgctxt "ST_Computer,Content_Init_030" msgid "모듈 로딩 중: 입원, 진단, 청구..." msgstr ""   msgctxt의 경우 언리얼 엔진에서 StringTable의 namespace이고, msgid는 원어, msgstr은 번역본이 됩니다. 구조 자체는 굉장히 단순하므로, 사실 이것만으로도 충분히 CLI 기반 AI가 번역할 수 있다고 생각했습니다. msgstr의 내용을 채우라고 시키는 것이죠. Gemini CLI를 이용하여 번역을 시도했습니다. 결과는 쓸만하긴 했으나 여러 문제들이 있었습니다. 문장부호 처리   po 파일은 msgctxt, msgid, msgstr와 같은 key가 있고, 그 뒤에 큰따옴표와 함께 내용이 나오는 구조를 갖고 있습니다. 만약 번역 내용 안에 큰따옴표가 있다면, \"와 같은 형태로 작성해야 합니다. 문제는 AI가 이런 형태에서 잦은 실수를 한다는 것이었습니다. 파일의 구조를 무시하고 큰따옴표 없이 번역을 수행한다든가, \...

[Unreal Engine] 오디오 시각화를 이용한 대화 연출

이미지
   Phil in the Mirror에서는 전화를 통해 대화하는 장면들이 있는데요. 규모가 작아 더빙을 할 수도 없는데 단순히 대사만 떠있는 것이 조금 허전해, 목소리가 들리는 느낌을 연출하고자 소리의 시각화를 함께 해주면 좋을 것 같다는 생각을 했습니다. 다음처럼요: 가운데의 원형 표시는 음성이 시각화되어 보여집니다.   이를 수행하기 위해서, 다음과 같은 작업들이 필요했습니다: 음성 데이터가 필요합니다. 음성 데이터를 시간에 따라 분석하여, 각 주파수 영역의 크기가 어느정도 되는지 알아야 합니다. 이렇게 분석된 크기를 텍스처로 구성하여 머티리얼 파라미터로 전달해야 합니다. 머티리얼은 해당 파라미터를 기반으로 시각화를 하여 유저에게 보여줍니다. 음성 데이터   상술하였듯, 이 게임에는 더빙 등을 하지 않았기 때문에 직접적으로 활용할 수 있는 음성 데이터는 없었습니다. 처음에는 대사 자체를 분석해서 절차적인 정보를 얻어낼 수 있는 방법이 있을까 고민하였으나, 소 잡는 칼을 쓰는 느낌이 들어 다른 방법을 고민하기로 했습니다. 여기서 필요한 음성은 플레이어가 들을 수 있는 것이 아니었기 때문에, 단순히 무료 TTS 서비스를 이용하여 대사들을 말하는 음성들을 얻어내 활용하기로 하였습니다.    이렇게 얻은 TTS 음원을 사용하기 위해서는 분석이 필요한데요. 푸리에 변환을 통해 각 음역대의 진폭을 얻은 뒤 이들을 이용해서 그려내야 할 것입니다. 이는 언리얼 엔진에서 제공하는 AudioSynesthesia 플러그인을 활용하기로 했습니다. 해당 플러그인에서는 ConstantQNRT 에셋 등을 지원하며, 이를 통해서 음원의 특정 시간에서 각 주파수 영역의 크기가 어느정도인지 분석할 수 있습니다.   따라서 다음과 같은 에디터 스크립트를 작성하여 주어진 TTS 음원들을 자동으로 ConstantQNRT 에셋으로 생성하도록 하였습니다: import unreal def safe_update_or_create_nr...

[Unreal Engine] Custom Slate Widget 도입 사례

이미지
Custom Slate Widget   Phil in the Mirror에서는 언리얼 엔진 5에서 기본적으로 제공하지 않는 UI 요소 몇몇가지가 필요했는데요. 그러한 요소들을 어떻게 만들어서 해결했는지에 대해서 기술해볼까 합니다. 단순히 유저 위젯 블루프린트를 이용한 에디터 수준에서 해결할 수도 있었지만 코드 수준에서 이들을 적절히 구현해서, 유연함과 성능적 이점 및 사용성을 챙긴 사례를 공유합니다. 1) Redact Decorator   Phil in the Mirror에서 플레이어는 다양한 문서들을 읽고 사건들의 관계를 파악하여 숨겨진 날짜들을 추리해야 합니다. 따라서 중요한 정보들은 가려놓고, 이들을 추리할 수 있게 해야 했습니다. 다만 여기서 고려되어야 할 사항이 있었는데요. 바로 로컬라이제이션입니다. 텍스트 상에서 가려질 부분들은 어떤 언어이느냐에 따라서 위치가 달라지고, 길이도 달라지게 됩니다. 이를 자연스럽게 해결할 필요가 있었습니다. 텍스트의 일부가 검정색으로 가려져 있습니다.   단순히 U+2588(█) 문자를 이용할 수도 있었지만, 다음과 같은 문제가 있었습니다: 1) 언어마다 글자의 개수가 상이한데, 정말로 문맥 상의 단어가 자연스럽게 가려지는 느낌을 연출하기 어려웠습니다. 가령 "손가락 절단 사고"와 "finger amputation accident"는 글자수의 차이가 커서, 어느 한쪽 언어에서 어색하게 보이는 측면이 있습니다. 2) 사이에 공백이 없이 이어지는 느낌이 필요했습니다. (███처럼 이어서 써보면 사이에 애매한 공백이 보입니다)   따라서 텍스트가 렌더링될 때, 해당 영역 위에 검정색 박스를 그려낼 필요가 있었습니다. 이는 RichTextBlock의 기능을 확장하여 구현할 수 있었습니다. 정확히는, Decorator를 만드는 것이죠. RichTextBlock은 Decorator를 추가할 수 있는데, 이러한 Decorator들은 RichTextBlock의 내용에 접근하여 본인이 꾸밀 수 있는 ...