5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

vcpkg, CMake, MSBuild, VScodeでC++開発環境を整える

Posted at

導入

C++では開発環境としてVisualStudioが採用されることが多いです。
これは機能としては十分ですが起動が遅く、開発体験が悪い側面もあります。
複数言語を切り替えて開発を行う際にC++のためだけにIDEを変えたくないという方もいるでしょう。

CMakeプロジェクトにすればあらゆるIDE、エディターで開発が可能ですが、
CMakeのfind_PackageFetchContentのみで依存性解決を行うのは

  • 依存ライブラリが依存するライブラリを手動で解決する必要がある
  • ビルドキャッシュが効かない
  • プラットフォームごとの再利用性が低い

などの問題点があります。

FetchContentfind_packageのみで依存性解決する例

# CMakeのみで依存性解決する例
include(FetchContent)

# SDL2本体
FetchContent_Declare(
  SDL2
  GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
  GIT_TAG release-2.28.5
)
FetchContent_MakeAvailable(SDL2)

# SDL2_image(画像読み込み機能)
FetchContent_Declare(
  SDL2_image
  GIT_REPOSITORY https://github.com/libsdl-org/SDL_image.git
  GIT_TAG release-2.8.2
)
FetchContent_MakeAvailable(SDL2_image)

# PNGを使おうとしたら依存関係が足りなかった:
# SDL2_imageはPNG/JPEG/WebPなどの読み込みのために以下に依存する
# - libpng → zlib に依存
# - libjpeg
# - libwebp
# - libtiff → zlib, libjpeg に依存
# これらを全て手動でFetchContent_Declareする必要がある
# さらに各ライブラリのCMakeオプション(BUILD_SHARED_LIBSなど)も個別に設定が必要
# ビルドごとに全てソースからコンパイルされるため時間がかかる

このようにCMakeはあくまでツールチェーンでパッケージマネージャではないので、CMakeのみでの依存性解決は手間がかかり、ビルドキャッシュが効きづらく、プラットフォームごとの再利用性も低くなります。

そこでこの記事ではパッケージマネージャにvcpkgを、ツールチェーンにCMakeを、エディターとしてVSCodeを採用する例を紹介します。
なお、ここではMSbuildを利用しますが、vcpkgは任意のコンパイラを利用可能です。

vcpkgとは何か

vcpkg の概要
vcpkg は、Windows、macOS、および Linux で実行 、Microsoft と C++ コミュニティによって管理される無料のオープンソースの C/C++ パッケージ マネージャー。
2300 以上のオープンソース ライブラリ ABI の互換性を確認するために定期的に構築されたキュレーションされたレジストリから選択できます
vcpkg の概要 | Microsoft Learn

とあるようにvcpkgはWindowsに限らず、マルチプラットフォームに対応した依存性管理システムで
2300以上のキュレーション(選定)されたOSSがデフォルトで使用可能です。

vcpkgのインストール

MSbuildを使う場合、vcpkgのインストール方法は

  • ソースからビルドする方法
  • BuildToolsを使う方法

の2種類あります。

Githubからビルドする方法

git cloneしてbootstrap-vcpkg.batを実行します。

git clone https://github.com/microsoft/vcpkg.git
cd vcpkg; .\bootstrap-vcpkg.bat

環境変数への追加

vcpkgを使用するために、環境変数を設定します。

Windowsのシステム環境変数にVCPKG_ROOTを追加、VCPKG_ROOTをパスに通します。

一時的に追加する場合

$env:VCPKG_ROOT="C:\path\to\vcpkg"
$env:PATH="$env:VCPKG_ROOT;$env:PATH"

Microsoftのビルドツールを使う方法

Docker(Windows Container)と併用する場合や、コンパイラ、開発ランタイムも入れたい場合に便利です。

https://learn.microsoft.com/ja-jp/visualstudio/releases/2026/release-history
のビルドツール部分から最新版のDLリンクが確認できます。

インストール例

curl -SL --output vs_buildtools.exe https://download.visualstudio.microsoft.com/download/pr/1dac374e-2701-4b9e-b5b6-4438f957c242/6ced20b44c7ab087b6124f711938de5b64ca147538b811a70c0b24c406ee76e1/vs_BuildTools.exe `
 && vs_buildtools.exe --quiet --wait --norestart --nocache --installPath C:\BuildTools18 `
        --add Microsoft.Component.MSBuild `
        --add Microsoft.VisualStudio.Component.Roslyn.Compiler `
        --add Microsoft.VisualStudio.Component.TextTemplating `
        --add Microsoft.VisualStudio.Component.VC.CoreBuildTools `
        --add Microsoft.VisualStudio.Component.VC.Redist.14.Latest `
        --add Microsoft.VisualStudio.Component.Windows10SDK `
        --add Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core `
        --add Microsoft.VisualStudio.Component.TestTools.BuildTools `
        --add Microsoft.VisualStudio.Component.VC.14.44.17.14.x86.x64 `
        --add Microsoft.VisualStudio.Component.VC.ASAN `
        --add Microsoft.VisualStudio.Component.VC.CMake.Project `
        --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 `
#        --add Microsoft.VisualStudio.Component.Vcpkg `
        --add Microsoft.VisualStudio.Component.Windows11SDK.26100 `
 && del /q vs_buildtools.exe

オプションでインストールできるものはここで確認できます。

ビルドツールの使い方のサンプルはWindows Conatinerの紹介ページにも記載がありました。

今回はCMakeとMSBuildを入れるためにBuildToolsを利用しています。

ここではMSBuildとデスクトップ開発に必要なもの、CMakeをビルドツールで入れ、vcpkgのみをgithubからインストールしました。

VSCodeセットアップ

以下の拡張機能をインストールします。

これらをインストールすることでCMake, C++の補完が効き、VSCode上でCMakeプロジェクトの設定、ビルド、デバッグが容易になります。

vcpkg固有の用語

ポート(port) ... ライブラリをビルド・インストールするためのレシピです。実態はCMakeスクリプトで、基本はソースからビルドされますが、バイナリのみで構成するポートの作成も可能です。使用したいライブラリがデフォルトになければ、プライベートポートの登録も可能です。

トリプレット(triplet) ... プラットフォーム、アーキテクチャ、動的リンクするか静的リンクするかなどの構成を一つの名前でまとめた概念です。例えばx64-windows-staticは、Windows x64向けの静的リンクライブラリを意味します。インストール時やjsonでオプションで指定可能です。

vcpkgの種類:マニフェスト版クラシック版

vcpkgにはプロジェクトごとに依存性管理をするマニフェスト版とホスト環境全体で依存性を共有するクラシック版の二種類が存在します。
マニフェスト版を使うことが推奨されています。

マニフェスト版

プロジェクトのルートディレクトリで以下のコマンドを実行します。

# マニフェストファイルの作成
vcpkg new --application

# 追加したいライブラリの検索
vcpkg search sdl
# より詳細な説明を見たい場合
vcpkg search sdl --x-full-desc

# ライブラリの追加(例: SDL2)
vcpkg add port sdl2

vcpkg add portを実行すると、vcpkg.jsonに依存関係が追加され、同時にCMakeでの使用方法が出力されます。

Added dependency 'sdl2' to 'vcpkg.json'.

To use the package in CMake, add the following to your CMakeLists.txt:
    find_package(SDL2 CONFIG REQUIRED)
    target_link_libraries(main PRIVATE SDL2::SDL2 SDL2::SDL2main)

このように、CMakeでどう記述すればいいかを教えてくれるので非常に便利です。

vcpkg.json

vcpkg.jsonはプロジェクトの依存関係を記述するファイルです。

{
  "dependencies": [
    "sdl2"
  ]
}

バージョン指定や機能(feature)の選択など、より細かい設定も可能です。

vcpkg-configuration.json

vcpkg-configuration.jsonは、vcpkgのレジストリ設定などより高度な構成を行うためのファイルです。通常は自動生成されるので、カスタムレジストリを使う場合以外は特に編集する必要はありません。

マニフェスト版にはremove相当のコマンドがおそらくなく、依存関係を削除したい場合はvcpkg.jsonから該当する項目を手動削除する必要があります。

CMakePresets.json

CMakePresets.jsonは、CMakeの設定を定義するファイルです。vcpkgと連携させるための設定を記述します。このjsonは使用しなくてもcmakeのコマンドオプションで代替可能ですが、用意しておくと拡張機能と組み合わせることで容易にビルド可能になります。

細かい仕様はここを参考にしてください。

{
  "$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json",
  "version": 10,
  "configurePresets": [
    {
      "name": "vcpkg",
      "generator": "Visual Studio 18 2026",
      "binaryDir": "${sourceDir}/build",
      "cacheVariables": {
        "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
        "VCPKG_TARGET_TRIPLET": "x64-windows-static"
      }
    }
  ]
}

ポイント:

  • generator: ここではBuildToolsで入れたものに対応しています。 Ninjaも使用可能です。
  • CMAKE_TOOLCHAIN_FILE: vcpkgのツールチェーンファイルを指定
  • VCPKG_TARGET_TRIPLET: ここでは静的リンク(x64-windows-static)を指定

CMakeUserPresets.json

CMakeUserPresets.jsonは、ユーザー固有の設定を記述するファイルです。環境変数VCPKG_ROOTの実際のパスを指定します。

{
  "$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json",
  "version": 10,
  "configurePresets": [
    {
      "name": "default",
      "inherits": "vcpkg",
      "environment": {
        "VCPKG_ROOT": "C:\\Path\\To\\Your\\vcpkg"
      }
    }
  ]
}

このファイルは.gitignoreに含めて、Gitに追跡されないようにしてください。

.gitignoreの設定

.gitignoreの作成はgiboを活用すると便利です。
gibo dump CMakeの結果を利用すればCMakeUserPresets.jsonも無視されます。

# インストール(ソースからビルド、goが入っている必要がある)
git clone https://github.com/simonwhitaker/gibo.git
cd gibo
go install .

# 利用可能なテンプレート一覧
gibo list

# CMake用の.gitignoreを生成
gibo dump CMake > .gitignore

# 複数のテンプレートを組み合わせることも可能
gibo dump CMake C++ VisualStudio >> .gitignore

クラシック版

クラシック版は、ホスト環境全体で依存関係を共有する方式です。

# ライブラリのインストール
vcpkg install sdl2

# ライブラリの削除
vcpkg remove sdl2

マニフェスト版と異なり、プロジェクトごとの管理ではないため、非推奨です。
Dockerと併用する場合はクラシック版でも充分です。

サンプルプロジェクト

ここでは、クロスプラットフォーム対応の描画ライブラリであるSDL2を使った簡単なPongゲームのサンプルを示します。
内容はゲームプログラミングC++の第一章に少し修正を加えたものです。

vcpkg, CMake, VScodeとの併用が主題なのでゲームロジック自体の解説は行いません。
完全なソースコードはこちらで確認できます。

複数ディレクトリから構成されるCMakeプロジェクトで、ファイルや、依存ライブラリの追加が容易な構成の例になります。

最終的なプロジェクト構成

PongGame/
├── CMakeLists.txt
├── CMakePresets.json
├── CMakeUserPresets.example.json
├── CMakeUserPresets.json
├── vcpkg.json # 自動で追加される
├── vcpkg-configuration.json # 自動で追加される
├── .gitignore
├── .vscode/
│   └── settings.json
├── src/
│   ├── main.cpp
│   └── game/
│       ├── game.h
│       └── game.cpp
├── build/
└── vcpkg_installed/ # 自動で追加される

今回はMSBuildをジェネレータに使うため、環境変数の通ったPowershellを起動するためのショートカットを
タスクバーに用意しておくと便利です。

コマンドプロンプトを立ち上げてタスクバーにピン止め後、SHift + 右クリックでプロパティから
リンク先の値に以下を入力します。

%comspec% /k VsDevCmd.bat && powershell -NoLogo

%comspec%はデフォルトのコマンドプロンプト、VsDevCmd.batはMSBuildの環境変数を設定するbatファイルです。
VsDevCmd.bat立ち上げ後、環境変数を引き継いだPowershellを立ち上げています。
-NoLogoは余分なプロンプトを抑制しています。

これでタスクバーをクリックするだけでMSBuildでコンパイル可能なPowershellが起動するようになります。

image.png

以降はこのPowerShellで作業します。

任意の場所にPongGameディレクトリを作成してそこに移動します。

mkdir PongGame
cd .\PongGame\

vcpkgの初期化をします。

vcpkg new --application

依存関係にsdl2を追加します。

vcpkg add port sdl2

プロジェクト直下にCMakeLists.txtを、src配下にソースを用意します。

CMakeLists.txt

CMakeLists.txtでは、SDL2のパッケージ検索、ソースファイルの収集、リンク設定を行います。

cmake_minimum_required(VERSION 3.19)
project(PongGame LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 静的ランタイムライブラリを使用(vcpkgのx64-windows-staticと一致させる)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")

# MSVCでUTF-8ソースファイルを正しく認識させる
if(MSVC)
    add_compile_options(/utf-8)
endif()

# SDL2
find_package(SDL2 CONFIG REQUIRED)

# src/game配下のcppを収集
file(GLOB_RECURSE GAME_SOURCES src/game/*.cpp)
# ソースファイル
set(SOURCES
    src/main.cpp
    ${GAME_SOURCES}
)

# 実行可能ファイルの作成
# リリースビルド時のみWIN32フラグでコンソールウィンドウを非表示にする
if(CMAKE_BUILD_TYPE STREQUAL "Release")
    add_executable(${PROJECT_NAME} WIN32 ${SOURCES})
else()
    add_executable(${PROJECT_NAME} ${SOURCES})
endif()

# SDL2のリンク
target_link_libraries(${PROJECT_NAME}
    PRIVATE
    $<TARGET_NAME_IF_EXISTS:SDL2::SDL2main>
    $<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static>
)

ポイント:

  • CMAKE_MSVC_RUNTIME_LIBRARY: 静的ランタイムを使用(vcpkgのトリプレット設定と一致させる)
  • if(CMAKE_BUILD_TYPE STREQUAL "Release"): リリースビルド時のみWIN32フラグを付けてコンソールウィンドウを非表示にします
  • デバッグビルド時はコンソールが表示され、SDL_LogDebugなどのログ出力を確認できます

main.cpp

src/main.cppはエントリポイントです。

#include "game/game.h"

int main(int argc, char *argv[]) {
    using namespace game;
    Game game;

    bool success = game.Initialize();

    if(success) {
        game.RunLoop();
    }
    game.ShutDown();
    return 0;
}

game.h

src/game/game.hはゲームのメインクラスを定義します。

#include <SDL.h>
#include <vector>

namespace game {
 struct Vector2 {
    float x;
    float y;
 };

 struct Ball {
    Vector2 pos;
    Vector2 velocity;
 };

 class Game {
    public:
        Game();
        ~Game();
        bool Initialize();
        void RunLoop();
        void ShutDown();
    private:
        void ProcessInput();
        void UpdateGame();
        void GenerateOutPut();

        SDL_Window* mWindow;
        SDL_Renderer* mRenderer;
        bool running = false;

        Uint32 mPrevTick;

        // 左側のプレイヤー
        Vector2 mPlayer1PaddlePos;

        int mPlayer1PaddleDir;

        // 右側のプレイヤー
        Vector2 mPlayer2PaddlePos;

        int mPlayer2PaddleDir;

        std::vector<Ball> mBalls;

        const int INIT_BALL_AMOUNT = 5;
 };
}

game.cpp

src/game/game.cppはゲームロジックの実装です。

#include "game.h"
#include <cmath>
#include <random> 

namespace game {
    using namespace std;
    const char* TITLE = "title";
    const int INIT_WIDTH = 1024;
    const int INIT_HEIGHT =768;
    const int WALL_THICKNESS = 15;
    const int PADDLE_WIDTH = 5;
    const int PADDLE_HEIGHT = 40;
    const int BALL_WIDTH = 5;
    const int BALL_HEIGHT = 5;

    // constructor
    Game::Game() :  mWindow(nullptr), mRenderer(nullptr), running(true), mPrevTick(0) {

    }

    // destructor
    Game::~Game() {
        ShutDown();
    }

    // ┌ public function ┐
    bool Game::Initialize() {
        int sdlResult = SDL_Init(SDL_INIT_AUDIO);
        // デバッグログを有効化
        SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);

        if(sdlResult != 0) {
            SDL_Log("Unable to initialize SDL: %s", SDL_GetError());
            return false;
        }

        mPrevTick = SDL_GetTicks();

        mWindow = SDL_CreateWindow(
            TITLE,
            SDL_WINDOWPOS_CENTERED,
            SDL_WINDOWPOS_CENTERED,
            INIT_WIDTH,
            INIT_HEIGHT,
            0
        );

        if(!mWindow) {
            SDL_Log("Unable to initialize window: %s", SDL_GetError());
            return false;
        }

        mRenderer = SDL_CreateRenderer(
            mWindow,
            -1,
            SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC
        );

        if(!mRenderer) {
            SDL_Log("Unable to initialize renderer: %s", SDL_GetError());
            return false;
        }

        // Player1のパドルの初期化
        mPlayer1PaddlePos.x = 10;
        mPlayer1PaddlePos.y = INIT_HEIGHT / 2.0f;

        // Player2のパドルの初期化
        mPlayer2PaddlePos.x = INIT_WIDTH - 10;
        mPlayer2PaddlePos.y = INIT_HEIGHT / 2.0f;

        // ballの初期化
        std::random_device rd;
        std::mt19937 gen(rd());

        // 0°~360°のランダムな角度
        std::uniform_real_distribution<float> angleDist(0.0f, 2.0f * M_PI);

        // X方向の速度: INIT_WIDTH/6.5 ~ INIT_WIDTH/6.0
        std::uniform_real_distribution<float> speedXDist(INIT_WIDTH / 6.5f, INIT_WIDTH / 6.0f);

        // Y方向の速度: INIT_HEIGHT/6.5 ~ INIT_HEIGHT/6.0
        std::uniform_real_distribution<float> speedYDist(INIT_HEIGHT / 6.5f, INIT_HEIGHT / 6.0f);

        for(int i = 0; i < INIT_BALL_AMOUNT; i++){
            Ball ball;
            ball.pos.x = INIT_WIDTH / 2.0f;
            ball.pos.y = INIT_HEIGHT / 2.0f;

            float angle = angleDist(gen);
            float speedX = speedXDist(gen);
            float speedY = speedYDist(gen);

            // 角度から単位ベクトルを計算し、速度の大きさを掛ける
            ball.velocity.x = std::cos(angle) * speedX;
            ball.velocity.y = std::sin(angle) * speedY;

            mBalls.push_back(ball);
        }

        return true;
    }
    void Game::RunLoop() {
        while(running) {
            ProcessInput();
            UpdateGame();
            GenerateOutPut();
        }

    }
    void Game::ShutDown() {
        SDL_DestroyRenderer(mRenderer);
        SDL_DestroyWindow(mWindow);
        SDL_Quit();
    }
    // └ public function ┘

    // ┌ private function ┐
    void Game::ProcessInput() {
        SDL_Event event;
        while(SDL_PollEvent(&event)){
            switch(event.type) {
                case SDL_QUIT:
                    running = false;
                    break;
            }
        }

        const Uint8* keyState = SDL_GetKeyboardState(NULL);

        // Player1
        mPlayer1PaddleDir = 0;
        if(keyState[SDL_SCANCODE_W]) {
            SDL_LogDebug(SDL_LOG_CATEGORY_INPUT , "W");
            mPlayer1PaddleDir -= 1;
        }

        if(keyState[SDL_SCANCODE_A]) {
            SDL_LogDebug(SDL_LOG_CATEGORY_INPUT , "A");
        }

        if(keyState[SDL_SCANCODE_S]) {
            SDL_LogDebug(SDL_LOG_CATEGORY_INPUT , "S");
            mPlayer1PaddleDir += 1;
        }

        if(keyState[SDL_SCANCODE_D]) {
            SDL_LogDebug(SDL_LOG_CATEGORY_INPUT , "D");
        }

        // Player2
        mPlayer2PaddleDir = 0;
        if(keyState[SDL_SCANCODE_I]) {
            SDL_LogDebug(SDL_LOG_CATEGORY_INPUT , "I");
            mPlayer2PaddleDir -= 1;
        }

        if(keyState[SDL_SCANCODE_K]) {
            SDL_LogDebug(SDL_LOG_CATEGORY_INPUT , "K");
            mPlayer2PaddleDir += 1;
        }

        if(keyState[SDL_SCANCODE_LCTRL] && keyState[SDL_SCANCODE_C]) {
            running = false;
            SDL_LogDebug(SDL_LOG_CATEGORY_INPUT , "quit(Ctrl + C)");
        }
    }
    void Game::UpdateGame() {

        // 物理演算の誤差軽減のため、高FPSに制限をかける(最低16msは待たせる) 1000/60 = 62.5FPS
        while(!SDL_TICKS_PASSED(SDL_GetTicks(), mPrevTick + 16));

        // 前フレームからの経過時間(秒)
        float deltaTime = (SDL_GetTicks() - mPrevTick) / 1000.0f;

        // 低FPSすぎる場合や、pause時にdeltaTimeが肥大しないように上限を20FPSにする
        // 1000/20 = 50ms, 50/1000 = 0.05
        if(0.05 <= deltaTime){
            deltaTime = 0.05;
        }

        // 次フレームのために更新
        mPrevTick = SDL_GetTicks();

        // deltaTimeが0.016 ~ 0.05になっているはず(62.5FPS ~ 20FPS)
        //SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "deltaTime: %.6f", deltaTime);

        // Player1のpaddle更新
        if(mPlayer1PaddleDir != 0){
            mPlayer1PaddlePos.y += mPlayer1PaddleDir * (INIT_HEIGHT / 1.5f) * deltaTime;

            // paddle境界補正
            if(
            mPlayer1PaddlePos.y < (WALL_THICKNESS + PADDLE_HEIGHT / 2.0f) 
            ) {
                mPlayer1PaddlePos.y = WALL_THICKNESS + PADDLE_HEIGHT / 2.0f;
            } else if (
                mPlayer1PaddlePos.y > (INIT_HEIGHT - WALL_THICKNESS - PADDLE_HEIGHT / 2.0f)
            ) {
                mPlayer1PaddlePos.y = INIT_HEIGHT - WALL_THICKNESS - PADDLE_HEIGHT / 2.0f;
            }
        }

        // Player2のpaddle更新
        if(mPlayer2PaddleDir != 0){
            mPlayer2PaddlePos.y += mPlayer2PaddleDir * (INIT_HEIGHT / 1.5f) * deltaTime;

            // paddle境界補正
            if(
            mPlayer2PaddlePos.y < (WALL_THICKNESS + PADDLE_HEIGHT / 2.0f) 
            ) {
                mPlayer2PaddlePos.y = WALL_THICKNESS + PADDLE_HEIGHT / 2.0f;
            } else if (
                mPlayer2PaddlePos.y > (INIT_HEIGHT - WALL_THICKNESS - PADDLE_HEIGHT / 2.0f)
            ) {
                mPlayer2PaddlePos.y = INIT_HEIGHT - WALL_THICKNESS - PADDLE_HEIGHT / 2.0f;
            }
        }

        // ball更新
        for(size_t i = 0; i < mBalls.size(); ++i){
            Ball& ball = mBalls[i];
            ball.pos.x += ball.velocity.x * deltaTime;
            ball.pos.y += ball.velocity.y * deltaTime;

            // 範囲外判定(範囲外に出たボールは削除)
            if(ball.pos.x < -BALL_WIDTH / 2.0f || ball.pos.x > INIT_WIDTH + BALL_WIDTH / 2.0f) {
                SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "ball removed");
                mBalls.erase(mBalls.begin() + i);
                --i;
                continue;
            }

            // Player1のpaddleとballの当たり判定
            if(
                std::fabs(mPlayer1PaddlePos.y - ball.pos.y) <= PADDLE_HEIGHT / 2.0f &&
                ball.pos.x >= mPlayer1PaddlePos.x &&
                ball.pos.x <= mPlayer1PaddlePos.x + PADDLE_WIDTH / 2.0f + BALL_WIDTH / 2.0f &&
                ball.velocity.x < 0
            ) {
                ball.velocity.x *= -1;
            }

            // Player2のpaddleとballの当たり判定
            if(
                std::fabs(mPlayer2PaddlePos.y - ball.pos.y) <= PADDLE_HEIGHT / 2.0f &&
                ball.pos.x <= mPlayer2PaddlePos.x &&
                ball.pos.x >= mPlayer2PaddlePos.x - PADDLE_WIDTH / 2.0f - BALL_WIDTH / 2.0f &&
                ball.velocity.x > 0
            ) {
                ball.velocity.x *= -1;
            }

            // ball下壁判定
            if(
                ball.velocity.y > 0 &&
                ball.pos.y > INIT_HEIGHT - WALL_THICKNESS - BALL_HEIGHT / 2.0f
            ) {
                ball.velocity.y *= -1;
            }

            // ball上壁判定
            if(
                ball.velocity.y < 0 &&
                ball.pos.y < WALL_THICKNESS + BALL_HEIGHT / 2.0f
            ) {
                ball.velocity.y *= -1;
            }
        }

        // 全てのボールがなくなったらgameover
        if(mBalls.empty()) {
            SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "game over - no balls remaining");
            running = false;
        }

    }
    void Game::GenerateOutPut() {
        // 背景色の設定(RGBA)
        SDL_SetRenderDrawColor(mRenderer, 0, 0, 255, 255);

        // 現在の色でバックバッファを塗りつぶす
        SDL_RenderClear(mRenderer);

        // 壁の色を設定
        SDL_SetRenderDrawColor(mRenderer, 255, 255, 255, 200);

        // 壁用構造体の用意
        SDL_Rect wall {
            0,
            0,
            INIT_WIDTH,
            WALL_THICKNESS
        };

        // 上の壁を描画
        SDL_RenderFillRect(mRenderer, &wall);

        // 下の壁を描画
        wall.x = 0;
        wall.y = INIT_HEIGHT - WALL_THICKNESS;
        wall.w = INIT_WIDTH;
        wall.h = WALL_THICKNESS;
        SDL_RenderFillRect(mRenderer, &wall);

        // Player1のパドルの描画
        SDL_Rect player1Paddle {
            static_cast<int>(mPlayer1PaddlePos.x - PADDLE_WIDTH/2.0f),
            static_cast<int>(mPlayer1PaddlePos.y - PADDLE_HEIGHT/2.0f),
            PADDLE_WIDTH,
            PADDLE_HEIGHT
        };

        SDL_RenderFillRect(mRenderer, &player1Paddle);

        // Player2のパドルの描画
        SDL_Rect player2Paddle {
            static_cast<int>(mPlayer2PaddlePos.x - PADDLE_WIDTH/2.0f),
            static_cast<int>(mPlayer2PaddlePos.y - PADDLE_HEIGHT/2.0f),
            PADDLE_WIDTH,
            PADDLE_HEIGHT
        };

        SDL_RenderFillRect(mRenderer, &player2Paddle);

        // ball描画
        for(const Ball& ball : mBalls){
            SDL_Rect renderBall {
                static_cast<int>(ball.pos.x - BALL_WIDTH/2.0f),
                static_cast<int>(ball.pos.y - BALL_HEIGHT/2.0f),
                BALL_WIDTH,
                BALL_HEIGHT
            };

            SDL_RenderFillRect(mRenderer, &renderBall);
        }

        // フロントバッファと交換して反映させる
        SDL_RenderPresent(mRenderer);
    }
    // └ private function ┘
}

プロジェクト直下にCMakePresets.json, CMakeUserPresets.jsonを配置します。

CMakePresets.json

{
  "$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json",
  "version": 10,
  "configurePresets": [
    {
      "name": "vcpkg",
      "generator": "Visual Studio 18 2026",
      "binaryDir": "${sourceDir}/build",
      "cacheVariables": {
        "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
        "VCPKG_TARGET_TRIPLET": "x64-windows-static"
      }
    }
  ]
}

CMakeUserPresets.json

{
    "$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json",
    "version": 10,
    "configurePresets": [
        {
            "name": "default",
            "inherits": "vcpkg",
            "environment": {
                "VCPKG_ROOT": "C:\\Path\\To\\Your\\vcpkg"
            }
        },
        {
            "name": "debug",
            "inherits": "vcpkg",
            "cacheVariables": {
                "CMAKE_BUILD_TYPE": "Debug"
            },
            "environment": {
                "VCPKG_ROOT": "C:\\Path\\To\\Your\\vcpkg"
            }
        },
        {
            "name": "release",
            "inherits": "vcpkg",
            "cacheVariables": {
                "CMAKE_BUILD_TYPE": "Release"
            },
            "environment": {
                "VCPKG_ROOT": "C:\\Path\\To\\Your\\vcpkg"
            }
        }
    ],
    "buildPresets": [
        {
            "name": "default",
            "description": "",
            "displayName": "",
            "configurePreset": "default"
        },
        {
            "name": "debug",
            "description": "Debug build",
            "displayName": "Debug",
            "configurePreset": "debug"
        },
        {
            "name": "release",
            "description": "Release build",
            "displayName": "Release",
            "configurePreset": "release"
        }
    ]
}

CMakeLists.txt側でビルドタイプがReleaseの時にWIN32をつけるようにしているので、Release/Debugに対応したプリセットを用意しています。

.vscode/settings.jsonの用意

ビルド時のログが文字化けしないようにsettings.jsonを最小限用意します。

{
    "cmake.buildEnvironment": {
        "MSBUILDLOGENCODING": "UTF-8"
    },
    "cmake.outputLogEncoding": "utf-8"
}

ビルド

ビルドの準備が整いました。
MSBuildの環境変数が用意されたPowershell(タスクバーに設定したもの)からcode .でプロジェクトを開きます。
VSCodeが開いたら、以下の手順でビルドします。

  1. コマンドパレットを開く(Ctrl+Shift+PまたはF1

  2. CMake: Select Configure Presetを選択
    image.png

  3. debugプリセットを選択(CMakeUserPresets.jsonで定義したもの)
    image.png
    image_mosaic_1.png

  4. buildディレクトリ作成後、コマンドパレットでCMake: Buildを実行
    image.png
    image_mosaic3.png

実行

ビルドが成功したら、build/Debug/PongGame.exeを実行してください。

起動後ボールがランダムに飛び散り、左右のプレイヤーで打ち返し合うPongGameです。
ステートマシンや、終了時のアニメーションなどは用意していないので、面白みはないですが、ボールが全て無くなったらゲーム終了になります。

操作方法
  • Player1(左側): W/Sキーでパドルを上下移動
  • Player2(右側): I/Kキーでパドルを上下移動
  • 終了: ウィンドウを閉じるか、Ctrl+C

起動すると、Debugのプリセットでビルドしたため、デバッグ用コンソールが表示されていることが確認できます。
image_mosaic4.png

同様にReleaseプリセットを選択後、ビルドし直して結果を確認してみましょう。
image.png

デバッグウィンドウが生成されないことが確認できました。

まとめ

vcpkgとCMakeを組み合わせることで、C++のライブラリ管理が非常に簡単になります。VSCodeと組み合わせれば、軽量で柔軟な開発環境が構築できます。

今回はジェネレータにMSBuildを使いましたが、vcpkgはWindows、macOS、および Linuxで利用可能です。

クロスプラットフォーム開発を行う場合はvcpkg.jsonは使いまわして、CMakeのプリセットで差分を吸収することで、修正の負担を軽減することも可能でしょう。

今回紹介したサンプルプロジェクトを参考に、ぜひ自身のプロジェクトでもvcpkgを活用してみてください。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?