C#とDXライブラリで弾幕STGを作ってみよう！ Part2 自機編(2)

# 自機編(2)？

自機編(1)はC言語の方で終わらせていて、今回はその続きから始めるので(2)になっています。
自機編(1)までを追いたい方は[こちら](https://qiita.com/Saigyo_HBK/items/7bf767131753314edab9)を確認してください。

Part1 : https://qiita.com/Saigyo_HBK/items/18f1d59b1b5acb03373c


# 目次

- 自機クラスの作成
- 定数クラスの作成
- システムクラスの作成
- プロジェクトのウインドウ設定


# 自機クラスの作成

前回のコードではシステムの初期化やプレイヤーの処理を一つのファイルにまとめていました。この場合だと、C#のオブジェクト指向プログラミングに反しており、とても可読性が低いものになってしまいます。
今回はいくつかのファイルに分けていくのですが、まずは自機クラスを作ってみましょう。

現在のソースコード(**Program.cs**)は、自機の移動と描画機能のみがあります。
移動機能などの変数の更新が入る処理を`Update`メソッド、変数が変化しない描画機能を`Draw`メソッドとしてクラスにまとめてみました。

```cs:Player.cs
using DxLibDLL;

namespace Qiita
{
    class Player
    {
        private readonly float MOVE_SPEED;
        private readonly int IMG;
        private float _x, _y;

        public Player()
        {
            MOVE_SPEED = 10.0f;
            IMG = DX.LoadGraph("img/jiki.png");
            _x = Define.WIN_W / 2.0F;
            _y = Define.WIN_H - 100.0F;
        }

        public bool Update()
        {
            if (DX.CheckHitKey(DX.KEY_INPUT_LEFT) == 1) _x -= MOVE_SPEED;
            if (DX.CheckHitKey(DX.KEY_INPUT_RIGHT) == 1) _x += MOVE_SPEED;
            if (DX.CheckHitKey(DX.KEY_INPUT_UP) == 1) _y -= MOVE_SPEED;
            if (DX.CheckHitKey(DX.KEY_INPUT_DOWN) == 1) _y += MOVE_SPEED;

            //自機の画面外へのはみ出しをチェックする
            if (_x < 0.0F) _x = 0.0F;
            if (_x > Define.WIN_W) _x = Define.WIN_W;
            if (_y < 0.0F) _y = 0.0F;
            if (_y > Define.WIN_H) _y = Define.WIN_H;

            return true;
        }

        public void Draw()
        {
            DX.DrawRotaGraphF(_x, _y, 1.0F, 0.0F, IMG, DX.TRUE);
        }
    }
}
```

とりあえずはこのようにクラスにして分割ができたのですが、ここから機能を付け足します。
移動に関する処理は新たに`Move`メソッドを作成してさらに処理を小分けにすることを考えます。

そうして簡潔になった`Update`メソッドに、自機のカウント変数を用意して毎フレームインクリメントしていきましょう。

移動に関する処理には以下の機能を付け足します。
- 斜め移動の時に速度が速くならないようにx,y方向の速度を√2で割る。
- 低速移動

以下がソースコードです。連想配列や平方根の計算のために使用する名前空間が増えています。

```cs:Player.cs
using System;
using System.Collections.Generic;
using DxLibDLL;

namespace Qiita
{
    class Player
    {
        private readonly float MOVE_SPEED;
        private readonly int IMG;
        private int _cnt;
        private float _x, _y;
        private bool _slow;

        public Player()
        {
            MOVE_SPEED = 10.0f;
            IMG = DX.LoadGraph("img/jiki.png");
            _cnt = 0;
            _x = Define.WIN_W / 2.0F;
            _y = Define.WIN_H - 100.0F;
            _slow = false;
        }

        /// <summary>
        /// 変数の更新
        /// </summary>
        /// <returns></returns>
        public bool Update()
        {
            _cnt++;
            Move();

            return true;
        }

        /// <summary>
        /// 描画
        /// </summary>
        public void Draw()
        {
            DX.DrawRotaGraphF(_x, _y, 1.0F, 0.0F, IMG, DX.TRUE);
        }

        /// <summary>
        /// 移動処理
        /// </summary>
        private void Move()
        {
            _slow = DX.CheckHitKey(DX.KEY_INPUT_LSHIFT) == 1 ? true : false;
            // 低速移動なら0.4倍
            float move_rate = _slow ? 0.4F : 1.0F;

            Dictionary<string, int> key = new Dictionary<string, int>();
            key["left"] = DX.CheckHitKey(DX.KEY_INPUT_LEFT);
            key["right"] = DX.CheckHitKey(DX.KEY_INPUT_RIGHT);
            key["up"] = DX.CheckHitKey(DX.KEY_INPUT_UP);
            key["down"] = DX.CheckHitKey(DX.KEY_INPUT_DOWN);

            // 斜め移動ならルート2で割る
            if ((key["left"] == 1 || key["right"] == 1) && (key["up"] == 1 || key["down"] == 1)) move_rate /= (float)Math.Sqrt(2.0);

            // 移動処理
            if (key["left"] == 1) _x -= MOVE_SPEED * move_rate;
            if (key["right"] == 1) _x += MOVE_SPEED * move_rate;
            if (key["up"] == 1) _y -= MOVE_SPEED * move_rate;
            if (key["down"] == 1) _y += MOVE_SPEED * move_rate;

            //自機の画面外へのはみ出しをチェックする
            if (_x < 0.0F) _x = 0.0F;
            if (_x > Define.WIN_W) _x = Define.WIN_W;
            if (_y < 0.0F) _y = 0.0F;
            if (_y > Define.WIN_H) _y = Define.WIN_H;
        }
    }
}

```


# 定数クラスの作成

次はウインドウの縦横の大きさを表す定数変数を定数クラスの中で定義しましょう。
元々は`const`な変数にする予定だったのですが、[こちらの記事](https://qiita.com/Nossa/items/b874fa6c115898e2a9c8)のように問題になるような場合があるので、`static readonly`に変更しました。
また、classの前に`sealed`と付けることでこのクラスを継承することを禁止しています。
以下がソースコードです。

```cs:Define.cs
namespace Qiita
{
    sealed class Define
    {
        public const int WIN_W = 960;
        public const int WIN_H = 720;
    }
}
```


# システムクラスの作成

次にDXライブラリの初期化処理や終了処理などのシステム的な処理を行うクラスを用意しましょう。
Program.csには極力書くことを少なくし、システムの処理は**System.cs**に、ゲームのメインループ部分は**Looper.cs**に書いてSystem.csでメソッドをループさせます。

```cs:Program.cs
namespace Qiita
{
    sealed class Program
    {
        /// <summary>
        /// メイン関数
        /// </summary>
        public static void Main()
        {
            System sys = new System();
            if (sys.Initialize())
            {
                sys.Game();
                sys.Finalize_();
            }
        }
    }
}
```

```cs:System.cs
using DxLibDLL;

namespace Qiita
{
	sealed class System
	{
		/// <summary>
		/// 初期化処理
		/// </summary>
		/// <returns>初期化結果</returns>
		public bool Initialize()
		{
			DX.SetOutApplicationLogValidFlag(DX.FALSE);
			DX.SetAlwaysRunFlag(DX.TRUE);
			DX.SetWindowSizeChangeEnableFlag(DX.TRUE);
			DX.SetFullScreenResolutionMode(DX.DX_FSRESOLUTIONMODE_DESKTOP);
			DX.SetWindowText("シューティングゲーム");
			DX.ChangeWindowMode(DX.TRUE);
			DX.SetGraphMode(Define.WIN_W, Define.WIN_H, 32);

			// DXライブラリの初期化
			if (DX.DxLib_Init() == -1) return false;
			DX.SetDrawScreen(DX.DX_SCREEN_BACK); //裏画面処理を設定する
			return true;
		}

		/// <summary>
		/// ゲームループ
		/// </summary>
		public void Game()
		{
			Looper looper = new Looper();
			while (CheckLoop())
			{
				if (!looper.Loop()) break;
			}
		}


		/// <summary>
		/// 終了処理
		/// </summary>
		public void Finalize_()
		{
			DX.DxLib_End();
		}


		/// <summary>
		/// ループ時の処理及び確認
		/// </summary>
		/// <returns>処理が正常か</returns>
		private bool CheckLoop()
		{
			return (DX.ScreenFlip() == 0 && DX.ProcessMessage() == 0 && DX.ClearDrawScreen() == 0) ? true : false;
		}
	}
}
```

```cs:Looper.cs
using DxLibDLL;

namespace Qiita
{
    sealed class Looper
    {
        private Player _player = new Player();

        /// <summary>
        /// ゲームのメイン処理
        /// </summary>
        /// <returns>ループを続けるか</returns>
        public bool Loop()
        {
            if (DX.CheckHitKey(DX.KEY_INPUT_ESCAPE) == 1) return false;

            _player.Update();
            _player.Draw();

            return true;
        }
    }
}
```

# まとめ

プログラムを走らせるときちんと動作すると思います。

C#の様々な機能を使っていてC言語しか触れていないような人にはわかりづらい部分があると思います。その場合は質問していただければ可能な範囲でお答えします。

次回から敵や弾といったゲームの要素を追加していきたいと思います。