반응형

Roto-Versus를 개발하면서 최근 해결할 수 없는 '버그'로 인하여 새롭게 프로젝트를 구성하고자 합니다.

그 전에, 이전에는 작성하지 못했던 개발에 필요한 기획서(GDD) / 계획서 / 개발 과정 체크리스트 / 게임 방법(설명서) 등 모든 문서에 대하여 작성하고 기록하고자 합니다.

모든 통합 문서는 본 게시물을 지속적으로 업데이트 하는 것으로 진행합니다.


  • [2025.09.09] Roto-Versus 통합 문서 자료 (Ver1.0) 작성

 


Roto-Versus 게임 기획서 (Ver 1.0)

 

더보기

Roto-Versus 게임 기획서 (Ver 1.0)

1. 개요 (Overview)

항목 내용
게임 제목 Roto-Versus (가제)
플랫폼 모바일 (Android, iOS), PC (향후 고려)
장르 1:1 턴제 퍼즐 보드게임
타겟 유저 간단한 규칙의 전략적인 두뇌 싸움을 즐기는 캐주얼 게이머
로그라인 한 명은 자신의 말을 이동시키고, 다른 한 명은 '세상의 방향'을 바꾸는 나침반을 회전시키는 비대칭 1:1 턴제 퍼즐 대전 게임.

2. 핵심 컨셉 (Core Concept)

본 게임의 핵심 재미는 **'비대칭 역할'**과 **'예측 불가능성'**에서 비롯되는 깊이 있는 심리전에 있다. 플레이어는 매 턴 '이동(Mover)'과 '회전(Rotator)'이라는 상이한 역할을 번갈아 맡게 된다. 이동자는 자신의 말을 목표 지점으로 보내는 데 집중하고, 회전자는 상대방의 의도를 예측하고 방해하는 데 집중하며, 이 과정에서 발생하는 치열한 수 싸움이 게임의 중심을 이룬다.

3. 게임 플레이 (Gameplay)

3.1. 게임 목표

  • 5x5 크기의 보드 위에서, 상대보다 먼저 자신의 '말(Pawn)'을 중앙 (0,0)에 위치한 목표 지점에 도달시키는 것.

3.2. 게임 흐름 (Game Flow)

  1. 선공 정하기: 게임 시작 시, 두 플레이어는 '가위바위보'를 진행한다.
  2. 우선권 선택: 가위바위보의 승자는 '선공(Mover)' 또는 '후공(Rotator)' 역할을 첫 턴에 맡을지 선택할 권리를 갖는다.
  3. 턴 진행:
    • 각 턴은 'Mover'의 선택 단계와 'Rotator'의 선택 단계로 나뉜다.
    • Mover: 4장의 방향 카드(앞, 뒤, 좌, 우) 중 하나를 선택한다.
    • Rotator: 2장의 회전 카드(좌 90도, 우 90도) 중 하나를 선택한다.
  4. 턴 결과 실행:
    • 두 플레이어의 선택이 모두 끝나면, '회전'이 먼저 적용된다. (맵 아래의 나침반이 회전)
    • 그 후, 회전된 방향을 기준으로 '이동'이 적용된다.
    • 이동 효과는 Mover 자신의 말에만 적용된다.
  5. 턴 종료: 한 턴이 끝나면 두 플레이어의 역할(Mover/Rotator)이 서로 바뀐다.
  6. 게임 종료: 한 플레이어의 말이 목표 지점에 도착하면 즉시 해당 플레이어의 승리로 게임이 종료된다.

3.3. 핵심 시스템

  • 정적 맵(Static Map): 게임 보드 자체는 회전하지 않아 플레이어의 시각적 혼란을 최소화한다.
  • 회전 나침반(Rotating Compass): 맵 아래에 위치한 '나침반' 오브젝트가 회전하며 현재 '앞' 방향이 어디인지를 시각적으로 알려준다. 모든 이동은 이 나침반의 방향을 기준으로 계산된다.
  • 시작 시 랜덤 회전: 매 게임 시작 시, 나침반이 0, 90, 180, 270도 중 무작위 방향으로 설정되어 매번 새로운 퍼즐을 제공한다.

4. 게임 모드 (Game Modes)

  • 솔로 플레이 (vs AI): 1인 플레이어가 컴퓨터 AI와 대결하는 모드.
  • 함께하기 (2인 로컬 PvP): 하나의 기기를 이용해 두 명의 플레이어가 마주 보고 플레이하는 모드.
  • 온라인 대전: (향후 확장 목표) 인터넷을 통해 다른 플레이어와 대결하는 모드.

5. UI/UX 컨셉

  • 세로 화면 최적화: 모바일 기기에 최적화된 세로 UI 레이아웃을 채택한다.
  • 마주보고 플레이: '함께하기' 모드에서 2P의 UI는 180도 회전되어, 맞은편에 앉은 플레이어가 직관적으로 조작할 수 있도록 한다.
  • 순차적 턴 UI: '함께하기' 모드에서는 한 번에 한 플레이어의 카드 선택 화면만 보여주어, 상대방이 어떤 카드를 선택했는지 알 수 없도록 한다.
  • 시각적 피드백: 카드 선택, 나침반 회전, 말 이동 등 모든 행동에 부드러운 애니메이션과 효과를 적용하여 '보는 재미'를 더한다.

6. 아트 스타일

  • 테마: 미니멀하고 깔끔한 '디지털 보드게임' 컨셉.
  • 맵 & 말: 심플한 기하학적 형태나 직관적인 아이콘 형태를 사용. (커스터마이징 가능성을 열어둠)
  • 나침반: 게임의 핵심 오브젝트로서, 디테일하고 미려한 3D 모델을 사용하여 시각적 중심을 잡는다.
  • UI: 직관적인 아이콘과 가독성 높은 폰트를 사용하여 명확한 정보 전달에 집중한다.

Roto-Versus 개발 계획서

더보기

Roto-Versus 개발 계획서

본 문서는 'Roto-Versus' 프로젝트를 성공적으로 완성하기 위한 단계별 개발 로드맵을 정의한다. 각 단계는 명확한 목표를 가지며, 이전 단계의 완성을 기반으로 진행된다.

Phase 1: 프로토타입 재구축 및 안정화 (현재 단계)

목표: 이전 프로젝트에서 확인된 모든 버그를 해결하고, 기획서 1.0 버전에 명시된 핵심 게임플레이 루프를 완벽하게 구현한 안정적인 기반을 다시 구축한다.

  • Task 1.1: 새 Unity 프로젝트 설정 및 기본 아키텍처(폴더, 씬 구조) 구축.
  • Task 1.2: '함께하기' 모드의 전체 게임 흐름(타이틀 -> 메뉴 -> 게임) 구현.
    • 씬 전환 시스템(SceneLoader) 구현.
    • 반응형 세로 UI 레이아웃(Canvas Scaler, Anchors) 설정.
  • Task 1.3: GameManager, UIManager, BoardManager 등 핵심 매니저 스크립트 재작성.
    • '정적 맵 + 회전 나침반' 시스템 적용.
    • '순차적 턴 UI' 로직 적용.
  • Task 1.4: 모든 기능(가위바위보, 선/후공 선택, 턴 진행, 승리 판정, 재시작)이 버그 없이 완벽하게 동작하는지 검증.

Phase 2: UI/UX 고도화 및 '보는 재미' 강화

목표: 현재의 기능적 UI를 넘어, 플레이어에게 시각적 즐거움과 만족감을 주는 세련된 UI/UX를 완성한다.

  • Task 2.1: 게임의 전체적인 아트 스타일 및 디자인 테마 확정.
  • Task 2.2: 최종 디자인의 카드, 배경, 플레이어 말 등 그래픽 에셋 제작 및 적용.
  • Task 2.3: UI 애니메이션 구현.
    • 카드가 나타나고 사라지는 애니메이션.
    • 버튼 클릭 시 시각적 피드백 (색상 변경, 크기 변화 등).
  • Task 2.4: 사운드 시스템 구현.
    • 배경 음악(BGM) 추가.
    • 주요 행동(카드 선택, 나침반 회전, 승리 등)에 대한 효과음(SFX) 추가.

Phase 3: "혼자 하기" 모드 완성 (AI 개발)

목표: 플레이어가 혼자서도 게임을 충분히 즐길 수 있도록, 도전적인 AI 상대를 구현한다.

  • Task 3.1: AIPlayerController 스크립트 구체화.
  • Task 3.2: AI 의사결정 로직 구현.
    • Level 1 (Random AI): 모든 선택을 무작위로 하는 AI. (기본 기능 검증용)
    • Level 2 (Greedy AI): 목표 지점으로 가는 최단 경로를 우선적으로 선택하는 AI.
    • Level 3 (Strategic AI): 상대방을 방해하는 회전을 고려하는 등, 좀 더 전략적인 수를 계산하는 AI.
  • Task 3.3: AI 피드백 UI 추가.
    • "AI가 생각 중입니다..." 와 같은 시각적 표시.
    • AI의 선택을 시각적으로 보여주는 기능 (예: AI가 선택한 카드가 잠시 나타났다 사라짐).

Phase 4: 최종 폴리싱 및 빌드

목표: 게임의 완성도를 최종적으로 높이고, 실제 플레이가 가능한 빌드를 생성한다.

  • Task 4.1: 게임 편의 기능 추가.
    • 게임 오버 시 '메뉴로 돌아가기', '다시 하기' 버튼 기능 활성화.
    • 간단한 설정 메뉴 (사운드 On/Off 등).
  • Task 4.2: 전체적인 테스트 및 밸런스 조정.
  • Task 4.3: 플랫폼별(PC/모바일) 빌드 및 테스트.

프로토타입 재구축 체크리스트

더보기

Roto-Versus 프로토타입 재구축 체크리스트

새 프로젝트에서 안정적인 '함께하기' 모드 프로토타입을 다시 구축하기 위한 기술적 작업 목록.

1. 프로젝트 및 씬 설정

  • [ ] 새 3D (URP) 프로젝트 생성
  • [ ] 폴더 구조 생성 (_Scripts, Scenes, Prefabs, Models, Materials 등)
  • [ ] 씬 3개 생성 및 이름 지정 (00_TitleScene, 01_MenuScene, 02_GameScene)
  • [ ] Build Settings에 3개 씬 순서대로 등록

2. UI 시스템 구축

  • [ ] 각 씬에 CanvasEventSystem 배치
  • [ ] 모든 CanvasCanvas Scaler를 세로 모드(1080x1920, Match 0.5)로 설정
  • [ ] 00_TitleScene: 로고, 시작 버튼 UI 배치 및 Anchor 설정
  • [ ] 01_MenuScene: 모드 선택 버튼 UI 배치 및 Anchor 설정
  • [ ] 02_GameScene: P1_Area, P2_Area 등 세로 레이아웃 구조 설계
  • [ ] P2_AreaScale(-1, -1, 1)로 설정하여 '마주보기' UI 구현
  • [ ] 한글 폰트 에셋 생성 및 모든 TextMeshPro 오브젝트에 적용

3. 핵심 스크립트 재작성

  • [ ] SceneLoader.cs 작성 및 타이틀/메뉴 씬의 버튼에 연결
  • [ ] PlayerController, HumanPlayerController, AIPlayerController 기본 구조 작성
  • [ ] GameAction.cs (enum 정의) 작성
  • [ ] PlayerPawn.cs (이동 코루틴) 작성
  • [ ] BoardManager.cs 재작성
    • [ ] boardTransform, compassTransform 변수 선언
    • [ ] CreateBoard: 타일, 목표, 나침반 생성 로직
    • [ ] SpawnPlayers: 플레이어 생성 로직
    • [ ] RotateBoardRoutine: 나침반 회전 코루틴
  • [ ] UIManager.cs 재작성
    • [ ] 모든 UI 요소(Panel, Text, Button 등) 변수 연결
    • [ ] ShowRPSUI, ShowWinnerChoiceUI, ShowTurnUI 등 상태별 UI 제어 함수
  • [ ] GameManager.cs 재작성
    • [ ] SetupRoutine: 가위바위보 -> 우선권 선택 흐름 구현
    • [ ] GameLoopTurnRoutine: 순차적 턴 진행 흐름 구현
    • [ ] GetRotatedVector: 나침반 각도를 기준으로 이동 방향 계산
    • [ ] 승리 조건, 경계 체크 등 모든 보조 로직 구현

4. 프리팹(Prefab) 및 에셋 재설정

  • [ ] Tile 프리팹 생성 및 Scale 설정
  • [ ] Player1, Player2 프리팹 생성 (빈 부모 + 2D Quad 자식)
  • [ ] Goal 프리팹 생성
  • [ ] Compass 3D 모델 임포트 및 PBR 머티리얼 설정
  • [ ] Compass 프리팹 생성 (빈 부모 + 3D 모델 자식, Pivot 정렬)
  • [ ] GameManager 오브젝트 재설정 및 모든 컴포넌트, 변수 연결
  • [ ] 모든 카드 버튼(가위바위보, 선/후공, 이동/회전)의 OnClick() 이벤트 재연결

Roto-Versus 게임 방법

더보기

Roto-Versus 게임 방법

게임 목표

상대방보다 먼저 당신의 '말'을 보드 중앙에 있는 목표 지점에 도달시키세요!

게임 시작하기: 선공 정하기

  1. 가위바위보: 게임이 시작되면, 두 플레이어는 가위바위보를 합니다. 화면에 나타나는 가위, 바위, 보 카드 중 하나를 선택하세요.
  2. 우선권 선택: 가위바위보에서 이긴 플레이어는 이번 게임의 첫 턴에 '선공'을 할지, '후공'을 할지 선택할 수 있습니다.
    • 선공: 첫 턴에 자신의 말을 움직이는 '이동(Move)' 역할을 맡습니다.
    • 후공: 첫 턴에 세상의 방향을 바꾸는 '회전(Rotate)' 역할을 맡습니다.

당신의 턴

매 턴마다 두 플레이어는 서로 다른 역할을 맡게 됩니다. 한 턴이 끝나면 역할은 자동으로 서로 바뀝니다.

1. 이동(Move) 역할일 때

당신은 '이동자(Mover)'입니다. 당신의 목표는 말을 목표 지점으로 옮기는 것입니다.

  • 화면에 4장의 방향 카드(앞, 뒤, 왼쪽, 오른쪽)가 나타납니다.
  • 당신의 말이 이동할 방향을 선택하세요.

2. 회전(Rotate) 역할일 때

당신은 '회전자(Rotator)'입니다. 당신의 목표는 상대방의 움직임을 방해하거나, 다음 내 턴에 유리한 상황을 만드는 것입니다.

  • 화면에 2장의 회전 카드(왼쪽 90도, 오른쪽 90도)가 나타납니다.
  • 맵 아래에 있는 '나침반'을 회전시킬 방향을 선택하세요.

턴 결과

두 플레이어가 모두 선택을 마치면, 다음과 같은 순서로 결과가 실행됩니다.

  1. 회전: '회전자'가 선택한 방향으로 맵 아래의 나침반이 먼저 회전합니다.
  2. 이동: 그 후, 회전된 나침반이 가리키는 '앞' 방향을 기준으로 '이동자'가 선택한 방향으로 말이 한 칸 움직입니다.

예시:

  • 나침반이 정면을 가리키고 있습니다.
  • 당신(이동자)은 '앞으로' 카드를 선택했습니다.
  • 상대방(회전자)은 '오른쪽 90도 회전' 카드를 선택했습니다.
  • 결과: 나침반이 오른쪽으로 90도 회전한 뒤, 당신의 말은 새로운 '앞' 방향, 즉 오른쪽으로 한 칸 움직입니다.

승리하기

이 과정을 반복하여, 상대보다 먼저 당신의 말을 보드 중앙의 목표 지점에 올려놓으면 승리합니다!

Roto-Versus 버그 리포트

더보기

Roto-Versus 개발 문제 해결 기록 (Post-mortem)

이전 프로젝트에서 발생했던 주요 문제점과 그 해결 과정을 기록하여, 새 프로젝트에서 동일한 실수를 반복하지 않기 위한 문서.

[초기 개발 단계] 3D 오브젝트 배치 및 설정 오류

1. 플레이어가 맵 타일에 파묻히는 현상

  • 문제 현상: BoardManager가 생성한 플레이어 말이 타일 내부에 절반쯤 파묻혀서 보임.
  • 디버깅 과정: 플레이어 생성 시 Y좌표 값을 0.01f로 설정했으나 문제가 지속됨. Game 뷰에서 직접 Y값을 조절하며 확인.
  • 근본 원인: 기본 Cube 프리팹의 높이는 1이며, 중심점(Pivot)이 정중앙에 있다. 따라서 Position Y=0에 생성된 타일의 윗면은 정확히 Y=0.5에 위치한다.
  • 최종 해결책: BoardManagerSpawnUnits 함수에서 플레이어 생성 시 Y좌표를 0.51f로 설정하여 타일 윗면에 정확히 위치하도록 수정.

2. 탑뷰에서 2D 플레이어가 보이지 않는 문제

  • 문제 현상: 2D 이미지를 적용한 Quad 오브젝트가 탑뷰에서 선으로만 보이거나 거의 보이지 않음.
  • 디버깅 과정: 프리팹 수정 모드에서 QuadRotation 값을 확인.
  • 근본 원인: Quad 오브젝트가 수직으로 서 있도록 Rotation X 값이 0으로 설정되어 있었음.
  • 최종 해결책: QuadRotationX: 90, Y: 0, Z: 0으로 설정하여 바닥에 평평하게 눕도록 수정.

[UI 시스템 구현 단계] 반응형 UI 및 에셋 설정 오류

3. 화면 비율에 따라 UI가 깨지는 현상

  • 문제 현상: Game 뷰의 화면 비율을 바꾸면 버튼이나 이미지가 화면 밖으로 나가거나 크기가 이상해짐.
  • 디버깅 과정: Canvas Scaler 설정과 각 UI 요소의 Rect Transform을 점검.
  • 근본 원인: Canvas Scaler가 세로 모드(1080x1920)에 맞게 설정되지 않았고, 각 UI 요소에 **앵커(Anchor)**가 올바르게 지정되지 않았음.
  • 최종 해결책: Canvas ScalerScale With Screen Size, 기준 해상도 1080x1920, Match0.5로 설정. 모든 UI 요소에 위치에 맞는 앵커(top-center, bottom-stretch 등)를 지정.

4. 한글 폰트가 'ㅁ'으로 깨지는 문제

  • 문제 현상: TextMeshPro 텍스트에 한글을 입력하면 모두 네모(ㅁ)로 표시됨.
  • 근본 원인: TextMeshPro의 기본 폰트 에셋에 한글 글리프(Glyph) 정보가 포함되어 있지 않음.
  • 최종 해결책: 무료 한글 폰트(.ttf)를 프로젝트에 추가하고, Window > TextMeshPro > Font Asset Creator를 이용해 한글 문자셋이 포함된 새로운 폰트 에셋(SDF)을 생성하여 적용.

5. 2P UI가 상하/좌우 모두 뒤집히는 문제

  • 문제 현상: '마주보기' UI를 위해 2P 영역을 뒤집었더니, 상하뿐만 아니라 좌우까지 반전되어 카드의 순서가 이상해짐.
  • 근본 원인: Rotation Z: 180을 사용하여 점대칭으로 회전시켰기 때문.
  • 최종 해결책: Rotation 대신 Scale 값을 (X: -1, Y: -1, Z: 1)로 설정하여, 2P 플레이어 입장에서 직관적인 좌우 대칭이 되도록 수정. 이 과정에서 UI의 **피봇(Pivot)**을 반드시 정중앙(0.5, 0.5)으로 맞춰야 함을 확인.

[후기 개발 단계] 원인 불명의 '유령 버그' (프로젝트 손상)

6. 카드 UI가 사라지지 않고, 플레이어가 맵을 탈출하는 현상

  • 문제 현상: 턴이 시작되면 모든 카드가 동시에 나타나고 사라지지 않음. 플레이어는 명령과 상관없이 대각선으로 맵 끝까지 이동함.
  • 디버깅 과정:
    • GameManagerTurnRoutine에 상세한 Debug.Log를 추가하여 변수 값 추적. 로그 상으로는 모든 좌표 계산과 함수 호출이 정상적으로 이루어짐을 확인.
    • Hierarchy에 중복된 GameManager 오브젝트나 컴포넌트가 없는지 확인.
    • PlayerPawn의 이동 기능을 고립시켜 테스트했을 때 정상 작동함을 확인.
    • Reimport All Assets 시도 후 일시적으로 문제가 해결되는 듯 했으나 재발.
  • 근본 원인 (추정): 코드 자체의 논리적 오류가 아닌, 프로젝트의 메타데이터나 캐시(Library 폴더)가 손상되어 발생한 **'유령 버그'**로 최종 판단. 에디터에서 보이는 것과 실제 내부적으로 실행되는 코드 사이에 불일치가 발생.
  • 최종 해결책: 새 프로젝트를 생성하고, 기존 프로젝트의 Assets 폴더만 이주하여 오염된 프로젝트 설정을 모두 초기화하기로 결정.
반응형
반응형

2025.08.14 - [Unity Project/Roto-Versus (로토 버서스)] - [Unity 개발 일지 #2] 게임에 생명 불어넣기: 게임 루프와 상호작용 구현

 

[Unity 개발 일지 #2] 게임에 생명 불어넣기: 게임 루프와 상호작용 구현

지난 1편에서는 아이디어 구체화부터 유니티 프로젝트 생성, 그리고 코드를 통해 보드와 유닛을 자동으로 배치하는 '정적인' 무대를 완성했습니다. 하지만 아직은 아무것도 움직이지 않는 멋진

kyj931221.tistory.com

 

Roto-Versus 세 번째 개발 일지입니다.

지난 포스팅에서는 플레이어의 입력에 따라 맵이 회전하고 말이 움직이는, 게임의 핵심 플레이 루프를 완성했습니다. 하지만 아직은 단 하나의 씬(Scene) 안에서 모든 것이 이루어지고 있었습니다. 이번에는 본격적인 AI 개발과 같은 살을 붙이기에 앞서, 게임의 전체적인 구조를 다지고 뼈대를 세우는 작업을 진행했습니다.


1. 한 걸음 뒤로: 전체 구조에 대한 고민과 대화

AI 개발을 바로 시작하기 전에, 현재 게임의 구조와 앞으로 나아갈 방향에 대해 다시 한번 점검하는 시간을 가졌습니다.

"AI 모드, 함께하기 모드, 나중에 온라인 모드도 고려, UI는 세로 화면으로 만들고, 카드는 아래에서 위로 올라오는 애니메이션을 추가. 특히 '함께하기' 모드에서는 (같은 공간에 있기 때문에) 서로 선택을 못 보게 하는 아이디어가 필요함."

이러한 생각을 통해, 단순히 기능을 추가하는 것을 넘어 게임의 전체적인 틀을 먼저 잡아야 한다는 결론에 도달했습니다. 특히 '함께하기' 모드에서 선택을 숨기는 방법에 대한 아이디어가 이번 개발의 핵심이 되었습니다. 바로 '맵 자체가 움직여서 턴을 넘겨주는' 방식입니다.


2. 대공사 시작: 3개의 씬(Scene)으로 분리하기

체계적인 관리를 위해, 게임을 3개의 독립된 씬으로 분리했습니다.

  • 00_TitleScene: 게임 로고와 시작 버튼만 있는 첫 화면
  • 01_MenuScene: '혼자하기' / '함께하기' 모드를 선택하는 메뉴 화면
  • 02_GameScene: 실제 게임 플레이가 이루어지는 메인 화면

이 씬들을 자유롭게 오갈 수 있도록, 간단하지만 강력한 SceneLoader 스크립트를 작성했습니다.

// SceneLoader.cs
using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneLoader : MonoBehaviour
{
    // string 타입의 씬 이름을 받아서 해당 씬을 로드하는 함수입니다.
    public void LoadSceneByName(string sceneName)
    {
        SceneManager.LoadScene(sceneName);
    }
}

이 스크립트를 각 씬의 버튼 OnClick() 이벤트에 연결하고, File > Build Settings에 3개의 씬을 순서대로 등록하는 것으로 게임의 기본 흐름이 완성되었습니다.


3. Q&A: 반응형 UI와의 싸움 (feat. 앵커, 피봇, 폰트)

세로 화면으로 UI를 재구성하면서 생겼던 문제는.

Q: "화면 비율을 바꾸니 UI가 전부 깨지는 문제!" A: Canvas Scaler의 기준 해상도를 1080x1920으로 설정하고, 각 UI 요소에 **'앵커(Anchors)'**를 설정하여 해결했습니다. 로고는 상단, 버튼은 하단, 카드 영역은 상/하단 스트레치로 고정하여 어떤 비율에서도 위치를 유지하도록 만들었습니다.

Q: "한글 폰트가 'ㅁ'으로 나오는 문제!" A: TextMeshPro의 기본 폰트에는 한글 정보가 없기 때문이었습니다. 무료 한글 폰트(.ttf)를 다운로드하고, 유니티의 Font Asset Creator를 이용해 한글이 포함된 새로운 폰트 에셋(SDF)을 만들어 해결했습니다.

Q: "2P 화면을 180도 뒤집으니 화면 밖으로 사라지는 문제!" A: UI의 Rotation이 아닌 Scale 값을 (1, -1, 1)로 변경하여 해결했습니다. 이 과정에서 UI의 변형 기준점인 **'피봇(Pivot)'**을 중앙(0.5, 0.5)으로 맞춰야 제자리에서 뒤집힌다는 중요한 원리를 깨달았습니다.


4. 최종 스크립트: 현재까지의 완성본

현재 안정화된 버전의 핵심 스크립트 입니다.

GameManager.cs - SetupRoutine (게임 시작 흐름)

IEnumerator SetupRoutine()
{
    // 가위바위보 진행
    while (true)
    {
        currentState = GameState.RPS_Input;
        // ... (선택 대기) ...
        int winner = DetermineRPSWinner();
        if (winner == 1 || winner == 2)
        {
            // ... (결과 표시) ...
            // 승자 선택 UI 표시 및 대기
            currentState = GameState.WinnerChoice;
            uiManager.ShowWinnerChoiceUI(winner);
            yield return new WaitUntil(() => winnerChoseFirst.HasValue);
            // ... (선/후공 결정) ...
            break;
        }
        else { /* 무승부 처리 */ }
    }
    // 모든 준비 UI 숨기기
    uiManager.HideAllSetupUI();
    // 보드 생성 및 게임 루프 시작
    boardManager.CreateBoard();
    StartCoroutine(GameLoop());
}

UIManager.cs - AnimateMapTransition (맵 슬라이드 애니메이션)

public IEnumerator AnimateMapTransition(int targetState) // 0: 중앙, 1: P1턴(맵이 위로), 2: P2턴(맵이 아래로)
{
    float duration = 0.4f;
    float elapsed = 0f;
    Vector2 startPos = mapContainer.anchoredPosition;
    Vector2 endPos;
    // ... (목표 위치 계산) ...
    while (elapsed < duration)
    {
        mapContainer.anchoredPosition = Vector2.Lerp(startPos, endPos, elapsed / duration);
        elapsed += Time.deltaTime;
        yield return null;
    }
    mapContainer.anchoredPosition = endPos;
}

마무리하며

큰 산을 하나 넘었습니다. 단순히 동작하는 것을 넘어, 게임의 전체적인 구조와 뼈대를 세우고, 사용자 경험(UX)까지 고려한 UI를 완성했습니다. 이제 기반 위에서 새로운 콘텐츠를 추가하는 일만 남았습니다.

다음 개발 일지에서는 드디어 '혼자 하기' 모드의 핵심, AI 플레이어 개발을 시작하겠습니다.

반응형
반응형

지난 1편에서는 아이디어 구체화부터 유니티 프로젝트 생성, 그리고 코드를 통해 보드와 유닛을 자동으로 배치하는 '정적인' 무대를 완성했습니다. 하지만 아직은 아무것도 움직이지 않는 멋진 장식품에 불과했죠.

이번 포스팅에서는 드디어 이 무대에 생명을 불어넣어, 플레이어의 선택에 따라 맵이 회전하고 말이 움직이는 게임의 핵심 로직을 구현한 과정을 기록해보려고 합니다.

2025.08.12 - [Unity Project/Roto-Versus (로토 버서스)] - [Unity 개발 일지 #1] 나만의 2인용 퍼즐 게임 만들기: 아이디어부터 보드 세팅까지

 

[Unity 개발 일지 #1] 나만의 2인용 퍼즐 게임 만들기: 아이디어부터 보드 세팅까지

오랫동안 머릿속에만 있던 게임 아이디어를 드디어 유니티(Unity)를 이용해 직접 만들어보기로 했습니다. 이 블로그에는 아무것도 없던 빈 화면에서부터 게임이 완성되기까지의 여정을 기록해보

kyj931221.tistory.com


1. 게임의 지휘자: GameManager와 게임 루프 설계

모든 게임 로직을 지휘할 '두뇌'인 GameManager 스크립트의 구조부터 잡았습니다. 특히 나중에 '혼자 하기(VS AI)' 기능을 쉽게 추가할 수 있도록, '조작의 주체'를 분리하는 컨트롤러 패턴을 도입했습니다.

  • PlayerController (설계도): 모든 컨트롤러(사람/AI)가 따라야 할 규칙을 정의한 추상 클래스입니다.
  • HumanPlayerController (구현): 키보드/마우스 입력을 처리할 '사람'용 컨트롤러입니다.
  • AIPlayerController (미래를 위한 준비): 나중에 AI 로직을 채워 넣을 '인공지능'용 컨트롤러 껍데기도 미리 만들었습니다.

그리고 GameManager에는 '코루틴(Coroutine)'을 이용해 게임의 전체 흐름을 관리하는 메인 GameLoop를 만들었습니다.

// GameManager.cs의 GameLoop와 TurnRoutine 구조
IEnumerator GameLoop()
{
    // 게임이 끝날 때까지 무한히 턴을 반복합니다.
    while (currentState != GameState.GameOver)
    {
        yield return StartCoroutine(TurnRoutine());
    }
}

IEnumerator TurnRoutine()
{
    // 1. 이동 플레이어의 선택을 기다림
    // 2. 회전 플레이어의 선택을 기다림
    // 3. 두 선택을 합쳐 턴을 실행 (회전 -> 이동)
    // 4. 다음 턴 준비
}

이 구조 덕분에 복잡한 턴제 로직을 시간 순서에 따라 명확하게 작성할 수 있었습니다.


2. 보이는 손과 발: 카드 UI와 이벤트 연결

플레이어가 실제로 자신의 의사를 표현할 카드 UI를 만들고, 이 버튼 클릭이 게임 로직에 전달되도록 '이벤트'를 연결했습니다.

카드 UI(이동/회전)

'UIManager'라는 스크립트가 중간 다리 역할을 하도록 설계했습니다.

  1. 플레이어가 카드 버튼(Button)을 클릭합니다.
  2. 버튼의 onClick 이벤트는 UIManager의 함수를 호출합니다. (예: OnMoveCardSelected(0) 호출)
  3. UIManager는 전달받은 값을 GameManager에게 넘겨줍니다. (예: gameManager.ReceiveMoveChoice(MoveDirection.Up))
  4. GameManager는 선택을 변수에 저장하고, TurnRoutine의 다음 단계를 진행합니다.

이 과정은 유니티 Inspector의 Button 컴포넌트에서 OnClick() 이벤트를 설정하여 구현했습니다.

버튼의 OnClick() 이벤트에 UIManager의 함수와 파라미터(정수)를 설정


3. 드디어 움직인다! 보드 회전과 플레이어 이동

가장 신나는 순간이었습니다! 플레이어의 선택을 실제 움직임으로 변환하는 코드입니다. 모든 움직임은 Vector3.Lerp와 Quaternion.Lerp를 이용해 부드럽게 보이도록 만들었습니다.

[BoardManager.cs - 보드 회전 코드]

// 보드를 부드럽게 회전시키는 코루틴 함수
public IEnumerator RotateBoardRoutine(RotateDirection direction)
{
    float duration = 0.5f;
    float elapsed = 0f;
    Quaternion startRotation = transform.rotation;
    float angle = direction == RotateDirection.Right ? 90f : -90f;
    Quaternion endRotation = startRotation * Quaternion.Euler(0, angle, 0);

    while (elapsed < duration)
    {
        transform.rotation = Quaternion.Lerp(startRotation, endRotation, elapsed / duration);
        elapsed += Time.deltaTime;
        yield return null;
    }
    transform.rotation = endRotation;
}

[PlayerPawn.cs - 플레이어 이동 코드]

// 말을 부드럽게 움직이는 코루틴 함수
public IEnumerator MoveRoutine(Vector3 targetPosition)
{
    float duration = 0.3f;
    float elapsed = 0f;
    Vector3 startPosition = transform.position;

    while (elapsed < duration)
    {
        transform.position = Vector3.Lerp(startPosition, targetPosition, elapsed / duration);
        elapsed += Time.deltaTime;
        yield return null;
    }
    transform.position = targetPosition;
}

'GameManager'는 이 두 함수를 '회전 먼저, 이동 나중' 순서로 호출하여 턴을 진행시킵니다.


4. 현실적인 문제들: 버그 수정

물론 구현 과정에서 문제점도 있었습니다.

  1. 맵이 공전하는 문제: 맵이 제자리에서 돌지 않고 어딘가를 중심으로 크게 도는 현상이 있었습니다. 원인은 GameManager 오브젝트의 Position이 (0,0,0)이 아니었기 때문이었죠. 위치를 원점으로 초기화하여 간단히 해결했습니다.
  2. 가출하는 플레이어: 플레이어들이 맵 밖으로 무한정 나가는 문제가 있었습니다. Mathf.Clamp 함수를 이용해 플레이어의 다음 좌표가 맵의 경계(-2~2)를 벗어나지 않도록 강제로 막아주는 로직을 추가하여 해결했습니다.


마무리하며

이제 Roto-Versus는 단순히 보여주기만 하는 것을 넘어, 플레이어와 상호작용하며 실제 게임처럼 동작하는 핵심적인 뼈대를 갖추게 되었습니다. 카드 UI를 클릭하면 보드가 회전하고, 그에 맞춰 플레이어들이 움직이는 모습을 확인할 수 있었습니다.

아직 승리/패배 조건도 없고, 점수도 없고, AI도 없고, 게임성 부분에서 수정해야 할 부분이 많이 보이네요

다음 개발 일지에서는 '승리 조건'을 체크하는 로직과, 간단한 게임 오버 화면을 구현하는 과정을 다뤄볼 예정입니다. 읽어주셔서 감사합니다!

반응형
반응형

오랫동안 머릿속에만 있던 게임 아이디어를 드디어 유니티(Unity)를 이용해 직접 만들어보기로 했습니다. 이 블로그에는 아무것도 없던 빈 화면에서부터 게임이 완성되기까지의 여정을 기록해보려고 합니다. 


1. 이건 어떤 게임? - 게임 기획

제가 만들고 싶은 게임은 "서로 방해하며 목표를 향해 나아가는 2인용 퍼즐 대전 게임" 입니다.

  • 핵심 컨셉: 한 플레이어는 '이동'을, 다른 플레이어는 '맵 회전'을 담당하며 매 턴 역할을 바꿉니다.
  • 게임 목표: 계속해서 회전하고 변하는 맵 위에서, 상대보다 먼저 중앙의 목표 지점에 도달하면 승리!
  • 게임 가제: Roto-Versus (로토 버서스) - 회전(Rotation)과 대결(Versus)의 의미를 담았습니다.

간단한 기획서(GDD)를 정리하고, 본격적으로 유니티 허브를 켰습니다.


2. 무대 만들기: 절차적 보드 생성

가장 먼저 한 일은 게임의 무대가 될 보드를 만드는 것이었습니다. 타일을 하나하나 손으로 놓는 대신, 코드가 자동으로 보드를 생성하도록 BoardManager라는 스크립트를 작성했습니다.

// BoardManager.cs 의 일부
public int boardSize = 5;
public GameObject tilePrefab;

void GenerateBoard()
{
    for (int x = 0; x < boardSize; x++)
    {
        for (int z = 0; z < boardSize; z++)
        {
            // ... 위치 계산 로직 ...
            Instantiate(tilePrefab, tilePosition, Quaternion.identity, this.transform);
        }
    }
}

이렇게 해두면 나중에 boardSize 변수 값만 5에서 7로 바꾸면 순식간에 7x7 보드가 만들어지죠. 정말 편리합니다.


3. 배우 등장: 플레이어와 목표 지점 프리팹(Prefab) 제작

텅 빈 무대 위에 주인공들을 올릴 차례입니다. 처음에는 3D 캡슐로 플레이어를 만들었는데, 탑뷰 시점에서는 밋밋해 보여서 2D 이미지로 방향을 틀었습니다.

플레이어 말은 바닥에 납작하게 누운 형태의 Quad 오브젝트를 사용했고, 목표 지점(Goal)은 눈에 잘 띄도록 스스로 빛나는(Emission) 머티리얼을 적용한 Cube로 만들었습니다. 이렇게 만들어둔 오브젝트들은 언제든 재사용할 수 있도록 '프리팹'으로 저장했죠.


4. 탑뷰 카메라와 조명 설정

이 게임은 하늘에서 보드를 내려다보는 탑뷰(Top-down View)가 가장 어울립니다. 카메라의 Projection을 Perspective(원근)에서 Orthographic(직교)로 바꾸는 것이 핵심이었습니다. 그래야 보드 격자가 왜곡 없이 깔끔하게 보이니까요.

밋밋한 화면에 입체감을 주기 위해 Directional Light를 수직이 아닌, 살짝 비스듬한 각도(X: 50, Y: -30)로 비춰서 그림자가 예쁘게 생기도록 조절했습니다.


5. 플레이어 위치 문제 해결

물론 모든 과정이 순탄하지만은 않았습니다. 플레이어가 바닥에 파묻히거나, 아예 보이지 않는 문제에 부딪혔죠.

원인은 다양했습니다. 오브젝트의 중심점(Pivot) 문제로 Y 위치 계산이 틀렸었고, 2D 말을 바닥에 눕히기 위한 회전 값을 잘못 설정하기도 했습니다. Y값을 0.01로 했다가, 타일의 높이를 고려해서 0.51로 바꿔주니 비로소 모든 유닛이 제자리를 찾았습니다. 이런 문제 해결 과정이야말로 개발의 진짜 묘미가 아닐까 싶네요.


이렇게 해서 드디어 게임의 '정적인' 무대 설정이 모두 끝났습니다. 코드를 실행하면 지정된 크기의 보드와 플레이어, 목표 지점이 완벽하게 배치됩니다.

아직은 아무것도 움직이지 않는 그림에 불과하지만, 모든 준비는 끝났습니다. 다음에는 이 위에서 플레이어의 입력을 받아 말을 움직이고 맵을 회전시키는, 게임의 핵심 로직 구현을 해보겠습니다.

반응형
반응형

특별한 순간들을 어떻게 기념하고, 생일, 기념일, 임신 소식 등 소중한 순간을 간직하고 공유하기 위하여, 이러한 순간을 쉽게 공유할 수 있도록 앱을 기획하였습니다. 오늘은 제가 이벤트리 앱의 기획서와 개발 과정을 작성해보고자 합니다.

서비스는 이용이 어려운 것이 아니라 마치 편지와 같이 쉽고 간단하게 이용할 수 있도록 하는 목표를 가지고 있습니다. 기획을 시작으로 개발 과정을 하나씩 작성해보도록 하겠습니다.


1. 이벤트리 앱 기획서

1.1 서비스 개요

이벤트리(Eventree)는 다양한 이벤트를 위한 맞춤형 콘텐츠를 생성하고 공유할 수 있는 서비스입니다. 사용자들은 임신 소식, 생일, 기념일 등 다양한 이벤트를 위해 텍스트, 이미지, 멀티미디어 등의 맞춤형 콘텐츠를 생성할 수 있습니다. 생성된 콘텐츠는 HTML 형식으로 다운로드하고 공유할 수 있으며, 각 이벤트에 맞는 특수효과와 애니메이션을 제공합니다.

1.2 초기 서비스: 임신 소식 알림 서비스

이벤트리의 초기 서비스는 임신 소식 알림 서비스입니다. 사용자는 날짜, 성별, 태명, 이미지, 문구 등의 임신 소식 관련 정보를 입력하고, 이를 기반으로 디자인된 HTML 페이지를 자동으로 생성할 수 있습니다. 성별에 따른 맞춤형 시각 효과와 애니메이션도 제공되며, 생성된 페이지는 다운로드 및 공유가 가능합니다.

1.3 화면 구성

이벤트리 앱은 다음과 같은 화면으로 구성됩니다:

  • 메인 시작 화면: 앱 로고와 간단한 소개 문구, "서비스 시작" 버튼이 있습니다.
  • 이벤트 리스트 화면: 다양한 이벤트 목록과 각 이벤트별 간단한 소개 및 미리보기를 제공합니다.
  • 이벤트 서비스 화면 (임신 소식 알림): 날짜 선택, 성별 선택, 태명/이름 입력, 이미지 등록, 안내 문구 입력, 생성 버튼 등이 포함됩니다.
  • 결과 화면: 생성된 HTML 페이지 미리보기, 성별에 따른 시각 효과 및 애니메이션, 다운로드 및 공유 버튼이 있습니다.

1.4 기술 스택

이벤트리 앱 개발에는 다음과 같은 기술 스택이 사용됩니다:

  • Unity 6: 앱 개발 플랫폼
  • HTML, CSS, JavaScript: 웹 페이지 생성 및 디자인
  • C#: Unity 스크립트 언어
  • Firebase 또는 AWS: 데이터 저장 및 관리 (선택 사항)

2. 개발 과정

2.1 프로젝트 설정

Unity Hub를 사용하여 Unity 6 프로젝트를 생성하고, 필요한 에셋과 플러그인을 설치합니다. 프로젝트 구조를 설계하고 씬, 스크립트, 에셋 등을 체계적으로 관리합니다.

2.2 UI 디자인 및 구현

각 화면별 UI 디자인을 진행합니다. 메인 시작 화면, 이벤트 리스트 화면, 이벤트 서비스 화면, 결과 화면을 설계하고, 사용자 입력 요소를 구현합니다. 또한, 성별에 따른 시각 효과와 애니메이션을 적용합니다.

2.3 기능 구현

사용자 입력 데이터를 처리하고 저장하는 기능을 구현합니다. HTML 페이지 템플릿을 생성하고 데이터 바인딩을 통해 웹 페이지를 생성하고 미리볼 수 있도록 합니다. 다운로드 및 공유 기능도 구현합니다.

2.4 테스트 및 디버깅

각 기능별 단위 테스트와 통합 테스트를 수행합니다. 다양한 기기와 환경에서 앱을 테스트하고, 발견된 버그를 수정하며 성능을 개선합니다.

2.5 배포

Android 및 iOS 빌드 설정을 구성하고, 각 앱 스토어에 앱을 등록하여 배포합니다. 사용자 피드백을 수렴하여 앱을 지속적으로 업데이트하고 개선합니다.

3. 추가 고려 사항

사용자 경험(UX)을 고려한 직관적이고 사용하기 쉬운 UI/UX 디자인, 다양한 기기에서 원활하게 작동하도록 성능 최적화, 사용자 데이터 보호를 위한 보안 강화, 글로벌 사용자 확보를 위한 다국어 지원, 공유 기능 강화를 위한 소셜 미디어 연동, 사용자 행동 분석을 통한 서비스 개선 등을 추가로 고려해야 합니다.

더보기

1. 프로젝트 설정

1.1. Unity 6 프로젝트 생성

  1. Unity Hub 설치: Unity Hub를 설치하고 실행
  2. Unity 6 버전 다운로드: Unity Hub에서 Unity 6 버전을 다운로드하여 설치
  3. 새 프로젝트 생성: Unity Hub에서 "새 프로젝트(New Project)"를 클릭하고 프로젝트 템플릿을 선택(예를 들어, "3D" 또는 "2D" 템플릿을 선택)
  4. 프로젝트 이름 지정: 프로젝트 이름을 "Eventree"로 지정하고 저장할 위치를 선택
  5. 프로젝트 생성: "Create" 버튼을 클릭하여 프로젝트를 생성

1.2. 필요한 에셋 및 플러그인 설치

  1. Asset Store 사용: Unity Asset Store에서 필요한 에셋(예: UI, 이미지 처리, 웹뷰 등)을 검색하여 다운로드
  2. 에셋 가져오기: 다운로드한 에셋을 Unity 프로젝트에 가져오기
  3. 플러그인 설치: WebView 및 Native Share와 같은 플러그인을 프로젝트에 추가

1.3. 프로젝트 구조 설계

  1. 씬 구성: 각 화면(메인 시작 화면, 이벤트 리스트 화면, 이벤트 서비스 화면, 결과 화면)에 해당하는 씬을 생성
  2. 폴더 구조 설정: 프로젝트 내 폴더 구조를 설계하여 씬, 스크립트, 에셋 등을 체계적으로 관리(예를 들어, Scenes, Scripts, Assets 등의 폴더를 생성)

2. UI 디자인 및 구현

2.1. 각 화면별 UI 디자인

  1. 메인 시작 화면 UI 디자인:
    • Canvas 생성: 씬 내에 Canvas를 생성하여 UI 요소를 배치할 공간을 마련
    • 앱 로고 및 소개 문구: Text 요소를 추가하여 앱 로고와 소개 문구를 표시
    • 서비스 시작 버튼: Button 요소를 추가하여 "서비스 시작" 버튼을 생성
  2. 이벤트 리스트 화면 UI 디자인:
    • 이벤트 목록: ScrollView 요소를 추가하여 이벤트 목록을 스크롤하여 표시
    • 이벤트 미리보기: 각 이벤트마다 Preview UI 요소를 추가
  3. 이벤트 서비스 화면 UI 디자인:
    • 날짜 선택: DatePicker 요소를 추가
    • 성별 선택: Toggle 요소를 사용하여 성별 선택 UI를 구현
    • 태명/이름 입력: TextInput 요소를 추가
    • 이미지 등록: Image Picker 요소를 추가
    • 안내 문구 입력: TextInput 요소를 추가
    • 생성 버튼: Button 요소를 추가하여 생성 기능을 구현
  4. 결과 화면 UI 디자인:
    • HTML 페이지 미리보기: WebView 요소를 추가하여 생성된 HTML 페이지를 미리볼 수 있도록 하기.
    • 다운로드 및 공유 버튼: Button 요소를 추가하여 다운로드 및 공유 기능을 구현

3. 기능 구현

3.1. 사용자 입력 데이터 처리 및 저장

  1. 데이터 클래스 생성: C# 스크립트에서 사용자 입력 데이터를 저장할 클래스를 생성
  2. 입력 데이터 처리: 각 입력 요소에 대한 이벤트 핸들러를 구현하여 입력 데이터를 처리하고 저장

3.2. HTML 페이지 템플릿 생성 및 데이터 바인딩

  1. HTML 템플릿 생성: 임신 소식 알림 페이지에 대한 HTML 템플릿을 작성
  2. 데이터 바인딩: C# 스크립트에서 사용자 입력 데이터를 HTML 템플릿에 바인딩

3.3. 웹 페이지 생성 및 미리보기

  1. WebView 구성: WebView 플러그인을 사용하여 HTML 페이지를 로드하고 미리볼 수 있도록 설정

3.4. 다운로드 및 공유 기능 구현

  1. Native Share 플러그인 사용: 생성된 HTML 페이지를 다운로드하고 공유할 수 있도록 Native Share 플러그인을 사용하여 기능을 구현

3.5. 성별에 따른 맞춤형 시각 효과 및 애니메이션 적용

  1. Particle System 및 Animator 사용: 성별에 따른 시각 효과 및 애니메이션을 구현

4. 테스트 및 디버깅

4.1. 단위 테스트 및 통합 테스트

  1. 각 기능별 테스트: 각 기능별로 단위 테스트를 수행하여 오류를 확인하고 수정
  2. 통합 테스트: 모든 기능이 원활하게 작동하는지 통합 테스트를 수행

4.2. 다양한 기기 및 환경에서의 테스트

  1. 기기별 테스트: 다양한 Android 및 iOS 기기에서 앱을 테스트

4.3. 버그 수정 및 성능 개선

  1. 버그 수정: 발견된 버그를 수정하고 성능을 개선

5. 배포

5.1. Android 및 iOS 빌드 설정

  1. 빌드 설정: Android 및 iOS 빌드 설정을 구성하고 최적화

5.2. 앱 스토어에 앱 등록 및 배포

  1. 앱 등록: Android 및 iOS 앱 스토어에 앱을 등록하고 배포

5.3. 사용자 피드백 수렴 및 지속적인 업데이트

피드백 반영: 사용자 피드백을 수렴하여 앱을 지속적으로 업데이트하고 개선

반응형
반응형

지금까지 진행한 게임을 배포하는 방법에 대하여 알아봅니다.

게임을 제작하고 개발자 환경에서만 플레이하지 않고, 안드로이드, IOS, 웹 등.. 다양한 환경에서 플레이 할 수 있도록 배포할 수 있습니다.

이번에는 Unity Play 웹사이트에 배포하기 위하여 Web으로 빌드하고자 합니다.

 

 

Unity Play

The place for aspiring game creators to share their latest Unity creation. Gain inspiration and find thousands of FPS, Karting, 2D Platformer and other creations with Unity. No experience needed, just jump in for the chance to be featured!

play.unity.com

 

  • 유니티 메뉴에서 File -  Build Profile 클릭하면 다양한 플랫폼으로 빌드할 수 있는 창이 나옵니다.
  • 현재 Window가 기본으로 설정되어 있고, Web으로 배포하기 위해 Web을 선택 후 Switch Platform 클릭합니다.
  • 변환 작업이 시작되고, 게임의 볼륨이 클 수록 변환 작업이 오래걸리기 때문에 보통 처음에 해당 플랫폼으로 변환 후 프로젝트를 시작합니다.

  • 메뉴에서 Window - Package Manager 클릭하면 다양한 에셋이나 환경에 필요한 기능을 다운받을 수 있는 창이 나옵니다.
  • 왼쪽 Unity Registry 탭을 클릭 후 WebGL을 검색하여 Install(설치) 합니다.
  • Web 플랫폼에 게임을 배포할 때, 필요한 패키지를 다운받을 수 있습니다.

  • 유니티 메뉴에 Publish 메뉴가 새로 나타났습니다.
  • Publish - WebGL Project를 클릭하면 웹으로 빌드할 수 있는 창이 나타납니다.

  • Get started를 클릭 후 Bulid and Publish 를 클릭합니다.
  • 프로젝트를 빌드할 폴더를 선택하고 기다리면 빌드가 완료됩니다.

  • 빌드가 되고 자동으로 Unity Play에 업로드가 되면서 인터넷 창이 나옵니다.
  • 게임 제목과 설명을 입력하고 업로드를 완료합니다.
 

Kimchi-Run_by.김깜냥 on Unity Play

2024.12.26 [Unity 6 Challenge] : Kimchi-Run Pubilsh. Name : Kim Young Jun (김깜냥)

play.unity.com

 

[문제사항]

  • 빌드 과정에서 오류로 빌드가 안되는 경우가 생겼습니다.

  • 스크립트에서 컴파일 과정에 문제가 생기는 오류입니다.
  • CS0234: The type or namespace name 'SearchService' does not exist in the namespace 'UnityEditor'
using UnityEditor.SearchService; [문제가 되는 부분]
using UnityEngine;
using UnityEngine.SceneManagement;
using TMPro;

 

  • 스크립트에서 SearchService 라는 네임스페이스를 확인하지 못합니다.
  • 이부분은 유니티 스크립트 작성과정에서 자동으로 생기면서 스크립트에서 오류 확인은 안되지만 빌드 과정에서 문제가 될 수 있기 때문에 삭제 후 다시 빌드를 하면 정상적으로 빌드가 됩니다.

 

반응형
반응형

게임 플레이를 하면서 나타나는 점수UI와 최종 점수 UI에 대하여 작업합니다.

 

  • DeadUI 오브젝트를 생성하고 Start button 이미지를 넣어줍니다.
 [Header("Reference")]
 ...

 public GameObject DeadUI;

 ...
 
if(state == GameState.Playing && Lives == 0)
{
    ...
    
    DeadUI.SetActive(true);
    
    ...
}
  • 게임 매니저 스크립트를 수정합니다.
  • DeadUI 오브젝트를 참조할 수 있도록 하며, 플레이어가 죽으면 DeadUI가 활성화 되도록 합니다.

  • 게임 화면에 체력 이미지인 하트를 적용합니다.
  • 화면 비율에 맞춰서 On 하트와 Off 하트 크기를 변경하고 On 하트 이미지를 화면에 배치합니다.
using UnityEngine;

public class Heart : MonoBehaviour
{
    public Sprite OnHeart;

    public Sprite OffHeart;

    public SpriteRenderer SpriteRenderer;

    public int LiveNumber;
    void Start()
    {
        
    }

    void Update()
    {
        if(GameManager.Instance.Lives >= LiveNumber)
        {
            SpriteRenderer.sprite = OnHeart;
        }
        else
        {
            SpriteRenderer.sprite = OffHeart;
        }
    }
}
  • 하트 표시를 위한 스크립트 입니다.
  • On 상태의 하트와 Off 상태의 하트 이미지를 참조할 수 있도록 합니다.
 void Update()
 {
     if(GameManager.Instance.Lives >= LiveNumber)
     {
         SpriteRenderer.sprite = OnHeart;
     }
     else
     {
         SpriteRenderer.sprite = OffHeart;
     }
 }
  • 게임매니저 스크립트에서 Lives 와 비교하여 각 하트의 LiveNumber 의 이미지를 On -> Off 로 변경합니다.

  • Canvas 를 추가하고, Canvas의 Render Mode 를 Screen Space - Camera 로 변경합니다.
  • Render Camera에 메인 카메라를 적용합니다.
  • 캔버스의 위치나 크기가 카메라와 동일하게 적용됩니다.

  • 캔버스에 텍스트를 추가합니다.
  • Import TMP Essentials 클릭합니다.
  • 화면에 맞춰서 위치와 크기를 변경합니다.
..
using TMPro;

public enum GameState
{
   ...
}

public class GameManager : MonoBehaviour
{
    ...

    public float PlayStartTime;

    ...

    public TMP_Text scoreText;
    
    private void Awake()
    {
        ...
    }
    void Start()
    {
        ...
    }

    float CalculateScore()
    {
        return Time.time - PlayStartTime;
    }

    void SaveHighScore()
    {
        int score = Mathf.FloorToInt(CalculateScore());
        int currentHighScore = PlayerPrefs.GetInt("highScore");
        if(score > currentHighScore)
        {
            PlayerPrefs.SetInt("highScore", score);
            PlayerPrefs.Save();
        }
    }

    int GetHighScore()
    {
        return PlayerPrefs.GetInt("highScore");
    }

    
    void Update()
    {
        if(state == GameState.Playing)
        {
            scoreText.text = "Score: " + Mathf.FloorToInt(CalculateScore());
        }
        else if(state == GameState.Dead)
        {
            scoreText.text = "High Score: " + GetHighScore();
        }
        if(state== GameState.Intro && Input.GetKeyDown(KeyCode.Space))
        {
            ...
            PlayStartTime = Time.time;
        }
        if(state == GameState.Playing && Lives == 0)
        {
           ...

            SaveHighScore();

            ...
        }
    }
}
  • 게임 매니저 스크립트를 수정합니다.
using TMPro;
  • TMP_Text 가 정상적으로 참조되고 동작하기 위해 필요합니다.
public TMP_Text scoreText;
  • Canvas의 Score 점수를 수정할 수 있도록 참조합니다.
if(state == GameState.Playing)
{
    scoreText.text = "Score: " + Mathf.FloorToInt(CalculateScore());
}
else if(state == GameState.Dead)
{
    scoreText.text = "High Score: " + GetHighScore();
}
  • 게임 상태에 따라 화면에 점수를 표시합니다.
  • 진행 상태에서는 점수가 실시간으로 변하며, 플레이어가 죽으면 최고 점수를 업데이트 하여 화면에 표시합니다.
if(state == GameState.Playing && Lives == 0)
{
    ...

    SaveHighScore();

   	...
}
  • 게임 매니저 스크립트에서 생명이 0이 될 때 스코어를 저장할 수 있도록 SaveHighScore 호출합니다.

  • 게임 매니저에 스코어 텍스트를 적용합니다.

   public float CalculateGameSpeed()
   {
       if(state != GameState.Playing)
       {
           return 5f;
       }
       float speed = 5f + Mathf.Log10(CalculateScore() + 1) * 2f;
       speed = Mathf.Clamp(speed, 5f, 20f); // Mathf.Min을 Clamp로 수정
       //Debug.Log($"Calculated Speed: {speed}");
       return speed;
   }
  • 게임 매니저 스크립트에서 게임 속도를 게임의 상태와 플레이어의 점수에 따라 속도를 점진적으로 증가 시키며, 최대 속도를 30으로 제한 합니다.
void Update()
{
    if(state == GameState.Playing)
    {
        ScoreText.text = "Score : " + Mathf.FloorToInt(CalculateScore());

        float speed = CalculateGameSpeed();
        GameSpeedText.text = "Game Speed : " + Mathf.FloorToInt(speed);
        Debug.Log($"Updated Game Speed: {speed}");
    }
  • 게임 화면에 게임 스피드를 표시하기 위해서 텍스트를 추가 하였습니다.
public class Mover : MonoBehaviour
{
    ...

    
    void Update()
    {
        transform.position += Vector3.left * GameManager.Instance.CalculateGameSpeed() * Time.deltaTime;
    }
}
public class BackgroundScroll : MonoBehaviour
{
    ...

    void Start()
    {
        
    }

   
    void Update()
    {
        meshRenderer.material.mainTextureOffset += new Vector2(scrollSpeed * GameManager.Instance.CalculateGameSpeed() / 20 * Time.deltaTime, 0);
    }
}
  • 배경화면 이나 장애물 이동속도에 게임매니저 게임 스피드를 적용합니다.

반응형
반응형

게임 매니저는 유니티에서 전체적인 게임을 관리 기능을 합니다.

게임 매니저는 다른 스크립트에서 참조할 수도 있고, 게임의 상태 파악에 대한 기능을 제공합니다.

 

  • 게임의 UI에 사용할 타이틀과 Start 버튼의 이미지를 설정합니다.
  • Sprite Mode를 Single 로 변경합니다.

  • 빈 오브젝트 (IntroUI)를 만들고 UI 이미지를 Intro UI에 넣어줍니다.
  • Intro UI 오브젝트는 0,0,0 에 위치합니다.
  • 이미지는 게임 비율에 맞춰서 위치와 스케일을 변경합니다.
using UnityEngine;

public enum GameState
{
    Intro,

    Playing,

    Dead
}

public class GameManager : MonoBehaviour
{
    public static GameManager Instance;

    public GameState state = GameState.Intro;

    [Header("Reference")]
    public GameObject IntroUI;

    public GameObject EnemySpawner;

    public GameObject FoodSpawner;

    public GameObject GoldenSpawner;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
        }
    }
    void Start()
    {
        IntroUI.SetActive(true);
    }

    // Update is called once per frame
    void Update()
    {
        if(state== GameState.Intro && Input.GetKeyDown(KeyCode.Space))
        {
            state= GameState.Playing;
            IntroUI.SetActive(false);

            EnemySpawner.SetActive(true);
            FoodSpawner.SetActive(true);
            GoldenSpawner.SetActive(true);
        }
    }
}
  • 게임 매니저 스크립트 입니다.
  • 게임 시작과 함께 UI가 활성화 되고 스페이스를 눌러서 게임이 시작되면 비활성화 된 장애물 오브젝가 활성화 됩니다.
public enum GameState
{
    Intro,

    Playing,

    Dead
}
  • 게임의 상태를 나타내는 부분입니다.
  • 게임 인트로, 게임 시작, 플레이어 죽음(게임 끝) 상태를 나타냅니다.
  • 게임 상태에 따라 게임의 형태를 다르게 적용합니다.
public static GameManager Instance;

public GameState state = GameState.Intro;

[Header("Reference")]
public GameObject IntroUI;

public GameObject EnemySpawner;

public GameObject FoodSpawner;

public GameObject GoldenSpawner;

private void Awake()
{
    if (Instance == null)
    {
        Instance = this;
    }
    else
    {
        Destroy(gameObject);
    }
}
  • static 함수의 경우 전역 상태 관리, 게임 내에서 단일한 데이터 또는 상태를 관리하는데 사용합니다.
  • 현재 스크립트는 싱글톤 패턴으로 다른 스크립트에서 접근 가능하도록 합니다.
void Start()
{
    IntroUI.SetActive(true);
}

// Update is called once per frame
void Update()
{
    if(state== GameState.Intro && Input.GetKeyDown(KeyCode.Space))
    {
        state= GameState.Playing;
        IntroUI.SetActive(false);

        EnemySpawner.SetActive(true);
        FoodSpawner.SetActive(true);
        GoldenSpawner.SetActive(true);
    }
}
  • 게임 시작시 비활성화 된 IntroUI가 활성화 됩니다.
  • 게임이 Intro 상태에서 스페이스 키를 입력하면 게임 플레이 상태로 변환됩니다.
  • IntroUI는 비활성화 되면서 장애물 오브젝트는 활성화 됩니다.

  • GameManager 오브젝트를 생성하여 스크립트를 적용합니다.
  • 각 IntroUI 와 장애물 Spawner를 스크립트에 적용합니다.
  • 적용된 오브젝트는 비활성화 시켜줍니다.
public class GameManager : MonoBehaviour
{
    public static GameManager Instance;

    public GameState state = GameState.Intro;

    public int Lives = 3;

 

  • 플레이어 사망 상태를 확인하기 위해서 게임 매니저 스크립트에 생명(Lives) 를 추가하였습니다.
  void Hit()
  {
      GameManager.Instance.Lives -= 1;
  }

  void Heal()
  {
      GameManager.Instance.Lives = Mathf.Min(3, GameManager.Instance.Lives + 1);
  }
  • Player 스크립트에서 기존 Hit 와 Heal 부분을 수정합니다.
  • 게임 매니저는 다른 스크립트에서 참조할 수 있기 때문에 GameManager.Instance.Lives 로 수정합니다.
public class Spawner : MonoBehaviour
{
    ...

    private void OnDisable()
    {
        CancelInvoke();
    }
  • Spawner 스크립드도 다음과 같이 수정합니다.
  • OnDisable은 게임 오브젝트나 스크립트가 비활성화될 때 호출됩니다.
  • 즉, 게임플레이어가 죽으면 스크립트가 비활성화 되면서 해당 함수가 호출됩니다.
using UnityEditor.SearchService;
using UnityEngine;
using UnityEngine.SceneManagement;

...

public class GameManager : MonoBehaviour
{
    ...

    public Player PlayerScript;

    private void Awake()
    {
        ...
    }
    void Start()
    {
       ...
    }

    void Update()
    {
        if(state== GameState.Intro && Input.GetKeyDown(KeyCode.Space))
        {
            state= GameState.Playing;
            IntroUI.SetActive(false);

            EnemySpawner.SetActive(true);
            FoodSpawner.SetActive(true);
            GoldenSpawner.SetActive(true);
        }
        if(state == GameState.Playing && Lives == 0)
        {
            PlayerScript.KillPlayer();

            EnemySpawner.SetActive(false);
            FoodSpawner.SetActive(false);
            GoldenSpawner.SetActive(false);
            state = GameState.Dead;
        }
        if(state == GameState.Dead && Input.GetKeyDown(KeyCode.Space))
        {
            SceneManager.LoadScene("main");
        }
    }
}
  • 게임 매니저 스크립트를 수정합니다.
  • 플레이어 사망 상태를 확인하도록 플레이어 스크립트를 추가합니다.
  • 각 게임 상태에 따른 게임 진행을 추가하였습니다.

  • 수정된 스크립트에 따라서 오브젝트에 각 오브젝트를 적용합니다.

 

반응형

+ Recent posts