⇒ 動作確認はこちらからどうぞ。
←↑→↓キーで移動、Zキーで穴を堀り、Xキーで埋めます。黄色いやさぐれひよこは落とせば死にますが、ピンク色のものは2段、青いやさぐれひよこは3段連続で落とさないと死にません。
前回はGameManagerクラスと基本的なクラスだけ作成しましたが、今回はPlayerクラスを作成します。
Playerクラス
プレイヤーを初期座標に戻す関数と穴を掘ったり埋めたりするときの音を鳴らす関数を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Player { // プレイヤーの初期座標 X: number = 7 * GameManager.CharctorSize; Y: number = 15 * GameManager.CharctorSize; DrawCount: number = 0; Reset() { this.X = 7 * GameManager.CharctorSize; this.Y = 15 * GameManager.CharctorSize; this.DrawCount = 0; } PlayHitSound() { GameManager.SoundHit.currentTime = 0; GameManager.SoundHit.play(); } } |
プレイヤーの描画
プレイヤーを描画する関数を示します。プレイヤーが左右のどちらを向いているかで使用されるイメージが違います。また4種類のイメージを切り替えることで走っているように見せかけています。
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 |
class Player { DrawPlayer(con: CanvasRenderingContext2D) { let point: Point = GameManager.GetPointForShow(this.X, this.Y); let points: Point[] = []; points.push(new Point(point.X + GameManager.CharctorSize / 2, point.Y)); points.push(new Point(point.X, point.Y + GameManager.CharctorSize)); points.push(new Point(point.X + GameManager.CharctorSize, point.Y + GameManager.CharctorSize)); let index = 0; this.DrawCount++; let division = this.DrawCount % 16; if (this.PlayerMoveDirect == MoveDirect.Right) { if (division < 4) index = 0; else if (division < 8) index = 1; else if (division < 12) index = 2; else if (division < 16) index = 3; } else { if (division < 4) index = 4; else if (division < 8) index = 5; else if (division < 12) index = 6; else if (division < 16) index = 7; } // やや大きめに描画する con.drawImage(GameManager.PlayerImages[index], point.X - 8, point.Y - 8, GameManager.CharctorSize + 16, GameManager.CharctorSize + 8); } } |
プレイヤーの移動
プレイヤーを移動させるときにほんとうに移動できるかどうかを確認するための関数を示します。プレイヤーはフロアの端、未完成の穴、敵がハマった穴には移動できません。またハシゴがない場所で上下に移動したり、ハシゴの上り下りをしているときに左右に移動することもできません。
上に移動できるかどうかを確認する関数を示します。ハシゴの上端やハシゴがない位置では上には移動できません。ただしフロアからハシゴに移動するとき、8ピクセル以内のズレであれば移動できるものとして処理します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Player { CanMoveUp(x: number, y: number): boolean { let length = GameManager.LadderPositions.length; for (let i = 0; i < length; i++) { let point: Point = GameManager.LadderPositions[i]; // point.Y - 3 * CharctorSizeはハシゴの上端のY座標 if (point.X - 8 <= x && x <= point.X + 8 && point.Y - 3 * GameManager.CharctorSize < y && y <= point.Y) return true; } return false; } } |
下に移動できるかどうかを確認する関数を示します。
1 2 3 4 5 6 7 8 9 10 11 |
class Player { CanMoveDown(x: number, y: number): boolean { let length = GameManager.LadderPositions.length; for (let i = 0; i < length; i++) { let point: Point = GameManager.LadderPositions[i]; if (point.X - 8 <= x && x <= point.X + 8 && point.Y - 3 * GameManager.CharctorSize <= y && y < point.Y) return true; } return false; } } |
左右に移動できるかどうかを確認する関数を示します。フロアの両端をこえたり、ハシゴの上り下りをしているときは左右に移動できません。ただしハシゴからフロアに移動するとき、8ピクセル以内のズレであれば移動できるものとして処理します。
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 |
class Player { CanMoveLeft(x: number, y: number): boolean { if (x <= 0 || x > 14 * GameManager.CharctorSize) return false; // 未完成の穴やモンスターが入っている穴には入れない let playerX = this.X; let holePlayerFalls: Hole[] = GameManager.Holes.filter( hole => hole.Rectangle.Right == playerX && hole.Rectangle.Y == this.Y); if (holePlayerFalls.length == 1) { if (holePlayerFalls[0].Depth < 3) return false; if (GameManager.Enemies.filter(enemy => enemy.Hole == holePlayerFalls[0]).length > 0) return false; } for (let i = 1; i <= 6; i++) { let y1 = GameManager.GetYFromNumberOfFloors(i); if (y1 - 8 <= y && y <= y1 + 8) return true; } return false; } CanMoveRight(x: number, y: number): boolean { if (x < 0 || x >= 14 * GameManager.CharctorSize) return false; // 未完成の穴やモンスターが入っている穴には入れない let playerX = this.X + GameManager.CharctorSize; let holePlayerFalls: Hole[] = GameManager.Holes.filter( hole => hole.Rectangle.Left == playerX && hole.Rectangle.Y == this.Y); if (holePlayerFalls.length > 0) { if (holePlayerFalls[0].Depth < 3) return false; if (GameManager.Enemies.filter(enemy => enemy.Hole == holePlayerFalls[0]).length > 0) return false; } for (let i = 1; i <= 6; i++) { let y1 = GameManager.GetYFromNumberOfFloors(i); if (y1 - 8 <= y && y <= y1 + 8) return true; } return false; } } |
ハシゴとフロアのあいだを移動するとき、8ピクセル以内のズレであれば移動できるものとして処理しました。そのためズレを補正しておかないと表示がおかしくなったり当たり判定で正しい結果が得られなくなる場合があります。これを補正する関数を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Player { // プレイヤーのY座標を補正する(GameManager.CharctorSizeの整数倍にする) VerticalCorrection() { let dif = this.Y % GameManager.CharctorSize; if (dif <= 8) this.Y -= dif; else if (dif != 0) this.Y += GameManager.CharctorSize - dif; } // プレイヤーのX座標を補正する HorizontalCorrection() { let dif = this.X % GameManager.CharctorSize; if (dif <= 8) this.X -= dif; else if (dif != 0) this.X += GameManager.CharctorSize - dif; } } |
移動するためのキーが押されていて、移動できることが確認できたら移動させます。そのための関数を示します。
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 |
class Player { PlayerMoveDirect: MoveDirect = MoveDirect.Left; public Move() { if (GameManager.LeftKeyDown && this.CanMoveLeft(this.X, this.Y)) { this.X -= GameManager.CharctorSize / 8; this.VerticalCorrection(); this.PlayerMoveDirect = MoveDirect.Left; } if (GameManager.RightKeyDown && this.CanMoveRight(this.X, this.Y)) { this.VerticalCorrection(); this.X += GameManager.CharctorSize / 8; this.PlayerMoveDirect = MoveDirect.Right; } if (GameManager.UpKeyDown && this.CanMoveUp(this.X, this.Y)) { this.Y -= GameManager.CharctorSize / 8; this.HorizontalCorrection(); this.PlayerMoveDirect = MoveDirect.Up; } if (GameManager.DownKeyDown && this.CanMoveDown(this.X, this.Y)) { this.HorizontalCorrection(); this.Y += GameManager.CharctorSize / 8; this.PlayerMoveDirect = MoveDirect.Down; } // 移動さきに穴があったら落ちる this.FallIfInHole(); } } |
移動さきに穴があったら落ちます。その処理をおこなう関数を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Player { async FallIfInHole() { let holes: Hole[] = GameManager.Holes.filter(x => x.Rectangle.X == this.X && x.Rectangle.Y == this.Y); if (holes.length > 0) { let nextPlayerY = this.Y + GameManager.CharctorSize * 3; for (let i = 0; i < 6; i++) { this.Y += GameManager.CharctorSize / 2; await sleep(50); } this.Y = nextPlayerY; } } } |
穴を掘る
スペースパニックでは穴を縦に並べないと倒せない敵が現れます。そこで縦に穴を並べやすいように穴のX座標はCharctorSizeの半分の整数倍に限定します。そのX座標を求めるための処理を示します。
1 2 3 4 5 6 7 8 9 |
class Player { GetXPositionToDig() { // CharctorSizeの半分の倍数を返す let half = GameManager.CharctorSize / 2; let d = this.X / half; let i = Math.round(d); return i * half; } } |
穴はどこにでも掘れるわけではありません。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 |
class Player { CanDigHole(): boolean { // 1階に穴は掘れない if (this.Y >= GameManager.GetYFromNumberOfFloors(1)) return false; // ハシゴを上り下りしているときは穴は掘れない if (this.PlayerMoveDirect == MoveDirect.Up || this.PlayerMoveDirect == MoveDirect.Down) return false; // どこにでも穴は掘れない。GetXPositionToDigメソッドで制限を加える let xPosition = this.GetXPositionToDig(); // プレイヤーの向きで穴のX座標は異なる let holeX = 0; if (this.PlayerMoveDirect == MoveDirect.Left) holeX = xPosition - GameManager.CharctorSize; if (this.PlayerMoveDirect == MoveDirect.Right) holeX = xPosition + GameManager.CharctorSize; // フロアの端には穴は掘れない if (holeX < 0 || holeX > 14 * GameManager.CharctorSize) return false; // ハシゴがある位置には穴は掘れない let points: Point[] = GameManager.LadderPositions.filter(x => ( x.X - GameManager.CharctorSize < holeX && holeX < x.X + GameManager.CharctorSize) && (x.Y == this.Y || x.Y - GameManager.CharctorSize * 3 == this.Y) ); if (points.length > 0) return false; // すでに掘られている未完成の穴であれば掘ることができる // ただし敵がはまっている穴を掘ることはできない let holeCount = GameManager.Holes.length; for (let i = 0; i < holeCount; i++) { let hole = GameManager.Holes[i]; if (hole.Rectangle.Y == this.Y && hole.Rectangle.X == holeX) { if (hole.Depth < 3 && hole.Enemy == null) return true; else return false; } } // 連続して繋がっている穴は掘れない 16ピクセルより(実質20ピクセル以上)開けること for (let i = 0; i < holeCount; i++) { let hole = GameManager.Holes[i]; let b1: boolean = hole.Rectangle.Y == this.Y; let b2: boolean = hole.Rectangle.Right + 16 > holeX && holeX + GameManager.CharctorSize >= hole.Rectangle.Left - 16; if (b1 && b2) return false; } // 上記以外のケースであれば穴を掘ることができる return true; } } |
穴を掘ることができる場合は穴を掘ります。未完成の穴であれば深さを増やし、新規の穴であればGameManager.Holesに追加します。
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 |
class Player { DigHole() { // 穴を掘ることができるのできる場合 if (this.CanDigHole()) { // プレイヤーを穴を掘るために最適化されたX座標に移動 this.X = this.GetXPositionToDig(); // 穴のX座標を求める let holeX = 0; if (this.PlayerMoveDirect == MoveDirect.Left) holeX = this.X - GameManager.CharctorSize; if (this.PlayerMoveDirect == MoveDirect.Right) holeX = this.X + GameManager.CharctorSize; // そこにあるのは未完成の穴ではないのか? let count = GameManager.Holes.length; for (let i = 0; i < count; i++) { let hole: Hole = GameManager.Holes[i]; // 未完成の穴なら深さ3まで掘ることができる if (hole.Rectangle.X == holeX && hole.Rectangle.Y == this.Y) { if (hole.Depth < 3) { hole.Depth++; this.PlayHitSound(); } return; } } // 新しく掘られた穴ならリストにいれる GameManager.Holes.push(new Hole(holeX, this.Y)); this.PlayHitSound(); return; } } } |
穴を埋める
穴を埋める処理を示します。プレイヤーの現在位置に埋めるべき穴があるか確認し、存在する場合は埋めます。そして敵がいる穴を埋めたら敵を落とします。敵が落ちる処理はEnemyクラスのなかでおこないます。
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 |
class Player { async FillHole() { let xPosition = this.GetXPositionToDig(); let holeX = 0; if (this.PlayerMoveDirect == MoveDirect.Left) holeX = xPosition - GameManager.CharctorSize; if (this.PlayerMoveDirect == MoveDirect.Right) holeX = xPosition + GameManager.CharctorSize; // 埋めるべき穴がみつかった場合 let holes: Hole[] = GameManager.Holes.filter(x => x.Rectangle.X == holeX && x.Rectangle.Y == this.Y); if (holes.length == 1) { let hole = holes[0]; this.X = xPosition; // 埋めようとしている穴に敵はいるか? let enemies: Enemy[] = GameManager.Enemies.filter(x => x.Hole == hole); // 未完成の穴に敵がいる場合は埋めることはできない if (enemies.length == 1 && enemies[0].HoleDepth < 3) return; let enemy = enemies[0]; hole.Depth--; this.PlayHitSound(); if (hole.Depth == 0) { GameManager.Holes = GameManager.Holes.filter(x => x != hole); // 埋めた穴に敵がいた場合は落とす if (enemy != null) { // 効果音を鳴らす GameManager.SoundFall.currentTime = 0; GameManager.SoundFall.play(); await enemy.Fall(); // 敵を倒した場合 if (enemy.Life <= 0) { // 敵を倒したら効果音を鳴らす GameManager.SoundGet.currentTime = 0; GameManager.SoundGet.play(); // 倒された敵はリストから取り除く GameManager.Enemies = GameManager.Enemies.filter(x => x.Life > 0); gameManager.OnEnemyDead(enemy); if (GameManager.Enemies.length == 0) gameManager.OnCleared(); } } } } } } |