チクタクバンバンのようなゲームをつくります。
チクタクバンバンは1981年12月に野村トーイより販売されたスライディング・パズル・ゲームです。タテ4マス、ヨコ4マスの枠に配置されている15枚の線路プレートを動かして、進み続ける目覚まし時計を常に動き続けるようにします。目覚まし時計がプレートから落ちたり走行不能になったら負けです。また意図的にループを作る事は禁止であり、ベルが鳴り終わるまでにループを崩さなければ反則負けとなります。
下の動画は本物のチクタクバンバンです。
それでは実際につくってみましょう。
Contents
線路プレートをつくる
まず、線路プレートを表示させます。
PlateType列挙体
線路プレートの種類を区別するために列挙体を作成します。東西南北でどの方向が繋がっているかを示しています。
1 2 3 4 5 6 7 8 9 10 11 12 |
public enum PlateType { NE, NS, NW, SE, SW, WE, NSWE, NESW, NWSE, } |
TrackPlateクラス
次に線路プレートをつくるクラスを作成します。表示させるときの色は元々のチクタクバンバンにあわせて2種類にしたのですが、青と黄色を青とオレンジ色に変えています(線路を白で表示させたとき黄色では見えにくいという個人的な好みによる理由)。
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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
public class TrackPlate { public int Row = 0; public int Colum = 0; public TrackPlate(int colum, int row, PlateType plateType) { Row = row; Colum = colum; PlateType = plateType; } public static int Size { get { return 100; } } static int _marginLeft = 10; public static int MarginLeft { get { return _marginLeft; } } static int _marginTop = 10; public static int MarginTop { get { return _marginTop; } } public PlateType PlateType { get; } public int X { get { return Colum * Size + MarginLeft; } } public int Y { get { return Row * Size + MarginTop; } } Pen Pen = new Pen(Color.White, 4); Pen BorderPen = new Pen(Color.Black); public void Draw(Graphics graphics) { if (PlateType == PlateType.NSWE) { graphics.FillRectangle(Brushes.DeepSkyBlue, new Rectangle(X, Y, Size, Size)); graphics.DrawLine(Pen, new Point(X, Y + Size / 2), new Point(X + Size, Y + Size / 2)); graphics.DrawLine(Pen, new Point(X + Size / 2, Y), new Point(X + Size / 2, Y + Size)); } if (PlateType == PlateType.WE) { graphics.FillRectangle(Brushes.Orange, new Rectangle(X, Y, Size, Size)); graphics.DrawLine(Pen, new Point(X, Y + Size / 2), new Point(X + Size, Y + Size / 2)); } if (PlateType == PlateType.SE) { graphics.FillRectangle(Brushes.DeepSkyBlue, new Rectangle(X, Y, Size, Size)); graphics.DrawArc(Pen, new Rectangle(X + Size / 2, Y + Size / 2, Size, Size), 180, 90); } if (PlateType == PlateType.SW) { graphics.FillRectangle(Brushes.DeepSkyBlue, new Rectangle(X, Y, Size, Size)); graphics.DrawArc(Pen, new Rectangle(X - Size / 2, Y + Size / 2, Size, Size), 270, 90); } if (PlateType == PlateType.NW) { graphics.FillRectangle(Brushes.DeepSkyBlue, new Rectangle(X, Y, Size, Size)); graphics.DrawArc(Pen, new Rectangle(X - Size / 2, Y - Size / 2, Size, Size), 0, 90); } if (PlateType == PlateType.NWSE) { graphics.FillRectangle(Brushes.Orange, new Rectangle(X, Y, Size, Size)); graphics.DrawArc(Pen, new Rectangle(X - Size / 2, Y - Size / 2, Size, Size), 0, 90); graphics.DrawArc(Pen, new Rectangle(X + Size / 2, Y + Size / 2, Size, Size), 180, 90); } if (PlateType == PlateType.NESW) { graphics.FillRectangle(Brushes.Orange, new Rectangle(X, Y, Size, Size)); graphics.DrawArc(Pen, new Rectangle(X + Size / 2, Y - Size / 2, Size, Size), 90, 90); graphics.DrawArc(Pen, new Rectangle(X - Size / 2, Y + Size / 2, Size, Size), 270, 90); } if (PlateType == PlateType.NE) { graphics.FillRectangle(Brushes.Orange, new Rectangle(X, Y, Size, Size)); graphics.DrawArc(Pen, new Rectangle(X + Size / 2, Y - Size / 2, Size, Size), 90, 90); } graphics.DrawRectangle(BorderPen, new Rectangle(X, Y, Size, Size)); } } |
目覚まし時計を表示させる
次に目覚まし時計の位置を管理するためのAlarmClockクラスを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class AlarmClock { // 目覚まし時計はどのプレートの上にあるか? public TrackPlate TrackPlate = null; // 目覚まし時計はプレートの上でどの方向からどの方向へ移動するか? public DirectOfMove DirectOfMove = DirectOfMove.NorthToSouth; // 目覚まし時計が新しいプレートの上に移動してから何回移動したか? public int ElapsedTimeAfterEntering = 0; // ElapsedTimeAfterEnteringの最大値 //(double型にしておかないと大きめの値を設定したときバグる。整数では除算で端数切り捨てがおきるため) public static double ElapsedTimeAfterEnteringMax = 180; // 目覚まし時計が表示される座標 public int PositionX = 0; public int PositionY = 0; public AlarmClock() { } } |
これは目覚まし時計がプレートの上でどの方向からどの方向へ移動するかを示す列挙体です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public enum DirectOfMove { NorthToWest, NorthToSouth, NorthToEast, WestToNorth, WestToSouth, WestToEast, SouthToNorth, SouthToWest, SouthToEast, EastToNorth, EastToWest, EastToSouth, } |
線路プレートを表示させる
次にForm1クラスの処理を考えます。
線路プレートの初期化
線路プレートを表示させるだけならこれでできます。
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 |
public partial class Form1 : Form { List<TrackPlate> TrackPlates = new List<TrackPlate>(); public Form1() { InitializeComponent(); InitTrackPlates(); DoubleBuffered = true; int width = ColumMax * TrackPlate.Size + TrackPlate.MarginLeft * 2; int height = RowMax * TrackPlate.Size + TrackPlate.MarginTop * 2; ClientSize = new Size(width, height); BackColor = Color.Green; } void InitTrackPlates() { TrackPlates.Add(new TrackPlate(0, 0, PlateType.NE)); TrackPlates.Add(new TrackPlate(1, 0, PlateType.NESW)); TrackPlates.Add(new TrackPlate(2, 0, PlateType.NESW)); TrackPlates.Add(new TrackPlate(3, 0, PlateType.WE)); TrackPlates.Add(new TrackPlate(0, 1, PlateType.SE)); TrackPlates.Add(new TrackPlate(1, 1, PlateType.SW)); TrackPlates.Add(new TrackPlate(2, 1, PlateType.SW)); TrackPlates.Add(new TrackPlate(3, 1, PlateType.NSWE)); TrackPlates.Add(new TrackPlate(0, 2, PlateType.NSWE)); TrackPlates.Add(new TrackPlate(1, 2, PlateType.NWSE)); TrackPlates.Add(new TrackPlate(2, 2, PlateType.NESW)); TrackPlates.Add(new TrackPlate(3, 2, PlateType.NWSE)); TrackPlates.Add(new TrackPlate(0, 3, PlateType.NW)); TrackPlates.Add(new TrackPlate(1, 3, PlateType.NSWE)); TrackPlates.Add(new TrackPlate(2, 3, PlateType.WE)); // (3,3)は空ける } } |
線路プレートを移動させる
方向キーが押されたら空白部分を埋めるように線路プレートを移動させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public partial class Form1 : Form { protected override void OnKeyDown(KeyEventArgs e) { if (e.KeyCode == Keys.Left) MoveLeft(); // 後述 if (e.KeyCode == Keys.Up) MoveUp(); // 後述 if (e.KeyCode == Keys.Right) MoveRight(); // 後述 if (e.KeyCode == Keys.Down) MoveDown(); // 後述 base.OnKeyDown(e); } } |
線路プレートの空白部分を探すメソッドがFindBlankです。見つからない場合は(ないと思いますが)retRowとretColumに-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 |
public partial class Form1 : Form { void FindBlank(out int retColum, out int retRow) { for (int row = 0; row < RowMax; row++) { for (int colum = 0; colum < ColumMax; colum++) { TrackPlate trackPlate = TrackPlates.FirstOrDefault(x => x.Row == row && x.Colum == colum); if (trackPlate == null) { retRow = row; retColum = colum; return; } } } retRow = -1; retColum = -1; } public int RowMax { get { return TrackPlates.Max(x => x.Row) + 1; } } public int ColumMax { get { return TrackPlates.Max(x => x.Colum) + 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 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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
public partial class Form1 : Form { void MoveLeft() { FindBlank(out int retColum, out int retRow); // retColum == -1になることはないはずだが念のため if (retColum == -1) return; // 移動させる線路プレート TrackPlate trackPlate = TrackPlates.FirstOrDefault(x => x.Row == retRow && x.Colum == retColum + 1); // 移動させる線路プレートがみつかったら移動させる if (trackPlate != null) { trackPlate.Row = retRow; trackPlate.Colum = retColum; // もし線路プレートの上に目覚まし時計がある場合はいっしょに移動させる if (AlarmClock.TrackPlate == trackPlate) AlarmClock.PositionX -= TrackPlate.Size; // 再描画 Invalidate(); // 音を鳴らす SoundMove.URL = Application.StartupPath + "\\move.mp3"; } } void MoveUp() { FindBlank(out int retColum, out int retRow); if (retColum == -1) return; TrackPlate trackPlate = TrackPlates.FirstOrDefault(x => x.Row == retRow + 1 && x.Colum == retColum); if (trackPlate != null) { trackPlate.Row = retRow; trackPlate.Colum = retColum; if (AlarmClock.TrackPlate == trackPlate) AlarmClock.PositionY -= TrackPlate.Size; Invalidate(); SoundMove.URL = Application.StartupPath + "\\move.mp3"; } } void MoveRight() { FindBlank(out int retColum, out int retRow); if (retColum == -1) return; TrackPlate trackPlate = TrackPlates.FirstOrDefault(x => x.Row == retRow && x.Colum == retColum - 1); if (trackPlate != null) { trackPlate.Row = retRow; trackPlate.Colum = retColum; if (AlarmClock.TrackPlate == trackPlate) AlarmClock.PositionX += TrackPlate.Size; Invalidate(); SoundMove.URL = Application.StartupPath + "\\move.mp3"; } } void MoveDown() { FindBlank(out int retColum, out int retRow); if (retColum == -1) return; TrackPlate trackPlate = TrackPlates.FirstOrDefault(x => x.Row == retRow - 1 && x.Colum == retColum); if (trackPlate != null) { trackPlate.Row = retRow; trackPlate.Colum = retColum; if (AlarmClock.TrackPlate == trackPlate) AlarmClock.PositionY += TrackPlate.Size; Invalidate(); SoundMove.URL = Application.StartupPath + "\\move.mp3"; } } } |
線路プレートの描画
あとは線路プレートを描画するだけです。
1 2 3 4 5 6 7 8 9 10 |
public partial class Form1 : Form { protected override void OnPaint(PaintEventArgs e) { foreach (TrackPlate trackPlates in TrackPlates) trackPlates.Draw(e.Graphics); base.OnPaint(e); } } |
目覚まし時計の移動
チクタクバンバンでは目覚まし時計を移動させなければなりません。今回は赤丸で表示します(次回以降、もっとちゃんとしたものにする)。
目覚まし時計がどこに存在するかはどの線路プレートの上に存在するか、その線路プレートの上でどの方向からどの方向に移動しているか、そのプレートの上で何回移動したかがわかれば特定できます。以下は目覚まし時計を移動させるためのメソッドです。
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 |
public partial class Form1 : Form { void MoveAlarmClock() { AlarmClock.ElapsedTimeAfterEntering++; if (AlarmClock.DirectOfMove == DirectOfMove.NorthToSouth) MoveAlarmClockOnNorthToSouth(); else if (AlarmClock.DirectOfMove == DirectOfMove.WestToSouth) MoveAlarmClockOnWestToSouth(); else if (AlarmClock.DirectOfMove == DirectOfMove.EastToSouth) MoveAlarmClockOnEastToSouth(); else if (AlarmClock.DirectOfMove == DirectOfMove.NorthToWest) MoveAlarmClockOnNorthToWest(); else if (AlarmClock.DirectOfMove == DirectOfMove.EastToWest) MoveAlarmClockOnEastToWest(); else if (AlarmClock.DirectOfMove == DirectOfMove.SouthToWest) MoveAlarmClockOnSouthToWest(); else if (AlarmClock.DirectOfMove == DirectOfMove.SouthToNorth) MoveAlarmClockOnSouthToNorth(); else if (AlarmClock.DirectOfMove == DirectOfMove.WestToNorth) MoveAlarmClockOnWestToNorth(); else if (AlarmClock.DirectOfMove == DirectOfMove.EastToNorth) MoveAlarmClockOnEastToNorth(); else if (AlarmClock.DirectOfMove == DirectOfMove.NorthToEast) MoveAlarmClockOnNorthToEast(); else if (AlarmClock.DirectOfMove == DirectOfMove.WestToEast) MoveAlarmClockOnWestToEast(); else if (AlarmClock.DirectOfMove == DirectOfMove.SouthToEast) MoveAlarmClockOnSouthToEast(); Invalidate(); } } |
目覚まし時計はどこからどこへ移動するか
目覚まし時計が北から南に移動するときの処理を示します。目覚まし時計が南北に移動できる線路プレートの上に移動してから何回移動したかはAlarmClock.ElapsedTimeAfterEnteringを見ればわかります。AlarmClock.ElapsedTimeAfterEnteringが取り得る最大値とAlarmClock.ElapsedTimeAfterEnteringから目覚まし時計の座標を調べて再セットすることができます。またAlarmClock.ElapsedTimeAfterEnteringMaxに達した場合は次の線路プレートに移動したということなので、そのプレートを調べます。
次のプレートは取得できないかもしれません。この場合はミスをしたことになります。また目覚まし時計が北から南に移動したのであれば次に取得された線路プレートは北から内部に入れるプレートでなければなりません。そうでない場合もミスとなります。ミスをするとすれば次のプレートに移動する瞬間です。WhenLeavingSouthメソッドは南から目覚まし時計が出たときに呼び出されます。ここでミス判定をおこないます。
1 2 3 4 5 6 7 8 9 10 11 |
public partial class Form1 : Form { void MoveAlarmClockOnNorthToSouth() { AlarmClock.PositionX = AlarmClock.TrackPlate.X + TrackPlate.Size / 2; AlarmClock.PositionY = (int)(AlarmClock.TrackPlate.Y + TrackPlate.Size / AlarmClock.ElapsedTimeAfterEnteringMax * AlarmClock.ElapsedTimeAfterEntering); if (AlarmClock.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax) WhenLeavingSouth(); } } |
南から目覚まし時計が出たときに呼び出されるWhenLeavingSouthメソッドを示します。次の線路プレートは取得できるか、次の線路プレートは北から内部に入れるかを調べ、これができなかった場合はCantMoveToNextイベント(この線路プレートには移動できない)またはNextDoesntExistイベント(移動先の線路プレートが存在しない)を発生させます。
次の線路プレートは北から内部に入れる場合は、この線路プレートの出口を調べます。そして対応するDirectOfMoveをAlarmClock.DirectOfMoveにセットします。
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 |
public partial class Form1 : Form { public event EventHandler NextDoesntExist; public event EventHandler CantMoveToNext; void WhenLeavingSouth() { // 南側から出たので、その下(南)にある線路プレートを取得する TrackPlate nextPlate = GetLowerPlate(AlarmClock.TrackPlate); if (nextPlate != null) { AlarmClock.TrackPlate = nextPlate; AlarmClock.ElapsedTimeAfterEntering = 0; if (nextPlate.PlateType == PlateType.NS || nextPlate.PlateType == PlateType.NSWE) AlarmClock.DirectOfMove = DirectOfMove.NorthToSouth; else if (nextPlate.PlateType == PlateType.NE || nextPlate.PlateType == PlateType.NESW) AlarmClock.DirectOfMove = DirectOfMove.NorthToEast; else if (nextPlate.PlateType == PlateType.NW || nextPlate.PlateType == PlateType.NWSE) AlarmClock.DirectOfMove = DirectOfMove.NorthToWest; else CantMoveToNext?.Invoke(this, new EventArgs()); } else NextDoesntExist?.Invoke(this, new EventArgs()); } } |
GetLowerPlateメソッドは引数で渡された線路プレートの下側にある線路プレートを返します。同様に引数で渡された線路プレートの左側、右側、上側の線路プレートを取得する処理を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public partial class Form1 : Form { public TrackPlate GetLowerPlate(TrackPlate trackPlate) { return TrackPlates.FirstOrDefault(x => x.Row == trackPlate.Row + 1 && x.Colum == trackPlate.Colum); } public TrackPlate GetLeftPlate(TrackPlate trackPlate) { return TrackPlates.FirstOrDefault(x => x.Row == trackPlate.Row && x.Colum == trackPlate.Colum - 1); } public TrackPlate GetRightPlate(TrackPlate trackPlate) { return TrackPlates.FirstOrDefault(x => x.Row == trackPlate.Row && x.Colum == trackPlate.Colum + 1); } public TrackPlate GetUpperPlate(TrackPlate trackPlate) { return TrackPlates.FirstOrDefault(x => x.Row == trackPlate.Row - 1 && x.Colum == trackPlate.Colum); } } |
同様にすれば他の方向に移動する処理もできます。
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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
public partial class Form1 : Form { void MoveAlarmClockOnWestToSouth() { double rad0 = Math.PI / 2 / AlarmClock.ElapsedTimeAfterEnteringMax; double rad = Math.PI / 2 * 3 + rad0 * AlarmClock.ElapsedTimeAfterEntering; AlarmClock.PositionX = AlarmClock.TrackPlate.X + (int)(TrackPlate.Size / 2 * Math.Cos(rad)); AlarmClock.PositionY = AlarmClock.TrackPlate.Y + TrackPlate.Size + (int)(TrackPlate.Size / 2 * Math.Sin(rad)); if (AlarmClock.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax) WhenLeavingSouth(); } void MoveAlarmClockOnEastToSouth() { double rad0 = Math.PI / 2 / AlarmClock.ElapsedTimeAfterEnteringMax; double rad = Math.PI / 2 * 3 - rad0 * AlarmClock.ElapsedTimeAfterEntering; AlarmClock.PositionX = AlarmClock.TrackPlate.X + TrackPlate.Size + (int)(TrackPlate.Size / 2 * Math.Cos(rad)); AlarmClock.PositionY = AlarmClock.TrackPlate.Y + TrackPlate.Size + (int)(TrackPlate.Size / 2 * Math.Sin(rad)); if (AlarmClock.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax) WhenLeavingSouth(); } void MoveAlarmClockOnNorthToWest() { double rad0 = Math.PI / 2 / AlarmClock.ElapsedTimeAfterEnteringMax; double rad = rad0 * AlarmClock.ElapsedTimeAfterEntering; AlarmClock.PositionX = AlarmClock.TrackPlate.X + (int)(TrackPlate.Size / 2 * Math.Cos(rad)); AlarmClock.PositionY = AlarmClock.TrackPlate.Y + (int)(TrackPlate.Size / 2 * Math.Sin(rad)); if (AlarmClock.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax) WhenLeavingWest(); } void MoveAlarmClockOnEastToWest() { AlarmClock.PositionX = (int)(AlarmClock.TrackPlate.X + TrackPlate.Size - TrackPlate.Size / AlarmClock.ElapsedTimeAfterEnteringMax * AlarmClock.ElapsedTimeAfterEntering); AlarmClock.PositionY = AlarmClock.TrackPlate.Y + TrackPlate.Size / 2; if (AlarmClock.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax) WhenLeavingWest(); } void MoveAlarmClockOnSouthToWest() { double rad0 = Math.PI / 2 / AlarmClock.ElapsedTimeAfterEnteringMax; double rad = -rad0 * AlarmClock.ElapsedTimeAfterEntering; AlarmClock.PositionX = AlarmClock.TrackPlate.X + (int)(TrackPlate.Size / 2 * Math.Cos(rad)); AlarmClock.PositionY = AlarmClock.TrackPlate.Y + TrackPlate.Size + (int)(TrackPlate.Size / 2 * Math.Sin(rad)); if (AlarmClock.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax) WhenLeavingWest(); } void MoveAlarmClockOnSouthToNorth() { AlarmClock.PositionX = AlarmClock.TrackPlate.X + TrackPlate.Size / 2; AlarmClock.PositionY = (int)(AlarmClock.TrackPlate.Y + TrackPlate.Size - TrackPlate.Size / AlarmClock.ElapsedTimeAfterEnteringMax * AlarmClock.ElapsedTimeAfterEntering); if (AlarmClock.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax) WhenLeavingNorth(); } void MoveAlarmClockOnWestToNorth() { double rad0 = Math.PI / 2 / AlarmClock.ElapsedTimeAfterEnteringMax; double rad = Math.PI / 2 - rad0 * AlarmClock.ElapsedTimeAfterEntering; AlarmClock.PositionX = AlarmClock.TrackPlate.X + (int)(TrackPlate.Size / 2 * Math.Cos(rad)); AlarmClock.PositionY = AlarmClock.TrackPlate.Y + (int)(TrackPlate.Size / 2 * Math.Sin(rad)); if (AlarmClock.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax) WhenLeavingNorth(); } void MoveAlarmClockOnEastToNorth() { double rad0 = Math.PI / 2 / AlarmClock.ElapsedTimeAfterEnteringMax; double rad = Math.PI / 2 + rad0 * AlarmClock.ElapsedTimeAfterEntering; AlarmClock.PositionX = AlarmClock.TrackPlate.X + TrackPlate.Size + (int)(TrackPlate.Size / 2 * Math.Cos(rad)); AlarmClock.PositionY = AlarmClock.TrackPlate.Y + (int)(TrackPlate.Size / 2 * Math.Sin(rad)); if (AlarmClock.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax) WhenLeavingNorth(); } void MoveAlarmClockOnNorthToEast() { double rad0 = Math.PI / 2 / AlarmClock.ElapsedTimeAfterEnteringMax; double rad = Math.PI - rad0 * AlarmClock.ElapsedTimeAfterEntering; AlarmClock.PositionX = AlarmClock.TrackPlate.X + TrackPlate.Size + (int)(TrackPlate.Size / 2 * Math.Cos(rad)); AlarmClock.PositionY = AlarmClock.TrackPlate.Y + (int)(TrackPlate.Size / 2 * Math.Sin(rad)); if (AlarmClock.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax) WhenLeavingEast(); } void MoveAlarmClockOnWestToEast() { AlarmClock.PositionX = (int)(AlarmClock.TrackPlate.X + TrackPlate.Size / AlarmClock.ElapsedTimeAfterEnteringMax * AlarmClock.ElapsedTimeAfterEntering); AlarmClock.PositionY = AlarmClock.TrackPlate.Y + TrackPlate.Size / 2; if (AlarmClock.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax) WhenLeavingEast(); } void MoveAlarmClockOnSouthToEast() { double rad0 = Math.PI / 2 / AlarmClock.ElapsedTimeAfterEnteringMax; double rad = Math.PI + rad0 * AlarmClock.ElapsedTimeAfterEntering; AlarmClock.PositionX = AlarmClock.TrackPlate.X + TrackPlate.Size + (int)(TrackPlate.Size / 2 * Math.Cos(rad)); AlarmClock.PositionY = AlarmClock.TrackPlate.Y + TrackPlate.Size + (int)(TrackPlate.Size / 2 * Math.Sin(rad)); if (AlarmClock.ElapsedTimeAfterEntering >= AlarmClock.ElapsedTimeAfterEnteringMax) WhenLeavingEast(); } } |
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 59 60 61 62 63 64 65 66 67 68 |
public partial class Form1 : Form { void WhenLeavingNorth() { TrackPlate nextPlate = GetUpperPlate(AlarmClock.TrackPlate); if (nextPlate != null) { AlarmClock.TrackPlate = nextPlate; AlarmClock.ElapsedTimeAfterEntering = 0; if (nextPlate.PlateType == PlateType.NS || nextPlate.PlateType == PlateType.NSWE) AlarmClock.DirectOfMove = DirectOfMove.SouthToNorth; else if (nextPlate.PlateType == PlateType.SW || nextPlate.PlateType == PlateType.NESW) AlarmClock.DirectOfMove = DirectOfMove.SouthToWest; else if (nextPlate.PlateType == PlateType.SE || nextPlate.PlateType == PlateType.NWSE) AlarmClock.DirectOfMove = DirectOfMove.SouthToEast; else CantMoveToNext?.Invoke(this, new EventArgs()); } else NextDoesntExist?.Invoke(this, new EventArgs()); } void WhenLeavingEast() { TrackPlate nextPlate = GetRightPlate(AlarmClock.TrackPlate); if (nextPlate != null) { AlarmClock.TrackPlate = nextPlate; AlarmClock.ElapsedTimeAfterEntering = 0; if (nextPlate.PlateType == PlateType.NW || nextPlate.PlateType == PlateType.NWSE) AlarmClock.DirectOfMove = DirectOfMove.WestToNorth; else if (nextPlate.PlateType == PlateType.WE || nextPlate.PlateType == PlateType.NSWE) AlarmClock.DirectOfMove = DirectOfMove.WestToEast; else if (nextPlate.PlateType == PlateType.SW || nextPlate.PlateType == PlateType.NESW) AlarmClock.DirectOfMove = DirectOfMove.WestToSouth; else CantMoveToNext?.Invoke(this, new EventArgs()); } else NextDoesntExist?.Invoke(this, new EventArgs()); } void WhenLeavingWest() { TrackPlate nextPlate = GetLeftPlate(AlarmClock.TrackPlate); if (nextPlate != null) { AlarmClock.TrackPlate = nextPlate; AlarmClock.ElapsedTimeAfterEntering = 0; if (nextPlate.PlateType == PlateType.NE || nextPlate.PlateType == PlateType.NESW) AlarmClock.DirectOfMove = DirectOfMove.EastToNorth; else if (nextPlate.PlateType == PlateType.WE || nextPlate.PlateType == PlateType.NSWE) AlarmClock.DirectOfMove = DirectOfMove.EastToWest; else if (nextPlate.PlateType == PlateType.SE || nextPlate.PlateType == PlateType.NWSE) AlarmClock.DirectOfMove = DirectOfMove.EastToSouth; else CantMoveToNext?.Invoke(this, new EventArgs()); } else NextDoesntExist?.Invoke(this, new EventArgs()); } } |
とりあえず完成させる
タイマーの初期と目覚まし時計の位置の初期化
もう一度コンストラクタを示します。タイマーの初期と目覚まし時計の位置の初期化の処理が追加されています。
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 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); InitTrackPlates(); InitAlarmClock(); DoubleBuffered = true; int width = ColumMax * TrackPlate.Size + TrackPlate.MarginLeft * 2; int height = RowMax * TrackPlate.Size + TrackPlate.MarginTop * 2; ClientSize = new Size(width, height); BackColor = Color.Green; NextDoesntExist += Form1_NextDoesntExist; CantMoveToNext += Form1_CantMoveToNext; Timer.Interval = 32; Timer.Tick += Timer_Tick; Timer.Start(); } private void Timer_Tick(object sender, EventArgs e) { MoveAlarmClock(); } void InitAlarmClock() { TrackPlate trackPlate = TrackPlates.FirstOrDefault(x => x.Row == 0 && x.Colum == 1); AlarmClock.TrackPlate = trackPlate; AlarmClock.DirectOfMove = DirectOfMove.NorthToEast; // ↑ trackPlateと矛盾しないように設定すること AlarmClock.ElapsedTimeAfterEntering = 1; } // いまはミス時はタイマーを止めることにする private void Form1_CantMoveToNext(object sender, EventArgs e) { Timer.Stop(); // ゲームオーバーらしい音を鳴らす SoundGameOver.URL = Application.StartupPath + "\\gameover.mp3"; } private void Form1_NextDoesntExist(object sender, EventArgs e) { Timer.Stop(); SoundGameOver.URL = Application.StartupPath + "\\gameover.mp3"; } } |
描画の処理
描画の処理を示します。
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 |
public partial class Form1 : Form { // ゲームオーバー表示用の変数 string MissString = "Game Over"; Font MissFont = new Font("MS ゴシック", 32, FontStyle.Bold); Point ShowMissPoint = Point.Empty; protected override void OnPaint(PaintEventArgs e) { foreach (TrackPlate trackPlates in TrackPlates) trackPlates.Draw(e.Graphics); e.Graphics.FillEllipse(Brushes.Red, new Rectangle(AlarmClock.PositionX - 5, AlarmClock.PositionY - 5, 10, 10)); // どこに文字列を表示するかわからない場合は調べる if (ShowMissPoint == Point.Empty) { Size size = TextRenderer.MeasureText(MissString, MissFont); int x = (ClientSize.Width - size.Width) / 2; int y = (ClientSize.Height - size.Height) / 2; ShowMissPoint = new Point(x, y); } // タイマーが止まっていたらゲームオーバーと表示する if (!Timer.Enabled) { TextRenderer.DrawText(e.Graphics, MissString, MissFont, ShowMissPoint, Color.Black); } base.OnPaint(e); } } |