• チュートリアル
  • 機能解説
  • リファレンス
  • 初代Altseedはコチラ

    • チュートリアル
    • 0章 : Altseedを初めよう
    • 1章 : ウィンドウを表示してみよう
    • 2章 : キャラクターを描画してみよう
    • 3章 : キャラクターに弾を撃たせてみよう
    • 4章 : クラスを自分で設計してみよう
    • 5章 : 敵・敵のショットを表示してみよう
    • 6章 : 当たり判定の機能を使ってみよう
    • 7章 : 音を鳴らしてみよう
    • 8章 : 得点を表示してみよう
    • 9章 : タイトル・ゲームオーバー画面を作ってみよう
    • 10章 : いざ、公開準備

    4章 : クラスを自分で設計してみよう

    前章ではキャラクターが弾を撃つようになりました。 しかし、このまま新しい要素をMainメソッドに書き込んでいくと、ソースコードは段々と長く、わかりにくくなっていきます。 そこで、今回はソースコードを整理して、キャラクターと弾の処理を個別に行えるようにしましょう。

    新たに導入する知識

    • C#によるプログラミング入門 : 関数
    • C#によるプログラミング入門 : 継承

    プレイヤーを動かす処理をメソッドを使って整理する

    プレイヤーや弾を移動させるソースコードがwhile文の中に書き込まれていたため、while文の中の処理が長くなっていました。 今はまだプレイヤーの移動と弾の発射だけなので、そこまで読みにくく感じることはないかもしれません。 しかし、ここに敵の追加や移動などの処理を加えると、ソースコードはどんどん長く、わかりにくくなっていきます。 しまいには開発者本人でさえ読めなくなってしまいます。 そこでまずは、メソッドを使って一連の処理をまとめていきましょう。

    手始めに、キャラクターの移動に関する処理をメソッドで表現します。

    using Altseed2;
    using System;
    using System.Collections.Generic;
    
    namespace Tutorial
    {
        class Program
        {
    +       // プレイヤーの移動を行う
    +       static void MovePlayer(SpriteNode player)
    +       {
    +           // ↑キーでY座標を減少
    +           if (Engine.Keyboard.GetKeyState(Key.Up) == ButtonState.Hold)
    +           {
    +               player.Position -= new Vector2F(0.0f, 2.5f);
    +           }
    +
    +           // ↓キーでY座標を増加
    +           if (Engine.Keyboard.GetKeyState(Key.Down) == ButtonState.Hold)
    +           {
    +               player.Position += new Vector2F(0.0f, 2.5f);
    +           }
    +
    +           // →キーでX座標を増加
    +           if (Engine.Keyboard.GetKeyState(Key.Right) == ButtonState.Hold)
    +           {
    +               player.Position += new Vector2F(2.5f, 0.0f);
    +           }
    +
    +           // ←キーでX座標を減少
    +           if (Engine.Keyboard.GetKeyState(Key.Left) == ButtonState.Hold)
    +           {
    +               player.Position -= new Vector2F(2.5f, 0.0f);
    +           }
    +       }
    
            [STAThread]
            static void Main(string[] args)
            {
                // エンジンを初期化
                Engine.Initialize("Tutorial", 960, 720);
    
                // 自機弾を格納するリスト
                var list = new List<SpriteNode>();
    
                // 自機
                var player = new SpriteNode();
                // 自機のテクスチャを読み込む
                player.Texture = Texture2D.LoadStrict("Resources/Player.png");
                // 自機の座標を設定
                player.Position = new Vector2F(100, 360);
                // 自機の中心座標を設定
                player.CenterPosition = player.ContentSize / 2;
    
                // 自機をエンジンに追加
                Engine.AddNode(player);
    
                // メインループ
                while (Engine.DoEvents())
                {
                    // エンジンを更新
                    Engine.Update();
    
    -               // ↑キーでY座標を減少
    -               if (Engine.Keyboard.GetKeyState(Key.Up) == ButtonState.Hold)
    -               {
    -                   player.Position -= new Vector2F(0.0f, 2.5f);
    -               }
    -
    -               // ↓キーでY座標を増加
    -               if (Engine.Keyboard.GetKeyState(Key.Down) == ButtonState.Hold)
    -               {
    -                   player.Position += new Vector2F(0.0f, 2.5f);
    -               }
    -
    -               // →キーでX座標を増加
    -               if (Engine.Keyboard.GetKeyState(Key.Right) == ButtonState.Hold)
    -               {
    -                   player.Position += new Vector2F(2.5f, 0.0f);
    -               }
    -
    -               // ←キーでX座標を減少
    -               if (Engine.Keyboard.GetKeyState(Key.Left) == ButtonState.Hold)
    -               {
    -                   player.Position -= new Vector2F(2.5f, 0.0f);
    -               }
    
    +               // プレイヤーを動かす
    +               MovePlayer(player);
    
                    // Zキーが押された時に実行
                    if (Engine.Keyboard.GetKeyState(Key.Z) == ButtonState.Push)
                    {
                        // 発射される自機弾
                        var bullet = new SpriteNode();
                        // 自機弾のテクスチャを読み込む
                        bullet.Texture = Texture2D.LoadStrict("Resources/Bullet_Blue.png");
                        // 自機弾の座標を設定
                        bullet.Position = player.Position;
                        // 自機弾の中心座標を設定
                        bullet.CenterPosition = bullet.ContentSize / 2;
                        // 自機弾の表示位置を自機より奥に設定
                        bullet.ZOrder--;
    
                        // 自機弾をエンジンに追加
                        Engine.AddNode(bullet);
                        // 自機弾をリストに追加
                        list.Add(bullet);
                    }
    
                    // 自機弾を右に進める
                    for (int i = 0; i < list.Count; i++)
                    {
                        list[i].Position += new Vector2F(10.0f, 0.0f);
                    }
    
                    // Escapeキーでゲーム終了
                    if (Engine.Keyboard.GetKeyState(Key.Escape) == ButtonState.Push)
                    {
                        break;
                    }
                }
    
                // エンジンの終了処理を行う
                Engine.Terminate();
            }
        }
    }
    

    ソースコードでは、以下のようにメソッドを追加しました。

    static void MovePlayer(SpriteNode player)
    {
        ......
    }
    

    MovePlayerメソッドは引数playerの移動に関する処理を行います。 MovePlayerメソッドの中の処理は、以下のように記述することで呼び出されます。

    MovePlayer(player);
    

    このように変更して、作成したプログラムを実行してみてください。 挙動は全く変わらないはずです。

    プレイヤーを動かす処理を継承を使って整理する

    Mainメソッドの中身がある程度すっきりしましたね。 しかし、これだけでは C#の良さは活かしきれません。 C#の機能にクラスというものがあったことを思い出してください。 ここからは、そのクラスを自分で設計していきましょう。

    まず、プレイヤーに相当するクラスを追加します。

    using Altseed2;
    using System;
    using System.Collections.Generic;
    
    namespace Tutorial
    {
    +   // プレイヤーのクラス
    +   public class Player : SpriteNode
    +   {
    +   }
    
        class Program
        {
            // プレイヤーの移動を行う
    -       static void MovePlayer(SpriteNode player)
    +       static void MovePlayer(Player player)
            {
                // ↑キーでY座標を減少
                if (Engine.Keyboard.GetKeyState(Key.Up) == ButtonState.Hold)
                {
                    player.Position -= new Vector2F(0.0f, 2.5f);
                }
    
                // ↓キーでY座標を増加
                if (Engine.Keyboard.GetKeyState(Key.Down) == ButtonState.Hold)
                {
                    player.Position += new Vector2F(0.0f, 2.5f);
                }
    
                // →キーでX座標を増加
                if (Engine.Keyboard.GetKeyState(Key.Right) == ButtonState.Hold)
                {
                    player.Position += new Vector2F(2.5f, 0.0f);
                }
    
                // ←キーでX座標を減少
                if (Engine.Keyboard.GetKeyState(Key.Left) == ButtonState.Hold)
                {
                    player.Position -= new Vector2F(2.5f, 0.0f);
                }
            }
    
            [STAThread]
            static void Main(string[] args)
            {
                // エンジンを初期化
                Engine.Initialize("Tutorial", 960, 720);
    
                // 自機弾を格納するリスト
                var list = new List<SpriteNode>();
    
                // 自機
    -           var player = new SpriteNode();
    +           var player = new Player();
                // 自機のテクスチャを読み込む
                player.Texture = Texture2D.LoadStrict("Resources/Player.png");
                // 自機の座標を設定
                player.Position = new Vector2F(100, 360);
                // 自機の中心座標を設定
                player.CenterPosition = player.ContentSize / 2;
    
                // 自機をエンジンに追加
                Engine.AddNode(player);
    
                // メインループ
                while (Engine.DoEvents())
                {
                    // エンジンを更新
                    Engine.Update();
    
                    // プレイヤーを動かす
                    MovePlayer(player);
    
                    // Zキーが押された時に実行
                    if (Engine.Keyboard.GetKeyState(Key.Z) == ButtonState.Push)
                    {
                        // 発射される自機弾
                        var bullet = new SpriteNode();
                        // 自機弾のテクスチャを読み込む
                        bullet.Texture = Texture2D.LoadStrict("Resources/Bullet_Blue.png");
                        // 自機弾の座標を設定
                        bullet.Position = player.Position;
                        // 自機弾の中心座標を設定
                        bullet.CenterPosition = bullet.ContentSize / 2;
                        // 自機弾の表示位置を自機より奥に設定
                        bullet.ZOrder--;
    
                        // 自機弾をエンジンに追加
                        Engine.AddNode(bullet);
                        // 自機弾をリストに追加
                        list.Add(bullet);
                    }
    
                    // 自機弾を右に進める
                    for (int i = 0; i < list.Count; i++)
                    {
                        list[i].Position += new Vector2F(10.0f, 0.0f);
                    }
    
                    // Escapeキーでゲーム終了
                    if (Engine.Keyboard.GetKeyState(Key.Escape) == ButtonState.Push)
                    {
                        break;
                    }
                }
    
                // エンジンの終了処理を行う
                Engine.Terminate();
            }
        }
    }
    

    ソースコードでは、以下のようにクラスを追加しました。

    class Player : SpriteNode
    {
        ......
    }
    

    第2章で、SpriteNodeは設計図であると述べました。 この変更では、SpriteNodeという設計図を利用して、Playerという新たな設計図を作成しています。 この機能を「継承」と呼びます。

    また、var player = new SpriteNode();の部分がvar player = new Player();に変化しています。 このようにすることで、playerはPlayerクラスのインスタンスになります。

    このPlayerクラスには、新たな機能が何も追加されていません。 そこで、プレイヤーを動かす処理をPlayerクラスの内部に持っていきます。

    using Altseed2;
    using System;
    using System.Collections.Generic;
    
    namespace Tutorial
    {
        // プレイヤーのクラス
        public class Player : SpriteNode
        {
    +       // 移動を行う
    +       public void Move()
    +       {
    +           // ↑キーでY座標を減少
    +           if (Engine.Keyboard.GetKeyState(Key.Up) == ButtonState.Hold)
    +           {
    +               Position -= new Vector2F(0.0f, 2.5f);
    +           }
    +
    +           // ↓キーでY座標を増加
    +           if (Engine.Keyboard.GetKeyState(Key.Down) == ButtonState.Hold)
    +           {
    +               Position += new Vector2F(0.0f, 2.5f);
    +           }
    +
    +           // →キーでX座標を増加
    +           if (Engine.Keyboard.GetKeyState(Key.Right) == ButtonState.Hold)
    +           {
    +               Position += new Vector2F(2.5f, 0.0f);
    +           }
    +
    +           // ←キーでX座標を減少
    +           if (Engine.Keyboard.GetKeyState(Key.Left) == ButtonState.Hold)
    +           {
    +               Position -= new Vector2F(2.5f, 0.0f);
    +           }
    +       }
        }
    
        class Program
        {
    -       // プレイヤーの移動を行う
    -       static void MovePlayer(Player player)
    -       {
    -           // ↑キーでY座標を減少
    -           if (Engine.Keyboard.GetKeyState(Key.Up) == ButtonState.Hold)
    -           {
    -               player.Position -= new Vector2F(0.0f, 2.5f);
    -           }
    -
    -           // ↓キーでY座標を増加
    -           if (Engine.Keyboard.GetKeyState(Key.Down) == ButtonState.Hold)
    -           {
    -               player.Position += new Vector2F(0.0f, 2.5f);
    -           }
    -
    -           // →キーでX座標を増加
    -           if (Engine.Keyboard.GetKeyState(Key.Right) == ButtonState.Hold)
    -           {
    -               player.Position += new Vector2F(2.5f, 0.0f);
    -           }
    -
    -           // ←キーでX座標を減少
    -           if (Engine.Keyboard.GetKeyState(Key.Left) == ButtonState.Hold)
    -           {
    -               player.Position -= new Vector2F(2.5f, 0.0f);
    -           }
    -       }
    
            [STAThread]
            static void Main(string[] args)
            {
                // エンジンを初期化
                Engine.Initialize("Tutorial", 960, 720);
    
                // 自機弾を格納するリスト
                var list = new List<SpriteNode>();
    
                // 自機
                var player = new Player();
                // 自機のテクスチャを読み込む
                player.Texture = Texture2D.LoadStrict("Resources/Player.png");
                // 自機の座標を設定
                player.Position = new Vector2F(100, 360);
                // 自機の中心座標を設定
                player.CenterPosition = player.ContentSize / 2;
    
                // 自機をエンジンに追加
                Engine.AddNode(player);
    
                // メインループ
                while (Engine.DoEvents())
                {
                    // エンジンを更新
                    Engine.Update();
    
                    // プレイヤーを動かす
    -               MovePlayer(player);
    +               player.Move();
    
                    // Zキーが押された時に実行
                    if (Engine.Keyboard.GetKeyState(Keys.Z) == ButtonState.Push)
                    {
                        // 発射される自機弾
                        var bullet = new SpriteNode();
                        // 自機弾のテクスチャを読み込む
                        bullet.Texture = Texture2D.LoadStrict("Resources/Bullet_Blue.png");
                        // 自機弾の座標を設定
                        bullet.Position = player.Position;
                        // 自機弾の中心座標を設定
                        bullet.CenterPosition = bullet.ContentSize / 2;
                        // 自機弾の表示位置を自機より奥に設定
                        bullet.ZOrder--;
    
                        // 自機弾をエンジンに追加
                        Engine.AddNode(bullet);
                        // 自機弾をリストに追加
                        list.Add(bullet);
                    }
    
                    // 自機弾を右に進める
                    for (int i = 0; i < list.Count; i++)
                    {
                        list[i].Position += new Vector2F(10.0f, 0.0f);
                    }
    
                    // Escapeキーでゲーム終了
                    if (Engine.Keyboard.GetKeyState(Key.Escape) == ButtonState.Push)
                    {
                        break;
                    }
                }
    
                // エンジンの終了処理を行う
                Engine.Terminate();
            }
        }
    }
    

    MovePlayerメソッドの処理を、PlayerクラスのMoveメソッドに移動させました。 Playerクラスは、継承元であるSpriteNodeクラスの情報を持っているため、PositionやTextureが自身の情報となります。 したがってMoveメソッドの中では、たとえばplayer.Positionとはせずに、単にPositionと記述します。 また、Mainメソッドからの呼び出し方が変わり、player.Move()とします。

    続いて、Altseed2のUpdate機能を使って、Playerクラスの更新をPlayerクラスの内部で行えるようにしましょう。

    using Altseed2;
    using System;
    using System.Collections.Generic;
    
    namespace Tutorial
    {
        // プレイヤーのクラス
        public class Player : SpriteNode
        {
    +       // フレーム毎に実行
    +       protected override void OnUpdate()
    +       {
    +           // 移動を実行
    +           Move();
    +       }
    
            // 移動を行う
    -       public void Move()
    +       void Move()
            {
                // ↑キーでY座標を減少
                if (Engine.Keyboard.GetKeyState(Key.Up) == ButtonState.Hold)
                {
                    Position -= new Vector2F(0.0f, 2.5f);
                }
    
                // ↓キーでY座標を増加
                if (Engine.Keyboard.GetKeyState(Key.Down) == ButtonState.Hold)
                {
                    Position += new Vector2F(0.0f, 2.5f);
                }
    
                // →キーでX座標を増加
                if (Engine.Keyboard.GetKeyState(Key.Right) == ButtonState.Hold)
                {
                    Position += new Vector2F(2.5f, 0.0f);
                }
    
                // ←キーでX座標を減少
                if (Engine.Keyboard.GetKeyState(Key.Left) == ButtonState.Hold)
                {
                    Position -= new Vector2F(2.5f, 0.0f);
                }
            }
        }
    
        class Program
        {
            [STAThread]
            static void Main(string[] args)
            {
                // エンジンを初期化
                Engine.Initialize("Tutorial", 960, 720);
    
                // 自機弾を格納するリスト
                var list = new List<SpriteNode>();
    
                // 自機
                var player = new Player();
                // 自機のテクスチャを読み込む
                player.Texture = Texture2D.LoadStrict("Resources/Player.png");
                // 自機の座標を設定
                player.Position = new Vector2F(100, 360);
                // 自機の中心座標を設定
                player.CenterPosition = player.ContentSize / 2;
    
                // 自機をエンジンに追加
                Engine.AddNode(player);
    
                // メインループ
                while (Engine.DoEvents())
                {
                    // エンジンを更新
                    Engine.Update();
    
    -               // プレイヤーを動かす
    -               player.Move();
    
                    // Zキーが押された時に実行
                    if (Engine.Keyboard.GetKeyState(Key.Z) == ButtonState.Push)
                    {
                        // 発射される自機弾
                        var bullet = new SpriteNode();
                        // 自機弾のテクスチャを読み込む
                        bullet.Texture = Texture2D.LoadStrict("Resources/Bullet_Blue.png");
                        // 自機弾の座標を設定
                        bullet.Position = player.Position;
                        // 自機弾の中心座標を設定
                        bullet.CenterPosition = bullet.ContentSize / 2;
                        // 自機弾の表示位置を自機より奥に設定
                        bullet.ZOrder--;
    
                        // 自機弾をエンジンに追加
                        Engine.AddNode(bullet);
                        // 自機弾をリストに追加
                        list.Add(bullet);
                    }
    
                    // 自機弾を右に進める
                    for (int i = 0; i < list.Count; i++)
                    {
                        list[i].Position += new Vector2F(10.0f, 0.0f);
                    }
    
                    // Escapeキーでゲーム終了
                    if (Engine.Keyboard.GetKeyState(Key.Escape) == ButtonState.Push)
                    {
                        break;
                    }
                }
    
                // エンジンの終了処理を行う
                Engine.Terminate();
            }
        }
    }
    

    OnUpdateは、Altseed2が更新されるたびに実行されるメソッドです。 この中にMoveメソッドを加えることで、今までと同じようにMoveメソッドがAltseed2が更新されるたびに実行されます。

    protected override void OnUpdate()
    {
        ......
    }
    

    overrideは、継承元のメソッドの名前を使って処理を上書きするための機能です。 単にOnUpdateと記述するだけでなく、overrideも併せて記述しなければ、正しい動作は望めません。

    なお、public void Move()の部分がvoid Move()に変化していることが見て取れます。 publicやprotectedの説明は次の章にて行います。

    弾を動かす処理を継承を使って整理する

    今度は、先ほどと同じようにして弾のソースコードを整理していきます。 弾に相当するクラスを追加しましょう。

    using Altseed2;
    using System;
    using System.Collections.Generic;
    
    namespace Tutorial
    {
        // プレイヤーのクラス
        public class Player : SpriteNode
        {
            // フレーム毎に実行
            protected override void OnUpdate()
            {
                // 移動を実行
                Move();
            }
    
            // 移動を行う
            void Move()
            {
                // ↑キーでY座標を減少
                if (Engine.Keyboard.GetKeyState(Key.Up) == ButtonState.Hold)
                {
                    Position -= new Vector2F(0.0f, 2.5f);
                }
    
                // ↓キーでY座標を増加
                if (Engine.Keyboard.GetKeyState(Key.Down) == ButtonState.Hold)
                {
                    Position += new Vector2F(0.0f, 2.5f);
                }
    
                // →キーでX座標を増加
                if (Engine.Keyboard.GetKeyState(Key.Right) == ButtonState.Hold)
                {
                    Position += new Vector2F(2.5f, 0.0f);
                }
    
                // ←キーでX座標を減少
                if (Engine.Keyboard.GetKeyState(Key.Left) == ButtonState.Hold)
                {
                    Position -= new Vector2F(2.5f, 0.0f);
                }
            }
        }
    +    // 弾のクラス
    +    public class Bullet : SpriteNode
    +    {
    +        // フレーム毎に実行
    +        protected override void OnUpdate()
    +        {
    +            // 座標を速度分進める
    +            Position += new Vector2F(10.0f, 0.0f);
    +        }
    +    }
    
        class Program
        {
            [STAThread]
            static void Main(string[] args)
            {
                // エンジンを初期化
                Engine.Initialize("Tutorial", 960, 720);
    
    -           // 自機弾を格納するリスト
    -           var list = new List<SpriteNode>();
    
                // 自機
                var player = new Player();
                // 自機のテクスチャを読み込む
                player.Texture = Texture2D.LoadStrict("Resources/Player.png");
                // 自機の座標を設定
                player.Position = new Vector2F(100, 360);
                // 自機の中心座標を設定
                player.CenterPosition = player.ContentSize / 2;
    
                // 自機をエンジンに追加
                Engine.AddNode(player);
    
                // メインループ
                while (Engine.DoEvents())
                {
                    // エンジンを更新
                    Engine.Update();
    
                    // Zキーが押された時に実行
                    if (Engine.Keyboard.GetKeyState(Key.Z) == ButtonState.Push)
                    {
                        // 発射される自機弾
    -                   var bullet = new SpriteNode();
    +                   var bullet = new Bullet();
                        // 自機弾のテクスチャを読み込む
                        bullet.Texture = Texture2D.LoadStrict("Resources/Bullet_Blue.png");
                        // 自機弾の座標を設定
                        bullet.Position = player.Position;
                        // 自機弾の中心座標を設定
                        bullet.CenterPosition = bullet.ContentSize / 2;
                        // 自機弾の表示位置を自機より奥に設定
                        bullet.ZOrder--;
    
                        // 自機弾をエンジンに追加
                        Engine.AddNode(bullet);
    -                   // 自機弾をリストに追加
    -                   list.Add(bullet);
                    }
    
    -               // 自機弾を右に進める
    -               for (int i = 0; i < list.Count; i++)
    -               {
    -                   list[i].Position += new Vector2F(10.0f, 0.0f);
    -               }
    
                    // Escapeキーでゲーム終了
                    if (Engine.Keyboard.GetKeyState(Key.Escape) == ButtonState.Push)
                    {
                        break;
                    }
                }
    
                // エンジンの終了処理を行う
                Engine.Terminate();
            }
        }
    }
    

    Bulletクラスを追加しました。

    public class Bullet : SpriteNode
    {
        ...
    }
    

    また、var bullet = new SpriteNode();の部分がvar bullet = new Bullet();に変化しています。 このようにすることで、bulletはBulletクラスのインスタンスになります。 更新処理がBulletクラスに記述されていることで、更新するたびに弾自身が自分で移動するようになります。 そのため、Listによる管理が必要なくなります。

    弾を撃つ処理をプレイヤーに移動する

    弾を撃っているのはプレイヤーなので、弾を撃つ処理をプレイヤーに移動してみましょう。

    using Altseed2;
    using System;
    using System.Collections.Generic;
    
    namespace Tutorial
    {
        // プレイヤーのクラス
        public class Player : SpriteNode
        {
            // フレーム毎に実行
            protected override void OnUpdate()
            {
                // 移動を実行
                Move();
    
    +           // ショットを実行
    +           Shot();
            }
    
            // 移動を行う
            void Move()
            {
                // ↑キーでY座標を減少
                if (Engine.Keyboard.GetKeyState(Key.Up) == ButtonState.Hold)
                {
                    Position -= new Vector2F(0.0f, 2.5f);
                }
    
                // ↓キーでY座標を増加
                if (Engine.Keyboard.GetKeyState(Key.Down) == ButtonState.Hold)
                {
                    Position += new Vector2F(0.0f, 2.5f);
                }
    
                // →キーでX座標を増加
                if (Engine.Keyboard.GetKeyState(Key.Right) == ButtonState.Hold)
                {
                    Position += new Vector2F(2.5f, 0.0f);
                }
    
                // ←キーでX座標を減少
                if (Engine.Keyboard.GetKeyState(Key.Left) == ButtonState.Hold)
                {
                    Position -= new Vector2F(2.5f, 0.0f);
                }
            }
    
    +       // ショット
    +       private void Shot()
    +       {
    +           // Zキーでショットを放つ
    +           if (Engine.Keyboard.GetKeyState(Key.Z) == ButtonState.Push)
    +           {
    +               // 発射される自機弾
    +               var bullet = new Bullet();
    +
    +               // 自機弾のテクスチャを読み込む
    +               bullet.Texture = Texture2D.LoadStrict("Resources/Bullet_Blue.png");
    +               // 自機弾の座標を設定
    +               bullet.Position = Position;
    +               // 自機弾の中心座標を設定
    +               bullet.CenterPosition = bullet.ContentSize / 2;
    +               // 自機弾の表示位置を自機より奥に設定
    +               bullet.ZOrder--;
    +
    +               // 自機弾をエンジンに追加
    +               Engine.AddNode(bullet);
    +           }
    +       }
        }
    
        // 弾のクラス
        public class Bullet : SpriteNode
        {
            // フレーム毎に実行
            protected override void OnUpdate()
            {
                // 座標を速度分進める
                Position += new Vector2F(10.0f, 0.0f);
            }
        }
    
        class Program
        {
            [STAThread]
            static void Main(string[] args)
            {
                // エンジンを初期化
                Engine.Initialize("Tutorial", 960, 720);
    
                // 自機
                var player = new Player();
                // 自機のテクスチャを読み込む
                player.Texture = Texture2D.LoadStrict("Resources/Player.png");
                // 自機の座標を設定
                player.Position = new Vector2F(100, 360);
                // 自機の中心座標を設定
                player.CenterPosition = player.ContentSize / 2;
    
                // 自機をエンジンに追加
                Engine.AddNode(player);
    
                // メインループ
                while (Engine.DoEvents())
                {
                    // エンジンを更新
                    Engine.Update();
    
    -               // Zキーが押された時に実行
    -               if (Engine.Keyboard.GetKeyState(Key.Z) == ButtonState.Push)
    -               {
    -                   // 発射される自機弾
    -                   var bullet = new Bullet();
    -
    -                   // 自機弾のテクスチャを読み込む
    -                   bullet.Texture = Texture2D.LoadStrict("Resources/Bullet_Blue.png");
    -                   // 自機弾の座標を設定
    -                   bullet.Position = player.Position;
    -                   // 自機弾の中心座標を設定
    -                   bullet.CenterPosition = bullet.ContentSize / 2;
    -                   // 自機弾の表示位置を自機より奥に設定
    -                   bullet.ZOrder--;
    -
    -                   // 自機弾をエンジンに追加
    -                   Engine.AddNode(bullet);
    -               }
    
                    // Escapeキーでゲーム終了
                    if (Engine.Keyboard.GetKeyState(Key.Escape) == ButtonState.Push)
                    {
                        break;
                    }
                }
    
                // エンジンの終了処理を行う
                Engine.Terminate();
            }
        }
    }
    

    プレイヤーに関する処理のほとんどがPlayerクラスに移動しましたね。 Mainメソッドのwhile文の中身がAltseed2の更新処理だけになりました。 ここまで変更したプログラムを実行してみてください。 挙動は全く変わらないはずです。

    コンストラクタを使って初期値を設定する

    大分、処理がクラスにまとまってきました。 しかし、画像と最初の位置の指定がクラスの外で行われています。 これら初期値の設定も、クラスの中で行いたいものです。 そこで登場するのが「コンストラクタ」です。

    using Altseed2;
    using System;
    using System.Collections.Generic;
    
    namespace Tutorial
    {
        // プレイヤーのクラス
        public class Player : SpriteNode
        {
    +       // コンストラクタ
    +       public Player(Vector2F position)
    +       {
    +           // 座標を設定
    +           Position = position;
    +
    +           // テクスチャを読み込む
    +           Texture = Texture2D.LoadStrict("Resources/Player.png");
    +
    +           // 中心座標を設定
    +           CenterPosition = ContentSize / 2;
    +       }
    
            // フレーム毎に実行
            protected override void OnUpdate()
            {
                // 移動を実行
                Move();
    
                // ショットを実行
                Shot();
            }
    
            // 移動を行う
            void Move()
            {
                // ↑キーでY座標を減少
                if (Engine.Keyboard.GetKeyState(Key.Up) == ButtonState.Hold)
                {
                    Position -= new Vector2F(0.0f, 2.5f);
                }
    
                // ↓キーでY座標を増加
                if (Engine.Keyboard.GetKeyState(Key.Down) == ButtonState.Hold)
                {
                    Position += new Vector2F(0.0f, 2.5f);
                }
    
                // →キーでX座標を増加
                if (Engine.Keyboard.GetKeyState(Key.Right) == ButtonState.Hold)
                {
                    Position += new Vector2F(2.5f, 0.0f);
                }
    
                // ←キーでX座標を減少
                if (Engine.Keyboard.GetKeyState(Key.Left) == ButtonState.Hold)
                {
                    Position -= new Vector2F(2.5f, 0.0f);
                }
            }
    
            // ショット
            private void Shot()
            {
                // Zキーでショットを放つ
                if (Engine.Keyboard.GetKeyState(Key.Z) == ButtonState.Push)
                {
                    // 発射される自機弾
    -               var bullet = new Bullet();
    +               var bullet = new Bullet(Position);
    
    -               // 自機弾のテクスチャを読み込む
    -               bullet.Texture = Texture2D.LoadStrict("Resources/Bullet_Blue.png");
    -               // 自機弾の座標を設定
    -               bullet.Position = Position;
    -               // 自機弾の中心座標を設定
    -               bullet.CenterPosition = bullet.ContentSize / 2;
    -               // 自機弾の表示位置を自機より奥に設定
    -               bullet.ZOrder--;
    
                    // 自機弾をエンジンに追加
                    Engine.AddNode(bullet);
                }
            }
        }
    
        // 弾のクラス
        public class Bullet : SpriteNode
        {
    +       // コンストラクタ
    +       public Bullet(Vector2F position)
    +       {
    +           // 座標を設定
    +           Position = position;
    +
    +           // テクスチャを読み込む
    +           Texture = Texture2D.LoadStrict("Resources/Bullet_Blue.png");
    +
    +           // 中心座標を設定
    +           CenterPosition = ContentSize / 2;
    +
    +           // 表示位置をプレイヤーや敵より奥に設定
    +           ZOrder--;
    +       }
    
            // フレーム毎に実行
            protected override void OnUpdate()
            {
                // 座標を速度分進める
                Position += new Vector2F(10.0f, 0.0f);
            }
        }
    
        class Program
        {
            [STAThread]
            static void Main(string[] args)
            {
                // エンジンを初期化
                Engine.Initialize("Tutorial", 960, 720);
    
                // 自機
    -           var player = new Player();
    +           var player = new Player(new Vector2F(100, 360));
    -           // 自機のテクスチャを読み込む
    -           player.Texture = Texture2D.LoadStrict("Resources/Player.png");
    -           // 自機の座標を設定
    -           player.Position = new Vector2F(100, 360);
    -           // 自機の中心座標を設定
    -           player.CenterPosition = player.ContentSize / 2;
    
                // 自機をエンジンに追加
                Engine.AddNode(player);
    
                // メインループ
                while (Engine.DoEvents())
                {
                    // エンジンを更新
                    Engine.Update();
    
                    // Escapeキーでゲーム終了
                    if (Engine.Keyboard.GetKeyState(Key.Escape) == ButtonState.Push)
                    {
                        break;
                    }
                }
    
                // エンジンの終了処理を行う
                Engine.Terminate();
            }
        }
    }
    

    コンストラクタは、インスタンスが生成されたときに実行されるメソッドの一種です。 インスタンス生成と同時に画像を読み込むには、このコンストラクタにその処理を記述します。 また、コンストラクタはメソッドであり、引数を持つことができます。 ソースコードでは、PlayerクラスとBulletクラスのコンストラクタに、引数 positionを持たせています。 このようにすることで、プレイヤーや弾の最初の位置を外部から設定できるようになります。

    次に、コンストラクタで弾の速度を設定できるようにしましょう。 Bulletクラスのコンストラクタに速度を引数として設定してそこからいじれるようにします。

    using Altseed2;
    using System;
    using System.Collections.Generic;
    
    namespace Tutorial
    {
        // プレイヤーのクラス
        public class Player : SpriteNode
        {
            // コンストラクタ
            public Player(Vector2F position)
            {
                // 座標を設定
                Position = position;
    
                // テクスチャを読み込む
                Texture = Texture2D.LoadStrict("Resources/Player.png");
    
                // 中心座標を設定
                CenterPosition = ContentSize / 2;
            }
    
            // フレーム毎に実行
            protected override void OnUpdate()
            {
                // 移動を実行
                Move();
    
                // ショットを実行
                Shot();
            }
    
            // 移動を行う
            void Move()
            {
                // ↑キーでY座標を減少
                if (Engine.Keyboard.GetKeyState(Key.Up) == ButtonState.Hold)
                {
                    Position -= new Vector2F(0.0f, 2.5f);
                }
    
                // ↓キーでY座標を増加
                if (Engine.Keyboard.GetKeyState(Key.Down) == ButtonState.Hold)
                {
                    Position += new Vector2F(0.0f, 2.5f);
                }
    
                // →キーでX座標を増加
                if (Engine.Keyboard.GetKeyState(Key.Right) == ButtonState.Hold)
                {
                    Position += new Vector2F(2.5f, 0.0f);
                }
    
                // ←キーでX座標を減少
                if (Engine.Keyboard.GetKeyState(Key.Left) == ButtonState.Hold)
                {
                    Position -= new Vector2F(2.5f, 0.0f);
                }
            }
    
            // ショット
            private void Shot()
            {
                // Zキーでショットを放つ
                if (Engine.Keyboard.GetKeyState(Key.Z) == ButtonState.Push)
                {
                    // 発射される自機弾
                    var bullet = new Bullet(Position);
    
                    // 自機弾をエンジンに追加
                    Engine.AddNode(bullet);
                }
            }
        }
    
        // 弾のクラス
        public class Bullet : SpriteNode
        {
    +       // フレーム毎に進む距離
    +       private Vector2F velocity;
    
            // コンストラクタ
    -       public Bullet(Vector2F position)
    +       public Bullet(Vector2F position, Vector2F velocity)
            {
                // 座標を設定
                Position = position;
     
                // テクスチャを読み込む
                Texture = Texture2D.LoadStrict("Resources/Bullet_Blue.png");
     
                // 中心座標を設定
                CenterPosition = ContentSize / 2;
    
    +           // 弾速を設定
    +           this.velocity = velocity;
     
                // 表示位置をプレイヤーや敵より奥に設定
                ZOrder--;
            }
    
            // フレーム毎に実行
            protected override void OnUpdate()
            {
                // 座標を速度分進める
    -           Position += new Vector2F(10.0f, 0.0f);
    +           Position += velocity;
            }
        }
    
        class Program
        {
            [STAThread]
            static void Main(string[] args)
            {
                // エンジンを初期化
                Engine.Initialize("Tutorial", 960, 720);
    
                // 自機
                var player = new Player(new Vector2F(100, 360));
    
                // 自機をエンジンに追加
                Engine.AddNode(player);
    
                // メインループ
                while (Engine.DoEvents())
                {
                    // エンジンを更新
                    Engine.Update();
    
                    // Escapeキーでゲーム終了
                    if (Engine.Keyboard.GetKeyState(Key.Escape) == ButtonState.Push)
                    {
                        break;
                    }
                }
    
                // エンジンの終了処理を行う
                Engine.Terminate();
            }
        }
    }
    

    Bulletクラスのコンストラクタの引数を変えたので、それを呼び出すコードを修正しましょう。

    using Altseed2;
    using System;
    using System.Collections.Generic;
    
    namespace Tutorial
    {
        // プレイヤーのクラス
        public class Player : SpriteNode
        {
            // コンストラクタ
            public Player(Vector2F position)
            {
                // 座標を設定
                Position = position;
    
                // テクスチャを読み込む
                Texture = Texture2D.LoadStrict("Resources/Player.png");
    
                // 中心座標を設定
                CenterPosition = ContentSize / 2;
            }
    
            // フレーム毎に実行
            protected override void OnUpdate()
            {
                // 移動を実行
                Move();
    
                // ショットを実行
                Shot();
            }
    
            // 移動を行う
            void Move()
            {
                // ↑キーでY座標を減少
                if (Engine.Keyboard.GetKeyState(Key.Up) == ButtonState.Hold)
                {
                    Position -= new Vector2F(0.0f, 2.5f);
                }
    
                // ↓キーでY座標を増加
                if (Engine.Keyboard.GetKeyState(Key.Down) == ButtonState.Hold)
                {
                    Position += new Vector2F(0.0f, 2.5f);
                }
    
                // →キーでX座標を増加
                if (Engine.Keyboard.GetKeyState(Key.Right) == ButtonState.Hold)
                {
                    Position += new Vector2F(2.5f, 0.0f);
                }
    
                // ←キーでX座標を減少
                if (Engine.Keyboard.GetKeyState(Key.Left) == ButtonState.Hold)
                {
                    Position -= new Vector2F(2.5f, 0.0f);
                }
            }
    
            // ショット
            private void Shot()
            {
                // Zキーでショットを放つ
                if (Engine.Keyboard.GetKeyState(Key.Z) == ButtonState.Push)
                {
                    // 発射される自機弾
    -               var bullet = new Bullet(Position);
    +               var bullet = new Bullet(Position, new Vector2F(10f, 0f));
    
                    // 自機弾をエンジンに追加
                    Engine.AddNode(bullet);
                }
            }
        }
    
        // 弾のクラス
        public class Bullet : SpriteNode
        {
            // フレーム毎に進む距離
            private Vector2F velocity;
    
            // コンストラクタ
            public Bullet(Vector2F position, Vector2F velocity)
            {
                // 座標を設定
                Position = position;
     
                // テクスチャを読み込む
                Texture = Texture2D.LoadStrict("Resources/Bullet_Blue.png");
     
                // 中心座標を設定
                CenterPosition = ContentSize / 2;
    
                // 弾速を設定
                this.velocity = velocity;
     
                // 表示位置をプレイヤーや敵より奥に設定
                ZOrder--;
            }
    
            // フレーム毎に実行
            protected override void OnUpdate()
            {
                // 座標を速度分進める
                Position += velocity;
            }
        }
    
        class Program
        {
            [STAThread]
            static void Main(string[] args)
            {
                // エンジンを初期化
                Engine.Initialize("Tutorial", 960, 720);
    
                // 自機
                var player = new Player(new Vector2F(100, 360));
    
                // 自機をエンジンに追加
                Engine.AddNode(player);
    
                // メインループ
                while (Engine.DoEvents())
                {
                    // エンジンを更新
                    Engine.Update();
    
                    // Escapeキーでゲーム終了
                    if (Engine.Keyboard.GetKeyState(Key.Escape) == ButtonState.Push)
                    {
                        break;
                    }
                }
    
                // エンジンの終了処理を行う
                Engine.Terminate();
            }
        }
    }
    

    プレイヤーが画面外に出ないようにする

    ここまででできたプログラムを実行してわかると思いますが、方向キーを押しっぱなしにしていると、プレイヤーが画面外に出てしまいます。 そこで、プレイヤーが画面外に出ないように、処理を追加する必要があります。

    using Altseed2;
    using System;
    using System.Collections.Generic;
    
    namespace Tutorial
    {
        // プレイヤーのクラス
        public class Player : SpriteNode
        {
            // コンストラクタ
            public Player(Vector2F position)
            {
                // 座標を設定
                Position = position;
    
                // テクスチャを読み込む
                Texture = Texture2D.LoadStrict("Resources/Player.png");
    
                // 中心座標を設定
                CenterPosition = ContentSize / 2;
            }
    
            // フレーム毎に実行
            protected override void OnUpdate()
            {
                // 移動を実行
                Move();
    
                // ショットを実行
                Shot();
            }
    
            // 移動を行う
            void Move()
            {
    +           // 現在のX座標を取得する
    +           var x = Position.X;
    +           // 現在のY座標を取得する
    +           var y = Position.Y;
    
                // ↑キーでY座標を減少
                if (Engine.Keyboard.GetKeyState(Key.Up) == ButtonState.Hold)
                {
    -               Position -= new Vector2F(0.0f, 2.5f);
    +               y -= 2.5f;
                }
    
                // ↓キーでY座標を増加
                if (Engine.Keyboard.GetKeyState(Key.Down) == ButtonState.Hold)
                {
    -               Position += new Vector2F(0.0f, 2.5f);
    +               y += 2.5f;
                }
    
                // →キーでX座標を増加
                if (Engine.Keyboard.GetKeyState(Key.Right) == ButtonState.Hold)
                {
    -               Position += new Vector2F(2.5f, 0.0f);
    +               x += 2.5f;
                }
    
                // ←キーでX座標を減少
                if (Engine.Keyboard.GetKeyState(Key.Left) == ButtonState.Hold)
                {
    -               Position -= new Vector2F(2.5f, 0.0f);
    +               x -= 2.5f;
                }
    
    +           // テクスチャのサイズの半分を取得する
    +           var halfSize = ContentSize / 2;
    +
    +           // X座標が画面外に行かないように調整
    +           x = MathHelper.Clamp(x, Engine.WindowSize.X - halfSize.X, halfSize.X);
    +           // Y座標が画面外に行かないように調整
    +           y = MathHelper.Clamp(y, Engine.WindowSize.Y - halfSize.Y, halfSize.Y);
    +
    +           // 調整された座標を設定
    +           Position = new Vector2F(x, y);
            }
    
            // ショット
            private void Shot()
            {
                // Zキーでショットを放つ
                if (Engine.Keyboard.GetKeyState(Key.Z) == ButtonState.Push)
                {
                    // 発射される自機弾
                    var bullet = new Bullet(Position, new Vector2F(10f, 0f));
    
                    // 自機弾をエンジンに追加
                    Engine.AddNode(bullet);
                }
            }
        }
    
        // 弾のクラス
        public class Bullet : SpriteNode
        {
            // フレーム毎に進む距離
            private Vector2F velocity;
    
            // コンストラクタ
            public Bullet(Vector2F position, Vector2F velocity)
            {
                // 座標を設定
                Position = position;
    
                // テクスチャを読み込む
                Texture = Texture2D.LoadStrict("Resources/Bullet_Blue.png");
    
                // 中心座標を設定
                CenterPosition = ContentSize / 2;
    
                // 弾速を設定
                this.velocity = velocity;
    
                // 表示位置をプレイヤーや敵より奥に設定
                ZOrder--;
            }
    
            // フレーム毎に実行
            protected override void OnUpdate()
            {
                // 座標を速度分進める
                Position += velocity;
            }
        }
    
        class Program
        {
            [STAThread]
            static void Main(string[] args)
            {
                // エンジンを初期化
                Engine.Initialize("Tutorial", 960, 720);
    
                // 自機
                var player = new Player(new Vector2F(100, 360));
    
                // 自機をエンジンに追加
                Engine.AddNode(player);
    
                // メインループ
                while (Engine.DoEvents())
                {
                    // エンジンを更新
                    Engine.Update();
    
                    // Escapeキーでゲーム終了
                    if (Engine.Keyboard.GetKeyState(Key.Escape) == ButtonState.Push)
                    {
                        break;
                    }
                }
    
                // エンジンの終了処理を行う
                Engine.Terminate();
            }
        }
    }
    

    追加した処理では、変更後の x 座標と y 座標について、画面に表示される範囲に収まるように数値を設定しています。 このようにすることで、プレイヤーが画面外に出ることはなくなります。

    弾が画面外に出たら消える処理を追加する

    弾はいずれ画面外に出ていきます。 しかし、画面外に出た弾を削除せずに、エンジンに弾を追加していくと、エンジンの処理が重くなっていきます。 そのため、弾が画面外に出て行ったら削除する処理を追加する必要があります。

    using Altseed2;
    using System;
    using System.Collections.Generic;
    
    namespace Tutorial
    {
        // プレイヤーのクラス
        public class Player : SpriteNode
        {
            // コンストラクタ
            public Player(Vector2F position)
            {
                // 座標を設定
                Position = position;
    
                // テクスチャを読み込む
                Texture = Texture2D.LoadStrict("Resources/Player.png");
    
                // 中心座標を設定
                CenterPosition = ContentSize / 2;
            }
    
            // フレーム毎に実行
            protected override void OnUpdate()
            {
                // 移動を実行
                Move();
    
                // ショットを実行
                Shot();
            }
    
            // 移動を行う
            void Move()
            {
                // 現在のX座標を取得する
                var x = Position.X;
                // 現在のY座標を取得する
                var y = Position.Y;
    
                // ↑キーでY座標を減少
                if (Engine.Keyboard.GetKeyState(Key.Up) == ButtonState.Hold)
                {
                    y -= 2.5f;
                }
    
                // ↓キーでY座標を増加
                if (Engine.Keyboard.GetKeyState(Key.Down) == ButtonState.Hold)
                {
                    y += 2.5f;
                }
    
                // →キーでX座標を増加
                if (Engine.Keyboard.GetKeyState(Key.Right) == ButtonState.Hold)
                {
                    x += 2.5f;
                }
    
                // ←キーでX座標を減少
                if (Engine.Keyboard.GetKeyState(Key.Left) == ButtonState.Hold)
                {
                    x -= 2.5f;
                }
    
                // テクスチャのサイズの半分を取得する
                var halfSize = ContentSize / 2;
    
                // X座標が画面外に行かないように調整
                x = MathHelper.Clamp(x, Engine.WindowSize.X - halfSize.X, halfSize.X);
                // Y座標が画面外に行かないように調整
                y = MathHelper.Clamp(y, Engine.WindowSize.Y - halfSize.Y, halfSize.Y);
    
                // 調整された座標を設定
                Position = new Vector2F(x, y);
            }
    
            // ショット
            private void Shot()
            {
                // Zキーでショットを放つ
                if (Engine.Keyboard.GetKeyState(Key.Z) == ButtonState.Push)
                {
                    // 発射される自機弾
                    var bullet = new Bullet(Position, new Vector2F(10f, 0f));
    
                    // 自機弾をエンジンに追加
                    Engine.AddNode(bullet);
                }
            }
        }
    
        // 弾のクラス
        public class Bullet : SpriteNode
        {
            // フレーム毎に進む距離
            private Vector2F velocity;
    
            // コンストラクタ
            public Bullet(Vector2F position, Vector2F velocity)
            {
                // 座標を設定
                Position = position;
    
                // テクスチャを読み込む
                Texture = Texture2D.LoadStrict("Resources/Bullet_Blue.png");
    
                // 中心座標を設定
                CenterPosition = ContentSize / 2;
    
                // 弾速を設定
                this.velocity = velocity;
    
                // 表示位置をプレイヤーや敵より奥に設定
                ZOrder--;
            }
    
            // フレーム毎に実行
            protected override void OnUpdate()
            {
                // 座標を速度分進める
                Position += velocity;
    
    +           // 画面外に出たら自身を削除
    +           RemoveMyselfIfOutOfWindow();
            }
    
    +       // 画面外に出た時自身を消去
    +       private void RemoveMyselfIfOutOfWindow()
    +       {
    +           var halfSize = Texture.Size / 2;
    +           if (Position.X < -halfSize.X
    +               || Position.X > Engine.WindowSize.X + halfSize.X
    +               || Position.Y < -halfSize.Y
    +               || Position.Y > Engine.WindowSize.Y + halfSize.Y)
    +           {
    +               // 自身を削除
    +               Parent?.RemoveChildNode(this);
    +           }
    +       }
        }
    
        class Program
        {
            [STAThread]
            static void Main(string[] args)
            {
                // エンジンを初期化
                Engine.Initialize("Tutorial", 960, 720);
    
                // 自機
                var player = new Player(new Vector2F(100, 360));
    
                // 自機をエンジンに追加
                Engine.AddNode(player);
    
                // メインループ
                while (Engine.DoEvents())
                {
                    // エンジンを更新
                    Engine.Update();
    
                    // Escapeキーでゲーム終了
                    if (Engine.Keyboard.GetKeyState(Key.Escape) == ButtonState.Push)
                    {
                        break;
                    }
                }
    
                // エンジンの終了処理を行う
                Engine.Terminate();
            }
        }
    }
    

    弾が画面外に出たら、親ノードを取得してRemoveChildNodeメソッドを実行します。 このメソッドを実行すると、弾のインスタンスはAltseed2の管理対象から除外されます。 すなわち、画面外に出た弾は管理されなくなるということです。 これにて、画面外の弾が消えないも解決です。

    クラスごとにファイルを分ける

    さて、ここまでソースコードをProgram.csに書き込んできたわけですが、行数が多くなり、見通しも悪くなってきました。 そこで、新しい C#のソースファイルを追加して、ソースコードを切り分けていきましょう。

    Windows の場合

    まず、下図に示すように、「ShootingGame」の青く示された部分を右クリックしてください。 その後、選択肢のウィンドウが出てくるので、「追加」、「新しい項目」の順にクリックしてください。

    newfile_win_1 newfile_win_2

    すると、「新しい項目の追加 - ShootingGame」というウィンドウが表示されます。 ウィンドウが表示されたら、画面左側の選択肢から「コード」を選択し、その後、画面中央の選択肢から「クラス」を選択します。 次に、画面下側の入力ボックスに、ファイル名を入力します。 今回はPlayerクラスを別のファイルに移動したいため、ファイル名を「Player.cs」とします。 入力が終わったら、「追加」をクリックします。

    newfile_win_3

    ここまで終わったら、「ソリューション」のところに「Player.cs」が追加されているはずです。 あとは、Program.csに記述されているPlayerクラスの部分を切り取って、Player.csに貼り付けるだけです。 ただし、ただ切り貼りしただけではエラーが発生します。 これは、ソースファイルにAltseedという名前空間が知らされていないことが原因です。 Player.csの頭に「using Altseed2;」と記述しましょう。

    newfile_win_4 newfile_win_5

    これと同様の手順を踏んで、Bullet.csを追加し、Bulletクラスを移してみてください。

    Mac の場合

    まず、下図に示すように、「ShootingGame」の青く示された部分を右クリックしてください。 その後、選択肢のウィンドウが出てくるので、「追加」、「新しいファイル」の順にクリックしてください。

    newfile_mac_1

    すると、「新しいファイル」というウィンドウが表示されます。 ウィンドウが表示されたら、画面左側の選択肢から「General」を選択し、その後、画面中央の選択肢から「空のクラス」を選択します。 次に、画面下側の入力ボックスに、ファイル名を入力します。 今回はPlayerクラスを別のファイルに移動したいため、ファイル名を「Player」とします。 入力が終わったら、「新規」をクリックします。

    newfile_mac_2

    ここまで終わったら、「ソリューション」のところに「Player.cs」が追加されているはずです。 あとは、Program.csに記述されているPlayerクラスの部分を切り取って、Player.csに貼り付けるだけです。 ただし、ただ切り貼りしただけではエラーが発生します。 これは、ソースファイルにAltseedという名前空間が知らされていないことが原因です。 Player.csの頭に「using Altseed;」と記述しましょう。

    newfile_mac_3 newfile_mac_4

    これと同様の手順を踏んで、Bullet.csを追加し、Bulletクラスを移してみてください。

    プレイヤーや弾をまとめて管理できるようにする

    ソースコードが複数のファイルに振り分けられたことで、Program.csの内容がこれだけになりました。

    using Altseed2;
    using System;
    using System.Collections.Generic;
    
    namespace Tutorial
    {
        class Program
        {
            [STAThread]
            static void Main(string[] args)
            {
                // エンジンを初期化
                Engine.Initialize("Tutorial", 960, 720);
    
                // 自機
                var player = new Player(new Vector2F(100, 360));
    
                // 自機をエンジンに追加
                Engine.AddNode(player);
    
                // メインループ
                while (Engine.DoEvents())
                {
                    // エンジンを更新
                    Engine.Update();
    
                    // Escapeキーでゲーム終了
                    if (Engine.Keyboard.GetKeyState(Key.Escape) == ButtonState.Push)
                    {
                        break;
                    }
                }
    
                // エンジンの終了処理を行う
                Engine.Terminate();
            }
        }
    }
    

    しかし、このソースコードでは、プレイヤーや弾を、エンジンに直接登録してしまっています。 このままでは、何か別の画面に切り替えようとした場合に、エンジンに直接登録されたオブジェクトをいちいち登録解除するのが面倒です。 そこで、プレイヤーや弾をまとめて管理できる、すなわちメインステージを追加しておきましょう。

    まず、新しくMainNode.csを作成します。 手順は、先ほどソースコードを振り分ける時に行ったものと同じです。

    ファイルを新規作成できたら、メインステージを表すMainNodeクラスに処理を書き込んでいきます。 MainNode.csに以下のように書いてください。

    using Altseed2;
    
    namespace Tutorial
    {
        // メインステージのクラス
        public class MainNode : Node
        {
            // キャラクターを表示するノード
            private Node characterNode = new Node();
            
            // プレイヤーの参照
            private Player player;
    
            // エンジンに追加された時に実行
            protected override void OnAdded()
            {
                // キャラクターノードを追加
                AddChildNode(characterNode);
    
                // UIを表示するノード
                var uiNode = new Node();
    
                // UIノードを追加
                AddChildNode(uiNode);
    
                // プレイヤーを設定
                player = new Player(new Vector2F(100, 360));
    
                // キャラクターノードにプレイヤーを追加
                characterNode.AddChildNode(player);
            }
        }
    }
    

    OnAddedは、このノードがエンジンに登録されたときに実行されるメソッドです。 ノードの初期状態を設定するには、このメソッドを使います。

    protected override void OnAdded()
    {
        ......
    }
    

    さて、MainNodeクラスは、ノードを表すNodeクラスを継承しています。 こうすることで、MainNodeクラスのインスタンスは、エンジンに登録されるノードとして作用します。 このノードにプレイヤーや弾を登録することで、これらをまとめて管理できるというわけです。

    また、MainNodeの中にも、characterNodeやuiNodeのような、Nodeクラスのインスタンスがあります。 同じ画面の中でも、プレイヤーや弾といったゲーム中のオブジェクトと、残機や得点といった UI に関わるオブジェクトを、それぞれまとめて管理したいものです。 このように、Nodeクラスのインスタンスをうまく利用することで、系統ごとにオブジェクトをまとめて管理することができます。

    では、MainNodeクラスのインスタンスを作成し、それをエンジンに登録してみましょう。 Program.csに移って、以下のようにソースコードを書き換えてください。

    using Altseed2;
    using System;
    using System.Collections.Generic;
    
    namespace Tutorial
    {
        class Program
        {
            [STAThread]
            static void Main(string[] args)
            {
                // エンジンを初期化
                Engine.Initialize("Tutorial", 960, 720);
    
    -           // 自機
    -           var player = new Player(new Vector2F(100, 360));
    -
    -           // 自機をエンジンに追加
    -           Engine.AddNode(player);
    +           // メイン画面をエンジンに追加
    +           Engine.AddNode(new MainNode());
    
                // メインループ
                while (Engine.DoEvents())
                {
                    // エンジンを更新
                    Engine.Update();
    
                    // Escapeキーでゲーム終了
                    if (Engine.Keyboard.GetKeyState(Key.Escape) == ButtonState.Push)
                    {
                        break;
                    }
                }
    
                // エンジンの終了処理を行う
                Engine.Terminate();
            }
        }
    }
    

    このようにすると、MainNodeの子ノードとして登録されているオブジェクト群が更新され、先ほどと同じ挙動をします。

    また、ゲームシーンとしてMainNodeを定義したため、Playerクラスで行っている自機弾の追加先をエンジンから、MainNodeにあるcharacterNodeに変更しましょう。

    using Altseed2;
    
    namespace Tutorial
    {
        // プレイヤーのクラス
        public class Player : SpriteNode
        {
            // コンストラクタ
            public Player(Vector2F position)
            {
                // 座標を設定
                Position = position;
    
                // テクスチャを読み込む
                Texture = Texture2D.LoadStrict("Resources/Player.png");
    
                // 中心座標を設定
                CenterPosition = ContentSize / 2;
            }
    
            // フレーム毎に実行
            protected override void OnUpdate()
            {
                // 移動を実行
                Move();
    
                // ショットを実行
                Shot();
            }
    
            // 移動を行う
            void Move()
            {
                // 現在のX座標を取得する
                var x = Position.X;
                // 現在のY座標を取得する
                var y = Position.Y;
    
                // ↑キーでY座標を減少
                if (Engine.Keyboard.GetKeyState(Key.Up) == ButtonState.Hold)
                {
                    y -= 2.5f;
                }
    
                // ↓キーでY座標を増加
                if (Engine.Keyboard.GetKeyState(Key.Down) == ButtonState.Hold)
                {
                    y += 2.5f;
                }
    
                // →キーでX座標を増加
                if (Engine.Keyboard.GetKeyState(Key.Right) == ButtonState.Hold)
                {
                    x += 2.5f;
                }
    
                // ←キーでX座標を減少
                if (Engine.Keyboard.GetKeyState(Key.Left) == ButtonState.Hold)
                {
                    x -= 2.5f;
                }
    
                // テクスチャのサイズの半分を取得する
                var halfSize = ContentSize / 2;
    
                // X座標が画面外に行かないように調整
                x = MathHelper.Clamp(x, Engine.WindowSize.X - halfSize.X, halfSize.X);
                // Y座標が画面外に行かないように調整
                y = MathHelper.Clamp(y, Engine.WindowSize.Y - halfSize.Y, halfSize.Y);
    
                // 調整された座標を設定
                Position = new Vector2F(x, y);
            }
    
            // ショット
            private void Shot()
            {
                // Zキーでショットを放つ
                if (Engine.Keyboard.GetKeyState(Key.Z) == ButtonState.Push)
                {
    -               // 発射される自機弾
    -               var bullet = new Bullet(Position, new Vector2F(10f, 0f));
    -
    -               // 自機弾をエンジンに追加
    -               Engine.AddNode(bullet);
    
    +               // Zキーでショットを放つ
    +               Parent.AddChildNode(new Bullet(Position , new Vector2F(10f, 0f)));
                }
            }
        }
    }
    

    ここで,MainNode.characterNodeがないではないかと思うかもしれませんが、ParentがMainNode.characterNodeを表しています。 MainNode.csにて、

    // キャラクターノードを追加
    AddChildNode(characterNode);
    
    ......
    
    // キャラクターノードにプレイヤーを追加
    characterNode.AddChildNode(player);
    

    という記述があります。これは、 エンジン-MainNode-characterNode-Player という親子関係を示しています。 ここでMainNode.characterNodeの参照が欲しいとなったときはPlayerからParentを呼び出すことで解決できます。

    背景を追加する

    ソースコードが整理できたので、ここから敵を追加していっても良い頃合いです。 しかしその前に、プレーンな背景で繰り広げられるゲームは、いまいち面白みに欠けますよね。 それっぽい背景が欲しいものです。 ということで、MainNodeに背景を追加する機能を追加しておきましょう。

    using Altseed2;
    
    namespace Tutorial
    {
        // メインステージのクラス
        public class MainNode : Node
        {
            // キャラクターを表示するノード
            private Node characterNode = new Node();
            
            // プレイヤーの参照
            private Player player;
    
            // エンジンに追加された時に実行
            protected override void OnAdded()
            {
                // キャラクターノードを追加
                AddChildNode(characterNode);
    
                // UIを表示するノード
                var uiNode = new Node();
    
                // UIノードを追加
                AddChildNode(uiNode);
    
    +           // 背景に使用するテクスチャ
    +           var backTexture = new SpriteNode();
    +           // 背景のテクスチャを読み込む
    +           backTexture.Texture = Texture2D.LoadStrict("Resources/Background.png");
    +           // 表示位置を奥に設定
    +           backTexture.ZOrder = -100;
    +
    +           // 背景テクスチャを追加
    +           AddChildNode(backTexture);
    
                // プレイヤーを設定
                player = new Player(new Vector2F(100, 360));
    
                // キャラクターノードにプレイヤーを追加
                characterNode.AddChildNode(player);
            }
        }
    }
    

    シューティングゲームらしい絵面になりました。

    まとめと次回予告

    今回は、C#のオブジェクト指向の機能を活用して、ソースコードを整理し、その過程で新たな機能を追加してきました。 このように、機能ごとにクラスを分けておくことで、ソースコードが読みやすくなります。 それによって、プログラムが落ちるなど、予期しない挙動が発生したとき、その原因となっている箇所を見つけやすくもなります。 クラスを自分で設計するということは、初めのうちは難しいかもしれません。 しかし、慣れていくと、自由自在にクラスを設計することができるようになります。

    次回からやっと、敵機・敵弾を追加していきます。

    Copyright © 2020 Altseed .