C言語とDXライブラリで弾幕STGを作ってみよう！ Part3 自機編 (1)

#はじめに
前回は画像を表示できるようにした。
今回は自機を描画して移動できるようにしよう。

#やること
- 自機の描画
- ゲームループ
- 自機の移動

#自機の描画
前回作成したソースコードを改良して作成する。
今回は自機のx座標をウインドウの中心に持っていきたいため、グローバル変数にてウインドウの大きさを宣言し、それを用いる。(~~グローバル変数が嫌いな人も目をつぶってください...~~)
自機の画像として以下を用いる。

![jiki.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/338279/dda77c10-71a0-39a9-cd2f-c08ae57407e8.png)

```c++:main3.cpp
#include "DxLib.h"

const int WIN_W = 960, WIN_H = 720; //ウインドウの幅と高さ

void setup()
{
	SetOutApplicationLogValidFlag(FALSE);
	SetAlwaysRunFlag(TRUE);
	SetWindowSizeChangeEnableFlag(TRUE);
	SetFullScreenResolutionMode(DX_FSRESOLUTIONMODE_DESKTOP);
	SetWindowText("シューティングゲーム");
	ChangeWindowMode(TRUE);
	SetGraphMode(WIN_W, WIN_H, 32);
}

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
	setup();
	if (DxLib_Init() == -1) return 1;

	int image = LoadGraph("./jiki.png");
	float x = WIN_W / 2.0f, y = WIN_H - 100.0f; //自機の画像の中心のx,y座標
	DrawRotaGraphF(x, y, 1.0f, 0.0f, image, TRUE);

	WaitKey();
	DxLib_End();

	return 0;
}
```
画像の中心の座標を設定して描画するため、描画関数にはDrawRotaGraphを用いるところだが、後に自機の座標に小数を用いるためにfloat型(小数)に対応した `DrawRotaGraphF` 関数を用いる。

実行すると以下のようなウインドウが出てくる。
![シューティングゲーム 2019_12_17 14_08_29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/338279/1463baa2-aef1-f05a-f481-bcee92a16ccf.png)

#ゲームループ
次は上記コードを特定のキー(今回はエスケープキー)が押されたことによって終了するようにwhileループを用いて変更する。
whileループは、 `while(条件式)` の条件式を満たしている間ループすることから、エスケープキーが押されていない間ループするようにすればよい。
特定のキーが押されているか確認するためにDXライブラリの `CheckHitKey` 関数を用いる。
Zキーが押されているか確認するときは `CheckHitKey(KEY_INPUT_Z)` 、エスケープキーの場合は `CheckHitKey(KEY_INPUT_ESCAPE)` とする。
この関数は、確認したいキーが押されていないときは0を、押されているときは1を返すので、エスケープキーが押されていない間ループされるときは

```
CheckHitKey(KEY_INPUT_ESCAPE) == 0
```

とする。

当該のコードを以下に示す。(main関数のみ)

```cpp:main4.cpp(一部)
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
	setup();
	if (DxLib_Init() == -1) return 1;

	int image = LoadGraph("./jiki.png");
	float x = WIN_W / 2.0f, y = WIN_H - 100.0f;

	while (CheckHitKey(KEY_INPUT_ESCAPE) == 0) {
		ProcessMessage(); //ループ毎に呼び出さなくてはならない関数
		DrawRotaGraphF(x, y, 1.0f, 0.0f, image, TRUE);
	}

	DxLib_End();

	return 0;
}
```

DXライブラリでループを用いて画像を毎フレーム描画する場合、 `ProcessMessage` 関数を毎フレーム呼び出さなくてはならない。~~めんどくさいですね~~

実行してエスケープキーを押して終了できることを確認してみよう。

#自機の移動
これからは変数x, yを毎フレーム変更して、自機を移動できるようにしよう。
先ほど使った `CheckHitKey` 関数を利用して矢印キーを用いて自機を移動させることを考えよう。

```cpp:main5.cpp(一部)
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
	setup();
	if (DxLib_Init() == -1) return 1;

	int image = LoadGraph("./jiki.png");
	float x = WIN_W / 2.0f, y = WIN_H - 100.0f;
	const float MOVE_SPEED = 3.0f;

	while (CheckHitKey(KEY_INPUT_ESCAPE) == 0) {
		ProcessMessage();
		if (CheckHitKey(KEY_INPUT_LEFT) == 1) x -= MOVE_SPEED;
		if (CheckHitKey(KEY_INPUT_RIGHT) == 1) x += MOVE_SPEED;
		if (CheckHitKey(KEY_INPUT_UP) == 1) y -= MOVE_SPEED;
		if (CheckHitKey(KEY_INPUT_DOWN) == 1) y += MOVE_SPEED;
		DrawRotaGraphF(x, y, 1.0f, 0.0f, image, TRUE);
	}

	DxLib_End();

	return 0;
}
```

実行してみると、矢印キーを押した瞬間すごい速度で自機が移動していき、残像が残ったままになったと思います。

![シューティングゲーム 2019_12_17 20_53_49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/338279/f523f7b5-0b68-085b-3fa2-ab89543b12c9.png)


これは、画面が初期化されずに自機が次々と描画されたことによるもので、DXライブラリの裏画面描画機能を使うことで解決します。

裏画面描画とは、簡単に言えば2つのキャンバスを用意して、片方の描画されているものをすべて消して、そこに描画して、画面に表示しているもう一つのキャンバスと入れ替えることを繰り返していくことである。
今までのように1つのキャンバスを消して描画してという方法でも問題はないが、画面を消してから次のシーンを描画するまでにちらつきが発生するので裏画面に描画することにする。
ついでに、自機が画面外にはみ出さないように処理したものを以下に示す

```cpp:main6.cpp(一部)
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
	setup();
	if (DxLib_Init() == -1) return 1;

	SetDrawScreen(DX_SCREEN_BACK); //裏画面処理を設定する

	int image = LoadGraph("./jiki.png");
	float x = WIN_W / 2.0f, y = WIN_H - 100.0f;
	const float MOVE_SPEED = 3.0f;

	while (CheckHitKey(KEY_INPUT_ESCAPE) == 0) {
		ProcessMessage();
		ClearDrawScreen(); //裏画面をクリアする

		if (CheckHitKey(KEY_INPUT_LEFT) == 1) x -= MOVE_SPEED;
		if (CheckHitKey(KEY_INPUT_RIGHT) == 1) x += MOVE_SPEED;
		if (CheckHitKey(KEY_INPUT_UP) == 1) y -= MOVE_SPEED;
		if (CheckHitKey(KEY_INPUT_DOWN) == 1) y += MOVE_SPEED;

		//自機の画面外へのはみ出しをチェックする
		if (x < 0.0f) x = 0.0f;
		if (x > (float)WIN_W) x = (float)WIN_W;
		if (y < 0.0f) y = 0.0f;
		if (y > (float)WIN_H) y = (float)WIN_H;

		DrawRotaGraphF(x, y, 1.0f, 0.0f, image, TRUE);
		ScreenFlip(); //2つの画面を入れ替える
	}

	DxLib_End();

	return 0;
}
```

これを実行するといい感じに自機が移動したと思います。

#まとめ
今回は自機の初歩的な部分を実装しました。
自機を表示したら次は敵ですね。次回は敵を実装してみましょう。