今回はパックマンもどきの主役パックマンをつくります。
Contents
パックマンを移動させる
・・・といいたいところなのですが、○印で表示させます。移動させる方法を考えていたらデザイン的にいいかげんなものしかできませんでした。
まずパックマンを表示させるためにはどこに表示させるか座標を指定する必要があります。上下左右のキーがおされることで座標がかわります。
ただ動きをなめらかにするためにどちら方向に移動しているのかを管理するフラグを作成してキーが押されたらフラグの値を変更します。そしてつねにフラグで指定されている方向に移動することにします。
これは移動している方向を示す列挙体です。
移動方向を表すDirect列挙体の作成
1 2 3 4 5 6 7 |
public enum Direct { North, East, South, West, } |
これはパックマンが描画されるセルと座標を管理するためのフィールド変数です。
1 2 3 4 5 6 7 8 9 10 |
public partial class Form1 : Form { int pacCellX = 0; int pacCellY = 0; int pacPosX = 0; int pacPosY = 0; Direct pacDirect = Direct.West; } |
パックマンの位置の初期化
ゲームがはじまったらパックマンの位置を初期化します。最初は上から19番目、左から11番目のセルがある位置に描画します。そしてGetMazeCellメソッドをつかってセルの位置を取得してpacPosXとpacPosYに格納します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public partial class Form1 : Form { void InitPacman() { pacCellX = 10; pacCellY = 18; Rectangle rect = GetMazeCell(10, 18); pacPosX = rect.X; pacPosY = rect.Y; pacDirect = Direct.West; } } |
OnKeyDownメソッドのオーバーライド
上下左右のキーがおされたらパックマンの移動方向を変えます。ただしその前にほんとうに移動できるかどうか調べる必要があります。この判定をするのがCanPacmanMoveDirectメソッドです。
移動できることが確認できた場合はpacDirectに値を格納しています。
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 partial class Form1 : Form { protected override void OnKeyDown(KeyEventArgs e) { if(e.KeyCode == Keys.Down && CanPacmanMoveDirect(Direct.South)) { pacDirect = Direct.South; } if(e.KeyCode == Keys.Up && CanPacmanMoveDirect(Direct.North)) { pacDirect = Direct.North; } if(e.KeyCode == Keys.Right && CanPacmanMoveDirect(Direct.East)) { pacDirect = Direct.East; } if(e.KeyCode == Keys.Left && CanPacmanMoveDirect(Direct.West)) { pacDirect = Direct.West; } base.OnKeyDown(e); } } |
CanPacmanMoveDirectメソッドの作成
次にその方向に移動できるかどうかの判定をするCanPacmanMoveDirectメソッドですが、このような内容になっています。
現在パックマンが存在するセルの文字を調べます。そこから移動できるかどうかを判定しています。
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 partial class Form1 : Form { bool CanPacmanMoveDirect(Direct direct) { string str = GetStringMazeCell(pacCellX, pacCellY); if(direct == Direct.North) { if(str == "├" || str == "┼" || str == "┤" || str == "└" || str == "┴" || str == "┘" || str == "│") return true; else return false; } if(direct == Direct.South) { if(str == "┌" || str == "┬" || str == "┐" || str == "├" || str == "┼" || str == "┤" || str == "│") return true; else return false; } if(direct == Direct.East) { if(str == "┌" || str == "┬" || str == "├" || str == "┼" || str == "└" || str == "┴" || str == "─") return true; else return false; } if(direct == Direct.West) { if(str == "┬" || str == "┐" || str == "┼" || str == "┤" || str == "┴" || str == "┘" || str == "─") return true; else return false; } return false; } } |
左から○番目、上から○番目のセルがどの文字に対応しているのかを取得するGetStringMazeCellメソッドを示します。
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 |
public partial class Form1 : Form { string GetStringMazeCell(int x, int y) { if(x < 0) return ""; if(y < 0) return ""; if(y > 23) return ""; if(x > 20) return ""; if(y == 0) return row01.Substring(x, 1); if(y == 1) return row02.Substring(x, 1); if(y == 2) return row03.Substring(x, 1); if(y == 3) return row04.Substring(x, 1); if(y == 4) return row05.Substring(x, 1); if(y == 5) return row06.Substring(x, 1); if(y == 6) return row07.Substring(x, 1); if(y == 7) return row08.Substring(x, 1); if(y == 8) return row09.Substring(x, 1); if(y == 9) return row10.Substring(x, 1); if(y == 10) return row11.Substring(x, 1); if(y == 11) return row12.Substring(x, 1); if(y == 12) return row13.Substring(x, 1); if(y == 13) return row14.Substring(x, 1); if(y == 14) return row15.Substring(x, 1); if(y == 15) return row16.Substring(x, 1); if(y == 16) return row17.Substring(x, 1); if(y == 17) return row18.Substring(x, 1); if(y == 18) return row19.Substring(x, 1); if(y == 19) return row20.Substring(x, 1); if(y == 20) return row21.Substring(x, 1); if(y == 21) return row22.Substring(x, 1); if(y == 22) return row23.Substring(x, 1); if(y == 23) return row24.Substring(x, 1); return "not"; } } |
MovePacmanDirectメソッドの作成
その方向に移動できることがわかったら、実際にその方向に移動させます。
パックマンが存在するセルの値を移動させたい方向に変化させます。そのあとパックマンを描画する座標を調べて適切な値をセットしてInvalidateメソッドを呼んでいます。これでOnPaintメソッドのなかで描画がおこなわれます。
セルからセルへの移動がなめらかにおこなわれているように見せるために4回にわけて移動させています。そのあいだループを止めながら回すのでTask.Delayメソッドをつかっています。
実際に移動してしまったあとではなく先にpacCellXとpacCellYの値を変更しているのは、移動が完了してしまうと次の移動がはじまってしまい、方向転換をすることができなくなるからです。方向転換するためには、移動先が決まって実際に移動が完了するまでにキーを押す必要があります。
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 |
public partial class Form1 : Form { const int PACMAN_DELAY = 40; async Task<bool> MovePacmanDirect(Direct direct) { if(CanPacmanMoveDirect(direct)) { if(direct == Direct.North) pacCellY--; if(direct == Direct.South) pacCellY++; if(direct == Direct.East) pacCellX++; if(direct == Direct.West) pacCellX--; for(int i = 0; i < 4; i++) { if(direct == Direct.North) pacPosY -= CELL_HEIGHT / 4; if(direct == Direct.South) pacPosY += CELL_HEIGHT / 4; if(direct == Direct.East) pacPosX += CELL_WIDTH / 4; if(direct == Direct.West) pacPosX -= CELL_WIDTH / 4; Invalidate(); await Task.Delay(PACMAN_DELAY); } // CELL_HEIGHTが4で割り切れない場合、ずれが発生するのでその補正のための処理 Rectangle newRect = GetMazeCell(pacCellX, pacCellY); pacPosX = newRect.X; pacPosY = newRect.Y; } return true; } } |
DrawPacメソッド
実際にパックマンを描画するのはDrawPacメソッドです。
画像はmame icons 03 OKINIIRIで公開しているものを使用しました。アイコンタイプです。これをリソースとして追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public partial class Form1 : Form { Icon pacmanIcon = Properties.Resources.puckman; protected override void OnPaint(PaintEventArgs e) { DrawMaze(e.Graphics); DrawPac(e.Graphics); base.OnPaint(e); } void DrawPac(Graphics g) { Rectangle rect = new Rectangle(new Point(pacPosX, pacPosY), new Size(CELL_WIDTH, CELL_HEIGHT)); g.DrawIcon(pacmanIcon, rect); } } |
Tickイベントを処理する
ボタンを押してもpacDirectの値が変更されるだけなので、実際にMovePacmanDirectメソッドを呼ぶ処理が必要です。
そこでタイマーをつかってTickイベントを処理しています。MovePacmanDirectメソッドの処理中、さらにMovePacmanDirectメソッドが呼ばれたときのことを考えて、ignoreTickフラグをセットしています。
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 partial class Form1 : Form { public Form1() { InitializeComponent(); this.DoubleBuffered = true; timer.Interval = 200; timer.Tick += Timer_Tick; timer.Start(); InitPacman(); Invalidate(); } bool ignoreTick = false; private async void Timer_Tick(object sender, EventArgs e) { if(ignoreTick) return; ignoreTick = true; Task<bool> pacTask = MovePacmanDirect(pacDirect); await pacTask; await monstarsTask; ignoreTick = false; } } |