SDL2 실전 가이드 | 게임 개발 입문

SDL2 실전 가이드 | 게임 개발 입문

이 글의 핵심

SDL2로 창 띄우고 입력 받고, 렌더러로 그리고, 믹서로 소리까지 내는 흐름을 한 번에 잡는다. 엔진 없이 직접 굴릴 때 자주 마주치는 지점 위주다.


목차

  1. SDL이란?
  2. 개발 환경 설정
  3. 첫 번째 윈도우
  4. 이벤트 처리
  5. 렌더링
  6. 이미지 및 텍스처
  7. 오디오
  8. 게임 루프
  9. 충돌 감지
  10. 실전 게임 프로젝트

사전 지식 (초보자를 위한 기초)

1. 게임 개발의 기본 구조

게임은 보통 입력 → 갱신 → 그리기무한 루프로 돌린다.

아래 코드는 text를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

게임 루프:

┌─────────────────────┐
│  1. 입력 처리       │ ← 키보드, 마우스
│     (Input)         │
├─────────────────────┤
│  2. 게임 로직       │ ← 물리, AI, 충돌
│     (Update)        │
├─────────────────────┤
│  3. 화면 그리기     │ ← 렌더링
│     (Render)        │
└─────────────────────┘

    (반복)

입력은 플레이어 의도를 받고, 업데이트에서 위치·충돌·AI를 갱신한 뒤, 렌더링에서 화면에 반영한다.

2. 좌표계

SDL 좌표계:

아래 코드는 text를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

(0,0) ────────→ X (오른쪽)




  Y (아래)

800×600 창을 예로 들면:
- 좌측 상단: (0, 0)
- 우측 상단: (800, 0)
- 좌측 하단: (0, 600)
- 우측 하단: (800, 600)
- 중앙: (400, 300)

3. 프레임레이트 (FPS)

FPS (Frames Per Second)는 초당 화면을 갱신하는 횟수다.

아래 코드는 text를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

60 FPS:
- 1초에 60번 화면 갱신
- 1프레임 = 16.67ms
- 부드러운 움직임

30 FPS:
- 1초에 30번 화면 갱신
- 1프레임 = 33.33ms
- 일반적

15 FPS:
- 1초에 15번 화면 갱신
- 1프레임 = 66.67ms
- 끊김 현상

4. 델타 타임 (Delta Time)

델타 타임은 직전 프레임과 이번 프레임 사이의 시간 간격이다.

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 잘못된 방법 (FPS에 따라 속도 다름)
x += 5;  // 60 FPS면 초당 300 픽셀, 30 FPS면 150 픽셀

// ✅ 올바른 방법 (FPS 무관)
x += 300 * deltaTime;  // 항상 초당 300 픽셀

// deltaTime 계산
float currentTime = SDL_GetTicks() / 1000.0f;
float deltaTime = currentTime - lastTime;
lastTime = currentTime;

5. Surface vs Texture

Surface:

  • CPU 메모리에 저장
  • 느림
  • 소프트웨어 렌더링

Texture:

  • GPU 메모리에 저장
  • 빠름
  • 하드웨어 가속

아래 코드는 text를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

Surface (느림):
CPU ─→ Surface ─→ 화면

Texture (빠름):
CPU ─→ Texture ─→ GPU ─→ 화면

1. SDL이란?

SDL (Simple DirectMedia Layer)크로스 플랫폼 멀티미디어 라이브러리다.

SDL의 역할

창·렌더러·입력(키보드·마우스·패드)·오디오·타이머 등을 묶어 준다. C API라 여러 언어에서 감싸 쓰기 좋다.

SDL vs 다른 라이브러리

SDL은 PC·모바일까지 포괄적으로 쓰이는 편이고, SFML은 C++에 익숙한 사람에게 더 단정하게 느껴질 수 있다. Raylib는 더 얇은 API다. 엔진(Unity, Unreal)은 도구가 많은 대신 무겁고 학습량도 크다.

SDL 모듈

아래 코드는 text를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

SDL2:           핵심 (윈도우, 렌더링, 이벤트)
SDL2_image:     이미지 로딩 (PNG, JPG, BMP)
SDL2_ttf:       트루타입 폰트
SDL2_mixer:     오디오 믹싱
SDL2_net:       네트워킹

2. 개발 환경 설정

설치

macOS:

brew install sdl2
brew install sdl2_image sdl2_ttf sdl2_mixer

Ubuntu/Debian:

sudo apt update
sudo apt install libsdl2-dev
sudo apt install libsdl2-image-dev libsdl2-ttf-dev libsdl2-mixer-dev

Windows (vcpkg):

vcpkg install sdl2
vcpkg install sdl2-image sdl2-ttf sdl2-mixer

Windows (수동):

아래 코드는 text를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

1. https://www.libsdl.org/download-2.0.php
2. Development Libraries 다운로드
3. 압축 해제
4. Visual Studio 프로젝트 설정:
   - Include Directories: SDL2/include
   - Library Directories: SDL2/lib/x64
   - Linker Input: SDL2.lib, SDL2main.lib

CMake 프로젝트

CMakeLists.txt:

다음은 cmake를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

cmake_minimum_required(VERSION 3.10)
project(SDLGame)

set(CMAKE_CXX_STANDARD 17)

# SDL2 찾기
find_package(SDL2 REQUIRED)
find_package(SDL2_image REQUIRED)
find_package(SDL2_ttf REQUIRED)
find_package(SDL2_mixer REQUIRED)

# 실행 파일
add_executable(game
    src/main.cpp
    src/Game.cpp
    src/Texture.cpp
    src/Player.cpp
)

# 라이브러리 링크
target_link_libraries(game
    SDL2::SDL2
    SDL2::SDL2main
    SDL2_image::SDL2_image
    SDL2_ttf::SDL2_ttf
    SDL2_mixer::SDL2_mixer
)

# 인클루드 디렉토리
target_include_directories(game PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/include
)

빌드:

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

mkdir build
cd build
cmake ..
make
./game

3. 첫 번째 윈도우

기본 윈도우

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <SDL2/SDL.h>
#include <iostream>

const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 600;

int main(int argc, char* argv[]) {
    // SDL 초기화
    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        std::cerr << "SDL 초기화 실패: " << SDL_GetError() << std::endl;
        return -1;
    }
    
    // 윈도우 생성
    SDL_Window* window = SDL_CreateWindow(
        "SDL Tutorial",
        SDL_WINDOWPOS_CENTERED,
        SDL_WINDOWPOS_CENTERED,
        SCREEN_WIDTH,
        SCREEN_HEIGHT,
        SDL_WINDOW_SHOWN
    );
    
    if (!window) {
        std::cerr << "윈도우 생성 실패: " << SDL_GetError() << std::endl;
        SDL_Quit();
        return -1;
    }
    
    // 렌더러 생성
    SDL_Renderer* renderer = SDL_CreateRenderer(
        window,
        -1,
        SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC
    );
    
    if (!renderer) {
        std::cerr << "렌더러 생성 실패: " << SDL_GetError() << std::endl;
        SDL_DestroyWindow(window);
        SDL_Quit();
        return -1;
    }
    
    // 메인 루프
    bool running = true;
    SDL_Event event;
    
    while (running) {
        // 이벤트 처리
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                running = false;
            }
        }
        
        // 렌더링
        SDL_SetRenderDrawColor(renderer, 100, 149, 237, 255);  // 코른플라워 블루
        SDL_RenderClear(renderer);
        SDL_RenderPresent(renderer);
    }
    
    // 정리
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
    
    return 0;
}

4. 이벤트 처리

키보드 입력

다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

bool running = true;
SDL_Event event;

while (running) {
    while (SDL_PollEvent(&event)) {
        if (event.type == SDL_QUIT) {
            running = false;
        }
        
        // 키 눌림
        if (event.type == SDL_KEYDOWN) {
            switch (event.key.keysym.sym) {
                case SDLK_ESCAPE:
                    running = false;
                    break;
                case SDLK_UP:
                    std::cout << "위쪽 화살표" << std::endl;
                    break;
                case SDLK_DOWN:
                    std::cout << "아래쪽 화살표" << std::endl;
                    break;
                case SDLK_LEFT:
                    std::cout << "왼쪽 화살표" << std::endl;
                    break;
                case SDLK_RIGHT:
                    std::cout << "오른쪽 화살표" << std::endl;
                    break;
                case SDLK_SPACE:
                    std::cout << "스페이스바" << std::endl;
                    break;
            }
        }
        
        // 키 떼어짐
        if (event.type == SDL_KEYUP) {
            std::cout << "키 떼어짐" << std::endl;
        }
    }
}

연속 입력 (키 누르고 있기):

다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 키보드 상태 배열
// 변수 선언 및 초기화
const Uint8* keyState = SDL_GetKeyboardState(nullptr);

// 게임 루프
while (running) {
    // 이벤트 처리
    while (SDL_PollEvent(&event)) {
        if (event.type == SDL_QUIT) running = false;
    }
    
    // 연속 입력 확인
    if (keyState[SDL_SCANCODE_W]) {
        playerY -= 5;  // 위로 이동
    }
    if (keyState[SDL_SCANCODE_S]) {
        playerY += 5;  // 아래로 이동
    }
    if (keyState[SDL_SCANCODE_A]) {
        playerX -= 5;  // 왼쪽으로 이동
    }
    if (keyState[SDL_SCANCODE_D]) {
        playerX += 5;  // 오른쪽으로 이동
    }
    
    // 렌더링...
}

마우스 입력

다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

while (SDL_PollEvent(&event)) {
    // 마우스 버튼 클릭
    if (event.type == SDL_MOUSEBUTTONDOWN) {
        if (event.button.button == SDL_BUTTON_LEFT) {
            int x = event.button.x;
            int y = event.button.y;
            std::cout << "왼쪽 클릭: (" << x << ", " << y << ")" << std::endl;
        }
        if (event.button.button == SDL_BUTTON_RIGHT) {
            std::cout << "오른쪽 클릭" << std::endl;
        }
    }
    
    // 마우스 이동
    if (event.type == SDL_MOUSEMOTION) {
        int x = event.motion.x;
        int y = event.motion.y;
        int dx = event.motion.xrel;  // 상대 이동
        int dy = event.motion.yrel;
    }
    
    // 마우스 휠
    if (event.type == SDL_MOUSEWHEEL) {
        if (event.wheel.y > 0) {
            std::cout << "휠 위로" << std::endl;
        } else if (event.wheel.y < 0) {
            std::cout << "휠 아래로" << std::endl;
        }
    }
}

// 현재 마우스 위치
int mouseX, mouseY;
Uint32 mouseState = SDL_GetMouseState(&mouseX, &mouseY);

if (mouseState & SDL_BUTTON(SDL_BUTTON_LEFT)) {
    std::cout << "왼쪽 버튼 누르고 있음" << std::endl;
}

게임패드 입력

다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 게임패드 초기화
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER);

// 게임패드 열기
SDL_GameController* controller = nullptr;
for (int i = 0; i < SDL_NumJoysticks(); i++) {
    if (SDL_IsGameController(i)) {
        controller = SDL_GameControllerOpen(i);
        if (controller) {
            std::cout << "게임패드 연결: " << SDL_GameControllerName(controller) << std::endl;
            break;
        }
    }
}

// 이벤트 처리
while (SDL_PollEvent(&event)) {
    // 버튼
    if (event.type == SDL_CONTROLLERBUTTONDOWN) {
        if (event.cbutton.button == SDL_CONTROLLER_BUTTON_A) {
            std::cout << "A 버튼" << std::endl;
        }
        if (event.cbutton.button == SDL_CONTROLLER_BUTTON_START) {
            std::cout << "Start 버튼" << std::endl;
        }
    }
    
    // 아날로그 스틱
    if (event.type == SDL_CONTROLLERAXISMOTION) {
        if (event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTX) {
            float value = event.caxis.value / 32767.0f;  // -1.0 ~ 1.0
            std::cout << "왼쪽 스틱 X: " << value << std::endl;
        }
    }
}

// 정리
SDL_GameControllerClose(controller);

5. 렌더링

기본 도형 그리기

다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 배경색 설정
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);  // 검정
SDL_RenderClear(renderer);

// 점
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);  // 흰색
SDL_RenderDrawPoint(renderer, 400, 300);

// 선
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);  // 빨강
SDL_RenderDrawLine(renderer, 100, 100, 700, 500);

// 사각형 (테두리)
SDL_Rect rect = {100, 100, 200, 150};
SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);  // 초록
SDL_RenderDrawRect(renderer, &rect);

// 사각형 (채우기)
SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);  // 파랑
SDL_RenderFillRect(renderer, &rect);

// 화면 업데이트
SDL_RenderPresent(renderer);

여러 도형 그리기

다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 여러 점
SDL_Point points[] = {
    {100, 100}, {150, 120}, {200, 100}, {250, 150}
};
SDL_RenderDrawPoints(renderer, points, 4);

// 여러 선 (연결된)
SDL_RenderDrawLines(renderer, points, 4);

// 여러 사각형
SDL_Rect rects[] = {
    {100, 100, 50, 50},
    {200, 100, 50, 50},
    {300, 100, 50, 50}
};
SDL_RenderDrawRects(renderer, rects, 3);
SDL_RenderFillRects(renderer, rects, 3);

6. 이미지 및 텍스처

SDL_image 사용

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <SDL2/SDL_image.h>

// SDL_image 초기화
int imgFlags = IMG_INIT_PNG | IMG_INIT_JPG;
if (!(IMG_Init(imgFlags) & imgFlags)) {
    std::cerr << "SDL_image 초기화 실패: " << IMG_GetError() << std::endl;
    return -1;
}

// 이미지 로딩
SDL_Texture* loadTexture(const char* path, SDL_Renderer* renderer) {
    SDL_Surface* surface = IMG_Load(path);
    if (!surface) {
        std::cerr << "이미지 로딩 실패: " << IMG_GetError() << std::endl;
        return nullptr;
    }
    
    SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
    SDL_FreeSurface(surface);
    
    if (!texture) {
        std::cerr << "텍스처 생성 실패: " << SDL_GetError() << std::endl;
        return nullptr;
    }
    
    return texture;
}

// 사용
SDL_Texture* playerTexture = loadTexture("player.png", renderer);

// 렌더링
SDL_Rect destRect = {100, 100, 64, 64};
SDL_RenderCopy(renderer, playerTexture, nullptr, &destRect);

// 정리
SDL_DestroyTexture(playerTexture);
IMG_Quit();

스프라이트 시트

다음은 cpp를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 스프라이트 시트에서 특정 영역 잘라내기
SDL_Rect srcRect = {0, 0, 32, 32};  // 시트에서의 위치
SDL_Rect destRect = {100, 100, 64, 64};  // 화면에서의 위치

SDL_RenderCopy(renderer, spriteSheet, &srcRect, &destRect);

// 애니메이션
int frameWidth = 32;
int frameHeight = 32;
int currentFrame = 0;
int totalFrames = 8;

// 프레임 업데이트 (60 FPS 기준, 0.1초마다)
if (SDL_GetTicks() % 100 < 16) {
    currentFrame = (currentFrame + 1) % totalFrames;
}

// 현재 프레임 렌더링
SDL_Rect srcRect = {
    currentFrame * frameWidth,
    0,
    frameWidth,
    frameHeight
};
SDL_RenderCopy(renderer, spriteSheet, &srcRect, &destRect);

회전 및 플립

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 회전
double angle = 45.0;  // 각도
SDL_Point center = {32, 32};  // 회전 중심
SDL_RenderCopyEx(renderer, texture, nullptr, &destRect, angle, &center, SDL_FLIP_NONE);

// 좌우 반전
SDL_RenderCopyEx(renderer, texture, nullptr, &destRect, 0, nullptr, SDL_FLIP_HORIZONTAL);

// 상하 반전
SDL_RenderCopyEx(renderer, texture, nullptr, &destRect, 0, nullptr, SDL_FLIP_VERTICAL);

// 회전 + 반전
SDL_RenderCopyEx(renderer, texture, nullptr, &destRect, angle, &center, SDL_FLIP_HORIZONTAL);

7. 텍스트 렌더링

SDL_ttf 사용

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <SDL2/SDL_ttf.h>

// TTF 초기화
if (TTF_Init() == -1) {
    std::cerr << "SDL_ttf 초기화 실패: " << TTF_GetError() << std::endl;
    return -1;
}

// 폰트 로딩
TTF_Font* font = TTF_OpenFont("arial.ttf", 24);
if (!font) {
    std::cerr << "폰트 로딩 실패: " << TTF_GetError() << std::endl;
    return -1;
}

// 텍스트 렌더링
SDL_Color textColor = {255, 255, 255, 255};  // 흰색
SDL_Surface* textSurface = TTF_RenderText_Solid(font, "Hello SDL!", textColor);
SDL_Texture* textTexture = SDL_CreateTextureFromSurface(renderer, textSurface);
SDL_FreeSurface(textSurface);

// 텍스트 크기 확인
int textWidth, textHeight;
TTF_SizeText(font, "Hello SDL!", &textWidth, &textHeight);

// 렌더링
SDL_Rect textRect = {100, 100, textWidth, textHeight};
SDL_RenderCopy(renderer, textTexture, nullptr, &textRect);

// 정리
SDL_DestroyTexture(textTexture);
TTF_CloseFont(font);
TTF_Quit();

고품질 텍스트:

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Solid (빠름, 낮은 품질)
SDL_Surface* surface = TTF_RenderText_Solid(font, text, color);

// Shaded (중간 품질, 배경색 포함)
SDL_Color bgColor = {0, 0, 0, 255};
SDL_Surface* surface = TTF_RenderText_Shaded(font, text, color, bgColor);

// Blended (느림, 최고 품질, 안티앨리어싱)
SDL_Surface* surface = TTF_RenderText_Blended(font, text, color);

// UTF-8 지원
SDL_Surface* surface = TTF_RenderUTF8_Blended(font, "안녕하세요", color);

8. 오디오

SDL_mixer 사용

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <SDL2/SDL_mixer.h>

// SDL_mixer 초기화
if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) {
    std::cerr << "SDL_mixer 초기화 실패: " << Mix_GetError() << std::endl;
    return -1;
}

// 음악 로딩
Mix_Music* music = Mix_LoadMUS("background.mp3");
if (!music) {
    std::cerr << "음악 로딩 실패: " << Mix_GetError() << std::endl;
}

// 효과음 로딩
Mix_Chunk* jumpSound = Mix_LoadWAV("jump.wav");
if (!jumpSound) {
    std::cerr << "효과음 로딩 실패: " << Mix_GetError() << std::endl;
}

// 음악 재생
Mix_PlayMusic(music, -1);  // -1: 무한 반복

// 음악 제어
Mix_VolumeMusic(64);  // 볼륨 (0~128)
Mix_PauseMusic();
Mix_ResumeMusic();
Mix_HaltMusic();

// 효과음 재생
Mix_PlayChannel(-1, jumpSound, 0);  // -1: 자동 채널 선택, 0: 반복 없음

// 효과음 제어
Mix_Volume(-1, 64);  // 모든 채널 볼륨
Mix_Volume(0, 128);  // 채널 0 볼륨

// 정리
Mix_FreeChunk(jumpSound);
Mix_FreeMusic(music);
Mix_CloseAudio();

9. 게임 루프

기본 게임 루프

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Game {
private:
    SDL_Window* window;
    SDL_Renderer* renderer;
    bool running;
    
    Uint32 lastTime;
    float deltaTime;
    
public:
    Game() : window(nullptr), renderer(nullptr), running(false), lastTime(0), deltaTime(0) {}
    
    bool init() {
        if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
            std::cerr << "SDL 초기화 실패" << std::endl;
            return false;
        }
        
        window = SDL_CreateWindow(
            "Game",
            SDL_WINDOWPOS_CENTERED,
            SDL_WINDOWPOS_CENTERED,
            800, 600,
            SDL_WINDOW_SHOWN
        );
        
        if (!window) {
            std::cerr << "윈도우 생성 실패" << std::endl;
            return false;
        }
        
        renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
        
        if (!renderer) {
            std::cerr << "렌더러 생성 실패" << std::endl;
            return false;
        }
        
        running = true;
        lastTime = SDL_GetTicks();
        
        return true;
    }
    
    void handleEvents() {
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                running = false;
            }
            
            if (event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_ESCAPE) {
                    running = false;
                }
            }
        }
    }
    
    void update() {
        // 델타 타임 계산
        Uint32 currentTime = SDL_GetTicks();
        deltaTime = (currentTime - lastTime) / 1000.0f;
        lastTime = currentTime;
        
        // 게임 로직
        // 플레이어 이동, 충돌 감지, AI 등
    }
    
    void render() {
        // 화면 지우기
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderClear(renderer);
        
        // 게임 오브젝트 그리기
        // ...
        
        // 화면 업데이트
        SDL_RenderPresent(renderer);
    }
    
    void run() {
        while (running) {
            handleEvents();
            update();
            render();
        }
    }
    
    void clean() {
        SDL_DestroyRenderer(renderer);
        SDL_DestroyWindow(window);
        SDL_Quit();
    }
};

int main(int argc, char* argv[]) {
    Game game;
    
    if (!game.init()) {
        return -1;
    }
    
    game.run();
    game.clean();
    
    return 0;
}

FPS 제한

다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const int FPS = 60;
const int frameDelay = 1000 / FPS;  // 16.67ms

Uint32 frameStart;
int frameTime;

while (running) {
    frameStart = SDL_GetTicks();
    
    handleEvents();
    update();
    render();
    
    frameTime = SDL_GetTicks() - frameStart;
    
    if (frameDelay > frameTime) {
        SDL_Delay(frameDelay - frameTime);
    }
}

10. 충돌 감지

AABB 충돌

AABB (Axis-Aligned Bounding Box):

다음은 cpp를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

bool checkCollision(SDL_Rect a, SDL_Rect b) {
    // 왼쪽 끝
    int leftA = a.x;
    int leftB = b.x;
    
    // 오른쪽 끝
    int rightA = a.x + a.w;
    int rightB = b.x + b.w;
    
    // 위쪽 끝
    int topA = a.y;
    int topB = b.y;
    
    // 아래쪽 끝
    int bottomA = a.y + a.h;
    int bottomB = b.y + b.h;
    
    // 충돌 확인
    if (bottomA <= topB) return false;  // A가 B 위에
    if (topA >= bottomB) return false;  // A가 B 아래에
    if (rightA <= leftB) return false;  // A가 B 왼쪽에
    if (leftA >= rightB) return false;  // A가 B 오른쪽에
    
    return true;  // 충돌!
}

// 또는 SDL 내장 함수
bool collision = SDL_HasIntersection(&rectA, &rectB);

원 충돌

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

struct Circle {
    int x, y;
    int radius;
};

bool checkCollision(Circle a, Circle b) {
    // 두 원의 중심 거리
    int dx = a.x - b.x;
    int dy = a.y - b.y;
    int distanceSquared = dx * dx + dy * dy;
    
    // 반지름 합
    int radiusSum = a.radius + b.radius;
    
    // 거리가 반지름 합보다 작으면 충돌
    return distanceSquared < (radiusSum * radiusSum);
}

점과 사각형 충돌

아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

bool pointInRect(int x, int y, SDL_Rect rect) {
    return (x >= rect.x && x <= rect.x + rect.w &&
            y >= rect.y && y <= rect.y + rect.h);
}

// 마우스 클릭 확인
if (event.type == SDL_MOUSEBUTTONDOWN) {
    int mouseX = event.button.x;
    int mouseY = event.button.y;
    
    if (pointInRect(mouseX, mouseY, buttonRect)) {
        std::cout << "버튼 클릭!" << std::endl;
    }
}

11. 실전 프로젝트

프로젝트 1: Pong 게임

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <SDL2/SDL.h>
#include <iostream>

const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 600;
const int PADDLE_WIDTH = 15;
const int PADDLE_HEIGHT = 100;
const int BALL_SIZE = 15;

class Pong {
private:
    SDL_Window* window;
    SDL_Renderer* renderer;
    bool running;
    
    // 패들
    SDL_Rect leftPaddle;
    SDL_Rect rightPaddle;
    int leftPaddleVel;
    int rightPaddleVel;
    
    // 공
    SDL_Rect ball;
    int ballVelX;
    int ballVelY;
    
    // 점수
    int leftScore;
    int rightScore;
    
public:
    Pong() : running(false), leftScore(0), rightScore(0) {
        // 패들 초기화
        leftPaddle = {50, SCREEN_HEIGHT / 2 - PADDLE_HEIGHT / 2, PADDLE_WIDTH, PADDLE_HEIGHT};
        rightPaddle = {SCREEN_WIDTH - 50 - PADDLE_WIDTH, SCREEN_HEIGHT / 2 - PADDLE_HEIGHT / 2, PADDLE_WIDTH, PADDLE_HEIGHT};
        leftPaddleVel = 0;
        rightPaddleVel = 0;
        
        // 공 초기화
        resetBall();
    }
    
    void resetBall() {
        ball = {SCREEN_WIDTH / 2 - BALL_SIZE / 2, SCREEN_HEIGHT / 2 - BALL_SIZE / 2, BALL_SIZE, BALL_SIZE};
        ballVelX = (rand() % 2 == 0) ? -5 : 5;
        ballVelY = (rand() % 10 - 5);
    }
    
    bool init() {
        if (SDL_Init(SDL_INIT_VIDEO) < 0) return false;
        
        window = SDL_CreateWindow("Pong", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
        if (!window) return false;
        
        renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
        if (!renderer) return false;
        
        running = true;
        return true;
    }
    
    void handleEvents() {
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                running = false;
            }
        }
        
        // 키보드 상태
        const Uint8* keyState = SDL_GetKeyboardState(nullptr);
        
        // 왼쪽 패들 (W/S)
        leftPaddleVel = 0;
        if (keyState[SDL_SCANCODE_W]) leftPaddleVel = -6;
        if (keyState[SDL_SCANCODE_S]) leftPaddleVel = 6;
        
        // 오른쪽 패들 (Up/Down)
        rightPaddleVel = 0;
        if (keyState[SDL_SCANCODE_UP]) rightPaddleVel = -6;
        if (keyState[SDL_SCANCODE_DOWN]) rightPaddleVel = 6;
    }
    
    void update() {
        // 패들 이동
        leftPaddle.y += leftPaddleVel;
        rightPaddle.y += rightPaddleVel;
        
        // 패들 경계 체크
        if (leftPaddle.y < 0) leftPaddle.y = 0;
        if (leftPaddle.y + PADDLE_HEIGHT > SCREEN_HEIGHT) leftPaddle.y = SCREEN_HEIGHT - PADDLE_HEIGHT;
        if (rightPaddle.y < 0) rightPaddle.y = 0;
        if (rightPaddle.y + PADDLE_HEIGHT > SCREEN_HEIGHT) rightPaddle.y = SCREEN_HEIGHT - PADDLE_HEIGHT;
        
        // 공 이동
        ball.x += ballVelX;
        ball.y += ballVelY;
        
        // 공 벽 충돌 (위/아래)
        if (ball.y <= 0 || ball.y + BALL_SIZE >= SCREEN_HEIGHT) {
            ballVelY = -ballVelY;
        }
        
        // 공 패들 충돌
        if (SDL_HasIntersection(&ball, &leftPaddle) || SDL_HasIntersection(&ball, &rightPaddle)) {
            ballVelX = -ballVelX;
        }
        
        // 득점
        if (ball.x < 0) {
            rightScore++;
            resetBall();
        }
        if (ball.x > SCREEN_WIDTH) {
            leftScore++;
            resetBall();
        }
    }
    
    void render() {
        // 배경
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderClear(renderer);
        
        // 중앙선
        SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
        for (int y = 0; y < SCREEN_HEIGHT; y += 20) {
            SDL_Rect line = {SCREEN_WIDTH / 2 - 2, y, 4, 10};
            SDL_RenderFillRect(renderer, &line);
        }
        
        // 패들
        SDL_RenderFillRect(renderer, &leftPaddle);
        SDL_RenderFillRect(renderer, &rightPaddle);
        
        // 공
        SDL_RenderFillRect(renderer, &ball);
        
        SDL_RenderPresent(renderer);
    }
    
    void run() {
        while (running) {
            handleEvents();
            update();
            render();
            SDL_Delay(16);  // ~60 FPS
        }
    }
    
    void clean() {
        SDL_DestroyRenderer(renderer);
        SDL_DestroyWindow(window);
        SDL_Quit();
    }
};

int main(int argc, char* argv[]) {
    Pong game;
    
    if (!game.init()) {
        return -1;
    }
    
    game.run();
    game.clean();
    
    return 0;
}

프로젝트 2: 플랫포머 게임

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Player {
public:
    SDL_Rect rect;
    float velocityX;
    float velocityY;
    bool onGround;
    
    Player(int x, int y) : velocityX(0), velocityY(0), onGround(false) {
        rect = {x, y, 32, 48};
    }
    
    void handleInput(const Uint8* keyState) {
        velocityX = 0;
        
        if (keyState[SDL_SCANCODE_LEFT]) {
            velocityX = -200;  // 픽셀/초
        }
        if (keyState[SDL_SCANCODE_RIGHT]) {
            velocityX = 200;
        }
        if (keyState[SDL_SCANCODE_SPACE] && onGround) {
            velocityY = -500;  // 점프
            onGround = false;
        }
    }
    
    void update(float deltaTime) {
        // 중력
        velocityY += 1000 * deltaTime;  // 픽셀/초²
        
        // 위치 업데이트
        rect.x += velocityX * deltaTime;
        rect.y += velocityY * deltaTime;
        
        // 바닥 충돌
        if (rect.y + rect.h >= 550) {
            rect.y = 550 - rect.h;
            velocityY = 0;
            onGround = true;
        }
        
        // 화면 경계
        if (rect.x < 0) rect.x = 0;
        if (rect.x + rect.w > 800) rect.x = 800 - rect.w;
    }
    
    void render(SDL_Renderer* renderer) {
        SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
        SDL_RenderFillRect(renderer, &rect);
    }
};

class Platform {
public:
    SDL_Rect rect;
    
    Platform(int x, int y, int w, int h) {
        rect = {x, y, w, h};
    }
    
    void render(SDL_Renderer* renderer) {
        SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
        SDL_RenderFillRect(renderer, &rect);
    }
};

class Platformer {
private:
    SDL_Window* window;
    SDL_Renderer* renderer;
    bool running;
    
    Player player;
    std::vector<Platform> platforms;
    
    Uint32 lastTime;
    float deltaTime;
    
public:
    Platformer() : player(100, 100), lastTime(0), deltaTime(0) {
        // 플랫폼 생성
        platforms.push_back(Platform(0, 550, 800, 50));      // 바닥
        platforms.push_back(Platform(200, 450, 150, 20));
        platforms.push_back(Platform(400, 350, 150, 20));
        platforms.push_back(Platform(600, 250, 150, 20));
    }
    
    bool init() {
        if (SDL_Init(SDL_INIT_VIDEO) < 0) return false;
        
        window = SDL_CreateWindow("Platformer", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN);
        if (!window) return false;
        
        renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
        if (!renderer) return false;
        
        running = true;
        lastTime = SDL_GetTicks();
        
        return true;
    }
    
    void handleEvents() {
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) running = false;
        }
        
        const Uint8* keyState = SDL_GetKeyboardState(nullptr);
        player.handleInput(keyState);
    }
    
    void update() {
        Uint32 currentTime = SDL_GetTicks();
        deltaTime = (currentTime - lastTime) / 1000.0f;
        lastTime = currentTime;
        
        // 플레이어 업데이트
        player.update(deltaTime);
        
        // 플랫폼 충돌 체크
        for (auto& platform : platforms) {
            if (SDL_HasIntersection(&player.rect, &platform.rect)) {
                // 위에서 착지
                if (player.velocityY > 0 && player.rect.y + player.rect.h - 10 < platform.rect.y) {
                    player.rect.y = platform.rect.y - player.rect.h;
                    player.velocityY = 0;
                    player.onGround = true;
                }
            }
        }
    }
    
    void render() {
        SDL_SetRenderDrawColor(renderer, 135, 206, 235, 255);  // 하늘색
        SDL_RenderClear(renderer);
        
        // 플랫폼 렌더링
        for (auto& platform : platforms) {
            platform.render(renderer);
        }
        
        // 플레이어 렌더링
        player.render(renderer);
        
        SDL_RenderPresent(renderer);
    }
    
    void run() {
        while (running) {
            handleEvents();
            update();
            render();
        }
    }
    
    void clean() {
        SDL_DestroyRenderer(renderer);
        SDL_DestroyWindow(window);
        SDL_Quit();
    }
};

int main(int argc, char* argv[]) {
    Platformer game;
    
    if (!game.init()) return -1;
    
    game.run();
    game.clean();
    
    return 0;
}

12. Python에서 SDL 사용

PySDL2 설치

pip install PySDL2 PySDL2-dll

기본 윈도우 (Python)

다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import sdl2
import sdl2.ext
import sys

def main():
    # SDL 초기화
    sdl2.ext.init()
    
    # 윈도우 생성
    window = sdl2.ext.Window("SDL Python", size=(800, 600))
    window.show()
    
    # 렌더러 생성
    renderer = sdl2.ext.Renderer(window)
    
    # 메인 루프
    running = True
    while running:
        # 이벤트 처리
        events = sdl2.ext.get_events()
        for event in events:
            if event.type == sdl2.SDL_QUIT:
                running = False
            
            if event.type == sdl2.SDL_KEYDOWN:
                if event.key.keysym.sym == sdl2.SDLK_ESCAPE:
                    running = False
        
        # 렌더링
        renderer.clear(sdl2.ext.Color(100, 149, 237))  # 코른플라워 블루
        renderer.present()
    
    # 정리
    sdl2.ext.quit()
    return 0

if __name__ == '__main__':
    sys.exit(main())

간단한 게임 (Python)

다음은 python를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import sdl2
import sdl2.ext
import random

class Ball:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.vx = random.randint(-5, 5)
        self.vy = random.randint(-5, 5)
        self.size = 20
        self.color = sdl2.ext.Color(255, 255, 255)
    
    def update(self):
        self.x += self.vx
        self.y += self.vy
        
        # 벽 충돌
        if self.x <= 0 or self.x >= 800 - self.size:
            self.vx = -self.vx
        if self.y <= 0 or self.y >= 600 - self.size:
            self.vy = -self.vy
    
    def draw(self, renderer):
        rect = sdl2.SDL_Rect(int(self.x), int(self.y), self.size, self.size)
        renderer.fill((rect,), self.color)

def main():
    sdl2.ext.init()
    
    window = sdl2.ext.Window("Bouncing Balls", size=(800, 600))
    window.show()
    
    renderer = sdl2.ext.Renderer(window)
    
    # 공 생성
    balls = [Ball(random.randint(0, 780), random.randint(0, 580)) for _ in range(10)]
    
    running = True
    clock = sdl2.ext.time.Clock()
    
    while running:
        # 이벤트
        events = sdl2.ext.get_events()
        for event in events:
            if event.type == sdl2.SDL_QUIT:
                running = False
            
            # 클릭 시 공 추가
            if event.type == sdl2.SDL_MOUSEBUTTONDOWN:
                balls.append(Ball(event.button.x, event.button.y))
        
        # 업데이트
        for ball in balls:
            ball.update()
        
        # 렌더링
        renderer.clear(sdl2.ext.Color(0, 0, 0))
        
        for ball in balls:
            ball.draw(renderer)
        
        renderer.present()
        
        # FPS 제한
        clock.tick(60)
    
    sdl2.ext.quit()

if __name__ == '__main__':
    main()

13. SDL + OpenGL

OpenGL 컨텍스트 생성

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <SDL2/SDL.h>
#include <glad/glad.h>

int main(int argc, char* argv[]) {
    SDL_Init(SDL_INIT_VIDEO);
    
    // OpenGL 속성 설정
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
    
    // 윈도우 생성 (OpenGL 플래그)
    SDL_Window* window = SDL_CreateWindow(
        "SDL + OpenGL",
        SDL_WINDOWPOS_CENTERED,
        SDL_WINDOWPOS_CENTERED,
        800, 600,
        SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN
    );
    
    // OpenGL 컨텍스트 생성
    SDL_GLContext context = SDL_GL_CreateContext(window);
    
    // GLAD 초기화
    if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) {
        std::cerr << "GLAD 초기화 실패" << std::endl;
        return -1;
    }
    
    // VSync 활성화
    SDL_GL_SetSwapInterval(1);
    
    // OpenGL 설정
    glViewport(0, 0, 800, 600);
    glEnable(GL_DEPTH_TEST);
    
    // 메인 루프
    bool running = true;
    SDL_Event event;
    
    while (running) {
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) running = false;
        }
        
        // OpenGL 렌더링
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        
        // 여기에 OpenGL 렌더링 코드...
        
        SDL_GL_SwapWindow(window);
    }
    
    // 정리
    SDL_GL_DeleteContext(context);
    SDL_DestroyWindow(window);
    SDL_Quit();
    
    return 0;
}

14. 고급 렌더링

알파 블렌딩

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 텍스처에 알파 블렌딩 활성화
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);

// 텍스처 투명도 설정
SDL_SetTextureAlphaMod(texture, 128);  // 0~255 (128 = 50% 투명)

// 색상 모드 설정
SDL_SetTextureColorMod(texture, 255, 0, 0);  // 빨간색 틴트

렌더 타겟

다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 텍스처를 렌더 타겟으로 생성
SDL_Texture* targetTexture = SDL_CreateTexture(
    renderer,
    SDL_PIXELFORMAT_RGBA8888,
    SDL_TEXTUREACCESS_TARGET,
    800, 600
);

// 렌더 타겟 변경
SDL_SetRenderTarget(renderer, targetTexture);

// 이 텍스처에 렌더링
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
SDL_RenderClear(renderer);
// ... 더 많은 렌더링 ...

// 기본 렌더 타겟으로 복원
SDL_SetRenderTarget(renderer, nullptr);

// 렌더링된 텍스처 사용
SDL_RenderCopy(renderer, targetTexture, nullptr, nullptr);

스케일 및 논리 크기

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 논리 크기 설정 (자동 스케일링)
SDL_RenderSetLogicalSize(renderer, 800, 600);

// 이제 윈도우 크기가 변해도 800×600으로 렌더링됨
// 자동으로 스케일링됨

// 스케일 품질 설정
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");  // 선형 필터링
// "0": 최근접 이웃 (픽셀 아트)
// "1": 선형 (부드러움)
// "2": 이방성 (최고 품질)

15. 타이머 및 시간

기본 타이머

다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 시작 시간
Uint32 startTime = SDL_GetTicks();  // 밀리초

// 경과 시간
Uint32 elapsed = SDL_GetTicks() - startTime;
std::cout << "경과: " << elapsed << "ms" << std::endl;

// 대기
SDL_Delay(1000);  // 1초 대기

// 고해상도 타이머
Uint64 start = SDL_GetPerformanceCounter();
// ... 작업 ...
Uint64 end = SDL_GetPerformanceCounter();
double elapsed = (end - start) / (double)SDL_GetPerformanceFrequency();
std::cout << "경과: " << elapsed << "초" << std::endl;

FPS 카운터

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class FPSCounter {
private:
    Uint32 frameCount;
    Uint32 lastTime;
    float fps;
    
public:
    FPSCounter() : frameCount(0), lastTime(SDL_GetTicks()), fps(0) {}
    
    void update() {
        frameCount++;
        
        Uint32 currentTime = SDL_GetTicks();
        Uint32 elapsed = currentTime - lastTime;
        
        // 1초마다 FPS 계산
        if (elapsed >= 1000) {
            fps = frameCount / (elapsed / 1000.0f);
            frameCount = 0;
            lastTime = currentTime;
        }
    }
    
    float getFPS() const {
        return fps;
    }
};

// 사용
FPSCounter fpsCounter;

while (running) {
    // ...
    
    fpsCounter.update();
    
    // FPS 표시
    if (SDL_GetTicks() % 1000 < 16) {
        std::cout << "FPS: " << fpsCounter.getFPS() << std::endl;
    }
}

16. 파티클 시스템

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

struct Particle {
    float x, y;
    float vx, vy;
    Uint8 r, g, b, a;
    float life;
    float maxLife;
};

class ParticleSystem {
private:
    std::vector<Particle> particles;
    int maxParticles;
    
public:
    ParticleSystem(int max) : maxParticles(max) {
        particles.reserve(maxParticles);
    }
    
    void emit(float x, float y, int count) {
        for (int i = 0; i < count && particles.size() < maxParticles; i++) {
            Particle p;
            p.x = x;
            p.y = y;
            
            // 랜덤 속도
            float angle = (rand() % 360) * 3.14159f / 180.0f;
            float speed = (rand() % 100 + 50) / 10.0f;
            p.vx = cos(angle) * speed;
            p.vy = sin(angle) * speed;
            
            // 랜덤 색상
            p.r = rand() % 256;
            p.g = rand() % 256;
            p.b = rand() % 256;
            p.a = 255;
            
            p.life = 1.0f;
            p.maxLife = 1.0f;
            
            particles.push_back(p);
        }
    }
    
    void update(float deltaTime) {
        for (auto it = particles.begin(); it != particles.end();) {
            it->life -= deltaTime;
            
            if (it->life <= 0) {
                it = particles.erase(it);
            } else {
                // 위치 업데이트
                it->x += it->vx * deltaTime * 60;
                it->y += it->vy * deltaTime * 60;
                
                // 중력
                it->vy += 9.8f * deltaTime * 60;
                
                // 페이드 아웃
                it->a = (Uint8)(255 * (it->life / it->maxLife));
                
                ++it;
            }
        }
    }
    
    void render(SDL_Renderer* renderer) {
        for (const auto& p : particles) {
            SDL_SetRenderDrawColor(renderer, p.r, p.g, p.b, p.a);
            SDL_Rect rect = {(int)p.x, (int)p.y, 4, 4};
            SDL_RenderFillRect(renderer, &rect);
        }
    }
};

// 사용
ParticleSystem particles(1000);

// 마우스 클릭 시 파티클 방출
if (event.type == SDL_MOUSEBUTTONDOWN) {
    particles.emit(event.button.x, event.button.y, 50);
}

// 업데이트 및 렌더링
particles.update(deltaTime);
particles.render(renderer);

17. 타일맵

타일맵 클래스

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class TileMap {
private:
    std::vector<std::vector<int>> map;
    SDL_Texture* tileset;
    int tileSize;
    int mapWidth;
    int mapHeight;
    
public:
    TileMap(const std::string& filename, SDL_Texture* tileset, int tileSize)
        : tileset(tileset), tileSize(tileSize) {
        loadMap(filename);
    }
    
    void loadMap(const std::string& filename) {
        std::ifstream file(filename);
        std::string line;
        
        while (std::getline(file, line)) {
            std::vector<int> row;
            std::stringstream ss(line);
            int tile;
            
            while (ss >> tile) {
                row.push_back(tile);
                if (ss.peek() == ',') ss.ignore();
            }
            
            map.push_back(row);
        }
        
        mapHeight = map.size();
        mapWidth = map[0].size();
    }
    
    void render(SDL_Renderer* renderer, int cameraX, int cameraY) {
        for (int y = 0; y < mapHeight; y++) {
            for (int x = 0; x < mapWidth; x++) {
                int tileID = map[y][x];
                
                if (tileID == 0) continue;  // 빈 타일
                
                // 타일셋에서 타일 위치 계산
                int tilesPerRow = 8;  // 타일셋 가로 타일 수
                int srcX = (tileID % tilesPerRow) * tileSize;
                int srcY = (tileID / tilesPerRow) * tileSize;
                
                SDL_Rect srcRect = {srcX, srcY, tileSize, tileSize};
                SDL_Rect destRect = {
                    x * tileSize - cameraX,
                    y * tileSize - cameraY,
                    tileSize,
                    tileSize
                };
                
                SDL_RenderCopy(renderer, tileset, &srcRect, &destRect);
            }
        }
    }
    
    int getTile(int x, int y) const {
        int tileX = x / tileSize;
        int tileY = y / tileSize;
        
        if (tileX < 0 || tileX >= mapWidth || tileY < 0 || tileY >= mapHeight) {
            return 0;
        }
        
        return map[tileY][tileX];
    }
};

// 맵 파일 (map.txt)
/*
1,1,1,1,1,1,1,1,1,1
1,0,0,0,0,0,0,0,0,1
1,0,2,2,0,0,3,3,0,1
1,0,0,0,0,0,0,0,0,1
1,1,1,1,1,1,1,1,1,1
*/

// 사용
TileMap tileMap("map.txt", tilesetTexture, 32);

// 카메라
int cameraX = playerX - 400;
int cameraY = playerY - 300;

tileMap.render(renderer, cameraX, cameraY);

18. 애니메이션

스프라이트 애니메이션 클래스

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Animation {
private:
    SDL_Texture* spriteSheet;
    std::vector<SDL_Rect> frames;
    int currentFrame;
    float frameTime;
    float elapsed;
    bool loop;
    
public:
    Animation(SDL_Texture* sheet, const std::vector<SDL_Rect>& frames, float frameTime, bool loop = true)
        : spriteSheet(sheet), frames(frames), currentFrame(0), frameTime(frameTime), elapsed(0), loop(loop) {}
    
    void update(float deltaTime) {
        elapsed += deltaTime;
        
        if (elapsed >= frameTime) {
            elapsed = 0;
            currentFrame++;
            
            if (currentFrame >= frames.size()) {
                if (loop) {
                    currentFrame = 0;
                } else {
                    currentFrame = frames.size() - 1;
                }
            }
        }
    }
    
    void render(SDL_Renderer* renderer, int x, int y, int width, int height, SDL_RendererFlip flip = SDL_FLIP_NONE) {
        SDL_Rect destRect = {x, y, width, height};
        SDL_RenderCopyEx(renderer, spriteSheet, &frames[currentFrame], &destRect, 0, nullptr, flip);
    }
    
    void reset() {
        currentFrame = 0;
        elapsed = 0;
    }
    
    bool isFinished() const {
        return !loop && currentFrame == frames.size() - 1;
    }
};

// 사용
std::vector<SDL_Rect> walkFrames = {
    {0, 0, 32, 48},
    {32, 0, 32, 48},
    {64, 0, 32, 48},
    {96, 0, 32, 48}
};

Animation walkAnimation(playerSheet, walkFrames, 0.1f, true);

// 게임 루프
walkAnimation.update(deltaTime);
walkAnimation.render(renderer, playerX, playerY, 64, 96);

19. 상태 머신

게임 상태 관리

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

enum class GameState {
    MENU,
    PLAYING,
    PAUSED,
    GAME_OVER
};

class Game {
private:
    GameState currentState;
    
public:
    void handleEvents(SDL_Event& event) {
        switch (currentState) {
            case GameState::MENU:
                handleMenuEvents(event);
                break;
            case GameState::PLAYING:
                handlePlayingEvents(event);
                break;
            case GameState::PAUSED:
                handlePausedEvents(event);
                break;
            case GameState::GAME_OVER:
                handleGameOverEvents(event);
                break;
        }
    }
    
    void update(float deltaTime) {
        switch (currentState) {
            case GameState::MENU:
                updateMenu(deltaTime);
                break;
            case GameState::PLAYING:
                updatePlaying(deltaTime);
                break;
            case GameState::PAUSED:
                // 일시정지 중에는 업데이트 안함
                break;
            case GameState::GAME_OVER:
                updateGameOver(deltaTime);
                break;
        }
    }
    
    void render() {
        switch (currentState) {
            case GameState::MENU:
                renderMenu();
                break;
            case GameState::PLAYING:
                renderPlaying();
                break;
            case GameState::PAUSED:
                renderPlaying();  // 게임 화면
                renderPauseOverlay();  // 일시정지 오버레이
                break;
            case GameState::GAME_OVER:
                renderGameOver();
                break;
        }
    }
    
    void changeState(GameState newState) {
        currentState = newState;
    }
};

20. 실전 게임: Space Shooter

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_mixer.h>
#include <vector>
#include <iostream>

const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 600;

class Entity {
public:
    float x, y;
    int width, height;
    SDL_Texture* texture;
    
    Entity(float x, float y, int w, int h, SDL_Texture* tex)
        : x(x), y(y), width(w), height(h), texture(tex) {}
    
    SDL_Rect getRect() const {
        return {(int)x, (int)y, width, height};
    }
    
    void render(SDL_Renderer* renderer) {
        SDL_Rect rect = getRect();
        SDL_RenderCopy(renderer, texture, nullptr, &rect);
    }
};

class Player : public Entity {
public:
    float speed;
    int health;
    
    Player(float x, float y, SDL_Texture* tex)
        : Entity(x, y, 64, 64, tex), speed(300), health(100) {}
    
    void handleInput(const Uint8* keyState, float deltaTime) {
        if (keyState[SDL_SCANCODE_LEFT]) x -= speed * deltaTime;
        if (keyState[SDL_SCANCODE_RIGHT]) x += speed * deltaTime;
        if (keyState[SDL_SCANCODE_UP]) y -= speed * deltaTime;
        if (keyState[SDL_SCANCODE_DOWN]) y += speed * deltaTime;
        
        // 화면 경계
        if (x < 0) x = 0;
        if (x + width > SCREEN_WIDTH) x = SCREEN_WIDTH - width;
        if (y < 0) y = 0;
        if (y + height > SCREEN_HEIGHT) y = SCREEN_HEIGHT - height;
    }
};

class Bullet : public Entity {
public:
    float speed;
    
    Bullet(float x, float y, SDL_Texture* tex)
        : Entity(x, y, 8, 16, tex), speed(500) {}
    
    void update(float deltaTime) {
        y -= speed * deltaTime;
    }
    
    bool isOffScreen() const {
        return y + height < 0;
    }
};

class Enemy : public Entity {
public:
    float speed;
    
    Enemy(float x, float y, SDL_Texture* tex)
        : Entity(x, y, 48, 48, tex), speed(100) {}
    
    void update(float deltaTime) {
        y += speed * deltaTime;
    }
    
    bool isOffScreen() const {
        return y > SCREEN_HEIGHT;
    }
};

class SpaceShooter {
private:
    SDL_Window* window;
    SDL_Renderer* renderer;
    bool running;
    
    Player* player;
    std::vector<Bullet*> bullets;
    std::vector<Enemy*> enemies;
    
    SDL_Texture* playerTexture;
    SDL_Texture* bulletTexture;
    SDL_Texture* enemyTexture;
    
    Mix_Chunk* shootSound;
    Mix_Chunk* explosionSound;
    Mix_Music* bgMusic;
    
    Uint32 lastTime;
    float deltaTime;
    
    Uint32 lastShootTime;
    Uint32 shootCooldown;
    
    Uint32 lastEnemySpawn;
    Uint32 enemySpawnInterval;
    
    int score;
    
public:
    SpaceShooter() : running(false), lastTime(0), deltaTime(0),
                     lastShootTime(0), shootCooldown(200),
                     lastEnemySpawn(0), enemySpawnInterval(1000),
                     score(0) {}
    
    bool init() {
        if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) return false;
        
        window = SDL_CreateWindow("Space Shooter", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
        if (!window) return false;
        
        renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
        if (!renderer) return false;
        
        // SDL_image 초기화
        IMG_Init(IMG_INIT_PNG);
        
        // SDL_mixer 초기화
        Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048);
        
        // 리소스 로딩
        playerTexture = IMG_LoadTexture(renderer, "player.png");
        bulletTexture = IMG_LoadTexture(renderer, "bullet.png");
        enemyTexture = IMG_LoadTexture(renderer, "enemy.png");
        
        shootSound = Mix_LoadWAV("shoot.wav");
        explosionSound = Mix_LoadWAV("explosion.wav");
        bgMusic = Mix_LoadMUS("background.mp3");
        
        // 플레이어 생성
        player = new Player(SCREEN_WIDTH / 2 - 32, SCREEN_HEIGHT - 100, playerTexture);
        
        // 음악 재생
        Mix_PlayMusic(bgMusic, -1);
        
        running = true;
        lastTime = SDL_GetTicks();
        
        return true;
    }
    
    void handleEvents() {
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                running = false;
            }
            
            if (event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_ESCAPE) {
                    running = false;
                }
            }
        }
        
        // 연속 입력
        const Uint8* keyState = SDL_GetKeyboardState(nullptr);
        player->handleInput(keyState, deltaTime);
        
        // 발사 (스페이스바)
        if (keyState[SDL_SCANCODE_SPACE]) {
            Uint32 currentTime = SDL_GetTicks();
            if (currentTime - lastShootTime >= shootCooldown) {
                bullets.push_back(new Bullet(player->x + player->width / 2 - 4, player->y, bulletTexture));
                Mix_PlayChannel(-1, shootSound, 0);
                lastShootTime = currentTime;
            }
        }
    }
    
    void update() {
        Uint32 currentTime = SDL_GetTicks();
        deltaTime = (currentTime - lastTime) / 1000.0f;
        lastTime = currentTime;
        
        // 총알 업데이트
        for (auto it = bullets.begin(); it != bullets.end();) {
            (*it)->update(deltaTime);
            
            if ((*it)->isOffScreen()) {
                delete *it;
                it = bullets.erase(it);
            } else {
                ++it;
            }
        }
        
        // 적 생성
        if (currentTime - lastEnemySpawn >= enemySpawnInterval) {
            int x = rand() % (SCREEN_WIDTH - 48);
            enemies.push_back(new Enemy(x, -48, enemyTexture));
            lastEnemySpawn = currentTime;
        }
        
        // 적 업데이트
        for (auto it = enemies.begin(); it != enemies.end();) {
            (*it)->update(deltaTime);
            
            if ((*it)->isOffScreen()) {
                delete *it;
                it = enemies.erase(it);
            } else {
                ++it;
            }
        }
        
        // 충돌 감지 (총알 vs 적)
        for (auto bulletIt = bullets.begin(); bulletIt != bullets.end();) {
            bool bulletHit = false;
            
            for (auto enemyIt = enemies.begin(); enemyIt != enemies.end();) {
                if (SDL_HasIntersection(&(*bulletIt)->getRect(), &(*enemyIt)->getRect())) {
                    // 충돌!
                    Mix_PlayChannel(-1, explosionSound, 0);
                    score += 10;
                    
                    delete *enemyIt;
                    enemyIt = enemies.erase(enemyIt);
                    
                    bulletHit = true;
                    break;
                } else {
                    ++enemyIt;
                }
            }
            
            if (bulletHit) {
                delete *bulletIt;
                bulletIt = bullets.erase(bulletIt);
            } else {
                ++bulletIt;
            }
        }
        
        // 충돌 감지 (플레이어 vs 적)
        for (auto& enemy : enemies) {
            if (SDL_HasIntersection(&player->getRect(), &enemy->getRect())) {
                player->health -= 10;
                std::cout << "피격! 체력: " << player->health << std::endl;
                
                if (player->health <= 0) {
                    std::cout << "게임 오버! 점수: " << score << std::endl;
                    running = false;
                }
            }
        }
    }
    
    void render() {
        // 배경
        SDL_SetRenderDrawColor(renderer, 0, 0, 20, 255);
        SDL_RenderClear(renderer);
        
        // 플레이어
        player->render(renderer);
        
        // 총알
        for (auto& bullet : bullets) {
            bullet->render(renderer);
        }
        
        // 적
        for (auto& enemy : enemies) {
            enemy->render(renderer);
        }
        
        SDL_RenderPresent(renderer);
    }
    
    void run() {
        while (running) {
            handleEvents();
            update();
            render();
        }
    }
    
    void clean() {
        // 엔티티 정리
        delete player;
        for (auto& bullet : bullets) delete bullet;
        for (auto& enemy : enemies) delete enemy;
        
        // 텍스처 정리
        SDL_DestroyTexture(playerTexture);
        SDL_DestroyTexture(bulletTexture);
        SDL_DestroyTexture(enemyTexture);
        
        // 오디오 정리
        Mix_FreeChunk(shootSound);
        Mix_FreeChunk(explosionSound);
        Mix_FreeMusic(bgMusic);
        Mix_CloseAudio();
        
        // SDL 정리
        SDL_DestroyRenderer(renderer);
        SDL_DestroyWindow(window);
        IMG_Quit();
        SDL_Quit();
    }
};

int main(int argc, char* argv[]) {
    SpaceShooter game;
    
    if (!game.init()) {
        return -1;
    }
    
    game.run();
    game.clean();
    
    return 0;
}

21. 네트워킹 (SDL_net)

서버

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <SDL2/SDL_net.h>

// SDL_net 초기화
SDLNet_Init();

// 서버 소켓 생성
IPaddress ip;
SDLNet_ResolveHost(&ip, nullptr, 1234);  // 포트 1234

TCPsocket server = SDLNet_TCP_Open(&ip);
if (!server) {
    std::cerr << "서버 소켓 생성 실패: " << SDLNet_GetError() << std::endl;
    return -1;
}

std::cout << "서버 시작 (포트 1234)" << std::endl;

// 클라이언트 연결 대기
TCPsocket client = SDLNet_TCP_Accept(server);
if (client) {
    std::cout << "클라이언트 연결됨" << std::endl;
    
    // 데이터 수신
    char buffer[512];
    int received = SDLNet_TCP_Recv(client, buffer, 512);
    if (received > 0) {
        buffer[received] = '\0';
        std::cout << "받음: " << buffer << std::endl;
    }
    
    // 데이터 송신
    const char* message = "Hello from server!";
    SDLNet_TCP_Send(client, message, strlen(message) + 1);
    
    SDLNet_TCP_Close(client);
}

SDLNet_TCP_Close(server);
SDLNet_Quit();

클라이언트

다음은 cpp를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 서버 연결
IPaddress ip;
SDLNet_ResolveHost(&ip, "127.0.0.1", 1234);

TCPsocket client = SDLNet_TCP_Open(&ip);
if (!client) {
    std::cerr << "서버 연결 실패: " << SDLNet_GetError() << std::endl;
    return -1;
}

std::cout << "서버 연결됨" << std::endl;

// 데이터 송신
const char* message = "Hello from client!";
SDLNet_TCP_Send(client, message, strlen(message) + 1);

// 데이터 수신
char buffer[512];
int received = SDLNet_TCP_Recv(client, buffer, 512);
if (received > 0) {
    buffer[received] = '\0';
    std::cout << "받음: " << buffer << std::endl;
}

SDLNet_TCP_Close(client);
SDLNet_Quit();

22. 최적화

텍스처 아틀라스

아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 느림 (텍스처 바인딩 많음)
for (auto& sprite : sprites) {
    SDL_RenderCopy(renderer, sprite.texture, nullptr, &sprite.rect);
}

// ✅ 빠름 (텍스처 아틀라스)
SDL_Texture* atlas = loadTexture("atlas.png", renderer);

for (auto& sprite : sprites) {
    SDL_RenderCopy(renderer, atlas, &sprite.srcRect, &sprite.destRect);
}

오브젝트 풀링

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

template<typename T>
class ObjectPool {
private:
    std::vector<T*> pool;
    std::vector<T*> active;
    
public:
    ObjectPool(int size) {
        for (int i = 0; i < size; i++) {
            pool.push_back(new T());
        }
    }
    
    T* acquire() {
        if (pool.empty()) {
            return new T();
        }
        
        T* obj = pool.back();
        pool.pop_back();
        active.push_back(obj);
        return obj;
    }
    
    void release(T* obj) {
        auto it = std::find(active.begin(), active.end(), obj);
        if (it != active.end()) {
            active.erase(it);
            pool.push_back(obj);
        }
    }
    
    ~ObjectPool() {
        for (auto obj : pool) delete obj;
        for (auto obj : active) delete obj;
    }
};

// 사용
ObjectPool<Bullet> bulletPool(100);

// 총알 생성
Bullet* bullet = bulletPool.acquire();
bullet->init(x, y);

// 총알 제거
if (bullet->isOffScreen()) {
    bulletPool.release(bullet);
}

23. 디버깅

렌더링 디버그

다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// FPS 표시
void renderDebugInfo(SDL_Renderer* renderer, TTF_Font* font, float fps) {
    char text[64];
    sprintf(text, "FPS: %.1f", fps);
    
    SDL_Color white = {255, 255, 255, 255};
    SDL_Surface* surface = TTF_RenderText_Solid(font, text, white);
    SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
    
    SDL_Rect rect = {10, 10, surface->w, surface->h};
    SDL_RenderCopy(renderer, texture, nullptr, &rect);
    
    SDL_FreeSurface(surface);
    SDL_DestroyTexture(texture);
}

// 충돌 박스 표시
void renderCollisionBox(SDL_Renderer* renderer, const SDL_Rect& rect) {
    SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
    SDL_RenderDrawRect(renderer, &rect);
}

// 그리드 표시
void renderGrid(SDL_Renderer* renderer, int gridSize) {
    SDL_SetRenderDrawColor(renderer, 50, 50, 50, 255);
    
    for (int x = 0; x < SCREEN_WIDTH; x += gridSize) {
        SDL_RenderDrawLine(renderer, x, 0, x, SCREEN_HEIGHT);
    }
    
    for (int y = 0; y < SCREEN_HEIGHT; y += gridSize) {
        SDL_RenderDrawLine(renderer, 0, y, SCREEN_WIDTH, y);
    }
}

24. 트러블슈팅

일반적인 문제

1) “SDL.h: No such file or directory”

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 원인: SDL 헤더 경로 문제
# 해결: 컴파일 시 인클루드 경로 추가

# macOS/Linux
g++ main.cpp -I/usr/local/include/SDL2 -L/usr/local/lib -lSDL2

# 또는 pkg-config 사용
g++ main.cpp `pkg-config --cflags --libs sdl2`

2) “Undefined reference to SDL_main”

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 원인: SDL_main 매크로 문제
// 해결 1: main 함수 시그니처 변경
int main(int argc, char* argv[]) {
    // ...
}

// 해결 2: SDL_main 비활성화
#define SDL_MAIN_HANDLED
#include <SDL2/SDL.h>

3) 검은 화면

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 원인 1: RenderPresent 누락
SDL_RenderPresent(renderer);  // 필수!

// 원인 2: 텍스처가 화면 밖
// 좌표 확인

// 원인 3: 알파 블렌딩 문제
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);

4) 느린 렌더링

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 원인 1: VSync 비활성화
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);

// 원인 2: 소프트웨어 렌더링
// SDL_RENDERER_ACCELERATED 플래그 확인

// 원인 3: 불필요한 렌더링
// 변경된 부분만 렌더링 (더티 렉트)

25. 모바일 (Android/iOS)

Android 빌드

다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# SDL2 Android 프로젝트 생성
# 1. SDL2 소스 다운로드
# 2. android-project 폴더 복사
# 3. jni/src에 코드 복사

# Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := main
LOCAL_SRC_FILES := main.cpp
LOCAL_SHARED_LIBRARIES := SDL2
LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -llog

include $(BUILD_SHARED_LIBRARY)

# 빌드
ndk-build
ant debug
adb install -r bin/MyGame-debug.apk

iOS 빌드

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# Xcode 프로젝트 생성
# 1. SDL2.framework 추가
# 2. main.cpp 추가
# 3. Bridging Header 설정

# main.cpp는 동일하게 사용 가능
# iOS 특화 기능 (터치, 가속도계)은 SDL 이벤트로 처리

FAQ

Q1. SDL 1.2와 SDL2는?

1.2는 유지보수가 끊긴 지 오래됐고, SDL2가 렌더러·입력·모바일 쪽을 정리한 현재 기준이다. 새 프로젝트는 SDL2로 보면 된다.

Q2. SDL과 SFML은 어떻게 고르나?

SDL은 C 중심 API이고 바인딩이 많다. SFML은 C++에 맞춘 설계가 강하다. 모바일·타깃 범위를 먼저 정하고 선택하는 경우가 많다.

Q3. SDL만으로 3D를 하나?

SDL 자체 렌더러는 2D에 가깝다. 3D는 OpenGL·Vulkan 컨텍스트를 SDL에 붙여 쓰는 식으로 한다.

Q4. 엔진과 SDL은?

엔진은 툴링·워크플로가 포함되어 생산성이 좋다. SDL은 가볍지만 렌더링·물리·오디오 파이프를 직접 구성해야 한다. 학습·소규모·특수 요구면 SDL, 제품 일정이 빠듯하면 엔진을 많이 고른다.

Q5. 어떤 언어로 SDL을 쓰나?

공식은 C이고, C++·Python 등 바인딩이 있다. 성능이 크리티컬하면 C/C++, 실험·도구면 Python 같은 선택도 흔하다.


요약

핵심 정리

SDL 기본 구조:

다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 1. 초기화
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
IMG_Init(IMG_INIT_PNG);
TTF_Init();
Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048);

// 2. 윈도우 및 렌더러
SDL_Window* window = SDL_CreateWindow(...);
SDL_Renderer* renderer = SDL_CreateRenderer(...);

// 3. 리소스 로딩
SDL_Texture* texture = IMG_LoadTexture(renderer, "image.png");
TTF_Font* font = TTF_OpenFont("font.ttf", 24);
Mix_Music* music = Mix_LoadMUS("music.mp3");

// 4. 게임 루프
while (running) {
    // 이벤트
    while (SDL_PollEvent(&event)) { ... }
    
    // 업데이트
    // 게임 로직...
    
    // 렌더링
    SDL_RenderClear(renderer);
    SDL_RenderCopy(renderer, texture, nullptr, &rect);
    SDL_RenderPresent(renderer);
}

// 5. 정리
SDL_DestroyTexture(texture);
TTF_CloseFont(font);
Mix_FreeMusic(music);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
Mix_CloseAudio();
TTF_Quit();
IMG_Quit();
SDL_Quit();

필수 개념:

아래 코드는 text를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 실행 예제
1. 게임 루프: Input → Update → Render
2. 델타 타임: FPS 무관한 움직임
3. 이벤트: 키보드, 마우스, 게임패드
4. 텍스처: GPU 가속 이미지
5. 충돌 감지: AABB, 원
6. 상태 관리: 메뉴, 플레이, 일시정지

학습 로드맵

창·이벤트·클리어·프레젠트를 먼저 안정화하고, 텍스처·스프라이트·충돌로 2D를 만든 뒤 게임 루프·상태·사운드를 붙인다. Pong·플랫포머·슈팅처럼 작은 것을 끝까지 완성하는 경험이 이후에 크게 도움이 된다.

추천 게임 프로젝트

초반에는 Pong·Breakout·Snake처럼 규칙이 명확한 것부터, 중간에는 플랫포머·슈팅·테트리스로 범위를 넓힌다. RPG·TD·멀티플레이는 시스템이 많아져서 앞 단계가 잡힌 뒤가 수월하다.

다음 글 추천


키워드: SDL, SDL2, Game, Graphics, Audio, 게임, 게임개발, C++, Python, OpenGL, 2D Game

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3