본문 바로가기
-----ETC-----/C++로 배우는 게임 엔진 개발

[C++로 배우는 게임 엔진 개발] Day 13: 사운드 시스템 기초 (SDL_mixer)

by cogito21_cpp 2024. 8. 1.
반응형

사운드 시스템 기초 (SDL_mixer)

오늘은 SDL2와 SDL_mixer를 사용하여 사운드 시스템을 구현하고, 게임 내에서 효과음과 배경음을 재생하는 방법을 학습하겠습니다.

1. SDL_mixer 설치 및 설정

SDL_mixer는 SDL2용 사운드 라이브러리로, 다양한 오디오 포맷을 지원합니다. 먼저 SDL_mixer를 설치하고 프로젝트에 설정합니다.

 

CMakeLists.txt 수정

CMakeLists.txt 파일에 SDL_mixer 라이브러리를 추가합니다.

cmake_minimum_required(VERSION 3.10)

# 프로젝트 이름과 버전 설정
project(GameEngine VERSION 1.0)

# C++ 표준 설정
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# 헤더 파일 디렉토리 포함
include_directories(include)

# SDL2 라이브러리 설정
find_package(SDL2 REQUIRED)
include_directories(${SDL2_INCLUDE_DIRS})

# SDL2_image 라이브러리 설정
find_package(SDL2_image REQUIRED)
include_directories(${SDL2_IMAGE_INCLUDE_DIRS})

# SDL2_mixer 라이브러리 설정
find_package(SDL2_mixer REQUIRED)
include_directories(${SDL2_MIXER_INCLUDE_DIRS})

# 소스 파일 설정
file(GLOB SOURCES "src/*.cpp")

# 실행 파일 생성
add_executable(GameEngine ${SOURCES})

# SDL2 및 SDL2_image, SDL2_mixer 라이브러리 링크
target_link_libraries(GameEngine ${SDL2_LIBRARIES} ${SDL2_IMAGE_LIBRARIES} ${SDL2_MIXER_LIBRARIES})

2. 사운드 로딩 및 재생

사운드를 로드하고 재생하는 기능을 구현합니다.

 

헤더 파일 작성

include/AudioManager.h 파일을 생성하고 다음과 같이 작성합니다.

#ifndef AUDIOMANAGER_H
#define AUDIOMANAGER_H

#include <SDL2/SDL_mixer.h>
#include <string>

class AudioManager {
public:
    AudioManager();
    ~AudioManager();

    bool Initialize();
    void Shutdown();
    bool LoadSound(const std::string& filePath, const std::string& soundID);
    void PlaySound(const std::string& soundID);
    void LoadMusic(const std::string& filePath);
    void PlayMusic();

private:
    Mix_Chunk* soundEffect;
    Mix_Music* music;
};

#endif // AUDIOMANAGER_H

 

소스 파일 작성

src/AudioManager.cpp 파일을 생성하고 다음과 같이 작성합니다.

#include "AudioManager.h"
#include <iostream>
#include <unordered_map>

std::unordered_map<std::string, Mix_Chunk*> soundMap;

AudioManager::AudioManager() : soundEffect(nullptr), music(nullptr) {}

AudioManager::~AudioManager() {
    Shutdown();
}

bool AudioManager::Initialize() {
    if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) {
        std::cerr << "SDL_mixer could not initialize! SDL_mixer Error: " << Mix_GetError() << std::endl;
        return false;
    }
    return true;
}

void AudioManager::Shutdown() {
    for (auto& sound : soundMap) {
        Mix_FreeChunk(sound.second);
    }
    soundMap.clear();

    if (music) {
        Mix_FreeMusic(music);
        music = nullptr;
    }

    Mix_Quit();
}

bool AudioManager::LoadSound(const std::string& filePath, const std::string& soundID) {
    soundEffect = Mix_LoadWAV(filePath.c_str());
    if (soundEffect == nullptr) {
        std::cerr << "Failed to load sound effect! SDL_mixer Error: " << Mix_GetError() << std::endl;
        return false;
    }
    soundMap[soundID] = soundEffect;
    return true;
}

void AudioManager::PlaySound(const std::string& soundID) {
    Mix_PlayChannel(-1, soundMap[soundID], 0);
}

void AudioManager::LoadMusic(const std::string& filePath) {
    music = Mix_LoadMUS(filePath.c_str());
    if (music == nullptr) {
        std::cerr << "Failed to load music! SDL_mixer Error: " << Mix_GetError() << std::endl;
    }
}

void AudioManager::PlayMusic() {
    Mix_PlayMusic(music, -1);
}

3. 게임 엔진에 사운드 시스템 추가

이제 게임 엔진에 사운드 시스템을 추가하고, 사운드를 재생하는 기능을 구현하겠습니다.

 

헤더 파일 수정

include/GameEngine.h 파일을 수정하여 AudioManager 클래스를 포함합니다.

#ifndef GAMEENGINE_H
#define GAMEENGINE_H

#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include "AudioManager.h"

class GameEngine {
public:
    GameEngine();
    ~GameEngine();
    bool Initialize(const char* title, int width, int height);
    void Run();
    void Shutdown();

private:
    SDL_Window* window;
    SDL_Renderer* renderer;
    AudioManager* audioManager;
    bool isRunning;
    Uint32 frameStart;
    int frameTime;

    void HandleEvents();
    void Update();
    void Render();
};

#endif // GAMEENGINE_H

 

소스 파일 수정

src/GameEngine.cpp 파일을 다음과 같이 수정합니다.

#include "GameEngine.h"
#include <iostream>

const int FPS = 60;
const int FRAME_DELAY = 1000 / FPS;

GameEngine::GameEngine()
    : window(nullptr), renderer(nullptr), audioManager(nullptr), isRunning(false), frameStart(0), frameTime(0) {}

GameEngine::~GameEngine() {
    Shutdown();
}

bool GameEngine::Initialize(const char* title, int width, int height) {
    if (SDL_Init(SDL_INIT_VIDEO) != 0) {
        std::cerr << "SDL_Init Error: " << SDL_GetError() << std::endl;
        return false;
    }

    window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_SHOWN);
    if (window == nullptr) {
        std::cerr << "SDL_CreateWindow Error: " << SDL_GetError() << std::endl;
        return false;
    }

    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    if (renderer == nullptr) {
        std::cerr << "SDL_CreateRenderer Error: " << SDL_GetError() << std::endl;
        return false;
    }

    audioManager = new AudioManager();
    if (!audioManager->Initialize()) {
        return false;
    }

    audioManager->LoadSound("path/to/your/sound.wav", "sound1");
    audioManager->LoadMusic("path/to/your/music.mp3");

    isRunning = true;
    return true;
}

void GameEngine::Run() {
    audioManager->PlayMusic();
    while (isRunning) {
        frameStart = SDL_GetTicks();

        HandleEvents();
        Update();
        Render();

        frameTime = SDL_GetTicks() - frameStart;
        if (FRAME_DELAY > frameTime) {
            SDL_Delay(FRAME_DELAY - frameTime);
        }
    }
}

void GameEngine::HandleEvents() {
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
        if (event.type == SDL_QUIT) {
            isRunning = false;
        }
        if (event.type == SDL_KEYDOWN) {
            switch (event.key.keysym.sym) {
                case SDLK_ESCAPE:
                    isRunning = false;
                    break;
                case SDLK_SPACE:
                    audioManager->PlaySound("sound1");
                    break;
                default:
                    break;
            }
        }
    }
}

void GameEngine::Update() {
    // 게임 상태 업데이트 로직을 여기에 추가합니다.
}

void GameEngine::Render() {
    // 화면을 검은색으로 지우기
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
    SDL_RenderClear(renderer);

    // 렌더링 결과를 화면에 출력
    SDL_RenderPresent(renderer);
}

void GameEngine::Shutdown() {
    if (audioManager) {
        audioManager->Shutdown();
        delete audioManager;
        audioManager = nullptr;
    }
    if (renderer) {
        SDL_DestroyRenderer(renderer);
        renderer = nullptr;
    }
    if (window) {
        SDL_DestroyWindow(window);
        window = nullptr;
    }
    SDL_Quit();
}

4. 사운드 파일 준비

사운드를 로드하기 위해 WAV 파일과 MP3 파일을 준비합니다. 예제에서는 path/to/your/sound.wavpath/to/your/music.mp3 경로에 사운드 파일을 배치합니다. 이 경로를 실제 사운드 파일 경로로 변경해야 합니다.

 

5. 프로젝트 빌드 및 실행

  1. Visual Studio에서 CMake 프로젝트 열기:
    • Visual Studio를 실행하고, File -> Open -> `CMake...`를 선택합니다.
  • GameEngine 디렉토리를 선택하여 프로젝트를 엽니다.
  1. SDL2_mixer 라이브러리 설치:
    • SDL2_mixer 라이브러리를 프로젝트에 설정합니다.
  2. 프로젝트 빌드:
    • Visual Studio 상단의 Build 메뉴에서 Build All을 선택하여 프로젝트를 빌드합니다.
  3. 프로젝트 실행:
    • Debug 메뉴에서 Start Without Debugging을 선택하여 프로그램을 실행합니다.
    • 윈도우 창이 생성되고, 스페이스바를 누르면 사운드가 재생되고, 배경음악이 루프로 재생됩니다.

마무리

오늘은 SDL2와 SDL_mixer를 사용하여 사운드 시스템을 구현하고, 게임 내에서 효과음과 배경음을 재생하는 방법을 학습했습니다. 다음 단계에서는 엔티티 컴포넌트 시스템(ECS)을 구현하여 게임 객체를 효율적으로 관리하는 방법을 배우겠습니다.

질문이나 추가적인 피드백이 있으면 언제든지 댓글로 남겨 주세요.

Day 14 예고

다음 날은 "엔티티 컴포넌트 시스템 (ECS) 기초"에 대해 다룰 것입니다. 엔티티 컴포넌트 시스템을 사용하여 게임 객체를 효율적으로 관리하고, 확장 가능한 게임 구조를 구현하는 방법을 배워보겠습니다.

반응형