アマゾンバナーリンク

弾幕シューティングゲーム制作その10 背景スクロール処理追加

こんにちはジェイです!
前回でエフェクトを追加したので、今回は背景のスクロール処理を追加します。

CBackGroundの修正

今までスコアボードを描画するクラスをCBackGroundクラスと定義していました。
今になってようやく気づいて、余計な修正しないといけなくなってすみません><
このままで、いこうかとも迷いましたが、後からわかりずらくなるで修正します。

置換機能で修正する

まず、一番下をソリューション全体にした後に上の図を参考に以下の手順をします。

  1. 上の欄にCBackGroundと入力し、下の欄にCBoardと入力した後すべてのワードを置換
  2. 上の欄にBackGroundと入力し、下の欄にBoardと入力した後すべてのワードを置換
  3. ファイル名をCBackGround.csからCBroadに変える
  4. 上の欄に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です。

今回で背景のスクロール処理を追加しました!だいぶゲームらしくなってきました(^^)
次回はスコアや残機などの設定をしてゲームをブラッシュアップしましょう!