GMap2D[y][x]가 의미하는 바(좌표/배열 접근)를 설명할 수 있다.모든 게임은 무한 루프로 동작하며, 다음 3단계로 구성됨:
while (true) {
// 1. 입력 (Input) - 키보드, 마우스 등
// 2. 로직 (Logic) - 이동, AI, 충돌 등
// 3. 출력 (Output) - 콘솔, 3D 그래픽 등
}
텍스트(콘솔)에서도 “입력/로직/출력”을 분리해서 생각하면,
나중에 그래픽(렌더링)으로 옮겨갈 때 구조가 그대로 살아남습니다.
| 값 | 의미 |
|---|---|
| 0 | 빈 공간 (□) |
| 1 | 벽 (■) |
| 2 | 플레이어 (♨) |
GMap1D[MAP_SIZE * MAP_SIZE], 인덱스 = y * MAP_SIZE + x.GMap2D[MAP_SIZE][MAP_SIZE], map[y][x]로 접근.이번 실습은 2D 배열을 사용합니다.
GMap2D[y][x]
y: 행(Row, 위→아래)
x: 열(Col, 왼→오)
Helper.h
#pragma once
enum MoveDir { MD_NONE, MD_LEFT, MD_RIGHT, MD_UP, MD_DOWN };
void HandleKeyInput();
void SetCursorPosition(int x, int y);
void SetCursorOnOff(bool visible);
extern MoveDir GMoveDir;
Helper.cpp - 핵심 기능
SetCursorPosition(x, y): 커서 위치 이동 → 맵을 같은 위치에 다시 그려 움직임처럼 보임.SetCursorOnOff(false): 커서 깜빡임 제거.HandleKeyInput(): GetAsyncKeyState(VK_LEFT) 등으로 방향키 감지.GetAsyncKeyState: 블로킹이 아닌 방식. 입력 대기 없이 "지금 눌리고 있나?"를 질의.& 0x8000: 키가 눌린 상태인지 비트 플래그 검사.cin 같은 입력은 “엔터를 칠 때까지” 멈추기 때문에, 게임 루프가 끊깁니다.Map.h
#pragma once
inline constexpr int MAP_SIZE = 5;
void PrintMap2D();
extern int GMap2D[MAP_SIZE][MAP_SIZE];
Map.cpp
#include "Map.h"
#include "Helper.h"
#include <iostream>
int GMap2D[MAP_SIZE][MAP_SIZE] = {
{1, 1, 1, 1, 1},
{1, 0, 0, 0, 1},
{1, 0, 2, 0, 1},
{1, 0, 0, 0, 1},
{1, 1, 1, 1, 1}
};
void PrintMap2D() {
SetCursorPosition(0, 0);
for (int y = 0; y < MAP_SIZE; y++) {
for (int x = 0; x < MAP_SIZE; x++) {
switch (GMap2D[y][x]) {
case 0: std::cout << "□"; break;
case 1: std::cout << "■"; break;
case 2: std::cout << "♨"; break;
}
}
std::cout << '\n';
}
}
system("cls")로 화면을 지우면 깜빡임이 심해질 수 있습니다.SetCursorPosition(0,0)으로 커서를 맨 위로 옮긴 뒤, “같은 자리”에 다시 그립니다.Player.cpp
bool canMove = true;
int GPlayerX = 2;
int GPlayerY = 2;
void MovePlayer(int x, int y) {
if (x < 0 || x >= MAP_SIZE || y < 0 || y >= MAP_SIZE)
return;
if (GMap2D[y][x] == 1)
return;
GMap2D[GPlayerY][GPlayerX] = 0;
GPlayerX = x;
GPlayerY = y;
GMap2D[GPlayerY][GPlayerX] = 2;
}
void HandleMove() {
if (GMoveDir == MD_NONE) {
canMove = true;
return;
}
if (!canMove)
return;
canMove = false;
switch (GMoveDir) {
case MD_LEFT: MovePlayer(GPlayerX - 1, GPlayerY); break;
case MD_RIGHT: MovePlayer(GPlayerX + 1, GPlayerY); break;
case MD_UP: MovePlayer(GPlayerX, GPlayerY - 1); break;
case MD_DOWN: MovePlayer(GPlayerX, GPlayerY + 1); break;
}
}
GetAsyncKeyState는 “키가 눌려있는 동안” 매 프레임 true가 될 수 있습니다.canMove는 “키를 뗐다가 다시 눌렀을 때만 1회 이동”되게 만드는 간단한 엣지 처리입니다.더 정교하게 하려면 “이전 프레임 키 상태”를 저장해, 눌림(Down) 순간만 감지하는 방식으로 발전시킬 수 있습니다.
#include "Helper.h"
#include "Map.h"
#include "Player.h"
int main() {
SetCursorOnOff(false);
while (true) {
HandleKeyInput(); // 1. 입력 처리
HandleMove(); // 2. 로직 처리
PrintMap2D(); // 3. 출력 처리
}
return 0;
}
MovePlayer()의 if (GMap2D[y][x] == 1) 줄에 브레이크포인트HandleMove()에서 GMoveDir, canMove를 Watch로 확인PrintMap2D()에서 x/y 루프 범위를 먼저 확인 (< MAP_SIZE)GMap2D[y][x]에서 왜 y가 먼저일까?MovePlayer()에서 “이전 위치를 0으로 지우는 코드”를 빼면 무슨 일이 생길까?canMove 없이 구현하면 어떤 현상이 생기고, 그 원인은 무엇일까?