今回の目標
Qiitaではまたお久しぶりになってしまいますた、まゆC#です(*'▽')
前回はアニメーションをやりましたがここまでゲームといえるものが作れていなかったように思います。
ですので今回は敵を作って自分を攻撃してくるものを作っていけたらなと思ってます!
※今回の記事は前回より少し難易度が上がります
この記事の対象
・Dxlibを使いゲーム開発をしてみたい方
・C++を始めたばかりの初心者の方で作りながら覚えたい方
・何かしらのライブラリやツールを使ってゲーム開発をしてみたい人方
まずは何が必要か考えよう!!!
目標は「敵を作って自分を攻撃してくるものを作っていく」となっています。
ですので敵をどのようにするか、自分をどのようにするかを適当に考えてみましょう!
・敵
敵といってもバリバリにキャラデザをしたりする作業は~~(同人では描いていますが)~~僕らプログラマーの仕事ではありませんので図形で作りましょう。
今回は丸い球を発射したいなと考えているのでこのような設定にします!
敵の形 : 正方形(50px,50px)
敵の色 : RGB(255, 0, 0)
敵の弾 : 円形の弾
弾の色 : RGB(255, 255, 255)
・プレイヤー(自機)
こちらも大したことはしないで色だけ変えておきましょう!
自機の形 : 正方形(50px,50px)
自機の色 : RGB(255, 255, 255)
自機の操作 : WASD
・当たり判定
当たり判定は円形の当たり判定を使います。
実際の当たり判定エリアは次のように設定します。(脳死で作った画像なのは申し訳)
作っていこう!!!
ここからは前回までの内容を応用して作っていきます。
わからない箇所があったら今までの記事を見直してみましょう(*'▽')
・敵を用意
実践編第1回 で使った描画関数を使って敵を描画します。
この時変数で座標情報を作ってやりましょう。
#include "DxLib.h"
const char *TITLE = "Untitled";
const int WIN_WIDTH = 960;
const int WIN_HEIGHT = 540;
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow)
{
ChangeWindowMode(true);
SetWindowSizeChangeEnableFlag(false, false);
SetMainWindowText(TITLE);
SetGraphMode(WIN_WIDTH, WIN_HEIGHT, 32);
SetWindowSizeExtendRate(1.0);
SetBackgroundColor(35, 35, 35);
SetDrawScreen(DX_SCREEN_BACK);
if (DxLib_Init() == -1) { return -1; }
//敵の変数
int Enemy_X = 0;
int Enemy_Y = 50;
const int Enemy_Size = 50;
while (true) {
ClearDrawScreen();
//敵の描画
DrawBox(Enemy_X, Enemy_Y, Enemy_X + Enemy_Size, Enemy_Y + Enemy_Size, GetColor(255, 0, 0), true);
ScreenFlip();
if (ProcessMessage() == -1) { break; }
if (CheckHitKey(KEY_INPUT_ESCAPE) == 1) { break; }
}
DxLib_End();
return 0;
}
このコードを実行すると赤い四角の敵が画面左端に表示されます。
実行結果は以下の通りです。
敵の座標は変数ですのでこれを画面端に行くと折り返すif文
を書いて座標位置を更新することによって敵が自動で動くようになります。
今回はx軸
だけを等速
で移動させてみましょう。
こちらは 実践編第2回 の内容になります。
#include "DxLib.h"
const char *TITLE = "Untitled";
const int WIN_WIDTH = 960;
const int WIN_HEIGHT = 540;
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow)
{
ChangeWindowMode(true);
SetWindowSizeChangeEnableFlag(false, false);
SetMainWindowText(TITLE);
SetGraphMode(WIN_WIDTH, WIN_HEIGHT, 32);
SetWindowSizeExtendRate(1.0);
SetBackgroundColor(35, 35, 35);
SetDrawScreen(DX_SCREEN_BACK);
if (DxLib_Init() == -1) { return -1; }
//敵の変数
int Enemy_X = 0;
int Enemy_Y = 50;
int Enemy_Speed = 10;
const int Enemy_BaseSpeed = 10;
const int Enemy_Size = 50;
while (true) {
ClearDrawScreen();
//敵の座標更新処理
Enemy_X += Enemy_Speed;
//画面左端に来たらSpeedを正の値にする
if (Enemy_X < 0) {
Enemy_Speed = Enemy_BaseSpeed;
}
//画面右端に来たらSpeedを負の値にする
else if (Enemy_X > WIN_WIDTH - Enemy_Size) {
Enemy_Speed = -(Enemy_BaseSpeed);
}
//敵の描画
DrawBox(Enemy_X, Enemy_Y, Enemy_X + Enemy_Size, Enemy_Y + Enemy_Size, GetColor(255, 0, 0), true);
ScreenFlip();
if (ProcessMessage() == -1) { break; }
if (CheckHitKey(KEY_INPUT_ESCAPE) == 1) { break; }
}
DxLib_End();
return 0;
}
ここまででコードを書いて敵が左右に動いていれば成功です。
こんな感じになります。
・プレイヤー(自機)の準備
こちらも 実践編第2回 の内容で、キー入力の場所になります。
ここに関してはほぼコピペみたいなものなのでパパっと自機を用意しちゃいましょう。
#include "DxLib.h"
const char *TITLE = "Untitled";
const int WIN_WIDTH = 960;
const int WIN_HEIGHT = 540;
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow)
{
ChangeWindowMode(true);
SetWindowSizeChangeEnableFlag(false, false);
SetMainWindowText(TITLE);
SetGraphMode(WIN_WIDTH, WIN_HEIGHT, 32);
SetWindowSizeExtendRate(1.0);
SetBackgroundColor(35, 35, 35);
SetDrawScreen(DX_SCREEN_BACK);
if (DxLib_Init() == -1) { return -1; }
//敵の変数
int Enemy_X = 0;
int Enemy_Y = 50;
int Enemy_Speed = 10;
const int Enemy_BaseSpeed = 10;
const int Enemy_Size = 50;
//自機の変数
int Player_X = 400;
int Player_Y = 300;
const int Player_Speed = 5;
const int Player_Size = 50;
//キー情報格納変数
char keys[256];
while (true) {
ClearDrawScreen();
//すべてのキー情報を格納
GetHitKeyStateAll(keys);
//敵の座標更新処理
Enemy_X += Enemy_Speed;
if (Enemy_X < 0) {
Enemy_Speed = Enemy_BaseSpeed;
}
else if (Enemy_X > WIN_WIDTH - Enemy_Size) {
Enemy_Speed = -(Enemy_BaseSpeed);
}
//自機のオペレーション処理
if (keys[KEY_INPUT_A]) {
Player_X -= Player_Speed;
}
else if (keys[KEY_INPUT_D]) {
Player_X += Player_Speed;
}
if (keys[KEY_INPUT_W]) {
Player_Y -= Player_Speed;
}
else if (keys[KEY_INPUT_S]) {
Player_Y += Player_Speed;
}
//敵の描画
DrawBox(Enemy_X, Enemy_Y, Enemy_X + Enemy_Size, Enemy_Y + Enemy_Size, GetColor(255, 0, 0), true);
//自機の描画
DrawBox(Player_X, Player_Y, Player_X + Player_Size, Player_Y + Player_Size, GetColor(255, 255, 255), true);
ScreenFlip();
if (ProcessMessage() == -1) { break; }
if (CheckHitKey(KEY_INPUT_ESCAPE) == 1) { break; }
}
DxLib_End();
return 0;
}
ここまで書くと自動で動く敵とWASDで動かせる自機が表示されている状態になります。
それではここに敵が弾を出す処理をしていきましょう。
具体的な流れは以下のようになります。
敵から+y方向に弾を発射
↓
画面外に出たら消失 →プレーヤー(自機)に当たったらゲーム終了
↓
消えたらまた発射する
このプログラムではwhile文
でゲームループを作っているのでbreak
でループを抜けるとゲームが終了します。
これらを踏まえてまずは球の発射処理と描画処理を書いていきましょう。
#include "DxLib.h"
const char *TITLE = "Untitled";
const int WIN_WIDTH = 960;
const int WIN_HEIGHT = 540;
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow)
{
ChangeWindowMode(true);
SetWindowSizeChangeEnableFlag(false, false);
SetMainWindowText(TITLE);
SetGraphMode(WIN_WIDTH, WIN_HEIGHT, 32);
SetWindowSizeExtendRate(1.0);
SetBackgroundColor(35, 35, 35);
SetDrawScreen(DX_SCREEN_BACK);
if (DxLib_Init() == -1) { return -1; }
//敵の変数
int Enemy_X = 0;
int Enemy_Y = 50;
int Enemy_Speed = 10;
const int Enemy_BaseSpeed = 10;
const int Enemy_Size = 50;
//敵の弾の変数
int Bullet_X = Enemy_X;
int Bullet_Y = Enemy_Y;
const int Bullet_Speed = 10;
const int Bullet_R = 25;
//自機の変数
int Player_X = 400;
int Player_Y = 300;
const int Player_Speed = 5;
const int Player_Size = 50;
//キー情報格納変数
char keys[256];
while (true) {
ClearDrawScreen();
//すべてのキー情報を格納
GetHitKeyStateAll(keys);
//敵の座標更新処理
Enemy_X += Enemy_Speed;
if (Enemy_X < 0) {
Enemy_Speed = Enemy_BaseSpeed;
}
else if (Enemy_X > WIN_WIDTH - Enemy_Size) {
Enemy_Speed = -(Enemy_BaseSpeed);
}
//自機のオペレーション処理
if (keys[KEY_INPUT_A]) {
Player_X -= Player_Speed;
}
else if (keys[KEY_INPUT_D]) {
Player_X += Player_Speed;
}
if (keys[KEY_INPUT_W]) {
Player_Y -= Player_Speed;
}
else if (keys[KEY_INPUT_S]) {
Player_Y += Player_Speed;
}
//敵の弾の座標更新処理
Bullet_Y += Bullet_Speed;
if (Bullet_Y > WIN_HEIGHT + Bullet_R * 2) {
Bullet_X = Enemy_X;
Bullet_Y = Enemy_Y;
}
//弾の描画
DrawCircle(Bullet_X + Bullet_R, Bullet_Y + Bullet_R, Bullet_R, GetColor(255, 255, 255), true);
//敵の描画
DrawBox(Enemy_X, Enemy_Y, Enemy_X + Enemy_Size, Enemy_Y + Enemy_Size, GetColor(255, 0, 0), true);
//自機の描画
DrawBox(Player_X, Player_Y, Player_X + Player_Size, Player_Y + Player_Size, GetColor(255, 255, 255), true);
ScreenFlip();
if (ProcessMessage() == -1) { break; }
if (CheckHitKey(KEY_INPUT_ESCAPE) == 1) { break; }
}
DxLib_End();
return 0;
}
動きましたでしょうか?
白い球が勝手に発射される状態になっていれば成功です。
こんな感じです。
・当たり判定処理
最後に当たり判定を付けましょう!
当たり判定については 実践編第3回 で説明しています。
こちらもパパっと書いていきましょう。
当たったらbreak
でゲームループを抜け、勝手にゲームが終わるようにします。
今回の当たり判定はプレーヤーが左上を始点として描画しているので中心からの半径で判定をするため一辺の半分のピクセルずつ足して中心点を作っています。
該当コードの式は以下の場所です。
int a = (Player_X + (Player_Size / 2)) - Bullet_X;
int b = (Player_Y + (Player_Size / 2)) - Bullet_Y;
#include "DxLib.h"
#include <cmath>
const char *TITLE = "Untitled";
const int WIN_WIDTH = 960;
const int WIN_HEIGHT = 540;
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow)
{
ChangeWindowMode(true);
SetWindowSizeChangeEnableFlag(false, false);
SetMainWindowText(TITLE);
SetGraphMode(WIN_WIDTH, WIN_HEIGHT, 32);
SetWindowSizeExtendRate(1.0);
SetBackgroundColor(35, 35, 35);
SetDrawScreen(DX_SCREEN_BACK);
if (DxLib_Init() == -1) { return -1; }
//敵の変数
int Enemy_X = 0;
int Enemy_Y = 50;
int Enemy_Speed = 10;
const int Enemy_BaseSpeed = 10;
const int Enemy_Size = 50;
//敵の弾の変数
int Bullet_X = Enemy_X;
int Bullet_Y = Enemy_Y;
const int Bullet_Speed = 10;
const int Bullet_R = 25;
//自機の変数
int Player_X = 400;
int Player_Y = 300;
const int Player_Speed = 5;
const int Player_Size = 50;
//キー情報格納変数
char keys[256];
while (true) {
ClearDrawScreen();
//すべてのキー情報を格納
GetHitKeyStateAll(keys);
//敵の座標更新処理
Enemy_X += Enemy_Speed;
if (Enemy_X < 0) {
Enemy_Speed = Enemy_BaseSpeed;
}
else if (Enemy_X > WIN_WIDTH - Enemy_Size) {
Enemy_Speed = -(Enemy_BaseSpeed);
}
//自機のオペレーション処理
if (keys[KEY_INPUT_A]) {
Player_X -= Player_Speed;
}
else if (keys[KEY_INPUT_D]) {
Player_X += Player_Speed;
}
if (keys[KEY_INPUT_W]) {
Player_Y -= Player_Speed;
}
else if (keys[KEY_INPUT_S]) {
Player_Y += Player_Speed;
}
//敵の弾の座標更新処理
Bullet_Y += Bullet_Speed;
if (Bullet_Y > WIN_HEIGHT + Bullet_R * 2) {
Bullet_X = Enemy_X;
Bullet_Y = Enemy_Y;
}
//弾の描画
DrawCircle(Bullet_X + Bullet_R, Bullet_Y + Bullet_R, Bullet_R, GetColor(255, 255, 255), true);
//敵の描画
DrawBox(Enemy_X, Enemy_Y, Enemy_X + Enemy_Size, Enemy_Y + Enemy_Size, GetColor(255, 0, 0), true);
//自機の描画
DrawBox(Player_X, Player_Y, Player_X + Player_Size, Player_Y + Player_Size, GetColor(255, 255, 255), true);
//当たり判定
int a = (Player_X + (Player_Size / 2)) - Bullet_X;
int b = (Player_Y + (Player_Size / 2)) - Bullet_Y;
int sum = sqrt(pow(a, 2) + pow(b, 2));
//当たっていたらbreakでゲームを終了
if (sum <= (Player_Size / 2) + Bullet_R) {
break;
}
ScreenFlip();
if (ProcessMessage() == -1) { break; }
if (CheckHitKey(KEY_INPUT_ESCAPE) == 1) { break; }
}
DxLib_End();
return 0;
}
ここまでできたら今回は完成です!!!
お疲れ様でした(*'▽')
次回
次回はこのコードを簡潔にするために自分で関数を作る方法を紹介します。
この関数について覚えるとクラスというとっても便利な機能が理解しやすくなります!!!
あとがき
お疲れ様でした!まゆC#です(*'▽')
まだまだ忙しいですが投稿間隔を小さくすることができるようになってきますた!
実は今音ゲーの筐体を作るなどしていてタッチパネルの制御基板や組み込みプログラムを作ってたりもします...w
その様子はTwitterに上がっているのでそちらもぜひ!
・センサー作ってた時のツイート
・液晶タッチパネルの動作が成功して興奮気味のまゆしぃのツイート
あと次回から急に難易度が上がります!覚悟しておいてください!
作者について
詳しくはわんころメソッド();HPにどうぞ(*'▽')
・わんころメソッド();HP
・Twitter(まゆC#)
・GitHub(まゆC#)
コミュニティ
Discordで雑談から質問まで様々なチャンネルがあるまゆC#のコミュニティサーバーがあります!
わからないことがあればこちらでもお答えするのでぜひ!
Discordサーバー : わんころメソッド(雑談);