3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C++でローグライクを作成してみる - 1. SDLでウィンドウ作成

3
Last updated at Posted at 2025-11-02

概要

C++でローグライクゲームを作成するために,まずは方針を定める.
そして第一歩として,画面の起動を行う.この際,クロスプラットフォームのライブラリであるSimple DirectMedia Layer (SDL)を用いる.

導入

動機

C++で何か作りたいと思う今日この頃.やはりプログラミングをしていて作りたくなるのはゲームでしょう.しかし,昨今のゲームは「美麗なグラフィックと爽快なアクションが~」というようなものが多く,作ろうにもどう始めてよいやら分かりません.
そこで,それならローグライクを作ろう,という話になります.ローグライクならこんな見た目をしていて,どうやら背景のコードが少しは見えてくる気がします.
dungeon_crawl_ss.png
こちらはこのジャンルの中でも比較的古いDungeon Crawl(日本語版)の実行画面です.ゲームそのものが古いからレトロな見た目というのもありますが,ローグライクの「死んだらすべてを失う」という特徴,つまりミスができない(ことを楽しむ)ゲーム性からすると,一手を着実に進められる,適度に離散的な見た目がちょうどよいのです.

…などと言いつつ,つまるところローグライクが好きなので,作ろうということに他なりません.

先行研究(研究ではない)

現在,オープンソースで開発されているローグライクゲームとして「変愚蛮怒(公式サイト)」があります.しかも使用言語はC++です.
こちらのプロジェクト,開発に携わるメンバーをずっと募集しているので,これに参加すれば良いじゃないか,と思わないでもないですが……実用的なプログラミングの知識や共同開発のノウハウがあまりないので,とりあえず自分で一から作ってみることにしました.
正直なところ,気軽に読むには変愚蛮怒のソースは膨大なので参考にするのもなかなか大変でした.これがサラサラ読めるようになりたい..

他にも調べると同様のことをやっている方がたくさんいらっしゃる.みなさんすごいですね.

手順

何を作るのか

さて,まずは作りたいものをもう少し明確にします.

  • 主人公がマップを歩く
    • 縦横ある程度の大きさのマップがあり,マス単位に分割されている
    • 主人公はそのうちの一マスを占め,縦横斜めの8方向にターンあたり一マス進むことができる
  • 敵がいる,アイテムがある
    • ローグライクなので,敵やアイテムも操作キャラと同じマップ上で一マス分のスペースに存在する
    • ステータスを割り当てる
    • 少なくとも敵は攻撃できる,アイテムは入手できるようにする
  • グラフィック
    • 古典的なローグライクらしくASCII表示にする(絵は用意しない)

このような具合でしょうか.逆に,以下のものはひとまず作りません.

  • サウンド要素
  • マップの自動生成

ロードマップ

これを踏まえて,開発の順番を(なんとなくですが)決めましょう.

  1. まずはゲーム画面を表示する ←今回はこれ
  2. 文字を表示する
  3. マップデータを保持,画面に表示
  4. 操作キャラクターの作成・表示
  5. 入力システム・操作キャラクターの移動
  6. テキスト管理
  7. アイテムのクラスの作成
  8. アイテム関連のアクションの実装
  9. 敵のクラスの作成
  10. 攻撃など敵関連のアクション

開発環境

Windows11
Visual Studio Community 2022

SDL3の導入とウィンドウ作成

では実装の方に入っていきましょう.
全体的な方針は,Sanjay Madhav「ゲームプログラミング C++」を参考にする予定です.ただ今回は,ほぼ公式にある手順(リンク)の通りに進めるのみとなります.

まず最新の安定版のSDLをダウンロードして任意の場所で展開します.2025/11/01時点ではバージョンは3.2.26でした.

次にVisual Studio で新しくC++の空のプロジェクトを作成し,以下のhello.cをソースコードに追加します.このコードの中身については後ほど検討します.

ソリューションエクスプローラーのソリューションを選択し,「追加▶既存のプロジェクト」よりダウンロードしたSDLのVisualC/SDLにあるSDL.vcxprojを追加します.
さらに,「プロジェクト▶参照の追加」あるいはソリューションエクスプローラー内の「参照」を選択して「参照の追加」より,SDLをサブプロジェクトとして追加します.

screen_shot_reference.png

最後に,メインのプロジェクトの「プロジェクト▶(プロジェクト名)のプロパティ」を選択し,構成:すべての構成/プラットフォーム:すべてのプラットフォームとしたうえで「C/C++▶全般▶追加のインクルードディレクトリ」にダウンロードしたSDLのincludeディレクトリを追加します.

screen_shot_add_directory.png

これで,実行すれば下のようにHello World!の文字が中央にフルスクリーンで表示されるはずです.

execute_screen.png

無事に表示されれば,作業内容としてはこれで完了です!

サンプルコードの解読

main関数が無い!

SDL3では,mein関数のあるソースにおいてSDL.hとSDL_main.hを読み込めばエントリーポイントとして機能します.SDL2までとほとんど変わりません.

main.cpp
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>

int main(int argc, char *argv[])
{
}

ちなみにこれは,SDL_main.hにおいて必要に応じてマクロを用いてmain関数をSDL_mainを置き換えることでプラットフォームによる違いを吸収しているようです(なので,プログラム中は不用意にmainという名前を用いない方が無難です).
しかし,今回利用したサンプルコードにはそもそもmain関数が存在しません.
これはSDL3で新しく利用できるようになった形式で,

#define SDL_MAIN_USE_CALLBACKS 1 // これを SDL_main.hのインクルードよりも前に書く
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>

とすることで,SDL_main.hの中で判定してmain関数に相当する部分をSDL側が用意してくれるようになります.そしてこの場合,main関数の代わりに

SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]);
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event);
SDL_AppResult SDL_AppIterate(void *appstate);
void SDL_AppQuit(void *appstate, SDL_AppResult result);

の四つの関数を利用することとなります.
そもそも,一般的にアプリは次のような形式になっているはずですね.

main.cpp
int main () {
    Initialize();
    while(IsRunning) {
        ProcessInput();
        update();
        GenerateOutput();
    }
    Quit();
}

このときのInitialize()がSDL_AppInit()に,ProcessInput()がSDL_AppEvent()に,update()とGenerateOutput()がSDL_AppIterate()に,Quit()がSDL_AppQuit()に対応していると考えれば良いです.
また,引数のappstateについては,グローバルに保持したいアプリの状態のような値を扱うために用いることが想定されています.具体的には次のような感じ.

main.cpp
typedef struct {
    // 必要なもの
} AppState;

SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
    AppState *state = SDL_calloc(1, sizeof(AppState));
    // stateの中で初期化したり必要な処理を行う
    *appstate = state;
    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
    AppState *state = (AppState *)appstate;
    // stateを更新
    return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppIterate(void *appstate)
{
    AppState *state = (AppState *)appstate;
    // stateを更新
    return SDL_APP_CONTINUE;
}

/* This function runs once at shutdown. */
void SDL_AppQuit(void *appstate, SDL_AppResult result)
{
    AppState *state = (AppState *)appstate;
    SDL_free(state);
}

…というわけで,SDL3においてはmain関数から書いても,上で紹介した四つの関数を利用して書いても良いということです.
今回のローグライクを作るプロジェクトでは,せっかくなので新しいこちらのmain関数を書かない方法で進めていこうと思います.

SDL_DestroyWindow()/SDL_DestroyRenderer()は不要なのか?

サンプルコードでは,SDL_AppQuit()の中では何もしていません.
しかしよく見るお手本としては,初期化の場面で

// 引数は省略
// なお今回のサンプルでは一つにまとめた関数
// SDL_CreateWindowAndRenderer()を使用している
SDL_Window *window = SDL_CreateWindow();
SDL_Renderer *renderer = SDL_CreateRenderer();

としたならば,終了する処理において

SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);

としていました(これがSDL2で必須だったかどうかは検証していません).
これに関しては,結論としては不要なようです.サンプルコードが動いているわけですし.SDL側で,開いているウィンドウは把握しており,プログラムの終了時には自動的に終了時処理を行うようになっているようです(参照リンク).ただし,SDL_DestroyWindow/SDL_DestroyRenderer()を明示的に書いても問題ありません.
これはSDL_Init()に対応するSDL_Quit()についても同様です.

終わりに

今回はローグライクについては意気込みのみで,主にSDL3を利用する話となりました.ほぼSDLのWikiにあるマニュアル通りですが,個人的に気になった部分についてはある程度丁寧に記載しているので,誰かの役に立てば幸いです.

ここまで読んでいただきありがとうございます.
次の記事はこちら:

参考文献

  1. Sanjay Madhav. 吉川邦夫訳. ゲームプログラミング C++. 翔泳社, 2018.
  2. SDL Wiki https://wiki.libsdl.org/SDL3/FrontPage
3
0
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
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?