Search Unity

2D 픽셀 퍼펙트: Unity로 레트로 8비트 게임 제작하기

, 3월 13, 2019

간단한 메카닉과 픽셀화된 그래픽스로 이루어진 레트로 게임은 전문 게이머들의 추억을 불러일으킬 뿐 아니라 젊은층에게도 친근감을 줍니다. 최근 다양한 게임이 “레트로”라는 수식어를 달고 나오지만 향수를 불러일으킬 만한 디자인(look and feel)의 타이틀을 만들려면 실제로 많은 노력과 계획이 필요합니다. 이와 같은 이유로 저희는 이 주제에 대해 많은 것을 이야기해 줄 수 있는 Mega Cat Studios 분들을 초대했습니다. 이 블로그 게시물에서는 중요한 Unity 설정, 그래픽스 구조, 컬러 팔레트를 비롯하여 진정한 NES 스타일 게임을 구현하기 위해 필요한 모든 기술에 대해 설명합니다.

샘플 프로젝트를 다운로드하여 직접 따라해 보세요!

펜실베니아주 피츠버그에 있는 Mega Cat Studios는 고도로 정교화된 레트로 게임 제작 과정을 예술의 경지에 올려놓았습니다. 실제로 이 스튜디오에서 작업한 타이틀 중 몇 가지는 카트리지 형태로 구입하여 Sega Genesis와 같은 레트로 콘솔에서 플레이할 수 있습니다.

리틀 메두사(Little Medusa)와 커피 크라이시스(Coffee Crisis)

레트로 효과를 최대화하기 위한 Unity 워크플로

최근 Unity 워크플로에 몇 가지 기능이 추가되면서 레트로 게임 제작에 최적화된 환경이 구현되었습니다. 2D 타일맵 시스템(2D Tilemap System)의 기능이 훨씬 개선되어 이제는 그리드, 6각 및 아이소매트릭 타일맵을 지원합니다. 또한, 새로운 Pixel Perfect Camera 컴포넌트를 사용하여 일관된 픽셀 기반 모션 및 비주얼을 구현할 수 있습니다. 포스트 프로세싱 스택(Post Processing Stack)을 사용하여 멋진 레트로 스크린 효과를 다양하게 추가할 수도 있습니다. 하지만 이런 작업을 하려면 먼저 에셋을 임포트하여 올바르게 설정해야 합니다.

스프라이트 에셋 준비

우선 에셋이 깔끔하고 선명해지도록 구성을 제대로 설정해야 합니다. 사용하는 각 에셋마다 프로젝트(Project) 뷰에서 에셋을 선택하고 인스펙터(Inspector)에서 다음 설정을 변경합니다.

  • Filter Mode를 ‘Point’로 변경
  • Compression을 ‘None’으로 변경

다른 필터 모드는 이미지를 약간 흐릿하게 만들기 때문에 우리가 원하는 선명한 픽셀 아트 스타일에 맞지 않습니다. 압축을 사용하면 이미지 데이터가 압축되어 원본 이미지에 비해 정확도가 다소 떨어집니다. 이로 인해 일부 픽셀의 컬러가 변경되거나 전체 컬러 팔레트 자체가 바뀔 수 있으므로 중요한 문제입니다. 컬러 수가 적고 스프라이트가 작을수록 압축으로 인한 시각적 차이가 더 커집니다. 다음 이미지에서 일반 압축(기본 설정)을 한 경우와 압축을 하지 않은 경우의 차이를 확인할 수 있습니다.

일반 압축(좌) / 압축하지 않은 경우(우) 원본을 정확히 나타냄

인스펙터에서 이미지의 Max Size 설정도 주의해야 합니다. 스프라이트 이미지가 어느 축에서든 ‘Max Size’ 프로퍼티(기본값 2048)보다 크면 자동으로 최대 크기로 조절됩니다. 그러면 대부분의 경우 품질이 약간 저하되고 이미지가 블러됩니다. 일부 하드웨어는 어느 축에서든지 2048을 초과하는 텍스처를 제대로 지원하지 않으므로 이미지 크기를 해당 제한 범위 이내로 유지하는 것이 좋습니다.

Max Size를 2048로 설정 / Max Size를 4096으로 설정

위 이미지는 한 축의 크기가 2208이고 Max Size가 2048로 설정된 스프라이트 시트의 스프라이트입니다. 이미지에서 확인할 수 있듯이 Max Size 프로퍼티를 4096으로 올리면 이미지 크기가 이에 맞게 조절되므로 품질 저하를 방지할 수 있습니다.

마지막으로, 스프라이트 또는 스프라이트 시트를 준비할 때 Pivot Unit Mode를 ‘Normalized’ 대신 ‘Pixels’로 설정해야 합니다.

이는 스프라이트의 피벗 포인트가 이미지의 각 축에서 0부터 1까지의 평활 범위가 아닌 픽셀을 기반으로 하기 위함입니다. 스프라이트가 한 픽셀 기준으로 정확하게 피벗하지 않으면 픽셀 완전성이 떨어집니다. 스프라이트 에셋을 선택하면 인스펙터에서 스프라이트 에디터를 열 수 있으며, 여기서 스프라이트의 피벗을 설정할 수 있습니다.

2D 픽셀 퍼펙트 패키지 설치

에셋이 준비되었으면 “완전한 픽셀”이 되도록 카메라를 설정해야 합니다. 완전한 픽셀은 깔끔하고 선명하게 표시됩니다. 흐릿해지는(앨리어싱) 현상이 발생하거나 정사각형이어야 하는 픽셀이 직사각형으로 표시되는 경우는 픽셀 아트의 픽셀이 완전하게 표시되지 않을 대 나타나는 대표적인 현상입니다.

2D 픽셀 퍼펙트 패키지는 Unity의 패키지 관리자에서 임포트할 수 있습니다. 툴바에서 ‘Window’ 메뉴를 클릭하고 ‘Package Manager’를 선택합니다. 새 창에서 ‘Advanced’를 클릭하고 ‘Show preview packages’가 활성화되었는지 확인합니다. 왼쪽 목록에서 2D 픽셀 퍼펙트를 선택하고 창 오른쪽 상단에서 설치를 선택합니다.

설치가 완료되면 Pixel-Perfect Camera 컴포넌트를 사용할 수 있습니다.

높은 수준의 픽셀 완전성

Unity의 카메라 컴포넌트에 Pixel Perfect Camera 컴포넌트를 추가하여 기능을 강화할 수 있습니다. 이 컴포넌트를 추가하려면 메인 카메라로 이동해서 Pixel Perfect Camera 컴포넌트를 추가하면 됩니다. Pixel Perfect Camera 컴포넌트 옵션이 없으면 위에서 설명한 대로 먼저 해당 컴포넌트를 프로젝트에 임포트합니다.

이제 사용 가능한 설정을 살펴보겠습니다.

우선, 게임 뷰의 크기를 자유롭게 조절할 수 있도록 ‘Run In Edit Mode’를 확인하고 게임 뷰의 디스플레이 종횡비를 ‘Free Aspect’로 설정하는 것이 좋습니다. 이 컴포넌트는 일정 해상도에서 픽셀이 완전히 표시되지 않는지 여부를 알려주는 안내 메시지를 게임 뷰에 표시합니다.

이제 각 설정을 살펴보면서 설정이 어떻게 사용되며 게임 디자인에 어떤 영향을 주는지 살펴볼 수 있습니다.

  • Assets Pixels Per Unit – 이 필드는 인스펙터에서 각 에셋마다 선택할 수 있는 설정과 관련된 것입니다. 일반적으로 게임의 월드 공간에 사용되는 각 에셋은 동일한 단위당 픽셀(PPU)을 사용해야 하므로 여기에도 해당 값을 입력해야 합니다. 게임 월드가 각각 16픽셀x16픽셀 크기의 타일 및 스프라이트로 이루어진 그리드일 경우 16PPU가 적합합니다. 그리드의 각 타일은 월드 공간 좌표에서 1단위가 됩니다. 선택한 PPU를 여기에 입력했는지 확인합니다.
  • Reference Resolution – 전체 에셋을 시청할 때 사용하고자 하는 해상도로 설정합니다. 레트로 디자인은 일반적으로 매우 낮은 해상도를 사용합니다. 예를 들어 Sega Genesis의 기본 해상도는 320×224입니다. Sega Genesis에서 게임을 포팅하는 경우 320×224의 레퍼런스 해상도를 사용합니다. 일반적인 16:9 비율에는 320×180뿐 아니라 398×224(수직 해상도를 유지하려는 경우)도 잘 작동합니다.
  • Upscale Render Texture – 이 설정은 씬을 최대한 레퍼런스 해상도와 가깝게 렌더링한 다음 실제 디스플레이 크기에 맞춰 업스케일합니다. 이 설정이 적용되면 꽉 찬 화면이 되므로 마진 없이 완전한 픽셀로 이루어진 풀스크린 환경을 구현하는 경우에 적합합니다. 또한 ‘Upscale Render Texture’는 스프라이트 회전 시의 모습에 큰 영향을 줍니다.

1. 원본(회전하지 않음) 2. Upscale Render Texture 적용 안 함(45도 회전, 픽셀 크기가 대각선으로 된 에지에 따라 다르므로 픽셀 완전성이 손상됨) 3. Upscale Render Texture 적용(45도 회전, 모든 픽셀이 동일한 크기이므로 픽셀 완전성이 유지되지만 원본 스프라이트에 비해 정확성이 떨어짐)

  • Pixel Snapping(Upscale Render Texture가 비활성화된 경우에만 사용 가능) – 이 설정을 사용하면 스프라이트 렌더러가 월드 공간 그리드에 자동으로 스내핑됩니다. 이 경우 그리드의 크기는 설정한 PPU를 기반으로 합니다. 참고로, 이 설정은 오브젝트의 트랜스폼 포지션에 실제로 영향을 주지 않습니다. 따라서 각 포지션 사이에 오브젝트를 부드럽게 보간하면서도 시각적인 움직임의 픽셀 완전성과 스냅되는 효과를 유지할 수 있습니다.
    • 예:

Pixel Snapping을 사용하지 않은 경우 포지션이 (0, 0)인 배경과 (1.075, 0)인 캐릭터 스프라이트를 사용하면 일부 픽셀이 제대로 정렬되지 않습니다. 그림자가 절반만 드리운 픽셀이 있다는 점에 주목하세요. Pixel Snapping을 사용하는 경우 포지션은 동일하게 배경은 (0, 0), 캐릭터 스프라이트는 (1.075, 0)입니다. 픽셀이 서로 완전히 스내핑됩니다.

  • Crop Frame(X 및 Y) – 이 설정은 월드 공간에 표시되는 영역이 레퍼런스 해상도와 정확히 일치하도록 잘라내고, 디스플레이에 검정색 마진을 추가하여 스크린 가장자리의 틈을 채웁니다.
  • Stretch Fill – 이 설정은 Crop Frame의 x와 y를 모두 활성화하면 사용할 수 있습니다. 이 설정이 적용되면 카메라가 종횡비를 유지하면서 게임 뷰에 따라 화면에 맞게 확대/축소됩니다. 이 확대/축소는 레퍼런스 해상도의 정수 배수로만 적용되는 것이 아니므로 레퍼런스 해상도의 정수 배수가 아닌 해상도에서는 픽셀 완전성이 손실됩니다. 이 설정을 사용하는 경우 다양한 해상도에서 픽셀 완전성이 손실되지만 검정색 마진이 생기지 않고 꽉 찬 화면이 표시된다는 이점이 있습니다. Stretch Fill로 인해 블러 현상이 자주 발생하지만 일반적인 경고 디스플레이 메시지는 표시되지 않습니다.

Stretch Fill로 블러된 캐릭터 및 배경

Pixel Perfect Camera 사용 시 권장사항

다양한 사용 사례에서 픽셀이 완전하고 또렷한 디스플레이를 구현하려면 다음 사항을 따르는 것이 좋습니다.

  • 플레이어의 창 해상도(예: 320×180)보다 높지 않은 레퍼런스 해상도를 사용합니다.
  • Upscale Render Texture 활성화 또는 비활성화
    • 90, 180, 270도 이외의 회전을 사용하고 회전된 스프라이트에 Upscale Render Texture의 시각적 효과를 적용하기 원하는 경우 이 설정을 활성화합니다.
    • Upscale Render Texture는 레퍼런스 해상도에 따라 일부 해상도에서 이미지의 픽셀 완전성이 손실될 수 있습니다. Pixel.Perfect Camera 컴포넌트에서 활성화된 ‘Run in Edit Mode’에서 이 설정과 다양한 스크린 해상도를 시험해보고 사용하는 해상도에서 문제가 생기는지를 확인합니다. 모든 타겟 해상도에서 픽셀 완전성을 갖춘 이미지를 생성할 수 있으면 최적의 픽셀 완전성을 갖춘 풀스크린 환경이 구현됩니다.
  • 필요에 따라 Pixel Snapping 활성화 또는 비활성화
    • 다른 설정보다 개인 선호도가 크게 작용하는 설정입니다. 스내핑을 사용하지 않는 경우 움직임이 훨씬 부드럽지만 픽셀이 정렬되지 않을 수 있습니다.
  • Upscale Render Texture를 사용하지 않는 경우 Crop Frame X 및/또는 Y 활성화
    • Upscale Render Texture로 일관되게 픽셀이 완전한 결과를 얻을 수 없는 경우 X 및/또는 Y 자르기를 수행하면 레퍼런스 해상도보다 높은 해상도에서 픽셀이 완전한 이미지를 구현할 수 있지만 일부 해상도에서는 화면 가장자리에 큰 마진이 생깁니다.
  • Stretch Fill 비활성화

카메라, 그리고 가능한 경우 레퍼런스 해상도를 16:9 종횡비 시청에 최적화되도록 설정하는 것이 좋습니다. 이 게시물 작성 시점을 기준으로 대부분의 게임은 16:9 모니터에서 1920×1080 해상도로 플레이됩니다. 예를 들어 320×180의 경우 레퍼런스 해상도는 16:9이므로 1920×1080 또는 320×180의 짝수 배수인 해상도(예: 1280×720)에서 플레이할 때 검정색 마진이 생기지 않습니다.

Unity 툴바에서 Edit > Project Settings > Player로 이동하여 게임에서 지원하는 종횡비를 제한할 수 있습니다. 특정 구성이 타겟팅한 비율에서 제대로 작동하지만 특정 종횡비에서 맞지 않는 경우 여기에서 해당 비율로 창이 설정되지 않도록 제한할 수 있습니다. 하지만 일부 사용자의 디스플레이 설정이 이러한 제한 사항과 맞지 않을 수 있으므로 이 설정은 권장하지 않습니다. 사용자가 자신의 화면과 맞지 않는 해상도에서 플레이하기보다는 자르기를 활성화하여 마진이 생기도록 하는 것이 좋습니다.

NES 스타일 아트워크 제작

지금까지 픽셀 완전성을 갖춘 아트를 제작할 수 있도록 Unity를 설정하는 방법에 대해서 알아봤습니다. 이제 클래식 Nintendo Entertainment System의 제약을 따르는 게임용 아트워크 제작에 대한 기본 사항을 살펴보겠습니다. NES 세대 콘솔은 콘솔에 맞는 이미지를 만들려는 아티스트에게 많은 제약이 있습니다. 여기에는 사용되는 팔레트, 화면상의 오브젝트의 크기 및 수와 같은 제약 사항이 있습니다. 또한 NES 콘솔을 “타겟”하는 경우 레퍼런스 해상도를 256×240으로 설정해야 합니다.

팔레트

NES에 적합한 아트워크를 제작하는 경우 아티스트가 따라야 하는 제약이 많습니다. 이 제약 중 일부는 아티스트가 에뮬레이트하려는 레트로 콘솔에 상관없이 적용되지만 그 외의 여러 제약은 NES에만 해당합니다. 제약 중에서 가장 중요한 사항은 이미지에 컬러 팔레트를 사용하는 방식입니다. NES는 콘솔의 전체 컬러 팔레트가 콘솔에 하드 코딩되므로 컬러 팔레트가 매우 독특합니다. NES는 자체 그래픽스 프로세서에 일련의 값을 전송하여 이미지에 사용할 컬러를 선택합니다. 그러면 그래픽스 프로세서는 해당 값에 연결된 컬러를 반환합니다. 아래 이미지는 NES의 컬러 팔레트입니다.

이 컬러 자체가 콘솔의 일부이므로 변경할 수 없습니다. NES 콘솔에서 플레이하는 모든 게임은 이와 같은 컬러 조합을 사용하여 이미지를 만듭니다.

하위 팔레트

게임에 사용되는 컬러 조합을 만들기 위해 하위 팔레트가 생성되어 게임 내 스프라이트 또는 배경 요소에 할당됩니다. NES는 자체 팔레트를 스프라이트와 배경에 할당할 수 있는 여러 개의 하위 팔레트로 나눕니다. 각 하위 팔레트에는 모든 하위 팔레트에서 사용되는 공통 컬러 한 개와 고유 컬러 3개가 포함되어 있습니다. 배경용 하위 팔레트 4개와 스프라이트용 하위 팔레트 4개를 로드할 수 있습니다. 스프라이트의 경우 각 하위 팔레트 첫 부분에 있는 공통 컬러가 투명도로 처리됩니다.

이 이미지는 게임에 사용되는 일련의 하위 팔레트를 보여주는 예시입니다. 위쪽 행은 배경용 하위 팔레트를 나타내고 아래쪽 행은 스프라이트용 하위 팔레트를 나타냅니다. 이 예시에서 검정색은 모든 하위 팔레트에 사용되는 공통 컬러입니다. 공통 컬러는 스프라이트에서 투명도로 처리되므로 실제 나타나는 컬러로 사용하려면 스프라이트용 하위 팔레트에 검정색 팔레트 엔트리를 하나 더 만들어야 합니다.

하위 팔레트 할당

아티스트가 게임 내에서 팔레트를 사용하는 방법에 대한 제한 사항은 더욱 많습니다. 이에 대해 설명하려면 레트로 콘솔에서 아트를 저장하고 사용하고 표시하는 방식에 대해 자세히 알아보아야 합니다. 레트로 콘솔의 아트워크는 게임에 8×8픽셀 타일로 저장됩니다. 이 타일 기반 접근 방식을 사용하면 아티스트가 타일을 다른 용도로 재사용할 수 있으므로 공간을 절약할 수 있습니다. (예를 들어 보도 조각의 용도를 바꿔서 건물의 턱을 만드는 데 사용할 수 있음). 타일 기반 스토리지에서 참고할 또 다른 사항은 일반적으로 컬러 정보가 그래픽스와 함께 저장되지 않는다는 점입니다. 모든 타일은 단색 팔레트로 저장됩니다. 따라서 게임에 표시되는 각 타일에 하위 팔레트를 할당할 수 있으므로 동일한 타일에 다양한 하위 팔레트를 적용하여 동시에 화면에 표시할 수 있습니다. 이 점은 아트워크에 팔레트를 할당하는 방식에 영향을 주므로 최신 플랫폼에서 레트로 콘솔에 적합한 아트워크를 제작하는 경우 매우 중요합니다.

NES는 팔레트를 스프라이트와 배경에 각자 다르게 할당합니다. 스프라이트의 경우 타일별로 하위 팔레트가 할당됩니다. 즉, 스프라이트의 모든 8×8 타일은 스프라이트 하위 팔레트 4개 중 하나를 할당 받을 수 있습니다.

이 닌자 캐릭터는 풍부한 색심도를 구현하기 위해 하위 팔레트 두 개를 활용합니다. 오른쪽은 캐릭터가 개별 8×8 스프라이트 타일로 분할된 모습입니다. 이 분할된 모습을 보면 검과 머리띠에 사용된 옅은 청록색과 가장 짙은 빨간색은 해당 타일에서만 사용되고, 짙은 보라색과 검정색 외곽선 조각은 나머지 타일 세 개에 사용된다는 것을 확실히 알 수 있습니다.

반면, 배경은 더욱 제한적입니다. 배경의 경우 팔레트를 16×16 청크에 할당합니다. 전체 화면의 배경에 할당된 하위 팔레트는 속성 테이블로 참조됩니다. 바로 이 속성 테이블 때문에 대부분의 레트로 아트워크에 반복되는 타일 기반 세그먼트가 많이 사용됩니다. 이 세그먼트는 대부분의 경우 16×16 타일로 구성되어 속성 테이블에 깔끔하게 맞춰집니다. 16×16 타일 기반 접근 방식은 하드웨어 제약으로 인해 만들어졌지만, 이는 배경에 있어 레트로 아트워크만의 특성이 되었으며 레트로 아트워크를 재현하기 위해 필수적인 요소로 자리잡았습니다.

위 이미지는 제한 사항에 맞춰 만들어진 멋진 RPG 스타일 마을 배경의 예시입니다. 오른쪽 이미지는 배경이 16×16픽셀 블록으로 깔끔하게 분할되고 블록별로 팔레트가 선택된 모습을 보여줍니다. 지붕 타일, 잔디, 다리의 벽돌과 같은 요소는 공간 절약을 위해 반복되는 블록 세그먼트로 구성됩니다. 작은 건물의 지붕 타일은 모두 동일한 타일을 사용하지만 서로 다른 하위 팔레트를 할당하여 각자만의 독특한 모습을 구현합니다.

스프라이트 레이어링

아티스트는 스프라이트의 각 8×8 타일에 여러 하위 팔레트를 자유롭게 할당할 수 있지만 할당 가능한 것보다 더욱 풍부한 색심도를 구현하고자 할 경우가 있습니다. 이때 스프라이트 레이어링을 사용할 수 있습니다. 스프라이트 레이어링은 하나의 스프라이트를 두 개로 분할한 다음 이 둘을 쌓아서 합하는 것입니다. 이 방법을 통해 아티스트는 8×8 타일당 하위 팔레트 한 개만 사용해야 하는 제한을 우회할 수 있습니다. 그러면 아티스트는 단일 8×8 영역에서 사용할 수 있는 컬러 수를 두 배로 늘릴 수 있습니다. 이 작업의 유일한 주요 문제로는 스프라이트 렌더링 제한이 있다는 것입니다. NES는 한 번에 8×8 스프라이트 타일 64개만 화면에 표시할 수 있으며 동일한 가로 라인에 스프라이트 타일 최대 8개만 함께 표시할 수 있습니다. 이 숫자에 도달하면 추가 스프라이트 타일은 화면에서 렌더링되지 않습니다. 많은 NES 게임에서 한 번에 많은 스프라이트가 화면에 표시되는 경우 스프라이트가 깜빡이는 이유가 바로 여기에 해당합니다. 이 방식을 사용하면 교대하는 프레임마다 특정 스프라이트를 표시합니다. 아티스트는 스프라이트를 레이어링할 때 이 점을 염두에 두어야 합니다. 스프라이트 레이어링을 통해 컬러 수를 두 배로 늘리면 동일한 가로 라인에 배치되는 스프라이트 타일 수도 두 배가 되기 때문입니다.

스프라이트 레이어링이 적용된 예입니다. 가장 왼쪽 이미지가 고스트 파이럿(Ghost Pirate) 스프라이트의 3 컬러 원본 버전입니다. 아티스트는 이 버전을 바디/모자, 얼굴/손으로 구성된 두 조각으로 분할하고 여기에 서로 다른 팔레트를 할당합니다. 마지막으로, 각 조각을 서로 레이어링한 결과를 확인할 수 있습니다.

스프라이트 레이어링을 배경에서 수행하여 속성 테이블 제한을 우회할 수도 있습니다. 이 트릭은 일반적으로 스토리 화면과 캐릭터 초상화와 같은 정적 이미지에 사용되어 더 풍부한 색심도를 구현합니다. 이를 위해 아티스트는 이미지의 일부를 배경으로 그린 다음 그 위에 스프라이트를 레이어링하여 나머지 부분을 채울 수 있습니다.

고스트 파이럿의 초상화에도 스프라이트 레이어링을 사용하여 색심도를 풍부하게 합니다. 녹색 두개골은 화면에서 스프라이트로 렌더링되며, 옷깃과 모자는 배경의 일부로 렌더링되었습니다. 이렇게 하면 아티스트가 16×16 영역에 더 많은 컬러를 사용하며 속성 테이블 제약을 피해갈 수 있습니다.

그래픽스 뱅크

NES의 다음 주요 제약을 설명하려면 먼저 그래픽스가 타일에 저장된다는 사실을 상기해야 합니다. 그래픽스 타일은 256개의 타일 페이지에 저장되며 이 페이지의 타일은 다른 위치에 있는 VRAM에 로드할 수 없기 때문에 서로 다른 페이지의 타일을 상황에 맞게 짜맞추기 어렵습니다. NES의 VRAM은 이 같은 타일을 한 번에 512개만 표시할 수 있습니다. 이외에도 스프라이트 및 배경에 사용하기 위해 타일을 반으로 나누는 제약이 있습니다. 즉, 동시에 스프라이트 타일 256개와 배경 타일 256개만 표시할 수 있습니다. 이런 점은 아티스트가 다양한 스프라이트와 배경 요소를 표시하는 것을 크게 제한할 수 있습니다.

이 이미지는 VRAM에 로드된 게임의 배경 및 스프라이트 타일의 모습을 시각화한 것입니다. 콘솔은 로드된 배경과 스프라이트를 별도의 페이지에 깔끔하게 유지합니다.

NES는 이러한 제약에 대비하여 아티스트가 각 페이지를 뱅크라고 불리는 부분 페이지 여러 개로 나눌 수 있는 기능을 제공합니다. NES가 그래픽스 데이터의 다양한 지점에 있는 개별 타일을 로드할 수 없지만 이 기능을 통해 한 페이지에서 다양한 섹션을 서로 다른 시점에 로드할 수 있습니다. 대부분의 게임에서 이런 뱅크는 1K 또는 2K 뱅크가 됩니다. 1K 뱅크는 한 페이지의 1/4 또는 타일 64개에 해당하고, 2K 뱅크는 반 페이지 또는 타일 128개에 해당합니다. 아티스트는 두 가지 뱅크 유형을 모두 활용해야 하므로 스프라이트 또는 배경 요소에 어떤 뱅크 유형을 사용하고 어떤 뱅크 유형을 남겨둘 것인지를 결정해야 합니다. 스프라이트와 배경 모두에 1K 뱅크를 사용할 수 없기 때문입니다. 한 페이지에는 1K 뱅크를 사용해야 하며 나머지 페이지는 2K를 사용해야 합니다. 일반적으로 대부분의 게임은 스프라이트에 1K 뱅크를 사용하고 배경에 2K 뱅크를 사용합니다. 배경 타일 세트가 더 정적인 편이고 상황에 따른 변형이 필요한 경우가 적기 때문입니다.

위 이미지는 앞서 봤던 이미지가 뱅크로 나뉜 모습입니다. 왼쪽의 배경 창은 2K 뱅크를 사용하므로 중간에서 분할되고, 오른쪽의 스프라이트 창은 1K 뱅크를 사용합니다. 각 뱅크는 필요에 따라 자유롭게 바꿀 수 있습니다.

스프라이트에 대한 1K 뱅크의 유용성은 매우 중요합니다. 플레이어 스프라이트의 애니메이션 수가 다양하여 로드해야 하는 다른 스프라이트와 함께 하나의 페이지에 들어가지 않는 경우 개별 액션을 1K 뱅크에 저장한 후 화면에서 진행되는 액션에 따라 서로 바꿀 수 있습니다. 이 방법을 통해 하나의 게임 영역에서 스프라이트를 더욱 다양하게 사용할 수 있습니다. 예를 들어 플레이어가 게임의 특정 영역에서 6종류의 적을 마주해야 하지만 스프라이트 페이지에는 플레이어와 다른 3가지 스프라이트 유형만 허용되는 경우 한 가지 적 유형이 화면에서 사라지면 게임은 적 뱅크 중 하나를 새 유형의 적으로 교체할 수 있습니다.

스프라이트에 1K 뱅크를 사용하고 배경에 2K 뱅크를 사용하는 경우 발생할 수 있는 유일한 문제는 NES가 배경 애니메이션을 처리하는 방식입니다. NES 게임에서 배경 요소를 애니메이션화하려면 아티스트가 애니메이션된 배경 요소에 대한 복제 뱅크를 만들어야 합니다. 복제된 새 뱅크는 애니메이션된 각 요소에 대한 다음 애니메이션 프레임을 포함합니다. 이 뱅크는 플립북처럼 한 번에 하나씩 교체되어 애니메이션을 생성합니다. 아티스트가 배경에 반 페이지 뱅크를 사용 중인 경우 해당하는 복제 뱅크를 모두 저장하면 공간을 많이 차지할 수 있습니다. 이 문제는 전체 게임의 애니메이션화된 모든 배경 요소를 하나의 뱅크에 넣는 방법으로 해결할 수 있습니다. 하지만 이를 통해 각 배경의 정적 요소에 남은 타일 128개만 사용할 수 있다는 제약이 생깁니다. 아티스트는 아트에 어떤 뱅크를 사용하는 것이 가장 적합한지를 판단해야 합니다.

레이어링 기법

NES 시대의 많은 게임은 배경에 패럴랙스 스크롤과 같은 효과를 내기 위해 트릭을 사용합니다. 하지만 이것 역시 아티스트와 디자이너에게 도전 과제가 됩니다. 이후의 16비트 콘솔에서는 다중 배경 레이어가 허용되지만 NES에서는 그렇지 않습니다. 모든 배경은 플랫한 단일 이미지입니다. 뎁스와 레이어링의 효과를 구현하기 위해 여러 프로그래밍 기법이 사용되었습니다. 예를 들어 패럴랙스 배경을 만들기 위해 개발자는 특정 가로 라인(래스터 라인)이 화면에서 렌더링되는 시기를 판별할 수 있는 레지스터를 설정할 수 있습니다. 레지스터를 사용하면 화면이 스크롤되는 속도와 방향을 제어할 수 있습니다. 이 방법을 통해 배경에서 지정한 가로행이 나머지 배경과 다른 속도로 스크롤되도록 할 수 있습니다. 여기서 아티스트와 디자이너가 기억해야 할 것은 배경은 여전히 하나의 플랫 이미지로 이루어졌다는 점입니다. 나머지 이미지보다 느리게 이동하는 배경의 “앞”에 있어야 할 플랫폼이나 다른 요소가 해당 영역에 배치된다면 이 요소 또한 나머지 이미지보다 느리게 스크롤됩니다. 즉, 디자이너는 배경 요소를 씬의 어느 곳에 배치할 것인지를 염두에 두어야 하고 아티스트는 효과가 원활하게 적용될 수 있는 배경을 만들어야 합니다.

이 예제 화면에서 빨간색으로 강조 표시된 영역은 나머지 배경보다 더 느리게 스크롤되도록 설정하여 뎁스를 시뮬레이션할 수 있습니다. 위의 헤즈 업 디스플레이는 플랫한 배경 이미지의 일부지만 스크롤되지 않도록 설정됩니다.

또한, 아티스트가 배경 요소 중 하나를 전경에 나타내려는 경우 사용할 수 있는 기법이 하나 있습니다. NES에서는 개발자가 스프라이트의 우선 순위를 0 미만으로 설정할 수 있습니다. 이렇게 하면 스프라이트가 투명하지 않은 배경 픽셀 뒤에 표시됩니다. 스프라이트 우선 순위는 상황에 맞게 수정 및 트리거할 수 있으므로 필요에 따라 특정 요소가 스프라이트의 우선 순위를 변경하도록 할 수 있습니다.

결론

레트로 콘솔에 맞는 프로젝트를 제작할 경우, 최신 개발 과정과 달리 고려해야 할 여러 기술적인 사항이 있습니다. 기존 머신은 작은 용량의 CPU 및 GPU로 이미지를 렌더링하고 처리하기 때문에 디자이너는 하드웨어의 한계를 극복하기 위한 창의적인 방법을 생각해야 합니다. 이 시대의 게임 디자인과 느낌을 오늘날에 제대로 재현하려면 이런 한계와 기법을 잘 알아 두어야 합니다. 다음 게시물에서는 16비트 시대의 디자인 제약과 “예전 TV” 느낌이 나게 하려면 어떤 Unity 작업을 해야 하는지에 대해 알아보겠습니다. 16비트 레트로 비주얼 구현을 위한 2D 픽셀 퍼펙트 가이드는 여기를 참고하세요.

타일맵 레벨 디자인이 처음이라면 Unity Learn의 초급자용 튜토리얼에서 2D 월드 제작에 대해 자세히 알아보세요.

12 replies on “2D 픽셀 퍼펙트: Unity로 레트로 8비트 게임 제작하기”

Excellent post! I’m currently revising a game I did in a NES retro style (Shuttle Scuttle – App Store and Google Play Store). The new version of the game is being developed in Unity with the PixelPerfectCamera and in parallel being developed for the NES using the NESmaker software. NESmaker is a great way to validate your 8-bit NES artwork on real hardware before using in Unity (I use an EverDrive to run the .NES rom on the system). Looking forward to the 16-bit post.

Thank you for this quite thorough blog post!

Unfortunately, I see no mention of the UI or Canvas. Is there any plans for a built-in component to make the canvas scale with the pixel-perfect camera?

The example project does not cover this issue and a few quick tests did not provide any satisfying results, in particular when enabling Crop Frame on the X and Y axises.

It may be worth remembering that these retro games didn’t really have a concept of a UI. If there was a UI, it was just sprites and the system didn’t know any different. As such, for truly retro, you would want to do the same. That being said, creating a world space canvas will adhere to world space rendering and may be your best bet. That being said, I am no expert and could be completely wrong :) Let me ask the folks at Mega Cat.

Awesome post! I had to look hard for a lot of this information for my latest project, so it’s great to see this in one place and still learn a lot of new things. :)

Another thing to note is that “Texture Quality” should be set to “Full Res” under the “Quality Settings”, which is not the default for the “Very Low” settings. Otherwise, your textures will look blurry.

One step forward to completely *nail* the rendering part is to use double-blit upscaling from the original render buffer, something that Sonic Mania does. The process is fairly simple:
-Render into a low-res buffer
-Upscale with integer to the closest lower resolution of your output display
-Upscale to display with linear filtering

As a result we eliminate bad pixel placement at the cost of an additional blit while retaining relatively clean look, which the default pixel perfect camera doesn’t provide with its 1 blit method (last time I checked it rendered the default low-res buffer and blitted it straight to the screen which resulted in a lot of interpolation)

This blog post is awesome for a number of reasons:

– Detailed, yet succinct explanations of common problems and solutions.
– Explains the how AND the why.
– Explores techniques that aren’t common knowledge to newcomers.
– Pixel graphics! <3

I appreciate these options, but if I really wanted to make an 8 bit game with Unity I would strip out most of what Unity is and base the graphics around a render texture and a framework that lets you DrawSprite( spriteX, x, y, rotation, scale ). The logic would be straight C# not monobehaviours and physics.
You really want low-level blit functions like RenderTexture.Draw(texture, x, y, rotation, scale); which can be done with Graphics.Blit but not easily

If you’re disabling Unity’s rendering and physics engine as well as rolling your own pure C# scripts instead of MonoBehaviors, at that point I have to wonder if there’s any point in using Unity at all anymore. You might as well use a lower level 2D game engine like GameMaker or Ogre, or even just roll your own engine from scratch.

Thank you so much for this article! I have a question regarding resolution and sprite size. You mention it is best to set the resolution to something small (like 320×180) so it will only be stretched larger and not cropped to a smaller view of the scene. When it comes to sprites, having such as small resolution means there is very little pixel density on the screen, so you would be forced to use small assets, such as 16×16, to actually fit a decent amount of sprites into a scene with such a small resolution. This would also mean your sprites can’t be very detailed as well. However, looking at some of the pictures in the article (such as Coffee Crisis) there is clearly a lot of pixels being displayed on the screen at one time. How do you go about fitting much larger sprite sizes (64×64, 128×128, etc.) with more detail into such a small resolution? Do you set the Pixels Per Unit to be larger than the actual sprite size (a 32×32 sprite is given a PPU of 64 so it fills less of the screen), or is there some other solution you use?

Correct! So this post covers some general setup and limitations for 8-bit consoles. The next post will cover 16-bit consoles which have more “oof” to work with. I believe Coffee Crisis was released as a Sega Genesis cartridge, so 16-bit.

Comments are closed.