はじめに
-
このコードはまだ実装途中です!
-
順次書き足していきます.
-
今回もGeminiの手を借りながらC++とSDL3でゲーム作りをしています.
-
私は独学・趣味でプログラムを書いています.
-
そのため、中身はあまり参考になりません.
-
自作のコードの置き場として使ってます.
-
追記
3/1 Bulletクラスの追加 / 弾の発射機能
3/2 Enemyクラスの追加 / ゲーム機能の基礎の完成
3/3 垂直同期の追加/それによる速度調整/コード修正
目次
| 1 | 2 |
|---|---|
| 1 | はじめに |
| 2 | デモ |
| 3 | 開発環境 |
| 4 | 仕様 |
| 5 | 使用した画像 |
| 6 | コード |
| 6-1 | Main.cpp |
| 6-2 | Config.h |
| 6-3 | Plane.cpp |
| 6-4 | Bullet.cpp |
| 6-5 | Enemy.cpp |
デモ
開発環境
- OS : Windows11
- IDE : Visual Studio Community 2026
- SDL3.4.2
- SDL_Image3.4.0
仕様
-
スペースキー = ゲームスタート / ゲームオーバー後のリスタート
-
矢印キー = 自機の操作
-
Fキー = 弾の発射
-
弾が敵に当たると敵が消える
-
自機が敵に当たるとゲームオーバー
使用した画像
コード
Main.cpp
Main.cpp
#define SDL_MAIN_USE_CALLBACKS 1
// ----------- ライブラリ -----------
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3_image/SDL_image.h>
#include <vector>
#include "Config.h"
#include "Plane.h"
#include "Bullet.h"
#include "Enemy.h"
// ----------- 定数 -----------
// キーによる進行方向
constexpr char DIRECT_UP = 1;
constexpr char DIRECT_RIGHT = 2;
constexpr char DIRECT_DOWN = 3;
constexpr char DIRECT_LEFT = 4;
// 初期/追加する敵の数
constexpr int ENEMY_NUM = 3;
// 敵の表示寿命の位置
constexpr float DRAW_LIMIT = 32.0f;
// ----------- 状態管理用の構造体 -----------
struct AppState
{
// ウィンドウ
SDL_Window *window = nullptr;
// レンダラー
SDL_Renderer *renderer = nullptr;
// 画像
SDL_Texture *spritesheet = nullptr;
// ゲームのスタート管理
bool isGameStart = false;
// 自機
Plane plane;
// 敵
std::vector<Enemy> enemies;
};
// ----------- 初期化 -----------
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
// 状態管理
AppState *as = new AppState();
*appstate = as;
// メタ情報
SDL_SetAppMetadata("Shooting Game", "0.1", "com.example.ShootingGame");
if( !SDL_Init(SDL_INIT_VIDEO) )
{
SDL_Log("SDL Init is Failed: %s\n", SDL_GetError());
return SDL_APP_FAILURE;
}
if( !SDL_CreateWindowAndRenderer("Shooting Game", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &as->window, &as->renderer) )
{
SDL_Log("SDL Create Window/Renderer is Failed: %s\n", SDL_GetError());
return SDL_APP_FAILURE;
}
// 画像の読み込み(PNG)
as->spritesheet = IMG_LoadTexture(as->renderer, "image/SpriteSheet.png");
if( !as->spritesheet )
{
SDL_Log("SDL IMG is Failed: %s\n", SDL_GetError());
return SDL_APP_FAILURE;
}
// 画像がぼやけないようにする設定
SDL_SetTextureScaleMode(as->spritesheet, SDL_SCALEMODE_NEAREST);
// 垂直同期の設定
SDL_SetWindowSurfaceVSync(as->window, 1);
SDL_SetRenderVSync(as->renderer, 1);
return SDL_APP_CONTINUE;
}
// ----------- イベント処理 -----------
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
// 状態管理
AppState *as = (AppState *)appstate;
// 閉じるボタンを押したとき(終了)
if( event->type == SDL_EVENT_QUIT )
{
return SDL_APP_SUCCESS;
}
// スペースキーを押したとき(ゲームスタート/リスタート)
else if( !as->isGameStart && event->key.key == SDLK_SPACE && !(event->key.down) )
{
as->enemies.clear();
as->plane.init();
as->isGameStart = true;
}
// Fキーを押したとき(弾の発射)
else if( as->isGameStart && event->key.key == SDLK_F && !(event->key.down) )
{
Bullet newBullet;
newBullet.init(as->plane.getX(), as->plane.getY());
as->plane.setBullet(newBullet);
}
return SDL_APP_CONTINUE;
}
// ----------- ゲームループ -----------
SDL_AppResult SDL_AppIterate(void *appstate)
{
// 状態管理
AppState *as = (AppState *)appstate;
// 背景色の設定
SDL_SetRenderDrawColor(as->renderer, 157, 204, 224, SDL_ALPHA_OPAQUE);
// バックバッファへ書き込み
SDL_RenderClear(as->renderer);
// ゲームスタート
if( as->isGameStart )
{
// 敵の出現
if( (int)as->enemies.size() <= 2 )
{
for( unsigned int i = 0; i < ENEMY_NUM; i++ )
{
Enemy newEnemy;
newEnemy.init();
as->enemies.push_back(newEnemy);
}
}
// 押されているキーの反応取得(飛行機の移動)
const bool *keysState = SDL_GetKeyboardState(nullptr);
// 上矢印キー
if( keysState[SDL_SCANCODE_UP] )
{
as->plane.move(DIRECT_UP);
}
// 右矢印キー
if( keysState[SDL_SCANCODE_RIGHT] )
{
as->plane.move(DIRECT_RIGHT);
}
// 下矢印キー
if( keysState[SDL_SCANCODE_DOWN] )
{
as->plane.move(DIRECT_DOWN);
}
// 左矢印キー
if( keysState[SDL_SCANCODE_LEFT] )
{
as->plane.move(DIRECT_LEFT);
}
// 敵の情報更新
for( Enemy &segment : as->enemies )
{
segment.update();
}
// 自機/弾の情報更新
as->plane.update();
// 敵が画面外に出たときの処理
for( Enemy &segment : as->enemies )
{
if( segment.getY() > WINDOW_HEIGHT )
{
segment.setIsNonActive();
}
}
// 当たり判定(弾と敵)
for( Bullet &bullet : as->plane.getBullet() )
{
if( !bullet.getIsActive() ) { continue; }
for( Enemy &enemy : as->enemies )
{
if( !enemy.getIsActive() ) { continue; }
SDL_FRect bRect = bullet.getRect();
SDL_FRect eRect = enemy.getRect();
if( SDL_HasRectIntersectionFloat(&bRect, &eRect) )
{
bullet.setIsNonActive();
enemy.setIsNonActive();
}
}
}
// 当たり判定(敵と自機)
for( Enemy &enemy : as->enemies )
{
SDL_FRect eRect = enemy.getRect();
SDL_FRect pRect = as->plane.getRect();
if( SDL_HasRectIntersectionFloat(&eRect, &pRect) )
{
enemy.setIsNonActive();
as->plane.setIsNonActive();
as->isGameStart = false;
}
}
// 敵の数の掃除
for( int i = (int)as->enemies.size() - 1; i >= 0; i-- )
{
if( !as->enemies[i].getIsActive() )
{
as->enemies.erase(as->enemies.begin() + i);
}
}
// 敵の表示
for( Enemy &segment : as->enemies )
{
segment.draw(as->renderer, as->spritesheet);
}
// 自機の表示
as->plane.draw(as->renderer, as->spritesheet);
}
// 画面へ書き込み
SDL_RenderPresent(as->renderer);
return SDL_APP_CONTINUE;
}
// ----------- 終了時処理 -----------
void SDL_AppQuit(void *appstate, SDL_AppResult result)
{
if( appstate )
{
AppState *as = (AppState *)appstate;
SDL_DestroyRenderer(as->renderer);
SDL_DestroyWindow(as->window);
delete(as);
}
}
Config.h
Config.h
#pragma once // CONFIG_H
// ----------- 定数 -----------
// ウィンドウのサイズ
constexpr int WINDOW_WIDTH = 320;
constexpr int WINDOW_HEIGHT = 640;
// 自機/敵のサイズ(縦横)
constexpr float CHARACTER_SIZE = 32.0f;
// 自機のプロペラ/敵のコマ数
constexpr int FRAME = 3;
// コマ送りをゆっくりにする定数
constexpr int ANIME_SPEED = 10;
Plane.cpp
Plane.h
#pragma once // PLANE_H
// ----------- ライブラリ -----------
#include <SDL3/SDL.h>
#include <vector>
#include "Config.h"
#include "Bullet.h"
// ---------- クラス ----------
class Plane
{
public:
// 初期化関数
void init();
// コマ送り/弾の管理用の関数
void update();
// 進行方向に位置を変更/壁の当たり判定用の関数
void move(char direct);
// 表示用の関数
void draw(SDL_Renderer *renderer, SDL_Texture *spritesheet);
// 自機の位置を返す関数
float getX();
float getY();
// 自機の無効化用の関数(敵に当たったとき)
void setIsNonActive();
// 表示させる弾を保存する関数
void setBullet(Bullet &bullet);
// 弾のリストを返す関数
std::vector<Bullet>& getBullet();
// 自機の位置/サイズを返す関数
SDL_FRect getRect();
private:
// 1フレームごとにカウント
unsigned int frameCount;
// 画像の切り出し位置/サイズ
SDL_FRect srcRect;
// 自機の表示位置/サイズ
SDL_FRect dstRect;
// 自機の有効化
bool isActive;
// 表示させる弾の数
std::vector<Bullet> bulletNum;
};
Plane.cpp
// ---------- ライブラリ ----------
#include "Plane.h"
// ---------- 定数 ----------
// 自機の初期生成位置
constexpr float SPAWN_X = 144.0f;
constexpr float SPAWN_Y = 544.0f;
// 進行方向に進む距離
constexpr float MOVE_SPEED = 5.0f;
// ---------- メソッド ----------
// 初期化関数
void Plane::init()
{
// 自機のコマ送りのカウントの初期化
frameCount = 3;
// 画像の切り出し位置/サイズの初期化
srcRect = {
.x = 0.0f,
.y = 0.0f,
.w = CHARACTER_SIZE,
.h = CHARACTER_SIZE
};
// 自機の表示位置/サイズの初期化
dstRect = {
.x = SPAWN_X,
.y = SPAWN_Y,
.w = CHARACTER_SIZE,
.h = CHARACTER_SIZE
};
// 自機の有効化
isActive = true;
// 弾の数をクリア
bulletNum.clear();
}
// コマ送り/弾の管理用の関数
void Plane::update()
{
if( !isActive ) { return; }
// 自機のコマ送り
srcRect.x = (float)((frameCount / ANIME_SPEED) % FRAME) * CHARACTER_SIZE;
frameCount++;
// 弾の表示位置変更
for( Bullet &bullet : bulletNum )
{
bullet.update();
}
// 使わなくなった弾の掃除
for( int i = (int)bulletNum.size() - 1; i >= 0 ; i-- )
{
if( !bulletNum[i].getIsActive() )
{
bulletNum.erase(bulletNum.begin() + i);
}
}
}
// 進行方向に位置を変更/壁の当たり判定用の関数
void Plane::move(char direct)
{
if( !isActive ) { return; }
// 進行方向に進む処理
switch( direct )
{
// 上方向
case 1:
dstRect.y -= MOVE_SPEED;
break;
// 右方向
case 2:
dstRect.x += MOVE_SPEED;
break;
// 下方向
case 3:
dstRect.y += MOVE_SPEED;
break;
// 左方向
case 4:
dstRect.x -= MOVE_SPEED;
break;
}
// 壁の当たり判定
// 上壁
if( dstRect.y < 0 )
{
dstRect.y = 0;
}
// 右壁
if( dstRect.x > (WINDOW_WIDTH - CHARACTER_SIZE) )
{
dstRect.x = (WINDOW_WIDTH - CHARACTER_SIZE);
}
// 下壁
if( dstRect.y > (WINDOW_HEIGHT - CHARACTER_SIZE) )
{
dstRect.y = (WINDOW_HEIGHT - CHARACTER_SIZE);
}
// 左壁
if( dstRect.x < 0 )
{
dstRect.x = 0;
}
}
// 表示用の関数
void Plane::draw(SDL_Renderer *renderer, SDL_Texture *spritesheet)
{
// 弾の表示
for( Bullet &segment : bulletNum )
{
segment.draw(renderer, spritesheet);
}
// バックバッファへ書き込み
SDL_RenderTexture(renderer, spritesheet, &srcRect, &dstRect);
}
// 自機のx値を返す関数
float Plane::getX()
{
return dstRect.x;
}
// 自機のy値を返す関数
float Plane::getY()
{
return dstRect.y;
}
// 自機の無効化用の関数(敵に当たったとき)
void Plane::setIsNonActive()
{
isActive = false;
}
// 表示させる弾を保存する関数
void Plane::setBullet(Bullet &bullet)
{
bulletNum.push_back(bullet);
}
// 弾のリストを返す関数
std::vector<Bullet>& Plane::getBullet()
{
return bulletNum;
}
// 自機の位置/サイズを返す関数
SDL_FRect Plane::getRect()
{
return { dstRect.x, dstRect.y, dstRect.w, dstRect.h };
}
Bullet.cpp
Bullet.h
#pragma once // BULLET_H
// ----------- ライブラリ -----------
#include <SDL3/SDL.h>
#include "Config.h"
// ----------- クラス -----------
class Bullet
{
public:
// 初期化関数
void init(float x, float y);
// 弾の位置の更新/表示寿命の管理用の関数
void update();
// 表示用の関数
void draw(SDL_Renderer *renderer, SDL_Texture *splitesheet);
// 弾の有効/無効を返す関数
bool getIsActive();
// 弾の無効化用の関数(敵に当たったとき)
void setIsNonActive();
// 弾の位置/サイズを返す関数
SDL_FRect getRect();
private:
// 画像の切り出し位置/サイズ
SDL_FRect srcRect;
// 弾の表示位置/サイズ
SDL_FRect dstRect;
// 弾の有効化
bool isActive;
};
Bullet.cpp
// ---------- ライブラリ ----------
#include "Bullet.h"
// ---------- 定数 ----------
// 弾のスピード
constexpr float BULLET_SPEED = 5.0f;
// 弾の表示寿命の位置
constexpr float DRAW_LIMIT = -11.0f;
// 弾の切り出し位置/サイズor表示サイズ
constexpr float BULLET_X = 110.0f;
constexpr float BULLET_Y = 11.0f;
constexpr float BULLET_WIDTH = 4.0f;
constexpr float BULLET_HEIGHT = 11.0f;
// ---------- メソッド ----------
// 初期化関数
void Bullet::init(float x, float y)
{
// 画像の切り出し位置/サイズの初期化
srcRect = {
.x = BULLET_X,
.y = BULLET_Y,
.w = BULLET_WIDTH,
.h = BULLET_HEIGHT
};
// 自機の表示位置/サイズの初期化
dstRect = {
.x = x + (int)(CHARACTER_SIZE / 2 - 1),
.y = y - 11.0f,
.w = BULLET_WIDTH,
.h = BULLET_HEIGHT
};
// 弾の有効化
isActive = true;
}
// 弾の位置の更新/表示寿命の管理用の関数
void Bullet::update()
{
if( !isActive ) { return; }
// 弾を上に飛ばす
dstRect.y -= BULLET_SPEED;
// 表示限界の判定
if( dstRect.y <= DRAW_LIMIT )
{
isActive = false;
}
}
// 表示用の関数
void Bullet::draw(SDL_Renderer *renderer, SDL_Texture *texture)
{
if( !isActive ){ return; }
// バックバッファへ書き込み
SDL_RenderTexture(renderer, texture, &srcRect, &dstRect);
}
// 弾の有効/無効を返す関数
bool Bullet::getIsActive()
{
return isActive;
}
// 弾の無効化用の関数(敵に当たったとき)
void Bullet::setIsNonActive()
{
isActive = false;
}
// 弾の位置/サイズを返す関数
SDL_FRect Bullet::getRect()
{
return { dstRect.x, dstRect.y, dstRect.w, dstRect.h };
}
Enemy.cpp
Enemy.h
#pragma once // ENEMY_H
// ----------- ライブラリ -----------
#include <SDL3/SDL.h>
#include "Config.h"
// ---------- クラス ----------
class Enemy
{
public:
// 初期化関数
void init();
// コマ送り/移動位置の更新用の関数
void update();
// 表示用の関数
void draw(SDL_Renderer *renderer, SDL_Texture *spritesheet);
// 弾の有効/無効を返す関数
bool getIsActive();
// 敵の無効化用の関数(弾に当たったとき)
void setIsNonActive();
// 敵の位置を返す関数
float getY();
// 敵の位置/サイズを返す関数
SDL_FRect getRect();
private:
// 1フレームごとにカウント
unsigned int frameCount;
// 画像の切り出し位置/サイズ
SDL_FRect srcRect;
// 敵の表示位置/サイズ
SDL_FRect dstRect;
// 敵の有効化
bool isActive;
};
Enemy.cpp
// ---------- ライブラリ ----------
#include <random>
#include "Enemy.h"
// ---------- 定数 ----------
constexpr float ENEMY_SPEED = 1.0f;
// ---------- メソッド ----------
// 初期化関数
void Enemy::init()
{
// 乱数生成器
static std::random_device rd;
static std::mt19937_64 gen(rd());
// 範囲指定
std::uniform_real_distribution<float> dist_x(CHARACTER_SIZE, WINDOW_WIDTH - CHARACTER_SIZE);
// 敵のコマ送りのカウントの初期化
frameCount = 3;
// 画像の切り出し位置/サイズの初期化
srcRect = {
.x = CHARACTER_SIZE * 4,
.y = 0.0f,
.w = CHARACTER_SIZE,
.h = CHARACTER_SIZE
};
// 敵の表示位置/サイズの初期化
dstRect = {
.x = dist_x(gen),
.y = -CHARACTER_SIZE,
.w = CHARACTER_SIZE,
.h = CHARACTER_SIZE
};
// 敵の有効化
isActive = true;
}
// コマ送り/移動位置の更新用の関数
void Enemy::update()
{
if( !isActive ){ return; }
// 敵のコマ送り
srcRect.x = (float)((CHARACTER_SIZE * 4) + ((frameCount / ANIME_SPEED) % FRAME) * CHARACTER_SIZE);
frameCount++;
// ゆらゆら動きの再現
if( (frameCount / ANIME_SPEED) % 2 == 0 )
{
dstRect.x -= ENEMY_SPEED;
}
else
{
dstRect.x += ENEMY_SPEED;
}
// 敵の降りてくるスピード
dstRect.y += ENEMY_SPEED;
}
// 表示用の関数
void Enemy::draw(SDL_Renderer *renderer, SDL_Texture *spritesheet)
{
if( !isActive ) { return; }
// バックバッファへ書き込み
SDL_RenderTexture(renderer, spritesheet, &srcRect, &dstRect);
}
// 敵の有効/無効を返す関数
bool Enemy::getIsActive()
{
return isActive;
}
// 敵の無効化用の関数(弾に当たったとき)
void Enemy::setIsNonActive()
{
isActive = false;
}
// 敵のy値を返す関数
float Enemy::getY()
{
return dstRect.y;
}
// 敵の位置/サイズを返す関数
SDL_FRect Enemy::getRect()
{
return { dstRect.x, dstRect.y, dstRect.w, dstRect.h };
}

