【新歓ブログリレー】DXライブラリでゲームプログラミング
はじめに
こんにちはUmiです。今回は、題名通りDXライブラリというc++用のゲーム開発用ライブラリでゲームプログラミングをしようと思います。と言ってもガチガチにソースコードを書くというよりは初心者向けに出来るだけ簡単に必要なものだけに絞って書きます。
具体的なブログラム内容としては、
・プレイヤー(自機)が十字方向に動ける
・横移動する敵を作る
・プレイヤーから弾を出し、敵に当たったら敵が消滅する
の三つだけです。このブログでDXライブラリでの書き方をなんとなく感じてもらえたら幸いです。それと、DXライブラリの入れ方などは公式のHPを見てもらえれば分かりやすい説明があるので本ブログでは行わないのであしからず。
ソースコードとコード解説
#include "DxLib.h" //ライブラリの呼び出し
int ObjCol(int x1, int y1, int r1, int x2, int y2, int r2) { //衝突判定の関数
if (x1 + r1 >= x2 - r2 && x1 - r1 <= x2 + r2 && y1 + r1 >= y2 - r2 && y1 - r1 <= y2 + r2) {
return 1;
}
else {
return 0;
}
}//0…衝突なし 1…衝突
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
ChangeWindowMode(TRUE); //ウインドウモードに変更する
if (DxLib_Init() == -1) //Dxlibの初期化処理
{
return -1;
}
SetDrawScreen(DX_SCREEN_BACK); //裏面に描画する
char Buf[256]; //キー入力用バッファー
bool push_flag[256] = {0};
bool shot_flag = 0;
int Px = 200;
int Py = 240;
int Pr = 10;
int Bx = -100;
int By = -100;
int Br = 10;
int Ex = 700;
int Ey = 240;
int Er = 10;
int score = 0;
while (ProcessMessage() == 0 && ScreenFlip() == 0 && ClearDrawScreen() == 0) {
GetHitKeyStateAll(Buf); //キー入力用関数
//処理
if (Buf[KEY_INPUT_RIGHT] == 1) { //プレイヤーの移動
Px += 2;
}
if (Buf[KEY_INPUT_LEFT] == 1) {
Px -= 2;
}
if (Buf[KEY_INPUT_DOWN] == 1) {
Py += 2;
}
if (Buf[KEY_INPUT_UP] == 1) {
Py -= 2;
}
if (Buf[KEY_INPUT_SPACE] == 1 && push_flag[KEY_INPUT_SPACE] == 0) { //弾の発射
push_flag[KEY_INPUT_SPACE] = 1;
shot_flag = 1;
Bx = Px + 20;
By = Py;
}
if (shot_flag == 1) { //弾があるなら弾を右に移動
Bx += 4;
}
if (Bx >= 650) { //弾が画面外に出たら消す
shot_flag = 0;
Bx = -100;
By = -100;
}
if (ObjCol(Bx, By, Br, Ex, Ey, Er) == 1) { //弾と敵の衝突判定
shot_flag = 0;
Bx = -100;
By = -100;
Ex = 700;
Ey = GetRand(460) + 10;
score++;
}
Ex -= 2; //敵の移動(左)
for (int i = 0; i < 256; i++) {
if (Buf[i] == 0)push_flag[i] = 0;
}
//描画
DrawBox(Px - Pr, Py - Pr, Px + Pr, Py + Pr, GetColor(0, 255, 0), TRUE); //プレイヤー
DrawBox(Bx - Br, By - Br, Bx + Br, By + Br, GetColor(0, 0, 255), TRUE); //弾
DrawBox(Ex - Er, Ey - Er, Ex + Er, Ey + Er, GetColor(255, 0, 0), TRUE); //敵
DrawFormatString(0, 0, GetColor(255, 255, 255), "スコア:%d", score); //スコア表示
if (Buf[KEY_INPUT_ESCAPE] == 1) { //ESCキーが押された場合終了する
break;
}
}
DxLib_End(); // DXライブラリ使用の終了処理
return 0; // ソフトの終了
}
では、上の方からソースコードの解説をしていきたいと思います。
#include "DxLib.h" //ライブラリの呼び出し
int ObjCol(int x1, int y1, int r1, int x2, int y2, int r2) { //衝突判定の関数
if (x1 + r1 >= x2 - r2 && x1 - r1 <= x2 + r2 && y1 + r1 >= y2 - r2 && y1 - r1 <= y2 + r2) {
return 1;
}
else {
return 0;
}
}//0…衝突なし 1…衝突
一番上にある文は皆さんお馴染みのinclude文でDXライブラリの関数を使えるようにしてくれるものです。
その次の関数(ObjCol関数)は私が適当に作った正方形と正方形の衝突判定を行ってくれるもので、衝突したなら1を返し、衝突しなかったら0を返します。この理論的な内容は以前私が投稿したブログにあるのでぜひ見てください。
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
ChangeWindowMode(TRUE); //ウインドウモードに変更する
if (DxLib_Init() == -1) //Dxlibの初期化処理
{
return -1;
}
SetDrawScreen(DX_SCREEN_BACK); //裏面に描画する
一番上のWinMainは通常のc++のmain関数と同じもので意味を理解する必要はないと思います(少なくともゲーム作りには支障はないはず)。ここは、DXライブラリを使うための呪文と思ってください。
その次のChangeWindowModeはその名の通り表示するゲームの画面をウィンドウモードにするもので私はデバッグ時や開発時にはウィンドウモードにしてるので使っています(ちなみにこの関数の引数をFALSEにするとフルスクリーンになります)。
Dxlib_InitはDXライブラリを初期化、つまり使用できる状態にする関数です。
最後にSetDrawScreenですが、これはゲーム画面の描画をどこに行うかを決定する関数です。どういう意味なのかというと、実はゲーム画面は表と裏の二面があります。表は直接表示される画面で裏は直接表示されない画面です。なら、表に書けば良いだろうと思うかもしれませんが実際はあまり良くありません。それは、表に直接描くと描いている途中の画面なども映り画面がちらついてしまうからです。なので、裏の画面に描画を行い描画し終わったものを表にコピーする形になるわけです。
char Buf[256];
bool push_flag[256] = {0};
bool shot_flag = 0;
int Px = 200;
int Py = 240;
int Pr = 10;
int Bx = -100;
int By = -100;
int Br = 10;
int Ex = 700;
int Ey = 240;
int Er = 10;
int score = 0;
char型配列のBuf変数はこの後使用するGetHitKeyStateAllの引数に使用するもので、これを使用するとキーボードの入力をゲームに反映できるようになります。また、このGetHitKeyStateAllは押している(1の状態)か押していないか(0の状態)の二つでしか判断できず、押しっぱなしという状態が判断できません。それを改善するのがbool型のpush_flagです。理屈としては、あるキー(ここではWキーとする)を押したときBufのWキーに相当する配列が1になる。それと同時にpush_flagのWキーに相当する配列を1にする。こうすることにより、Bufとpush_flagが両方1の時押しっぱなしであると判断できるようになるというものです。
次にshot_flagですが、これは弾が生きているかの変数です(1なら生きている)。主に、弾が動くか動かないかの設定に使用しています。
それ以降の変数ですが、P、B、Eとついてるものはそれぞれプレイヤー、弾、敵に関しての変数になっています。例えばPxはプレイヤーのx座標、Byは弾のy座標、Erは敵の半径(正方形なので一辺の長さの半分)となっています。ちなみに、scoreは敵に消滅させた回数のための変数になっています。
while (ProcessMessage() == 0 && ScreenFlip() == 0 && ClearDrawScreen() == 0) {
GetHitKeyStateAll(Buf); //キー入力用関数
このwhie文はゲームのループ部分となっていて(ゲームを動かすためにはループさせその度に描画しないといけない(このループの一秒間の回数が俗に言うfpsである))その引数の関数はそれぞれ
ProcessMessage ―― WindowsやAndroidアプリのメッセージ(イベント)処理をしてくれるもので一回のループに一度行うことが推奨されている。
ScreenFlip ―― 裏画面を表画面に反映する関数。
ClearDrawScreen ―― 裏画面の描画をクリアする関数。ScreenFlip関数を使用して後は使用しないといけない。
という機能となっています。また、次のGetHitKeyStateAllは上記で説明したため割愛します。
//処理
if (Buf[KEY_INPUT_RIGHT] == 1) { //プレイヤーの移動
Px += 2;
}
if (Buf[KEY_INPUT_LEFT] == 1) {
Px -= 2;
}
if (Buf[KEY_INPUT_DOWN] == 1) {
Py += 2;
}
if (Buf[KEY_INPUT_UP] == 1) {
Py -= 2;
}
上の四つのif文はそれぞれ右方向、左方向、下方向、上方向のプレイヤーの移動に関しての文です。
if文の条件式(括弧の中の文)は、それぞれキーボード右、左、下、上ボタンが押されたかの条件式であり、GetHitKeyStateAllではこのように書きます。
if (Buf[KEY_INPUT_SPACE] == 1 && push_flag[KEY_INPUT_SPACE] == 0) { //弾の発射
push_flag[KEY_INPUT_SPACE] = 1;
shot_flag = 1;
Bx = Px + 20;
By = Py;
}
if (shot_flag == 1) { //弾があるなら弾を右に移動
Bx += 4;
}
if (Bx >= 650) { //弾が画面外に出たら消す
shot_flag = 0;
Bx = -100;
By = -100;
}
if (ObjCol(Bx, By, Br, Ex, Ey, Er) == 1) { //弾と敵の衝突判定
shot_flag = 0;
Bx = -100;
By = -100;
Ex = 700;
Ey = GetRand(460) + 10;
score++;
}
if文を上から順に説明すると、一番上は弾の発射に関してのif文であり、条件式の内容はスペースボタンを押した瞬間だけ真となるようにしています。if文の中身は、push_flagを1にすること(スペースボタンが押しっぱなしになっている状態にする)、弾が生きている判定にすること、弾の座標をプレイヤーの前方部に変更することです。
次のif文は弾が生きている時、弾を右側に移動させるというものです。
三個目のif文は弾が画面外に出たらshot_flagを0にし弾の位置をゲームに支障の出ない位置(今回はx,y座標(-100,-100)地点)に移動させるものです。
最後のif文は弾と敵の衝突判定の文であり、弾が敵に当たった時、弾を消し弾の位置を初期化、敵の位置の初期化、スコアを1上げるようにしています。ちなみに、敵のy座標の初期化にはGetRand関数(0から引数までの乱数を返り値に出す関数)でランダムにしています。
Ex -= 2; //敵の移動(左)
for (int i = 0; i < 256; i++) {
if (Buf[i] == 0)push_flag[i] = 0;
}
上の文は敵の左向きの移動のための文です(それ以外特になし)。
下の文はキーボードのボタンが押されていなかったら、押しっぱなしのフラグを消すというものです(例えばWキーを話したらWキーに相当するpush_flagが0になる)。
//描画
DrawBox(Px - Pr, Py - Pr, Px + Pr, Py + Pr, GetColor(0, 255, 0), TRUE); //プレイヤー
DrawBox(Bx - Br, By - Br, Bx + Br, By + Br, GetColor(0, 0, 255), TRUE); //弾
DrawBox(Ex - Er, Ey - Er, Ex + Er, Ey + Er, GetColor(255, 0, 0), TRUE); //敵
DrawFormatString(0, 0, GetColor(255, 255, 255), "スコア:%d", score); //スコア表示
if (Buf[KEY_INPUT_ESCAPE] == 1) { //ESCキーが押された場合終了する
break;
}
DrawBox関数は長方形を描画する関数で左上の点の座標と右下の点の座標、Getcolor関数(それぞれの引数にRGBの値(0~256)を入れ色を作る関数)、塗りつぶすかの判定(TRUEで塗りつぶし)を引数に持っています。この関数によりプレイヤーと弾、敵の描画を行っています。
次にDrawFormatString関数は文章を描画する関数で文字の左上の座標とGetcolor関数、文を引数に待っています。今回はスコア表示に使っています。また、今回は使用しませんでしたが、他の関数をこの関数の前に使用することにより文字のフォントを変えることも出来ます。
最後のif文はESCキーを押した時、ゲームのループを抜けるようにしたものです。ちなみに、学園際などにゲームを出すときはサークル内で終了キーを統一する(ESCキーを終了キーにするなど)とゲーム制作者本人以外が消すとき楽だと思います(高校の時タスクマネージャーでしか消せないゲームがあって面倒だったので…)。
DxLib_End(); // DXライブラリ使用の終了処理
return 0; // ソフトの終了
}
Dxlib_EndはDXライブラリを終了させる関数でDXライブラリを使うプログラムの時は最後にはこの関数でDXライブラリを終わらせましょう。また、こういう風に始めたものや開いたものは終了させる、閉じるという事が必要なことが多いです(プログラム中にファイルを開くときなど)。
実際のゲーム画面
緑色がプレイヤーで赤色が敵です。ちなみに、敵は左に移動しているのですが静止画のため分からないかもしれません。
ここでスペースキーを押すと、さっきプログラムした通り、
プレイヤーから弾(青色の四角)が出ます。ここから、少し経つと、
とこのように青色と赤色の四角が消え、スコアが一つ上がりました。
最後に
初心者向けに簡単にゲームプログラミングをしましたが、これだけじゃゲームとしてはもちろんお粗末です。理由の例を挙げれば、弾が一つしか出せない(二つ目を出そうとすると一つ目の弾が消える)、敵が一人しか出てこないかつ動きが単調すぎる、ゲームオーバーやゲームクリアがない(ついでに言えばタイトル画面もスコア(リザルト)画面もない)などなどたくさんあります。
しかし、初心者が作るならまずはこのぐらいでも全然大丈夫と私は思います。というか、最悪プレイヤーが十字方向に動くくらいでも良いと思います。私は、まず何よりも自分で何かを作るということが重要だと思っています。例えそれが、お手本を見ながらでも自分自身が作ったという実感があればモチベにつながると思っています。実際、私も高校の時、部活の先輩のシューティングゲームのプログラムを見ながら自分で作った体験があるから今もこうしてプログラムを続けています。
なので、もし私の書いたこのプログラムで同じように自分でプログラムを書き、それがモチベにつながればと思っています。
これで、今年のブログリレーは終わりとなります。私たち部員一同のブログが新入生の方々の助けになることを願っています。
コメント入力