筆者:宇野 光純
前回に引き続き、Windowsアプリケーションとして動く簡単な2Dゲームの開発を紹介します。汎用プログラミング言語の「C++」と、オープンソースのパソコンゲーム開発用ライブラリの「DXライブラリ」を組み合わせることで、時間と労力は必要ですが、Unityなどのゲームエンジンよりも自由度の高いゲーム開発ができます。
シェルスクリプトマガジン Vol.62は以下のリンク先でご購入できます。


図2 「Shot.h」ファイルに記述するコード
#pragma once
#include "Object.h"
class Shot : public Object {
private:
bool flag;
bool image;
// true なら敵機の弾、false なら自機の弾
static int image1; // 画像ハンドル1
static int image2; // 画像ハンドル2
void SetImage(); // 画像関連設定用の関数
public:
double xv, yv; // X、Y 方向の移動量
Shot();
void Update(); // 更新
void Draw(); // 描画
// 座標、速度、画像を指定し発射する
void Shoot(double nx, double ny,
double nxv, double nvy, bool fimg);
};
図3 「Shot.cpp」ファイルに記述するコード
#include "DxLib.h"
#include "Shot.h"
#include "Info.h"
int Shot::image1 = -1;
int Shot::image2 = -1;
Shot::Shot() {
x = y = 0.0; xv = yv = 0.0; flag = true; image = false;
SetImage();
}
void Shot::Update() {
if (flag) {
x += xv; y += yv;
// 画面外に出た場合、無効にする
if (x < 0 || GetWidth() < x || y < 0 || GetHeight() < y) flag = false;
}
}
void Shot::Draw() {
if (flag) {
if (image) DrawGraph((int)(x - size / 2), (int)(y - size / 2), image1, TRUE);
else DrawGraph((int)(x - size / 2), (int)(y - size / 2), image2, TRUE);
}
}
void Shot::SetImage() {
size = 16;
if (image1 == -1) image1 = LoadGraph("./images/shot1.png");
if (image2 == -1) image2 = LoadGraph("./images/shot2.png");
}
// 座標、速度、画像を指定し発射する
void Shot::Shoot(double nx, double ny, double nxv, double nyv, bool fimg) {
x = nx; y = ny; xv = nxv; yv = nyv;
image = fimg; flag = true;
}
図4 「Object.h」ファイルに追加するコード
class Object {
public:
bool flag; // 有効無効を示すフラグ
};
図5 「Player.h」ファイルに追加するコード
#include "Shot.h"
class Player : public Object {
private:
int shot_num; // 現在の弾配列の添字
int shot_span; // 弾の発射間隔
void SetShot(); // 弾関連の設定用関数
void ShotFire(); // 弾発射用の関数
public:
static const int shot_max = 20; // 弾配列の要素数
Shot shot[shot_max]; // 弾配列
};
図6 「Player.cpp」ファイルに追加するコード
Player::Player() {
flag = true; // 有効フラグを設定立てる
this->SetShot(); // 自機の弾関連の設定
}
void Player::Update() {
this->ShotFire(); // 自機の弾の発射
for (int i = 0; i < shot_max; i++) shot[i].Update();
}
void Player::Draw() {
for (int i = 0; i < shot_max; i++) shot[i].Draw();
}
void Player::SetShot() {
shot_num = 0; shot_span = 0;
for (int i = 0; i < shot_max; i++) shot[i] = Shot();
}
void Player::ShotFire() {
if (GetKey(KEY_INPUT_Z)) {
// 発射間隔shot_span が4 以上になったとき
if (shot_span++ >= 4) {
// 自機位置から弾を発射する
shot[shot_num++].Shoot(x, y, 0, -8, false);
// 配列の添字が要素数以上になったときは0 にする
if (shot_num >= shot_max) { shot_num = 0; }
// 発射間隔のリセット
shot_span = 0;
}
}
}
図7 「MainScene.h」ファイルに記述するコード
#include "Shot.h"
#include <vector>
class MainScene {
private:
int enemy_span; // 弾の発射間隔
double enemy_shot_base; // 発射角度
std::vector<Shot> enemy_shot; // 敵機の弾配列
public:
void StageInitialize(); // パラメータ初期化用関数
void StageUpdate(); // 敵機の弾発射を実装する関数
};
図8 「MainScene.cpp」ファイルに追加するコード
#define _USE_MATH_DEFINES
#include <math.h>
MainScene::MainScene() {
StageInitialize(); // 追加したパラメータの初期化
}
void MainScene::Update() {
for (auto itr = enemy_shot.begin(); itr != enemy_shot.end();) {
if (!(*itr).flag) itr = enemy_shot.erase(itr);
else { (*itr).Update(); itr++; }
}
StageUpdate(); // ステージの更新
}
void MainScene::Draw() {
for (auto itr = enemy_shot.begin(); itr != enemy_shot.end(); ++itr)
(*itr).Draw(); // 敵機の弾の描画
}
void MainScene::StageInitialize() {
enemy_span = 0; enemy_shot_base = 0;
}
void MainScene::StageUpdate() {
if (enemy_span++ >= 50) {
double shot_v = 2.0; int shot_num = 36;
for (int i = 0; i < shot_num; i++) {
double angle = enemy_shot_base + M_PI / 18 * i; // 発射角度
enemy_shot.push_back(Shot()); // インスタンスを末尾に追加
enemy_shot.back().Shoot(enemy.x, enemy.y, shot_v * cos(angle),
shot_v * sin(angle), true); // 発射
}
enemy_shot_base += 0.1; // 基準の角度を更新
enemy_span = 0; // 発射間隔を初期化
}
}
図10 「Info.h」ファイルに追加するコード
#include "Object.h"
// 2 オブジェクトの当たり判定用関数
void Collision(Object *obj1, Object *obj2);
図11 「Info.cpp」ファイルに追加するコード
#include <math.h>
void Collision(Object *obj1, Object *obj2) {
double dx = obj1->x - obj2->x; // X 座標の差
double dy = obj1->y - obj2->y; // Y 座標の差
double ds = obj1->hit_size + obj2->hit_size; // 半径の合計
// 有効フラグが立っているかどうかの確認
if (!obj1->flag || !obj2->flag) return;
// 三平方の定理を使用
if (pow(dx, 2) + pow(dy, 2) <= pow(ds, 2)) {
// 当たり判定後の処理
obj1->CollisionResult();
obj2->CollisionResult();
}
}
図12 「Object.h」ファイルに追加するコード
class Object {
public:
int hit_size; // 当たり判定エリアの半径
// 当たり判定後の処理用関数
virtual void CollisionResult() {}
};
図13 「Player.h」ファイルに追加するコード
class Player : public Object {
public:
int hp_now, hp_max; // 体力の現在値、最大値
void CollisionResult(); // 当たり判定後の処理用関数
};
図14 「Player.cpp」ファイルに追加するコード
Player::Player() {
hp_now = hp_max = 3; // 体力の初期化
}
void Player::SetImage() {
hit_size = 8;
}
void Player::CollisionResult() {
if (hp_now-- < 0) flag = false;
}
図15 「Enemy.h」ファイルに追加するコード
class Enemy : public Object {
public:
int hp_now, hp_max;
void CollisionResult();
};
図16 「Enemy.cpp」ファイルに追加するコード
Enemy::Enemy() {
hp_now = hp_max = 100; // 体力
flag = true; // 有効フラグを立てる
}
void Enemy::SetImage() {
hit_size = 32;
}
void Enemy::CollisionResult() {
if (hp_now-- < 0) flag = false;
}
図17 「Shot.h」ファイルに追加するコード
class Shot : public Object {
public:
// 当たり判定後の処理用関数
void CollisionResult();
};
図18 「Shot.cpp」ファイルに追加するコード
void Shot::SetImage() {
hit_size = 8;
}
void Shot::CollisionResult() { flag = false; }
図19 「MainScene.cpp」ファイルに追加するコード
void MainScene::Update() {
// 自機の弾と敵機の当たり判定処理
for (int i = 0; i < player.shot_max; i++)
Collision(static_cast<Object*>(&player.shot[i]), static_cast<Object*>(&enemy));
// 自機と敵機の弾の当たり判定処理
for (auto itr = enemy_shot.begin(); itr != enemy_shot.end(); itr++)
Collision(static_cast<Object*>(&player), static_cast<Object*>(&(*itr)));
}
void MainScene::Draw() {
DrawFormatString(0, 0, GetColor(255, 255, 255), "Player : %d", player.hp_now);
DrawFormatString(GetWidth() - 120, 0, GetColor(255, 255, 255), "Enemy : %d", enemy.hp_now);
}
図20 「Info.h」ファイルに追加する コード
// ゲームシーンの取得用関数
int GetGameScene();
// ゲームシーンの設定用関数
void SetGameScene(int val);
図21 「Info.cpp」ファイルに追加するコード
int g_GameScene; // シーン管理用変数
int GetGameScene() { return g_GameScene; }
void SetGameScene(int val) { g_GameScene = val; }
図22 「TitleScene.h」ファイルに 記述するコード
#pragma once
class TitleScene {
private:
int wait; // 待ち時間
public:
TitleScene();
bool Update();
void Draw();
};
図23 「TitleScene.cpp」ファイルに記述するコード
#include "DxLib.h"
#include "TitleScene.h"
#include "Info.h"
TitleScene::TitleScene() {
SetBackgroundColor(100, 100, 100); // 背景を灰色に
wait = 30;
}
bool TitleScene::Update() {
// スペースキーを押したら、true を返す
if (wait-- < 0 && GetKey(KEY_INPUT_SPACE)) return true;
return false;
}
void TitleScene::Draw() {
DrawFormatString(200, 200, GetColor(255, 255, 255), " タイトルです.");
DrawFormatString(200, 400, GetColor(255, 255, 255), " スペースキーを押してください.");
}
図25 「ResultScene.h」ファイルに記述するコード
#pragma once
class ResultScene {
public:
bool Update();
void Draw();
};
図26 「ResultScene.cpp」ファイルに記述するコード
#include "DxLib.h"
#include "ResultScene.h"
#include "Info.h"
bool ResultScene::Update() {
if (GetKey(KEY_INPUT_SPACE)) return true; // メインシーンに遷移
return false;
}
void ResultScene::Draw() {
// ゲームクリア時
if (GetGameScene() == 2) {
SetBackgroundColor(255, 255, 255); // 背景を白色に
DrawFormatString(300, 200, GetColor(0, 0, 0), " ゲームクリア !!");
}
// ゲームオーバー時
else {
SetBackgroundColor(255, 100, 100); // 背景を赤色に
DrawFormatString(300, 200, GetColor(0, 0, 0), " ゲームオーバー...");
}
DrawFormatString(300, 360, GetColor(0, 0, 0), " スペースキーを押すと");
DrawFormatString(300, 380, GetColor(0, 0, 0), " タイトルに戻ります.");
DrawFormatString(300, 420, GetColor(0, 0, 0), " 終了には、ESC キー.");
}
図29 「Main.cpp」ファイルに追加するコード
#include "TitleScene.h"
#include "ResultScene.h"
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
TitleScene ts = TitleScene();
ResultScene rs = ResultScene();
while (ProcessMessage() == 0 && ScreenFlip() == 0 &&
ClearDrawScreen() == 0) {
// ms.Update(); ms.Draw(); は消してその部分に以下を追加
int t = 0;
switch (GetGameScene()) {
case 0:
if (ts.Update()) { ms = MainScene(); SetGameScene(1); break; }
ts.Draw();
break;
case 1:
if((t = ms.Update()) != 0) { SetGameScene(t); break; }
ms.Draw();
break;
default:
if (rs.Update()) { ts = TitleScene(); SetGameScene(0); break; }
rs.Draw();
break;
}
}
}
図30 「MainScene.h」ファイルに追加 するコード
class MainScene {
public:
// void Update(); 行は削除
int Update();
};
図31 「MainScene.cpp」ファイルに追加するコード
// void MainScene:Update() { 行を次の行に置き換えてから太字部分をブロック末尾に追加
int MainScene::Update() {
if (player.hp_now <= 0) { return 3; }
if (enemy.hp_now <= 0) { return 2; }
return 0;
}
void MainScene::StageUpdate() {
if (enemy_span++ >= 50) {
// 体力が半分より大きいとき
if (enemy.hp_now > enemy.hp_max / 2) {
// このブロックに既存のコードを挿入
}
// 体力が半分以下のとき、ランダムな角度に多数の弾を発射
else {
double shot_v = 1.0 + GetRand(40) / 10.0;
int shot_num = 2;
for (int i = 0; i < shot_num; i++) {
double angle = M_PI * (GetRand(3600) / 10.0) / 180;
enemy_shot.push_back(Shot());
enemy_shot.back().Shoot(
enemy.x, enemy.y, shot_v * cos(angle), shot_v * sin(angle), true
);
}
}
}
}