アマゾンバナーリンク

オブジェクト指向でゲームプログラミング(多様性)

当たり判定の追加

それでは前回に引き続き当たり判定をつけていきましょう。
当たり判定を行う関数はすでにCMoverのHit関数が定義してるので、呼び出すだけです。

const(定数)


これはマジックナンバー(直接数字を直に各こと)はなるべく使わない方がよいので、MIN_X、MAX_X、MIN_Y、MAX_Yの定数を使います。定数とは一度値を代入すると、書き換えられない値です。constを付けると定数になります。

CMainGame.csを以下の様に修正

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DxLibDLL;

namespace DxLibGame
{
    public static class CMainGame
    {
        public const int MIN_X = 0;
        public const int MAX_X = 9;
        public const int MIN_Y = 0;
        public const int MAX_Y = 7;
        public static CPlayer Player;
        public static CEnemy[] Enemy;
        public static void Initialize()
        {
            Player = new CPlayer(x: 4, y: 7, hp: 10);
            Enemy = new CEnemy[] { new CEnemy(x: DX.GetRand(MAX_X), y: DX.GetRand(MAX_Y), hp: 1), new CEnemy(x: DX.GetRand(MAX_X), y: DX.GetRand(MAX_Y), hp: 1) };
        }
        public static void Move()
        {
            Player.Move();          
            for (int i = 0; i < Enemy.Length; ++i)
            {
                if(!Enemy[i].Move())
                {
                    Enemy[i].Show = false;
                }
            }
        }
        public static void Draw()
        {
            Player.Draw();
            for (int i = 0; i < Enemy.Length; ++i)
            {
                Enemy[i].Draw();
            }
        }
    }
}

次にCPlayer.csを当たり判定と定数を使うように変更して、CheckHitKeyの処理をまとめる変更も加えてます 。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DxLibDLL;

namespace DxLibGame
{
    public class CPlayer : CMover
    {
        int[] InputCnt = new int[] { 0, 0, 0, 0 };
        public CPlayer(int x, int y, int hp) : base(x, y, hp)
        {
        
        }
        enum EInputState
        {
            KEY_INPUT_UP,
            KEY_INPUT_DOWN,
            KEY_INPUT_LEFT,
            KEY_INPUT_RIGHT
        }
;
        void CheckHitKey(int key, int input_state)
        {
            if (DX.CheckHitKey(key) == 1)
            {
                InputCnt[input_state]++;
            }
            else
            {
                InputCnt[input_state] = 0;
            }
        }
        public void Move()
        {
            CheckHitKey(DX.KEY_INPUT_UP, (int)EInputState.KEY_INPUT_UP); // 上キーを押したら
            CheckHitKey(DX.KEY_INPUT_DOWN, (int)EInputState.KEY_INPUT_DOWN); // 下キーを押したら
            CheckHitKey(DX.KEY_INPUT_LEFT, (int)EInputState.KEY_INPUT_LEFT); // 左を押したら
            CheckHitKey(DX.KEY_INPUT_RIGHT, (int)EInputState.KEY_INPUT_RIGHT); // 右を押したら

            // 上へ移動する
            if(InputCnt[(int)EInputState.KEY_INPUT_UP] == 1)
            {
                --Y;
            }
            // 下へ移動する
            if (InputCnt[(int)EInputState.KEY_INPUT_DOWN] == 1)
            {
                ++Y;
            }
            // 左へ移動する
            if (InputCnt[(int)EInputState.KEY_INPUT_LEFT] == 1)
            {
                --X;
            }
            // 右へ移動する
            if (InputCnt[(int)EInputState.KEY_INPUT_RIGHT] == 1)
            {
                ++X;
            }
            // 左へはみ出ないように調整
            if (X < CMainGame.MIN_X)
            {
                X = CMainGame.MIN_X;
            }
            // 右へはみ出ないように調整
            if (CMainGame.MAX_X < X)
            {
                X = CMainGame.MAX_X;
            }
            // 上へはみ出ないように調整
            if (Y < CMainGame.MIN_Y)
            {
                Y = CMainGame.MIN_Y;
            }
            // 下へはみ出ないように調整
            if (CMainGame.MAX_Y < Y)
            {
                Y = CMainGame.MAX_Y;
            }
			// 敵にあたったらHPを減らす
            for (int i = 0; i < CMainGame.Enemy.Length; ++i)
            {
                if (Hit(CMainGame.Enemy[i]))
                {
                    --HP;
                }
            }
        }

        public void Draw()
        {
            DX.DrawBox(X*32, Y*32 ,
                X*32 + 32, Y*32 + 32, DX.GetColor(255, 255, 255), 1);
        }
    }
}

次にCEnemy.csを以下の様にプレイヤーに当たった時の判定を加えます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DxLibDLL;
using System.Diagnostics;

namespace DxLibGame
{
    public class CEnemy : CMover
    {
        public bool Show { get; set; }
        public CEnemy(int x, int y, int hp) : base(x, y, hp)
        {
            X = x;
            Y = y;
            HP = hp;
            Show = true;
            _Stopwatch.Start();
        }
        Stopwatch _Stopwatch = new Stopwatch();
        public bool Move()
        {
            if (!Show) return false;

            if (500 < _Stopwatch.ElapsedMilliseconds)
            {
                X += DX.GetRand(2)-1;
                Y += DX.GetRand(2)-1;
                // 左へはみ出ないように調整
                if (X < CMainGame.MIN_X)
                {
                    X = CMainGame.MIN_X;
                }
                // 右へはみ出ないように調整
                if (CMainGame.MAX_X < X)
                {
                    X = CMainGame.MAX_X;
                }
                // 上へはみ出ないように調整
                if (Y < CMainGame.MIN_Y)
                {
                    Y = CMainGame.MIN_Y;
                }
                // 下へはみ出ないように調整
                if (CMainGame.MAX_Y < Y)
                {
                    Y = CMainGame.MAX_Y;
                }
                _Stopwatch.Restart();
            }
            if(Hit(CMainGame.Player))
            {
                --HP;
            }
            if (HP <= 0)
            {
                return false;
            }
            return true;
        }
        public void Draw()
        {
            if (!Show) return;
            DX.DrawBox(X * 32, Y * 32,
                X * 32 + 32, Y * 32 + 32, DX.GetColor(255, 0, 0), 1);
        }
    }
}

ポリモーフィズム(多様性)について

基底クラス型にはその派生クラスのインスタンスを入れる事ができます。


CMover Mover = new CPlayer(); // 基底クラスCMover型に派生クラスのCPlayer型インスタンスを生成
CMover Mover = new CEnemy(); // 基底クラスCMover型に派生クラスのCEnemy型インスタンスを生成
ただし、この場合にはCMoverで定義したメンバ変数とメンバ関数にしかアクセスできません。
ならなぜこの様な機能があるのでしょうか?

仮想関数

前回継承について説明しましたが、基底クラスで定義した関数を派生クラスで上書きすることができます。
これをオーバーライドといい、この機能を使うと、同じインスタンスで、異なった振る舞いをする関数を定義できます。

例
// 基底クラスCMoverに以下の関数を追加
public virtual bool Move()
{
     return true;
}
public virtual void Draw()
{

}

派生クラスでは以下の様にoverrideキーワードを定義して関数を上書きすることができます。
派生クラスで上書きしないで呼んだ場合は、基底クラスのメンバ関数が呼ばれます。

public override bool Move()
{
	return true;
}
public override void Draw()
{

}

つまり、基底クラス型に派生クラスのインスタンスを入れてオーバーライドすると、
どの派生クラスか意識することなくオーバーライドした関数を呼ぶことができます。
実際に使ってメリットを実感してみましょう。

ポリモーフィズム(多様性)を使ってコードを修正する

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace DxLibGame
{
    public class CMover
    {
        public int X { get; set; }
        public int Y { get; set; }
        public int HP { get; set; }
        public bool Show { get; set; }
        public CMover(int x, int y, int hp)
        {
            X = x;
            Y = y;
            HP = hp;
            Show = true;
        }
        public bool Hit(CMover mover)
        {
            if (X == mover.X && Y == mover.Y)
            {
                return true;
            }
            return false;
        }
        public virtual bool Move()
        {
            return true;
        }
        public virtual void Draw()
        {

        }
    }
}

次にCPlayer.csを修正します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DxLibDLL;

namespace DxLibGame
{
    public class CPlayer : CMover
    {
        int[] InputCnt = new int[] { 0, 0, 0, 0 };
        public CPlayer(int x, int y, int hp) : base(x, y, hp)
        {
        
        }
        enum EInputState
        {
            KEY_INPUT_UP,
            KEY_INPUT_DOWN,
            KEY_INPUT_LEFT,
            KEY_INPUT_RIGHT
        }
;
        void CheckHitKey(int key, int input_state)
        {
            if (DX.CheckHitKey(key) == 1)
            {
                InputCnt[input_state]++;
            }
            else
            {
                InputCnt[input_state] = 0;
            }
        }
        public override bool Move()
        {
            CheckHitKey(DX.KEY_INPUT_UP, (int)EInputState.KEY_INPUT_UP); // 上キーを押したら
            CheckHitKey(DX.KEY_INPUT_DOWN, (int)EInputState.KEY_INPUT_DOWN); // 下キーを押したら
            CheckHitKey(DX.KEY_INPUT_LEFT, (int)EInputState.KEY_INPUT_LEFT); // 左を押したら
            CheckHitKey(DX.KEY_INPUT_RIGHT, (int)EInputState.KEY_INPUT_RIGHT); // 右を押したら

            // 上へ移動する
            if(InputCnt[(int)EInputState.KEY_INPUT_UP] == 1)
            {
                --Y;
            }
            // 下へ移動する
            if (InputCnt[(int)EInputState.KEY_INPUT_DOWN] == 1)
            {
                ++Y;
            }
            // 左へ移動する
            if (InputCnt[(int)EInputState.KEY_INPUT_LEFT] == 1)
            {
                --X;
            }
            // 右へ移動する
            if (InputCnt[(int)EInputState.KEY_INPUT_RIGHT] == 1)
            {
                ++X;
            }
            // 左へはみ出ないように調整
            if (X < CMainGame.MIN_X)
            {
                X = CMainGame.MIN_X;
            }
            // 右へはみ出ないように調整
            if (CMainGame.MAX_X < X)
            {
                X = CMainGame.MAX_X;
            }
            // 上へはみ出ないように調整
            if (Y < CMainGame.MIN_Y)
            {
                Y = CMainGame.MIN_Y;
            }
            // 下へはみ出ないように調整
            if (CMainGame.MAX_Y < Y)
            {
                Y = CMainGame.MAX_Y;
            }
            // プレイヤーに当たったらHPを減らす
            for (int i = 0; i < CMainGame.Enemy.Length; ++i)
            {
                if (Hit(CMainGame.Enemy[i]))
                {
                    --HP;
                }
            }
            if(HP <= 0)
            {
                return false;
            }
            return true;
        }

        public override void Draw()
        {
            DX.DrawBox(X*32, Y*32 ,
                X*32 + 32, Y*32 + 32, DX.GetColor(255, 255, 255), 1);
        }
    }
}

次にCEnemy.csです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DxLibDLL;
using System.Diagnostics;

namespace DxLibGame
{
    public class CEnemy : CMover
    {
        public CEnemy(int x, int y, int hp) : base(x, y, hp)
        {
            X = x;
            Y = y;
            HP = hp;
            _Stopwatch.Start();
        }
        Stopwatch _Stopwatch = new Stopwatch();
        public override bool Move()
        {
            if (!Show)
            {
                return false;
            }

            if (500 < _Stopwatch.ElapsedMilliseconds)
            {
                X += DX.GetRand(2)-1;
                Y += DX.GetRand(2)-1;
                // 左へはみ出ないように調整
                if (X < CMainGame.MIN_X)
                {
                    X = CMainGame.MIN_X;
                }
                // 右へはみ出ないように調整
                if (CMainGame.MAX_X < X)
                {
                    X = CMainGame.MAX_X;
                }
                // 上へはみ出ないように調整
                if (Y < CMainGame.MIN_Y)
                {
                    Y = CMainGame.MIN_Y;
                }
                // 下へはみ出ないように調整
                if (CMainGame.MAX_Y < Y)
                {
                    Y = CMainGame.MAX_Y;
                }
                _Stopwatch.Restart();
            }
            if(Hit(CMainGame.Player))
            {
                --HP;
            }
            if (HP <= 0)
            {
                return false;
            }
            return true;
        }
        public override void Draw()
        {
            if (!Show) return;
            DX.DrawBox(X * 32, Y * 32,
                X * 32 + 32, Y * 32 + 32, DX.GetColor(255, 0, 0), 1);
        }
    }
}

最後にCGameMain.csです。ここでMoveTaskとDrawTaskという関数を見てみましょう。
引数でCMover[] moverを受け取って、関数内でmover[i].Move()やmover[i].Draw()を実行してますが、これはCMoverクラスのMove()やDraw()でなく、派生クラスであるCEnemyのMove()やDraw()が実行されています。これをポリモーフィズム(多様性)といいます。このテクニックを使えば、新しく派生クラスを作ってMove()やDraw()を追加すれば、そのクラス独自の機能を簡単に追加することができます。つまりコードがまとまり変更や柔軟性に強くなります。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DxLibDLL;

namespace DxLibGame
{
    public static class CMainGame
    {
        public const int MIN_X = 0;
        public const int MAX_X = 9;
        public const int MIN_Y = 0;
        public const int MAX_Y = 7;
        public static CPlayer Player;
        public static CEnemy[] Enemy;
        public static void Initialize()
        {
            Player = new CPlayer(x: 4, y: 7, hp: 10);
            Enemy = new CEnemy[] { new CEnemy(x: DX.GetRand(MAX_X), y: DX.GetRand(MAX_Y), hp: 1), new CEnemy(x: DX.GetRand(MAX_X), y: DX.GetRand(MAX_Y), hp: 1) };
        }
        private static void MoveTask(CMover[] mover)
        {
            for (int i = 0; i < mover.Length; ++i)
            {
                if (!mover[i].Move())
                {
                    mover[i].Show = false;
                }
            }
        }
        private static void DrawTask(CMover[] mover)
        {
            for (int i = 0; i < mover.Length; ++i)
            {
                mover[i].Draw();
            }
        }
        public static void Move()
        {
            Player.Move();
            MoveTask(Enemy);
        }
        public static void Draw()
        {
            Player.Draw();
            DrawTask(Enemy);
        }
    }
}

今回は当たり判定を追加すると同時にオブジェクト指向の多様性を勉強しました。
次回は画像を読み込んでゲームらしくします。