今回は対戦型のラリーエックスをつくります。
ラリーエックスは、1980年11月にナムコ (後のバンダイナムコアミューズメント) から稼働されたアーケード用固定画面アクションゲームです。青い車(マイカー)を操作して追ってくるレッドカーや岩を避けながら、迷路状のステージ上にある旗で示された10箇所のチェックポイントを通過するのが目的です。
仕様
ASP.NET Core版 対戦型で対戦型のゲームにできないかということで以下のようなゲームをつくります。
青、赤、緑の3つの車を複数台つくる。
青は赤から逃げないといけないが緑は体当たりして破壊できる。
赤は緑から逃げないといけないが青は体当たりして破壊できる。
緑は青から逃げないといけないが赤は体当たりして破壊できる。
追尾を振り切るために煙幕を使用できるが、煙幕は天敵にしか効果を発揮しない。
岩は設置しない。
レーダーに各プレイヤーの位置と旗の位置を表示する。
すべての旗を通過すればステージクリア。
旗を通過したときの得点は100点、同じステージでノーミスで連続で通過することで200点、300点とアップしていく。
燃料が切れたら速度が半減する。
ではさっそく作成していきましょう。名前空間はRallyXとします。
クラスの定義
各キャラクタの位置を格納するクラスを定義します。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | namespace RallyX {     public class Position     {         public Position()         {         }         public Position(int col, int row)         {             Column = col;             Row = row;         }         public int Column         {             protected set;             get;         }         public int Row         {             protected set;             get;         }         public int X         {             get { return Column * RallyXGame.CHARACTER_SIZE; }         }         public int Y         {             get { return Row * RallyXGame.CHARACTER_SIZE; }         }     } } | 
迷路のなかのプレイヤーが通過できない部分を位置を格納するクラスを定義します。
| 1 2 3 4 5 6 7 8 9 10 11 | namespace RallyX {     public class Wall : Position     {         public Wall(int col, int row)         {             Column = col;             Row = row;         }     } } | 
迷路上のフラッグの位置と通過、未通過の情報を格納するクラスを定義します。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | namespace RallyX {     public class Flag : Position     {         public Flag(int col, int row)         {             Column = col;             Row = row;             IsPassed = false;         }         public bool IsPassed         {             set;             get;         }     } } | 
スモークスクリーン(煙幕)の位置と消滅までの時間を格納するクラスを定義します。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | namespace RallyX {     public class Smoke : Position     {         public Smoke(int col, int row, int playerNumber)         {             Column = col;             Row = row;             PlayerNumber = playerNumber;             Life = RallyXGame.SMOKE_LIFE_MAX;         }         // 煙幕を発生させたプレイヤー         public int PlayerNumber         {             private set;             get;         }         // 消滅までの時間         public int Life         {             private set;             get;         }         public void Update()         {             Life--;         }     } } | 
プレイヤーが撃破されたときに飛ぶ火花の位置と消滅するまでの時間を格納するクラスを定義します。ボンバーマンとペンゴで使ったものとだいたい同じです。オリジナル性を出すため、派手に爆発させます。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | namespace RallyX {     public class Spark     {         int _x = 0;         int _y = 0;         double _vx = 0;         double _vy = 0;         int _update = 0;         // 引数は発生場所のX座標とY座標、X方向の初速とY方向の初速         public Spark(int x, int y, double vx, double vy)         {             Life = RallyXGame.SPARK_LIFE_MAX;             _x = x;             _y = y;             _vx = vx;             _vy = vy;         }         public int X         {             private set;             get;         }         public int Y         {             private set;             get;         }         // 消滅までの時間         public int Life         {             private set;             get;         }         public void Update()         {             _update++;             X = (int)(_x + _vx * _update);             Y = (int)(_y + _vy * _update);             Life--;         }     } } | 
迷路の初期化
次に迷路を初期化します。迷路をつくるためのテキストファイルを作成してリソースに追加しておきます。ここからダウンロードできます。#がある位置が壁で、R、G、B、r、g、bがプレイヤーの初期位置です。
ゲームに関する処理をおこなうRallyXGameクラスを定義します。
| 1 2 3 4 5 6 | namespace RallyX {     public class RallyXGame     {     } } | 
以降は名前空間部分は省略して書きます。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class RallyXGame {     // 各キャラクタは32ピクセル     public const int CHARACTER_SIZE = 32;     // 自機は5。ミス後に復活した場合、5秒間無敵にする     public const int REST_MAX = 5;     public const int INVINCIBLE_TIME_MAX = 36 * 5;     // 煙幕は8秒で消滅する。煙幕に突っ込んでしまった場合は1秒間スピンする     public const int SMOKE_LIFE_MAX = 36 * 8;     public const int SPIN_COUNT_MAX = 36;     // 燃料     public const int FUEL_MAX = 3200;     // 爆発が消滅するまでの時間 0.5秒弱     public const int SPARK_LIFE_MAX = 12;     // 乱数生成用     static Random _random = new Random(); } | 
初期化の処理を示します。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class RallyXGame {     public static void Init()     {         // 壁、プレイヤー、旗の位置を取得する         AddWalls();         GetCarPositions();         InitFlags();         // 火花と煙幕をクリアする         _sparks.Clear();         Smokes.Clear();     } } | 
壁の追加
ステージに壁を追加する処理とこれに関係するプロパティを示します。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | public class RallyXGame {     // マップの行の最大数     public static int RowMax     {         private set;         get;     }     // マップの列の最大数     public static int ColMax     {         private set;         get;     }     // リソースのテキストファイルから読み出した文字列を配列にして格納する     static string[] _mapText = { "" };     public static string[] MapText     {         get { return _mapText; }     }     // 壁の位置のリスト     static List<Wall> _walls = new List<Wall>();     public static List<Wall> Walls     {         get { return _walls; }     }     static void AddWalls()     {         // 壁を追加するが壁は動かないのでこの処理は1回だけでよい         if (_mapText.Length > 2)             return;         string mapText = Zero.Properties.Resources.rally_x_map1;         Walls.Clear();         _mapText = mapText.Split("\n", StringSplitOptions.RemoveEmptyEntries);         RowMax = _mapText.Length;         ColMax = _mapText[0].Length;         for (int row = 0; row < RowMax; row++)         {             string str = _mapText[row];             char[] vs = str.ToArray();             for (int col = 0; col < ColMax; col++)             {                 // '#'の部分が壁。それ以外は通路                 if (vs[col] == '#')                     Walls.Add(new Wall(col, row));             }         }     } } | 
車の初期位置を取得する
プレイヤーには強弱関係があるので色別にわけておきます。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class RallyXGame {     static List<Position> _bluePositions = new List<Position>();     public static List<Position> BluePositions     {         get { return _bluePositions; }     }     public static List<Position> _redPositions = new List<Position>();     public static List<Position> RedPositions     {         get { return _redPositions; }     }     public static List<Position> _greenPositions = new List<Position>();     public static List<Position> GreenPositions     {         get { return _greenPositions; }     } } | 
GetCarPositionsメソッドは上記のテキストファイルからプレイヤーの初期位置を取得し、上記プロパティに格納します。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | public class RallyXGame {     static void GetCarPositions()     {         // すでにプレイヤーの初期位置が取得されている場合はなにもしない         if (BluePositions.Count > 0)             return;         for (int row = 0; row < RowMax; row++)         {             string str = _mapText[row];             char[] vs = str.ToArray();             for (int col = 0; col < ColMax; col++)             {                 if (vs[col] == 'B')                     BluePositions.Insert(0, new Position(col, row));                 if (vs[col] == 'b')                     BluePositions.Add(new Position(col, row));                 if (vs[col] == 'R')                     RedPositions.Insert(0, new Position(col, row));                 if (vs[col] == 'r')                     RedPositions.Add(new Position(col, row));                 if (vs[col] == 'G')                     GreenPositions.Insert(0, new Position(col, row));                 if (vs[col] == 'g')                     GreenPositions.Add(new Position(col, row));             }         }     } } | 
フラッグを初期化する
Flagsプロパティは未通過のフラッグの位置のリストを返します。InitFlagsメソッドはテキストファイルのFが書かれている位置にフラッグをセットします。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public class RallyXGame {     public static List<Flag> _flags = new List<Flag>();     public static List<Flag> Flags     {         get { return _flags.Where(f => !f.IsPassed).ToList(); }     }     static void InitFlags()     {         _flags.Clear();         for (int row = 0; row < RowMax; row++)         {             string str = _mapText[row];             char[] vs = str.ToArray();             for (int col = 0; col < ColMax; col++)             {                 if (vs[col] == 'F')                     _flags.Add(new Flag(col, row));             }         }     } } | 
Smokesプロパティは煙幕のリストを返します。
| 1 2 3 4 5 6 7 8 | public class RallyXGame {     static List<Smoke> _smokes = new List<Smoke>();     public static List<Smoke> Smokes     {         get { return _smokes; }     } } | 
