개발일지공부아이디어
    • 3월
    • 2월
    • 1월
  • 2차 프로젝트 일지 - 5일차

    26. 3. 9.

  • 09

    26. 3. 9.

  • 2차 프로젝트 일지 - 4일차

    26. 3. 8.

  • 2차 프로젝트 일지 - 3일차

    26. 3. 7.

  • 06

    26. 3. 6.

  • 2차 프로젝트 일지 - 2일차

    26. 3. 6.

  • 05

    26. 3. 5.

  • 2차 프로젝트 일지 - 1일차

    26. 3. 5.

  • C++ 기본기 연습 프로젝트: 아이템 사전 프로그램

    26. 3. 4.

  • 03

    26. 3. 3.

  • C++ 기본기 연습 프로젝트: 카드 덱 브라우저

    26. 3. 2.

로딩 중...

2차 프로젝트 일지 - 3일차

2026. 3. 7.

3일차: 개발 시작

재밌는 게임

크레이지아케이드 베이스에서, 그리고 지난 1차 프로젝트 베이스에서 사람들이 내 게임을 할 때 재미를 느끼게 할 수 있는 것들은 뭐가 있을까?

  • 이동 컨트롤
  • 물풍선으로 상호작용
  • 적과의 전투

이동 컨트롤

지금의 이동 컨트롤은:

  • 설정한 frameRate 분의 1초마다 이동 입력을 받고 화면을 업데이트한다
  • 기본값이 60프레임이므로 1/60초마다 화면이 업데이트되는 셈이며 이는 인지하기에 매우 부드러운 반복주기를 가진다
  • 그럼에도 불구하고 내 게임이 '뻑뻑'하고 계단식으로 느껴지는 가장 큰 원인은 '화면의 표현 한계'에 있다
  • 아무리 프레임마다 캐릭터의 위치를 정밀하게 계산해도, 현재 환경에서는 '글자 단위'의 타일로만 표현할 수 있다. 즉 죽었다깨나도 0.5같은건 표현되지 않는다는 것이다 Copilot의 진단:
게임 좌표 (Vector2 int x,y)       ↓ 1:1 매핑콘솔 셀 (CHAR_INFO[width × height])       ↓ WriteConsoleOutputW화면 출력
  • Vector2가 정수이고, 그 정수가 곧 콘솔 셀 인덱스입니다. 콘솔 셀 하나는 대략 8×16 픽셀 크기이므로, 80×40 콘솔이면 사실상 80×40 해상도로 게임을 하는 것과 같습니다. 프레임레이트를 아무리 올려도 한 프레임에 이동할 수 있는 최소 단위가 "셀 1칸"이라 부드러운 움직임 자체가 불가능합니다.

Q. 그럼 좌표를 정수로 유지하되 화면 스케일을 키우면 결국 유저가 느끼기엔 더 부드러워졌다고 느끼지 않을까?

[현재: 1셀 = 1게임유닛]5 → 6 이동 = 화면에서 1칸 점프중간 단계: 0개 (7프레임 정지 → 1칸 점프)[화면 스케일 3배: 1셀 = 3게임유닛]15 → 18 이동 = 화면에서 3칸 거리프레임 0: Lerp(15, 18, 0.00) = 15프레임 2: Lerp(15, 18, 0.27) = 15프레임 3: Lerp(15, 18, 0.40) = 16프레임 5: Lerp(15, 18, 0.67) = 17프레임 7: Lerp(15, 18, 1.00) = 18중간 단계: 2개
  • Vector2를 float으로 형변환하는 것보다 화면 스케일(해상도)을 키우는게 낫겠다

Half-block Unicode

There are two block elements, the half block characters ("▀" and "▄"), that allow us to use the terminal as an actual canvas, with pixels that can be set individually. ... Those characters allow us to divide a cell into two vertically, by making smart use of foreground and background colors.

문자가 1byte이므로 8bit, half block unicode는 4bit짜리

Before

Actor::Draw()    │    ▼Renderer::Submit(L"P", pos(5,3), Green, 8)    │    ▼ 렌더큐에 저장RenderCommand { text=L"P", pos=(5,3), color=Green }    │    ▼ Draw()에서 CHAR_INFO에 직접 기록CHAR_INFO[3 * 80 + 5] = { UnicodeChar=L'P', Attributes=0x0002(Green) }    │    ▼WriteConsoleOutputW → 콘솔 셀 (5,3)에 초록색 'P' 출력

After

Actor::Draw()    │    ├─ [기존 경로 - HUD/텍스트용] Submit(L"Stage: 1", ...)  ← 변경 없음    │    └─ [새 경로 - 픽셀용] SubmitPixel(x, y, Green, 8)                                │                                ▼ 픽셀 버퍼에 기록                    pixelBuffer[y * width + x] = Green                    pixelSortingOrder[y * width + x] = 8                                │                                ▼ Draw()에서 변환: 픽셀 2개 → CHAR_INFO 1개                        픽셀 (5, 6) = Red    ──┐    ┌─ CHAR_INFO[3 * 80 + 5]    픽셀 (5, 7) = Blue   ──┘ →  │   UnicodeChar = L'▀'                                 │   Attributes  = 0x0014                                 │                 ~~~~                                 │   bits 0-3: Red(fg)   = 0x04 ← 윗 픽셀                                 │   bits 4-7: Blue(bg)  = 0x10 ← 아랫 픽셀                                 └─                                │                                ▼                    WriteConsoleOutputW → 셀 (5,3)의 윗절반 빨강, 아랫절반 파랑

WA! 드디어 글자가 아닌 타일이 표시된다 신나서 tileSize를 늘렸더니

해상도가 높아지는게 아니라 셀 자체가 늘어났다. 왜?

[현재: 1단계]게임 좌표 ──────────────────────→ 화면 픽셀         tileSize가 둘 다 결정[필요한 구조: 2단계]게임 좌표 ──→ 가상 픽셀 버퍼 ──→ 화면 픽셀         tileSize가 결정    축소(downscale)해서         (내부 정밀도)       콘솔에 맞춤가상 버퍼 (고해상도)              화면 버퍼 (콘솔 크기)┌─────────────────┐              ┌─────────┐│  272 × 224 px   │  ──축소──→   │ 51 × 42 │  ──half-block──→ 콘솔│  (17타일 × 16)  │              │  픽셀   │└─────────────────┘              └─────────┘가상 좌표 (0~271) → 화면 좌표 (0~50)비율: 272/51 ≈ 5.3 가상 픽셀 → 1 화면 픽셀

downScale하는 과정이 추가되어야 하는구나

가상 해상도(Virtual Resolution)

게임 로직이 기준 삼는 좌표계, 실제 콘솔 화면 크기와 별개로 게임이 항상 같은 '월드 크기'로 동작하게 하는 개념.

  • virtualWidth, virtualHeight: 게임 코드가 쓰는 해상도
    C++
    // 가상 해상도: 화면 픽셀 × (tileSize / displayTileSize)virtualWidth = pixelWidth * tileSize / displayTileSize;virtualHeight = pixelHeight * tileSize / displayTileSize;
  • pixelWidth, pixelHeight: 실제 렌더링에 쓰는 픽셀 버퍼 크기
  • tileSize, displayTileSize: 논리 좌표를 실제 크기로 얼마나 확대/축소할지 결정하는 스케일
  1. 게임 코드에서 가상 좌표로 그리기(Submit, SubmitRect, SubmitPixel)
  2. 렌더러가 그 좌표를 내부 픽셀 버퍼에 반영
  3. 프레임의 CHAR_INFO로 변환해서 화면 출력

Sprite 시스템

[현재]                    [에셋이 그려지려면]┌──────┐                  ┌──────┐│██████│ 단색 Green        │ ▓██▓ │ 픽셀마다 다른 색│██████│                  │██▓▓██││██████│                  │▓▓██▓▓│└──────┘                  └──────┘

이제 내가 원하는 에셋으로 그리려면 스프라이트 데이터가 필요하다

  • SubmitSprite(x, y, Color* pixels, w, h, sortingOrder)
  • SpriteAsset(2차원같은 1차원 배열), SpriteLoader 추가(그래픽스 특강이 도움이 되네)
  • Actor::Sprite → wchar_t* 에서 Color[][] 로 확장

Behavior tree

Selector (root)├── Sequence ─ 위험 회피 (최우선)│   ├── IsDangerNearbyCondition│   └── FleeFromDangerAction│├── Sequence ─ 근접 공격│   ├── IsPlayerAdjacentCondition│   └── BTCooldown(1.5s) → PlaceBubbleAction│├── Sequence ─ 경로가 열려있으면 추적│   └── BTCooldown(0.4s) → BFSMoveAction     ← 경로 없으면 Failure│└── Sequence ─ 경로 막혔으면 박스 제거    ├── HasBubbleAmmoCondition    └── BTCooldown(3s) → PlaceBubbleNearBoxAction
왼쪽 화살표2차 프로젝트 일지 - 4일차오른쪽 화살표