弾幕シューティングゲーム制作その10 背景スクロール処理追加
こんにちはジェイです!
前回でエフェクトを追加したので、今回は背景のスクロール処理を追加します。
CBackGroundの修正
今までスコアボードを描画するクラスをCBackGroundクラスと定義していました。
今になってようやく気づいて、余計な修正しないといけなくなってすみません><
このままで、いこうかとも迷いましたが、後からわかりずらくなるで修正します。
置換機能で修正する

まず、一番下をソリューション全体にした後に上の図を参考に以下の手順をします。
- 上の欄にCBackGroundと入力し、下の欄にCBoardと入力した後すべてのワードを置換
- 上の欄にBackGroundと入力し、下の欄にBoardと入力した後すべてのワードを置換
- ファイル名をCBackGround.csからCBroadに変える
- 上の欄にImageBackのと入力し、下の欄にImageBoardと入力した後すべてのワードを置換
以上がCBackGroundクラスをCBoradクラスに変更する方法です。
背景をスクロールさせる処理の追加
背景画像の読み込み
いつもの様にLoadGraphを使って画像を読み込みます。
public int[] ImageBack = new int[3]; // 背景画像のイメージハンドル
public void Initalize()
{
//通常スクロール時の背景画像
for (int i = 0; i < ImageBack.Length; ++i)
{
ImageBack[i] = DX.LoadGraph($"dat/img/back/0/back{i}.png");
if (ImageBack[i] == -1)
{
MessageBox.Show($"ImageBackの{i}番目のImageが読めこめません");
}
}
}
まずは以下の様にCBackクラスを追加します。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DxLibDLL;
using static ShootingGame.CShootingGame;
namespace ShootingGame
{
public class CBack : CMover
{
public CBack() : base(0, 0, 1)
{
}
public void Move()
{
// 0~699までの数字をループさせる
Cnt = (Cnt + 1) % 700;
}
public void Draw()
{
DX.SetDrawArea(32, 16, 416, 464);//描画可能エリアを設定
DX.DrawGraphF(32, Cnt - 700, SGP.ImageBack[0], 0);
DX.DrawGraphF(32, Cnt, SGP.ImageBack[0], 0);
DX.SetDrawArea(0, 0, 640, 480);//エリアを戻す
}
}
}
SetDrawAreaで描画エリアを制限しスクロールする背景を描画した後に描画エリアを元に戻します。
今までプレイヤー、敵、エフェクトなど様々なオブジェクトを追加してきましたが、必ず決まったパターンで追加してきました。
1.LoadGraphやLoadDivGraphで画像のイメージハンドルを読み込む
2.抽象クラスやインターフェイスを使って派生クラスのMoveとDrawをオーバーライドする
3.2で作った、基底クラス型のListに派生クラス型のオブジェクトをnewで生成し、ListにAddする
以上の流れになります。もうずっとこの流れで作業してきましたが、これから先も何か追加する時は、すべて上のサイクルで作ります。もちろんシューティングゲームに関わらず、他のすべてのゲームに使える重要なテクニックなので、ぜひ覚えてください。
では次に、CShooting.csのInitalize関数にBack.Add(new CBack);で追加しましょう。
今回修正も加えたので、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 double PI = 3.1415926535898;
public const double PI2 = PI * 2;
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 SGP
{
set { ShootingGame = value; }
get { return ShootingGame; }
}
public List<CPlayer> Player = new List<CPlayer>();
public List<CEnemy> Enemy = new List<CEnemy>();
public List<CShot> Shot = new List<CShot>();
public List<CBullet> Bullet = new List<CBullet>();
public List<CEffect> Effect = new List<CEffect>();
public List<CBorad> Borad = new List<CBorad>();
public List<CBack> Back = new List<CBack>();
public List<CPlayer> PlayerBuf = new List<CPlayer>();
public List<CEnemy> EnemyBuf = new List<CEnemy>();
public List<CShot> ShotBuf = new List<CShot>();
public List<CBullet> BulletBuf = new List<CBullet>();
public List<CEffect> EffectBuf = new List<CEffect>();
public int[] ImagePlayer = new int[12]; // プレイヤー画像のイメージハンドル
public int[] ImageBorad = new int[4]; // ボード画像のイメージハンドル
public int[] ImageBack = new int[3]; // 背景画像のイメージハンドル
public int[] ImageEnemy = new int[9]; // 敵画像のイメージハンドル
public int[] ImageShot = new int[5]; // ショット画像のイメージハンドル
public int[][] ImageBullet = new int[20][]; // 敵弾画像のイメージハンドル
public int[] ImageHitEffect = new int[5]; // エフェクト画像のイメージハンドル
public CCommandManager CommandManager = new CCommandManager();
// 初期化関数
public void Initalize()
{
string file_name = "dat/stage/stage1.txt";
// ここに初期化処理を追加する
if (!CommandManager.LoadScript(file_name))
{
MessageBox.Show($"{file_name}が開けませんでした");
DX.WaitKey();
DX.DxLib_End();
return;
}
// 自キャラ画像の読み込み
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("プレイヤーの画像の読み込みに失敗しました");
}
//通常スクロール時の背景画像
for (int i = 0; i < ImageBack.Length; ++i)
{
ImageBack[i] = DX.LoadGraph($"dat/img/back/0/back{i}.png");
if (ImageBack[i] == -1)
{
MessageBox.Show($"ImageBackの{i}番目のImageが読めこめません");
}
}
// スコアボード読み込み
int[] index = { 10, 11, 12, 20 };
for (int i = 0; i < ImageBorad.Length; ++i)
{
ImageBorad[i] = DX.LoadGraph($"dat/img/board/{index[i]}.png");
if (ImageBorad[i] == -1)
{
MessageBox.Show($"Boradの{i}番目のImageが読めこめません");
}
}
// ショット画像の読み込み
string[] img_str = new string[] { "bl_00", "bl1", "bl_01", "bl_011", "bl_11" };
for(int i = 0; i < img_str.Length; ++i)
{
ImageShot[i] = DX.LoadGraph($"dat/img/char/{img_str[i]}.png");
if (ImageShot[i] == -1)
{
MessageBox.Show($"ImageShotの{i}番目のImageが読めこめません");
}
}
ImageBullet[0] = new int[5];
ImageBullet[1] = new int[6];
ImageBullet[2] = new int[10];
ImageBullet[3] = new int[5];
ImageBullet[4] = new int[10];
ImageBullet[5] = new int[3];
ImageBullet[6] = new int[3];
ImageBullet[7] = new int[10];
ImageBullet[8] = new int[10];
ImageBullet[9] = new int[3];
ImageBullet[10] = new int[8];
ImageBullet[11] = new int[8];
ImageBullet[12] = new int[10];
ImageBullet[13] = new int[10];
ImageBullet[14] = new int[4];
ImageBullet[15] = new int[3];
if (DX.LoadDivGraph("dat/img/bullet/b0.png", 5, 5, 1, 76, 76, ImageBullet[0]) == -1) MessageBox.Show("画像ファイル読み込み失敗");
if (DX.LoadDivGraph("dat/img/bullet/b1.png", 6, 6, 1, 22, 22, ImageBullet[1]) == -1) MessageBox.Show("画像ファイル読み込み失敗");
if (DX.LoadDivGraph("dat/img/bullet/b2.png", 10, 10, 1, 5, 120, ImageBullet[2]) == -1) MessageBox.Show("画像ファイル読み込み失敗");
if (DX.LoadDivGraph("dat/img/bullet/b3.png", 5, 5, 1, 19, 34, ImageBullet[3]) == -1) MessageBox.Show("画像ファイル読み込み失敗");
if (DX.LoadDivGraph("dat/img/bullet/b4.png", 10, 10, 1, 38, 38, ImageBullet[4]) == -1) MessageBox.Show("画像ファイル読み込み失敗");
if (DX.LoadDivGraph("dat/img/bullet/b5.png", 3, 3, 1, 14, 16, ImageBullet[5]) == -1) MessageBox.Show("画像ファイル読み込み失敗");
if (DX.LoadDivGraph("dat/img/bullet/b6.png", 3, 3, 1, 14, 18, ImageBullet[6]) == -1) MessageBox.Show("画像ファイル読み込み失敗");
if (DX.LoadDivGraph("dat/img/bullet/b7.png", 10, 10, 1, 16, 16, ImageBullet[7]) == -1) MessageBox.Show("画像ファイル読み込み失敗");
if (DX.LoadDivGraph("dat/img/bullet/b8.png", 10, 10, 1, 12, 18, ImageBullet[8]) == -1) MessageBox.Show("b8.png読み込み失敗");
if (DX.LoadDivGraph("dat/img/bullet/b9.png", 3, 3, 1, 13, 19, ImageBullet[9]) == -1) MessageBox.Show("b9.png読み込み失敗");
if (DX.LoadDivGraph("dat/img/bullet/b10.png", 8, 8, 1, 8, 8, ImageBullet[10]) == -1) MessageBox.Show("b10.png読み込み失敗");
if (DX.LoadDivGraph("dat/img/bullet/b11.png", 8, 8, 1, 35, 32, ImageBullet[11]) == -1) MessageBox.Show("b11.png読み込み失敗");
if (DX.LoadDivGraph("dat/img/bullet/b12.png", 10, 10, 1, 12, 12, ImageBullet[12]) == -1) MessageBox.Show("b12.png読み込み失敗");
if (DX.LoadDivGraph("dat/img/bullet/b13.png", 10, 10, 1, 22, 22, ImageBullet[13]) == -1) MessageBox.Show("b13.png読み込み失敗");
if (DX.LoadDivGraph("dat/img/bullet/b14.png", 4, 4, 1, 6, 6, ImageBullet[14]) == -1) MessageBox.Show("b14.png読み込み失敗");//(56)
if (DX.LoadDivGraph("dat/img/bullet/b15.png", 3, 3, 1, 19, 18, ImageBullet[15]) == -1) MessageBox.Show("b15.png読み込み失敗");//花弾
// 敵消滅エフェクト
if (DX.LoadDivGraph("dat/img/enemy/hit_effect.png", 5, 5, 1, 140, 140, ImageHitEffect) == -1) MessageBox.Show("hit_effect.png読み込み失敗");
string[] sound_str = {
"enemy_shot.wav", "enemy_death.wav", "cshot.wav",
"char_death.wav", "b_change.wav", "power.wav",
"hit.wav", "boss_death.wav","boss_change.wav"
,"bom0.wav", "bom1.wav","lazer.wav",
"item_get.wav","shot3.ogg","final_hit.wav" };
for (int i = 0; i < sound_str.Length; ++i)
{
SoundSE[i] = DX.LoadSoundMem($"dat/se/{sound_str[i]}");
if(SoundSE[i] == -1)
{
MessageBox.Show($"dat/se/{sound_str[i]}が読み込めません");
}
}
PlayerBuf.Add(new CPlayer(FIELD_MAX_X/2, 240, 1));
Borad.Add(new CBorad());
Back.Add(new CBack());
}
// 毎ループ処理する関数
public void MainLoop()
{
CommandManager.Run();
if (0 < PlayerBuf.Count) { Player.AddRange(PlayerBuf); PlayerBuf.Clear(); }
if (0 < EnemyBuf.Count) { Enemy.AddRange(EnemyBuf); EnemyBuf.Clear(); }
if (0 < ShotBuf.Count) { Shot.AddRange(ShotBuf); ShotBuf.Clear(); }
if (0 < BulletBuf.Count) { Bullet.AddRange(BulletBuf); BulletBuf.Clear(); }
if (0 < EffectBuf.Count) { Effect.AddRange(EffectBuf); EffectBuf.Clear(); }
// ここに処理を追加する
// Lifeが0より多いなら処理する
foreach (var back in Back.Where(x => 0 < x.Life)) back.Move();
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 shot in Shot.Where(x => 0 < x.Life)) shot.Move();
foreach (var bullet in Bullet.Where(x => 0 < x.Life)) bullet.Move();
foreach (var effect in Effect.Where(x => 0 < x.Life)) effect.Move();
foreach (var borad in Borad.Where(x => 0 < x.Life)) borad.Move();
foreach (var back in Back.Where(x => 0 < x.Life)) back.Draw();
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 shot in Shot.Where(x => 0 < x.Life)) shot.Draw();
foreach (var bullet in Bullet.Where(x => 0 < x.Life)) bullet.Draw();
foreach (var effect in Effect.Where(x => 0 < x.Life)) effect.Draw();
foreach (var borad in Borad.Where(x => 0 < x.Life)) borad.Draw();
// Lifeが0以下の要素を削除する
Back.RemoveAll(s => s.Life <= 0);
Player.RemoveAll(s => s.Life <= 0);
Shot.RemoveAll(s => s.Life <= 0);
Bullet.RemoveAll(s => s.Life <= 0);
Enemy.RemoveAll(s => s.Life <= 0);
Effect.RemoveAll(s => s.Life <= 0);
Borad.RemoveAll(s => s.Life <= 0);
CFpsControl.DrawFps(0, 465);
CFpsControl.FpsWait();
// 裏画面の内容を表画面に反映する
DX.ScreenFlip();
}
// 終了処理関数
public void Destory()
{
// ここに終了処理を追加する
DX.DxLib_End();
}
int[] SoundSE = new int[15];
public void PlaySound(int type)
{
if (DX.CheckSoundMem(SoundSE[type]) != 0)
{
DX.StopSoundMem(SoundSE[type]);
}
DX.PlaySoundMem(SoundSE[type], DX.DX_PLAYTYPE_BACK);
}
// プレイヤーまでの角度を計算する
public double GetPlayerAngle(double x, double y)
{
if (0 < Player.Count)
{
// 一番近くのプレイヤーの要素を取得する
var player = Player.OrderByDescending(p => (p.X-x)*(p.X-x) + (p.Y-y)*(p.Y-y)).FirstOrDefault();
return Math.Atan2(player.Y - y, player.X - x) / PI2;
}
else
{
return 0.0;
}
}
// 渡された-ang~angまでのランダムな角度を返す
public double Rang(double angle)
{
return (double)(-angle + angle * 2 * DX.GetRand(10000) / 10000.0);
}
}
}
実行結果
今回のプロジェクトをダウンロードする。 プロジェクトに画像は同梱してませんのでこちらからダウンロードしてください。画像を置く階層はデバッグモードで実行するなら、bin/Debugでリリースモードで実行するならbin/Releaseです。
今回で背景のスクロール処理を追加しました!だいぶゲームらしくなってきました(^^)
次回はスコアや残機などの設定をしてゲームをブラッシュアップしましょう!