⇒ 動作確認はこちらからどうぞ。
←↑→↓キーで移動、Zキーで穴を堀り、Xキーで埋めます。黄色いやさぐれひよこは落とせば死にますが、ピンク色のものは2段、青いやさぐれひよこは3段連続で落とさないと死にません。
前回作成したスペース・パニックのようなゲームをJavaScript/TypeScriptでもつくってみました。
前回の記事はこちら。
フロアをつくる
プレイヤーを移動させる
穴を掘ってモンスターを倒す
モンスターにプレイヤーを追わせる
仕上げ
点と矩形を扱うためのクラス
点と矩形を扱うためのクラスを作成します。
1 2 3 4 5 6 7 8 9 |
class Point { constructor(x: number, y: number) { this.X = x; this.Y = y; } X: number = 0; Y: number = 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Rectangle { X = 0; Y = 0; Widht = 0; Height = 0; Left = 0; Right = 0; constructor(x: number, y: number, w: number, h: number) { this.X = x; this.Y = y; this.Widht = w; this.Height = h; this.Left = x; this.Right = x + w; } } |
これは移動方向を示す列挙体です。
1 2 3 4 5 6 7 |
enum MoveDirect { Left, Up, Right, Down, Stop, } |
これはsleep処理をする関数です。
1 2 |
// msecミリ秒スリープする const sleep = msec => new Promise(resolve => setTimeout(resolve, msec)); |
GameManagerクラス
次にGameManagerクラスを示します。これはスペース・パニックのようなゲームをつくるで作成したGameManagerクラスに相当するものです。
プロパティ
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 |
class GameManager { // キャラクターの基本サイズ public static CharctorSize: number = 28; // 全体を描画するときにどれだけの余白をとるか static MarginLeft: number = 40; static MarginTop: number = 60; // ハシゴの下部の開始位置 static LadderPositions: Point[] = []; // プレイヤー、敵、穴 static Player: Player; static Enemies: Enemy[] = []; static Holes: Hole[] = []; // 効果音 static SoundFall: HTMLAudioElement = new Audio('./sounds/fall.mp3'); static SoundGet: HTMLAudioElement = new Audio('./sounds/get.mp3'); static SoundHit: HTMLAudioElement = new Audio('./sounds/hit.mp3'); static SoundMiss: HTMLAudioElement = new Audio('./sounds/miss.mp3'); // イメージ static PlayerImages: HTMLImageElement[] = []; static FllorImage: HTMLImageElement; static LadderImage: HTMLImageElement; static EnemiesImages: HTMLImageElement[] = []; // キーが押されているかどうか static LeftKeyDown: boolean = false; static UpKeyDown: boolean = false; static RightKeyDown: boolean = false; static DownKeyDown: boolean = false; // Updateされた回数 UpdateCount = 0; // 自機数、スコアの表示にかんするもの Rest: number = 0; RestMax: number = 3; Score: number = 0; AdditionalPointsList: AdditionalPoints[] = []; // ゲームが中断されているかどうか IsGameStop = false; } |
コンストラクタ
GameManagerクラスのコンストラクタを示します。コンストラクタ内ではハシゴを描画するためのその位置の取得とプレイヤーの生成、描画のために必要なイメージを取得をおこなっています。
1 2 3 4 5 6 7 8 9 |
class GameManager { constructor() { GameManager.LadderPositions = GameManager.GetLadderPositions(); GameManager.Player = new Player(); this.InitImages(); this.InitGame(); this.Rest = this.RestMax; } } |
関数
InitImages関数を示します。プレイヤー、敵、フロアの描画に必要なイメージを取得しています。
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 GameManager { InitImages() { GameManager.FllorImage = GetHTMLImageElement("./images/floor.png"); GameManager.LadderImage = GetHTMLImageElement("./images/ladder.png"); GameManager.PlayerImages.push(GetHTMLImageElement("./images/player1.png")); GameManager.PlayerImages.push(GetHTMLImageElement("./images/player2.png")); GameManager.PlayerImages.push(GetHTMLImageElement("./images/player3.png")); GameManager.PlayerImages.push(GetHTMLImageElement("./images/player4.png")); GameManager.PlayerImages.push(GetHTMLImageElement("./images/player5.png")); GameManager.PlayerImages.push(GetHTMLImageElement("./images/player6.png")); GameManager.PlayerImages.push(GetHTMLImageElement("./images/player7.png")); GameManager.PlayerImages.push(GetHTMLImageElement("./images/player8.png")); GameManager.EnemiesImages.push(GetHTMLImageElement("./images/enemy1.png")); GameManager.EnemiesImages.push(GetHTMLImageElement("./images/enemy2.png")); GameManager.EnemiesImages.push(GetHTMLImageElement("./images/enemy3.png")); GameManager.EnemiesImages.push(GetHTMLImageElement("./images/enemy4.png")); GameManager.EnemiesImages.push(GetHTMLImageElement("./images/enemy5.png")); GameManager.EnemiesImages.push(GetHTMLImageElement("./images/enemy6.png")); GameManager.EnemiesImages.push(GetHTMLImageElement("./images/enemy7.png")); GameManager.EnemiesImages.push(GetHTMLImageElement("./images/enemy8.png")); GameManager.EnemiesImages.push(GetHTMLImageElement("./images/enemy9.png")); GameManager.EnemiesImages.push(GetHTMLImageElement("./images/enemy10.png")); GameManager.EnemiesImages.push(GetHTMLImageElement("./images/enemy11.png")); GameManager.EnemiesImages.push(GetHTMLImageElement("./images/enemy12.png")); GameManager.EnemiesImages.push(GetHTMLImageElement("./images/enemy13.png")); GameManager.EnemiesImages.push(GetHTMLImageElement("./images/enemy14.png")); GameManager.EnemiesImages.push(GetHTMLImageElement("./images/enemy15.png")); } } |
InitGame関数を示します。ここではステージクリア後にもとの状態に戻す処理をしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class GameManager { InitGame() { GameManager.Enemies.push(new Enemy(GameManager.CharctorSize * 3, GameManager.GetYFromNumberOfFloors(6))); GameManager.Enemies.push(new Enemy(GameManager.CharctorSize * 6, GameManager.GetYFromNumberOfFloors(5))); GameManager.Enemies.push(new Enemy(GameManager.CharctorSize * 9, GameManager.GetYFromNumberOfFloors(4))); GameManager.Enemies.push(new Enemy(GameManager.CharctorSize * 12, GameManager.GetYFromNumberOfFloors(3))); GameManager.Enemies.push(new Enemy(GameManager.CharctorSize * 15, GameManager.GetYFromNumberOfFloors(2))); GameManager.Player.X = 7 * GameManager.CharctorSize; GameManager.Player.Y = 15 * GameManager.CharctorSize; GameManager.Holes = []; } } |
Retry関数を示します。これはスコアを0に戻す、残機を最大に戻す、敵や穴をクリアなどのゲームオーバー後に再チャレンジするときにおこなわれる処理です。
1 2 3 4 5 6 7 8 9 10 11 12 |
class GameManager { Retry() { if (this.Rest == 0) { this.Score = 0; this.Rest = this.RestMax; GameManager.Enemies = []; GameManager.Holes = []; this.InitGame(); this.IsGameStop = false; } } } |
ハシゴの位置を取得するGetLadderPositions関数を示します。
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 |
class GameManager { static GetLadderPositions(): Point[] { let points: Point[] = []; let y1: number = GameManager.GetYFromNumberOfFloors(1); points.push(new Point(GameManager.CharctorSize * 0, y1)); points.push(new Point(GameManager.CharctorSize * 4, y1)); points.push(new Point(GameManager.CharctorSize * 10, y1)); points.push(new Point(GameManager.CharctorSize * 14, y1)); let y2: number = GameManager.GetYFromNumberOfFloors(2); points.push(new Point(GameManager.CharctorSize * 0, y2)); points.push(new Point(GameManager.CharctorSize * 10, y2)); points.push(new Point(GameManager.CharctorSize * 14, y2)); let y3: number = GameManager.GetYFromNumberOfFloors(3); points.push(new Point(GameManager.CharctorSize * 0, y3)); points.push(new Point(GameManager.CharctorSize * 6, y3)); points.push(new Point(GameManager.CharctorSize * 14, y3)); let y4 = GameManager.GetYFromNumberOfFloors(4); points.push(new Point(GameManager.CharctorSize * 0, y4)); points.push(new Point(GameManager.CharctorSize * 6, y4)); points.push(new Point(GameManager.CharctorSize * 14, y4)); let y5 = GameManager.GetYFromNumberOfFloors(5); points.push(new Point(GameManager.CharctorSize * 0, y5)); points.push(new Point(GameManager.CharctorSize * 4, y5)); points.push(new Point(GameManager.CharctorSize * 10, y5)); points.push(new Point(GameManager.CharctorSize * 14, y5)); return points; } } |
床とハシゴを描画するDrawFloor関数とDrawLadders関数、それと関連する関数を示します。
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 |
class GameManager { DrawFloor(con: CanvasRenderingContext2D) { for (let i = 1; i <= 6; i++) { for (let j = -1; j <= 15; j++) { // 床はキャラクターのY座標よりキャラクターの高さだけ下にある let x: number = GameManager.CharctorSize * j; let y: number = GameManager.GetYFromNumberOfFloors(i) + GameManager.CharctorSize; let pt: Point = GameManager.GetPointForShow(x, y); if (i == 1) { con.fillStyle = "aqua"; con.fillRect(pt.X, pt.Y, 32, 32); } else { con.drawImage(GameManager.FllorImage, pt.X, pt.Y, GameManager.CharctorSize, GameManager.CharctorSize); } } } } DrawLadders(con: CanvasRenderingContext2D): void { let points: Point[] = []; GameManager.LadderPositions.forEach(pt => { points.push(new Point(pt.X, pt.Y)); points.push(new Point(pt.X, pt.Y - GameManager.CharctorSize * 1)); points.push(new Point(pt.X, pt.Y - GameManager.CharctorSize * 2)); points.push(new Point(pt.X, pt.Y - GameManager.CharctorSize * 3)); }); points.forEach(pt => { let point: Point = GameManager.GetPointForShow(pt.X, pt.Y); con.drawImage(GameManager.LadderImage, point.X, point.Y, GameManager.CharctorSize, GameManager.CharctorSize); }); } static GetPointForShow(x: number, y: number): Point { return new Point(this.MarginLeft + x, this.MarginTop + y); } static GetYFromNumberOfFloors(numberOfFloors: number): number { return (18 - numberOfFloors * 3) * GameManager.CharctorSize; } } |
プレイヤーの移動と描画をおこなう関数を示します。実際の処理はPlayerクラスの関数を呼び出しておこないます。
1 2 3 4 5 6 7 8 9 |
class GameManager { MovePlayer(): void { GameManager.Player.Move(); } DrawPlayer(con: CanvasRenderingContext2D): void { GameManager.Player.DrawPlayer(con); } } |
敵の移動と描画をおこなう関数を示します。これも実際に処理はEnemyクラスでおこないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class GameManager { MoveEnemies() { GameManager.Enemies.forEach(enemy => { enemy.Move(); }); } DrawEnemies(con: CanvasRenderingContext2D) { GameManager.Enemies.forEach(enemy => { enemy.Draw(con); }); } } |
穴を描画する関数とこれに関連する関数を示します。ここではHolesのなかから深さが0になったものを取り除いて、hole.GetRectangle関数で深さに応じた背景色と同じ色の矩形を取得し、これを描画することで穴が開いているように見せかけています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class GameManager { public DrawHoles(con: CanvasRenderingContext2D) { GameManager.Holes = GameManager.Holes.filter(x => x.Depth > 0); GameManager.Holes.forEach(hole => { let rect = this.GetRectangleForShow(hole.GetRectangle()); con.fillStyle = "black"; con.fillRect(rect.X, rect.Y, rect.Widht, rect.Height); }); } public GetRectangleForShow(rect: Rectangle): Rectangle { let pt: Point = GameManager.GetPointForShow(rect.X, rect.Y); return new Rectangle(pt.X, pt.Y, rect.Widht, rect.Height); } } |
敵を倒したとき、敵を全滅させたときの処理を示します。敵を倒したときは加算される点数を倒した敵の近くに表示させます。加算される点数は一定時間、配列AdditionalPointsListのなかに格納されます。そのあいだだけDrawAdditionalPoints関数によって表示されます。
1 2 3 4 5 6 7 8 9 10 11 |
class GameManager { OnEnemyDead(enemy: Enemy) { this.AdditionalPointsList.push(new AdditionalPoints(enemy.AddScore, enemy.DeadPoint)); this.Score += enemy.AddScore; } async OnCleared() { await sleep(3000); this.InitGame(); } } |
スコアと残機、加算される点数を描画する関数を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class GameManager { DrawAdditionalPoints(con: CanvasRenderingContext2D) { this.AdditionalPointsList = this.AdditionalPointsList.filter(x => x.Life > 0); this.AdditionalPointsList.forEach(x => { x.Draw(con); }); } DrawScore(con: CanvasRenderingContext2D) { con.font = "18pt MS ゴシック"; con.fillStyle = "white"; let scoreText: string = ('00000' + this.Score).slice(-5); let restText: string = "残 " + this.Rest.toString(); con.fillText(scoreText + " " + restText, 20, 30, 200); } } |
プレイヤーが敵と接触しているか調べて接触時はミス時の処理をおこなう関数を示します。
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 |
class GameManager { async CheckPlayerDead() { // プレイヤーが敵と接触している if (GameManager.Enemies.filter(x => x.IsPlayerDead()).length > 0) { // 効果音を鳴らして残機を1減らす GameManager.SoundMiss.currentTime = 0; GameManager.SoundMiss.play(); this.Rest--; // 3秒間ゲームを中断 this.IsGameStop = true; await sleep(3000); if (this.Rest > 0) { // 残機が残っている場合はプレイヤーと敵の位置を最初の位置に戻してゲーム再開 GameManager.Enemies.forEach(enemy => { enemy.MoveInitPosition(); }); GameManager.Player.Reset(); GameManager.Holes = []; // 穴は埋めてしまう // ゲーム中断フラグをクリア this.IsGameStop = false; } } } } |
ゲームオーバーのときはゲームオーバー表示をする関数を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class GameManager { DrawStringIfGameOver(con: CanvasRenderingContext2D) { if (this.Rest > 0) return; con.font = "28pt MS ゴシック"; con.fillStyle = "white"; con.fillText("Game Over", 160, 260, 200); con.font = "18pt MS ゴシック"; con.fillStyle = "white"; con.fillText("Press S key to Retry", 140, 290, 200); } } |
データの更新と描画をおこなう関数を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class GameManager { public Update() { this.UpdateCount++; this.MovePlayer(); this.MoveEnemies(); this.CheckPlayerDead(); } Draw() { this.DrawFloor(con); this.DrawHoles(con); this.DrawLadders(con); this.DrawPlayer(con); this.DrawEnemies(con); this.DrawAdditionalPoints(con); this.DrawScore(con); this.DrawStringIfGameOver(con); } } |