面白そうな動画をみたのでC#で作ってみることにしました。スネーキーというレトロなゲームを作ります。
元ネタの動画は下の動画です。
Contents
フィールドをつくる
最初につくるべきは自機(というか自蛇?)かもしれないのですが、後回しにして先にフィールドを描画します。通路の交差する点の座標を配列にいれておきます。
1 2 3 4 5 |
public class Field { public int[] BranchX = { 50, 150, 250, 350, 450, 550, 650 }; public int[] BranchY = { 70, 170, 270, 370, 470 }; } |
フィールドを描画する
最初にフィールドを描画するメソッドをつくります。背景は黒にして通路はライトグレーで描画します。配列 BranchXとBranchYの最大値と最小値を使うと碁盤の目のような通路を描画することができます。30ピクセルの幅で直線を描画すると隅がうまく直角にならないので別の矩形を描画することで調整しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class Field { Pen Pen = new Pen(Color.LightGray, 30); SolidBrush Brush = new SolidBrush(Color.LightGray); public void Draw(Graphics graphics) { foreach (int x in BranchX) graphics.DrawLine(Pen, new Point(x, BranchY.Min()), new Point(x, BranchY.Max())); foreach (int y in BranchY) graphics.DrawLine(Pen, new Point(BranchX.Min(), y), new Point(BranchX.Max(), y)); int half = (int)Pen.Width / 2; graphics.FillRectangle(Brush, new Rectangle(BranchX.Min() - half, BranchY.Min() - half, half, half)); graphics.FillRectangle(Brush, new Rectangle(BranchX.Min() - half, BranchY.Max(), half, half)); graphics.FillRectangle(Brush, new Rectangle(BranchX.Max(), BranchY.Min() - half, half, half)); graphics.FillRectangle(Brush, new Rectangle(BranchX.Max(), BranchY.Max(), half, half)); } } |
その座標は交差点かどうか?
自機と敵は通路に沿って移動し、通路の交差点で進行方向を転換できます。そのときそのキャラクタが存在する座標は交差点なのかどうか、その座標に近い交差点はどこかを知る必要があるので、これを求めるメソッドをつくります。下記のメソッドは引数の値から一番近いX座標またはY座標を返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class Field { public int GetNeerestBranchX(int posX) { int minAbs = BranchX.Min(x => Math.Abs(posX - x)); foreach (int x in BranchX) { if (Math.Abs(posX - x) == minAbs) return x; } return -1; } public int GetNeerestBranchY(int posY) { int minAbs = BranchY.Min(y => Math.Abs(posY - y)); foreach (int y in BranchY) { if (Math.Abs(posY - y) == minAbs) return y; } return -1; } } |
敵を生成する
次に敵を描画します。敵の動きはUターンはしない、停止せずに移動し続けるものとします。自機の位置関係から移動方向を変えるアルゴリズムは実装しません。
自機は敵に正面からぶつかると敵を倒すことができますが、それ以外の方向から衝突されるとミスとなります。また自分自身にかみついてしまった場合もミスとなります。
まず移動方向を管理するための列挙体をつくります。
1 2 3 4 5 6 7 8 |
enum Direct { Up, Down, Left, Right, Max, // Max == 4 Direct.Maxとすると要素の数が取得できる } |
敵の初期化
敵は内部が塗りつぶされた円とし、半径は15ピクセル、位置は現在位置の中心座標で管理することにします。方向転換のとき、どの方向に変更するかを乱数で決めるので静的変数としてRandomオブジェクトを生成しています。RandomオブジェクトをそれぞれのEnemyオブジェクトごとに生成すると、それらは同じ疑似乱数しか生成しないので生成するRandomオブジェクトはひとつだけにします。
コンストラクタでは初期のXY座標をCenterプロパティにセットします。敵の初期座標はフィールドの交点とし、移動方向もランダムに決めてしまいます。
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 |
public class Enemy { public int Radius = 15; public int Speed = 1; static Field Field = new Field(); static Random Random = new Random(); Direct Direct = Direct.Up; public Enemy(int x, int y) { // 初期座標をCenterプロパティにセット Center = new Point(x, y); // 初期の移動方向をランダムに決める int r = Random.Next((int)Direct.Max); if(r == 0) Direct = Direct.Up; if (r == 1) Direct = Direct.Down; if (r == 2) Direct = Direct.Left; if (r == 3) Direct = Direct.Right; } // 敵の中心座標 public Point Center { get; private set; } } |
敵を移動させる
敵を移動させる処理ですが、進行方向に1ピクセルだけ移動させます。またフィールドの交点に存在する場合は進行方向を変更できる方向のなかからひとつをランダムに選択します。
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 |
public class Enemy { void Move() { int xPos = Center.X; int yPos = Center.Y; // 敵は今、フィールドの交点にいるかどうか? // Speedは1なので // Field.BranchX.Any(x => Center.X) && Field.BranchY.Any(y => Center.Y)でよいのだが // 将来変更されるかもしれないので交点とのズレの最小値とSpeedを比較する int minX = Field.BranchX.Min(x => Math.Abs(x - Center.X)); int minY = Field.BranchY.Min(y => Math.Abs(y - Center.Y)); if (minX < Speed && minY < Speed) { // 交点にいるのであれば方向転換 Direct = GetNextDirect(); } // 進行方向に応じて中心座標を変化させる if (Direct == Direct.Up) yPos -= Speed; if (Direct == Direct.Down) yPos += Speed; if (Direct == Direct.Left) xPos -= Speed; if (Direct == Direct.Right) xPos += Speed; Center = new Point(xPos, yPos); } } |
敵の中心座標が交点である場合(または最接近している場合)、方向転換をします。Uターンはしないので現在の進行方向が「上方向」である場合、候補として「下方向」はあり得ないことになります。またフィールドの上端、下端、左端、右端のときもそれ以上端へは進行できません。
移動できる方向を絞り込むことができたら乱数で新しい進行方向を決定します。
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 |
public class Enemy { Direct GetNextDirect() { Direct[] directs = { Direct.Up, Direct.Down, Direct.Left, Direct.Right, }; if (Direct == Direct.Up) directs = directs.Where(x => x != Direct.Down).ToArray(); if (Direct == Direct.Down) directs = directs.Where(x => x != Direct.Up).ToArray(); if (Direct == Direct.Left) directs = directs.Where(x => x != Direct.Right).ToArray(); if (Direct == Direct.Right) directs = directs.Where(x => x != Direct.Left).ToArray(); if(Field.BranchX.Min() - Center.X >= 0) directs = directs.Where(x => x != Direct.Left).ToArray(); if (Field.BranchX.Max() - Center.X <= 0) directs = directs.Where(x => x != Direct.Right).ToArray(); if (Field.BranchY.Min() - Center.Y >= 0) directs = directs.Where(x => x != Direct.Up).ToArray(); if (Field.BranchY.Max() - Center.Y <= 0) directs = directs.Where(x => x != Direct.Down).ToArray(); return directs[Random.Next(directs.Length)]; } } |
あとは敵をまとめて処理します。これはすべての敵を移動させるメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 |
public class Enemy { static List<Enemy> Enemies = new List<Enemy>(); static public void MoveAll() { foreach (Enemy enemy in Enemies) { enemy.Move(); } } } |
敵を描画する
これはすべての敵を描画するメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class Enemy { static public void DrawAll(Graphics graphics) { foreach (Enemy enemy in Enemies) { Rectangle rect = new Rectangle( enemy.Center.X - enemy.Radius, enemy.Center.Y - enemy.Radius, enemy.Radius * 2, enemy.Radius * 2); graphics.FillEllipse(Brushes.Red, rect); } } } |
敵を新しく追加するメソッドやゲーム開始時やステージクリアのあとなどに初期化するメソッドも必要です。
敵全体の初期化と追加
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 |
public class Enemy { static public void AddEnemy(Enemy enemy) { Enemies.Add(enemy); } // 初期の敵の出現位置は固定とする // 引数は敵の出現個数である static public void InitEnemies(int count) { Enemies.Clear(); if(count > 0) Enemy.AddEnemy(new Enemy(Field.BranchX[1], Field.BranchY[1])); if (count > 1) Enemy.AddEnemy(new Enemy(Field.BranchX[5], Field.BranchY[3])); if (count > 2) Enemy.AddEnemy(new Enemy(Field.BranchX[1], Field.BranchY[3])); if (count > 3) Enemy.AddEnemy(new Enemy(Field.BranchX[5], Field.BranchY[1])); if (count > 4) Enemy.AddEnemy(new Enemy(Field.BranchX[1], Field.BranchY[2])); if (count > 5) Enemy.AddEnemy(new Enemy(Field.BranchX[5], Field.BranchY[2])); if (count > 6) Enemy.AddEnemy(new Enemy(Field.BranchX[2], Field.BranchY[1])); if (count > 7) Enemy.AddEnemy(new Enemy(Field.BranchX[4], Field.BranchY[3])); } // 敵の数を返すメソッド static public int GetEnemiesCount() { return Enemies.Count; } } |