弾幕シューティングゲーム制作その3 スコアボードと敵キャラを動かす
前回は自キャラを動かすとこまでやりました。今回はスコアボードを表示、敵キャラを動かすところまでやりましょう。
スコアボードを制御するクラスを作る
背景画像の読み込み
最初に初期化処理で画像を読み込みます。
CShootingGame.csのInitalize関数に以下のコードを追加。
public int[] ImageEnemy = new int[9]; // 敵画像のイメージハンドルが入っている
public void Initalize()
{
// スコアボード読み込み
int[] index = new int[] { 10, 11, 12, 20 };
for (int i = 0; i < ImageBack.Length; ++i)
{
ImageBack[i] = DX.LoadGraph($"dat/img/board/{index[i]}.png");
if (ImageBack[i] == -1)
{
MessageBox.Show($"BackGroundの{i}番目のImageが読めこめません");
}
}
}
背景画像の描画
ファイルの新規追加で、CBackGround.csを追加。
DrawGraphFで以下の様に描画します。普通のDrawGraphとの違いは、Fが付いてる方はfloat型をついてない方は、int型を引数に受け取ります。画像のイメージハンドルはCShootingGame.ShootingGamePro.変数名でどこのファイルからでもアクセスできます。前回説明しましたが、using static宣言をしてるので、 CShootingGameは省略できます。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DxLibDLL;
using System.Windows.Forms;
using static ShootingGame.CShootingGame;
namespace ShootingGame
{
public class CBackGround : CMover
{
public CBackGround() : base(0,0,1)
{
}
public void Move()
{
}
public void Draw()
{
int[] x = new int[] { 0, 0, 0, 416 };
int[] y = new int[] { 0, 16, 464, 0 };
for(int i = 0; i < ShootingGamePro.ImageBack.Length; ++i)
{
DX.DrawGraphF(x[i], y[i], ShootingGamePro.ImageBack[i], 0);
}
}
}
}
背景の描画と追加
CShootingGame.csにInitalize関数に以下の一行を追加。先ほど背景画像を読み込んだ処理の下が良いでしょう。
- BackGround.Add(new CBackGround());
public int[] ImageEnemy = new int[9]; // 敵画像のイメージハンドルが入っている
public void Initalize()
{
// スコアボード読み込み
int[] index = new int[] { 10, 11, 12, 20 };
for (int i = 0; i < ImageBack.Length; ++i)
{
ImageBack[i] = DX.LoadGraph($"dat/img/board/{index[i]}.png");
if (ImageBack[i] == -1)
{
MessageBox.Show($"BackGroundの{i}番目のImageが読めこめません");
}
}
// ここに追加
BackGround.Add(new CBackGround());
}
次にMainLoop関数に以下の様に追加しましょう。
public void MainLoop()
{
// Lifeが0より多いなら処理する
foreach (var player in Player.Where(x => 0 < x.Life)) player.Move();
foreach (var back in BackGround.Where(x => 0 < x.Life)) back.Move();
foreach (var player in Player.Where(x => 0 < x.Life)) player.Draw();
foreach (var back in BackGround.Where(x => 0 < x.Life)) back.Draw();
// Lifeが0以下の要素を削除する
Player.RemoveAll(s => s.Life <= 0);
BackGround.RemoveAll(s => s.Life <= 0);
}
ここまでの処理で背景が描画されます。次に敵キャラを描画して動かす準備をします。
敵の描画
敵の描画も背景の追加とほとんど同じです。
しかし、それだけではつまらないので、少し動かしてみます。
まずは先ほどと画像の読み込み処理です。
CShootingGame.csのInitalize関数に以下のコードを追加。
public int[] ImageBack = new int[4]; // 敵画像のイメージハンドル
public void Initalize()
{
// 敵画像読み込み
if (DX.LoadDivGraph("dat/img/enemy/0.png", 9, 3, 3, 32, 32, ImageEnemy) == -1)
{
MessageBox.Show("プレイヤーの画像の読み込みに失敗しました");
}
}
CEnemy.csファイルを新規作成して以下のコードを追加
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DxLibDLL;
using System.Windows.Forms;
using static ShootingGame.CShootingGame;
namespace ShootingGame
{
public class CEnemy : CMover
{
int Muki;
int ImageCnt = 0;
public CEnemy(double x , double y , int life) : base(x, y, life)
{
Muki = 1;
}
public void Move()
{
if (Cnt < 60)
{
Y += 2.0;
}
if (Cnt > 60 + 240)
{
Y -= 2.0;
}
Cnt++;
ImageCnt = Muki * 3 + (Cnt % 18) / 6;
if (X < 10 + 20 || // 左座標の上限
X > FIELD_MAX_X - 10 + 42 || // 右座標の上限
Y < 0 || // 上座標の上限
Y > FIELD_MAX_Y - 5 + 20) // 下座標の上限
{
Life = 0;
}
}
public void Draw()
{
DX.DrawRotaGraphF((float)X, (float)Y, 1.0f, 0.0f, ShootingGamePro.ImageEnemy[ImageCnt], 1);
}
}
}
簡単に説明しておくとMove関数で、1フレームに1ずつ加算されるカウンターが、60になるになるまで、下に移動して、次にカウンターが300になるまで、上に移動します。そして、外にでたらLifeを0にして削除してます。
描画の際の注意点
DrawRotaGraphFを使って描画してます。 DrawGraphは座標の中心が左上になりますが、 DrawRotaGraphは座標の中心が真ん中なのに注意しましょう!
敵の描画と追加
これも背景と同じように追加します。
CShootingGame.csにInitalize関数に以下の一行を追加
- Enemy.Add(new CEnemy(FIELD_MAX_X / 2, 0, 1000));
最終的にCShootingGame.csのInitialize関数は以下のようになります。
public List<CPlayer> Player = new List<CPlayer>();
public List<CEnemy> Enemy = new List<CEnemy>();
public List<CBackGround> BackGround = new List<CBackGround>();
public int[] ImagePlayer = new int[12]; // プレイヤー画像のイメージハンドル
public int[] ImageBack = new int[4]; // 敵画像のイメージハンドル
public int[] ImageEnemy = new int[9]; // 敵画像のイメージハンドル
// 初期化関数
public void Initalize()
{
// ここに初期化処理を追加する
// 自キャラ画像の読み込み
if (DX.LoadDivGraph("dat/img/char/0.png", 12, 4, 3, 73, 73, ImagePlayer) == -1)
{
MessageBox.Show("プレイヤーの画像の読み込みに失敗しました");
}
// 敵画像読み込み
if (DX.LoadDivGraph("dat/img/enemy/0.png", 9, 3, 3, 32, 32, ImageEnemy) == -1)
{
MessageBox.Show("プレイヤーの画像の読み込みに失敗しました");
}
// スコアボード読み込み
int[] index = new int[] { 10, 11, 12, 20 };
for (int i = 0; i < ImageBack.Length; ++i)
{
ImageBack[i] = DX.LoadGraph($"dat/img/board/{index[i]}.png");
if (ImageBack[i] == -1)
{
MessageBox.Show($"BackGroundの{i}番目のImageが読めこめません");
}
}
Player.Add(new CPlayer(320, 240, 10));
Enemy.Add(new CEnemy(FIELD_MAX_X / 2, 0, 1000));
BackGround.Add(new CBackGround());
}
MainLoop関数も最終的には以下の様になります。
// 毎ループ処理する関数
public void MainLoop()
{
// ここに処理を追加する
// Lifeが0より多いなら処理する
foreach (var player in Player.Where(x => 0 < x.Life)) player.Move();
foreach (var enemy in Enemy.Where(x => 0 < x.Life)) enemy.Move();
foreach (var back in BackGround.Where(x => 0 < x.Life)) back.Move();
foreach (var player in Player.Where(x => 0 < x.Life)) player.Draw();
foreach (var enemy in Enemy.Where(x => 0 < x.Life)) enemy.Draw();
foreach (var back in BackGround.Where(x => 0 < x.Life)) back.Draw();
// Lifeが0以下の要素を削除する
Player.RemoveAll(s => s.Life <= 0);
Enemy.RemoveAll(s => s.Life <= 0);
BackGround.RemoveAll(s => s.Life <= 0);
CFpsControl.DrawFps(0, 465);
CFpsControl.FpsWait();
// 裏画面の内容を表画面に反映する
DX.ScreenFlip();
}
実行結果
念のためCShooting.csの中身もすべて載せておきます。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using DxLibDLL;
using System.Windows.Forms;
namespace ShootingGame
{
public class CShootingGame
{
public const int FIELD_X = 32;
public const int FIELD_Y = 16;
public const int FIELD_MAX_X = 384;
public const int FIELD_MAX_Y = 448;
// 実装は外部から隠蔽(privateにしておく)
private static CShootingGame ShootingGame; // staticで変数を宣言
// 変数の取得・変更用のプロパティ
public static CShootingGame ShootingGamePro
{
set { ShootingGame = value; }
get { return ShootingGame; }
}
public List<CPlayer> Player = new List<CPlayer>();
public List<CEnemy> Enemy = new List<CEnemy>();
public List<CBackGround> BackGround = new List<CBackGround>();
public int[] ImagePlayer = new int[12]; // プレイヤー画像のイメージハンドル
public int[] ImageBack = new int[4]; // 敵画像のイメージハンドル
public int[] ImageEnemy = new int[9]; // 敵画像のイメージハンドル
// 初期化関数
public void Initalize()
{
// ここに初期化処理を追加する
// 自キャラ画像の読み込み
if (DX.LoadDivGraph("dat/img/char/0.png", 12, 4, 3, 73, 73, ImagePlayer) == -1)
{
MessageBox.Show("プレイヤーの画像の読み込みに失敗しました");
}
// 敵画像読み込み
if (DX.LoadDivGraph("dat/img/enemy/0.png", 9, 3, 3, 32, 32, ImageEnemy) == -1)
{
MessageBox.Show("プレイヤーの画像の読み込みに失敗しました");
}
// スコアボード読み込み
int[] index = new int[] { 10, 11, 12, 20 };
for (int i = 0; i < ImageBack.Length; ++i)
{
ImageBack[i] = DX.LoadGraph($"dat/img/board/{index[i]}.png");
if (ImageBack[i] == -1)
{
MessageBox.Show($"BackGroundの{i}番目のImageが読めこめません");
}
}
Player.Add(new CPlayer(320, 240, 10));
//Enemy.Add(new CEnemy(FIELD_MAX_X / 2, 0, 1000));
BackGround.Add(new CBackGround());
}
// 毎ループ処理する関数
public void MainLoop()
{
// ここに処理を追加する
// Lifeが0より多いなら処理する
foreach (var player in Player.Where(x => 0 < x.Life)) player.Move();
foreach (var enemy in Enemy.Where(x => 0 < x.Life)) enemy.Move();
foreach (var back in BackGround.Where(x => 0 < x.Life)) back.Move();
foreach (var player in Player.Where(x => 0 < x.Life)) player.Draw();
foreach (var enemy in Enemy.Where(x => 0 < x.Life)) enemy.Draw();
foreach (var back in BackGround.Where(x => 0 < x.Life)) back.Draw();
// Lifeが0以下の要素を削除する
Player.RemoveAll(s => s.Life <= 0);
Enemy.RemoveAll(s => s.Life <= 0);
BackGround.RemoveAll(s => s.Life <= 0);
CFpsControl.DrawFps(0, 465);
CFpsControl.FpsWait();
// 裏画面の内容を表画面に反映する
DX.ScreenFlip();
}
// 終了処理関数
public void Destory()
{
// ここに終了処理を追加する
DX.DxLib_End();
}
}
}
今回のプロジェクトをダウンロードする。プロジェクトに画像は同梱してませんのでこちらからダウンロードしてください。画像を置く階層はデバッグモードで実行するなら、bin/Debugでリリースモードで実行するならbin/Releaseです。 それとダウンロードできるプロジェクトは少しいじっていて、Zキーを押すと敵が出現します。
今回は背景の描画と敵の移動と描画をしました。次回以降は弾を打たせて当たり判定を付けます。