⇒ 動作確認はこちらからどうぞ。
パックマンとモンスターのクラスをつくったので、餌のクラスもつくります。プログラムが開始されたら所定の位置に餌を配置します。そしてパックマンが通過したらIsEatenをtrueにします。IsEatenがtrueのオブジェクトは描画されません。すべてのDotオブジェクトとPowerDotオブジェクトのIsEatenがtrueになればステージクリアということになります。
1 2 3 4 5 6 7 8 9 10 |
class Dot { constructor(x, y) { this.X = x; this.Y = y; this.IsEaten = false; } X: number = 0; Y: number = 0; IsEaten: boolean = false; } |
1 2 3 4 5 6 7 8 9 10 |
class PowerDot { constructor(x, y) { this.X = x; this.Y = y; this.IsEaten = false; } X: number = 0; Y: number = 0; IsEaten: boolean = false; } |
また以下はDotオブジェクトを生成したり、これを描画するための関数です。
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 |
function CreateDots() { for (let x = 0; x <= 11; x++) dots.push(new Dot(x, 0)); for (let x = 14; x <= 25; x++) dots.push(new Dot(x, 0)); for (let y = 1; y <= 3; y++) { if (y != 2) dots.push(new Dot(0, y)); dots.push(new Dot(5, y)); dots.push(new Dot(11, y)); dots.push(new Dot(14, y)); dots.push(new Dot(20, y)); if (y != 2) dots.push(new Dot(25, y)); } for (let x = 0; x <= 25; x++) dots.push(new Dot(x, 4)); for (let y = 5; y <= 6; y++) { dots.push(new Dot(0, y)); dots.push(new Dot(5, y)); dots.push(new Dot(8, y)); dots.push(new Dot(17, y)); dots.push(new Dot(20, y)); dots.push(new Dot(25, y)); } for (let x = 0; x <= 5; x++) dots.push(new Dot(x, 7)); for (let x = 8; x <= 11; x++) dots.push(new Dot(x, 7)); for (let x = 14; x <= 17; x++) dots.push(new Dot(x, 7)); for (let x = 20; x <= 25; x++) dots.push(new Dot(x, 7)); for (let y = 8; y <= 18; y++) { dots.push(new Dot(5, y)); dots.push(new Dot(20, y)); } for (let x = 0; x <= 11; x++) dots.push(new Dot(x, 19)); for (let x = 14; x <= 25; x++) dots.push(new Dot(x, 19)); for (let y = 20; y <= 21; y++) { dots.push(new Dot(0, y)); dots.push(new Dot(5, y)); dots.push(new Dot(11, y)); dots.push(new Dot(14, y)); dots.push(new Dot(20, y)); dots.push(new Dot(25, y)); } for (let x = 1; x <= 2; x++) dots.push(new Dot(x, 22)); for (let x = 5; x <= 20; x++) dots.push(new Dot(x, 22)); for (let x = 23; x <= 24; x++) dots.push(new Dot(x, 22)); for (let y = 23; y <= 24; y++) { dots.push(new Dot(2, y)); dots.push(new Dot(5, y)); dots.push(new Dot(8, y)); dots.push(new Dot(17, y)); dots.push(new Dot(20, y)); dots.push(new Dot(23, y)); } for (let y = 26; y <= 27; y++) { dots.push(new Dot(0, y)); dots.push(new Dot(11, y)); dots.push(new Dot(14, y)); dots.push(new Dot(25, y)); } for (let x = 0; x <= 5; x++) dots.push(new Dot(x, 25)); for (let x = 8; x <= 11; x++) dots.push(new Dot(x, 25)); for (let x = 14; x <= 17; x++) dots.push(new Dot(x, 25)); for (let x = 20; x <= 25; x++) dots.push(new Dot(x, 25)); for (let x = 0; x <= 25; x++) dots.push(new Dot(x, 28)); } |
1 2 3 4 5 6 |
function CreatePowerDots() { powerDots.push(new PowerDot(0, 2)); powerDots.push(new PowerDot(25, 2)); powerDots.push(new PowerDot(0, 22)); powerDots.push(new PowerDot(25, 22)); } |
1 2 3 4 5 6 7 8 9 10 11 |
function DrawDots() { for (let i = 0; i < dots.length; i++) { let dot = dots[i]; if (!dot.IsEaten) { con.fillStyle = "white"; let x = dot.X * magnification - dotSize / 2 + offsetX; let y = dot.Y * magnification - dotSize / 2 + offsetY; con.fillRect(x, y, dotSize, dotSize); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 |
function DrawPowerDots() { for (let i = 0; i < powerDots.length; i++) { let dot = powerDots[i]; if (!dot.IsEaten) { let dotSize = 8; con.fillStyle = "yellow"; let x = dot.X * magnification - dotSize / 2 + offsetX; let y = dot.Y * magnification - dotSize / 2 + offsetY; con.fillRect(x, y, dotSize, dotSize); } } } |
では迷路を描画して餌を配置し、パックマンとモンスターを動かしてみましょう。
まずグローバル変数を示します。
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 |
let can: HTMLCanvasElement = null; let con: CanvasRenderingContext2D = null; // 迷路の左上の座標 const offsetX = 40; const offsetY = 60; // 迷路は横25、高さ28とする。これに対する拡大率 const magnification = 18.0; // 各キャラクタの表示サイズ const charctorSize = 35; const dotSize = 4; // dotを格納する配列 let dots: Dot[] = []; let powerDots: PowerDot[] = []; // パックマンのオブジェクトと描画するときに必要なイメージ let pacman: Pacman = null; let pacmanNorthImage: HTMLImageElement = null; let pacmanSouthImage: HTMLImageElement = null; let pacmanEastImage: HTMLImageElement = null; let pacmanWestImage: HTMLImageElement = null; // モンスターのオブジェクトを格納する配列と描画するときに必要なイメージの配列 let monsters: Monster[] = []; let monsterImages: HTMLImageElement[] = []; let ijikeImages: HTMLImageElement[] = []; // パワー餌の効果が続く時間 とりあえず最初は「10秒」とする let CounterattackTime = 0; let CounterattackTimeMax = 10 * 1000; // スコアと残機(ゲーム開始時の残機は3とする) let score = 0; let rest = 3; let restMax = 3; // ゲームオーバーになってしまったかどうか let isGameOver = false; // 各キャラクターの移動を停止させないときにtrueにする let stoping = false; // 連続でモンスターを食べた数(点数計算で必要) let eatMonsterCount = 0; |
メイン関数を実行したらプログラムスタートです。
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 |
Main(); function Main() { can = <HTMLCanvasElement>document.getElementById('can'); con = can.getContext("2d"); // Canvasのサイズ。迷路のサイズから導出する can.width = offsetX * 2 + magnification * 25; can.height = offsetY * 2 + magnification * 28; can.style.border = "1px solid #111"; // パックマンを描画するために必要なイメージを取得する pacmanNorthImage = GetHTMLImageElement("./images/pac_north.png"); pacmanSouthImage = GetHTMLImageElement("./images/pac_south.png"); pacmanEastImage = GetHTMLImageElement("./images/pac_east.png"); pacmanWestImage = GetHTMLImageElement("./images/pac_west.png"); // モンスターを描画するために必要なイメージを取得する monsterImages.push(GetHTMLImageElement("./images/red.png")); monsterImages.push(GetHTMLImageElement("./images/pink.png")); monsterImages.push(GetHTMLImageElement("./images/blue.png")); monsterImages.push(GetHTMLImageElement("./images/orange.png")); ijikeImages.push(GetHTMLImageElement("./images/ijike1.png")); ijikeImages.push(GetHTMLImageElement("./images/ijike2.png")); // 残機をセット(最初は3) rest = restMax; // パックマンオブジェクトをつくる pacman = new Pacman(); pacman.GoHome(); // モンスターを初期化。最初の位置に配置する MonstersInit(); // 餌とパワー餌のオブジェクトをつくる CreateDots(); CreatePowerDots(); // 方向キーが押されたときにパックマンを移動させることができるようにする document.onkeydown = OnKeyDown; // パックマンとモンスターは0.015秒ごとに0.1 × magnificationピクセル移動する // パックマンとモンスターの移動速度を変えられるように別々に処理をする setInterval(PacmanMove, 15); setInterval(MoveMonsters, 15); // 描画処理は0.01秒ごとにおこなう setInterval(Draw, 10); // パワー餌の効果の残り時間 0になったら効果はなくなる setInterval(() => { if (CounterattackTime > 0) CounterattackTime -= 100; }, 100); } |
こんな感じになります。
では関連する関数をみていきましょう。
GetHTMLImageElement関数はファイルのパスからイメージを取得します。
1 2 3 4 5 |
function GetHTMLImageElement(path: string) { let img: HTMLImageElement = new Image(); img.src = path; return img; } |
0.015秒ごとにパックマンを移動させます。
1 2 3 4 5 6 |
function PacmanMove() { if (stoping) return; pacman.Move(); } |
キーが押されたらパックマンがその方向に移動できるのであれば移動できるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 |
function OnKeyDown(e: KeyboardEvent) { if (e.keyCode == 37) // 左キー pacman.NextDirect = Direct.West; if (e.keyCode == 38) // 上キー pacman.NextDirect = Direct.North; if (e.keyCode == 39) // 右キー pacman.NextDirect = Direct.East; if (e.keyCode == 40) // 下キー pacman.NextDirect = Direct.South; if (e.keyCode == 83 && isGameOver == true) // Sキー GameRetart(); } |
モンスターも同様に動かします。パワー餌を食べたときはモンスターはイジケ状態になり、移動速度は半分になります。そこでMoveMonsters関数を呼び出した回数をカウントして、イジケ状態のときは2回に1回しかmonster.Move()が実行されないようにしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
let moveCount = 0; function MoveMonsters(): void { if (stoping) return; // moveCountが大きくなりすぎないようにときどきリセットする moveCount++; if (moveCount >= 10000) moveCount = 0; for (let i = 0; i < monsters.length; i++) { let monster = monsters[i]; // イジケ状態のときは2回に1回しかmonster.Move()が実行しない if (!monster.IsIjike || (moveCount % 2 == 0)) monster.Move(); } } |
モンスターは最初、赤色のモンスターを除いて巣のなかにいます。そこで初期状態にもどす関数も作成しました。SetMonstersStandbyCount関数は巣からでてくるまでの時間を設定するためのものです。ピンク色のモンスターは3秒後、ライトブルーは6秒後、オレンジは10秒後にでてくるようにしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
function MonstersInit() { monsters = []; monsters.push(new Monster(0, 12.5, 10, Direct.None)); monsters.push(new Monster(1, 12.5, 13, Direct.South)); monsters.push(new Monster(2, 10, 13, Direct.North)); monsters.push(new Monster(3, 15, 13, Direct.North)); SetMonstersStandbyCount([0, 3, 6, 10]); } function SetMonstersStandbyCount(counts: number[]) { let count = monsters.length; for (let i = 0; i < count; i++) { if (counts.length >= i) monsters[i].StandbyCount = counts[i]; } } |
それから描画の処理も必要です。DrawMazes関数はこのまえやりました。そのほかにパックマン、モンスター、食べられていないドット、スコアを表示します。ゲームオーバーになっているときはGame Overの文字も表示させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function Draw() { DrawMazes(); DrawDots(); DrawPowerDots(); if (!isGameOver) DrawPac(); DrawMonsters(); DrawScore(); DrawRest(); if (isGameOver) { con.fillStyle = "white"; con.font = "28px 'MS ゴシック'"; con.fillText("Game Over", offsetX + 170, offsetY+295, 250); } } |
パックマンを描画する関数です。
1 2 3 |
function DrawPac() { pacman.Draw(); } |
モンスターを描画する関数です。
1 2 3 |
function DrawMonsters() { monsters.forEach(monster => { monster.Draw() }); } |
DrawDots関数とDrawPowerDots関数で餌とパワー餌を描画します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
function DrawDots() { for (let i = 0; i < dots.length; i++) { let dot = dots[i]; if (!dot.IsEaten) { con.fillStyle = "white"; let x = dot.X * magnification - dotSize / 2 + offsetX; let y = dot.Y * magnification - dotSize / 2 + offsetY; con.fillRect(x, y, dotSize, dotSize); } } } function DrawPowerDots() { for (let i = 0; i < powerDots.length; i++) { let dot = powerDots[i]; if (!dot.IsEaten) { let dotSize = 8; con.fillStyle = "yellow"; let x = dot.X * magnification - dotSize / 2 + offsetX; let y = dot.Y * magnification - dotSize / 2 + offsetY; con.fillRect(x, y, dotSize, dotSize); } } } |
スコアを描画する関数です。5桁までは左を0で埋めます。
1 2 3 4 5 6 7 8 9 10 11 |
function DrawScore() { let strScore = ""; if (score < 10000) strScore = ('00000' + score).slice(-5); else strScore = score.toString(); con.fillStyle = "white"; con.font = "24px 'MS ゴシック'"; con.fillText(strScore, offsetX - 10, offsetY-30, 200); } |
DrawRest関数は残りのパックマンの数を描画するためのものです。パックマンは最初の状態では3です。残りのパックマンはそれよりも1つ少ない数で、これを表示します。残りのパックマンがいない状態で現在のパックマンがモンスターに捕まったらゲームオーバーです。
1 2 3 4 |
function DrawRest() { for (let i = 0; i < rest-1; i++) con.drawImage(pacmanEastImage, offsetX + 110 + 25 * i, offsetY-53, charctorSize * 0.7, charctorSize * 0.7); } |
ゲームオーバーのときにSキーをおせばもう一度ゲームができます。3秒後にキャラクターが動き始めます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function GameRetart() { rest = restMax; // 残機を初期化する score = 0; // スコアを初期化する // パックマンとモンスターをゲーム開始の位置に戻す pacman.GoHome(); MonstersInit(); // 餌とパワー餌を食べられていない状態に戻す dots.forEach(dot => dot.IsEaten = false); powerDots.forEach(dot => dot.IsEaten = false); isGameOver = false; // 3秒後にゲーム再開! setTimeout(() => { stoping = false; }, 3000); } |
Pacmanクラスのなかで移動したら餌を食べたか、モンスターと接触していないかを判定します。CheckEatDot関数は餌を食べたかどうかチェックする関数です。
パックマンがいる位置にまだ食べられていない餌があれば餌を食べたことになります。そのときは通常の餌であればOnEatDot関数、パワー餌の場合はOnEatPowerDot関数が呼び出されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
function CheckEatDot() { let dot = dots.find(dot => dot.X == pacman.Position.CenterX && dot.Y == pacman.Position.CenterY); if (dot != null) { if (!dot.IsEaten) { dot.IsEaten = true; OnEatDot(); } } let powerDot = powerDots.find(dot => dot.X == pacman.Position.CenterX && dot.Y == pacman.Position.CenterY); if (powerDot != null) { if (!powerDot.IsEaten) { powerDot.IsEaten = true; OnEatPowerDot(); } } } |
普通の餌を食べたときの処理です。10点追加し、すべての餌が食べられたかを調べます。すべての餌が食べられていればステージクリアと判定します。
1 2 3 4 5 6 |
function OnEatDot() { score += 10; if (CheckStageClear()) { OnStageClear(); } } |
パワー餌を食べたときは50点追加してモンスターをイジケ状態にします。そしてモンスターの移動方向を反転します。
それからパワー餌の効果の時間をセットします。またモンスターを食べるごとに得点が200点、400点、800点、1600点と増えていくのでこれを計算できるようにeatMonsterCountを0にリセットします。
またパワー餌を食べた瞬間ステージクリアになる場合もあるのでCheckStageClear関数を呼び出しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function OnEatPowerDot() { score += 50; eatMonsterCount = 0; if (CheckStageClear()) { OnStageClear(); } else { // モンスターをいじけ状態にする monsters.forEach(monster => { monster.IsIjike = true; monster.ReverseMove(); }); CounterattackTime = CounterattackTimeMax; } } |
パワー餌の効果が切れたらモンスターは元の状態に戻します。
1 2 3 |
function OnFinishCounterattack() { monsters.forEach(monster => monster.IsIjike = false); } |
ステージクリアしているかどうかはIsEatenがすべてtrueになっているかどうかで確認できます。
1 2 3 4 5 6 7 8 |
function CheckStageClear() { let dot = dots.find(dot => !dot.IsEaten); let powerDot = powerDots.find(dot => !dot.IsEaten); if (dot == null && powerDot == null) return true; else return false; } |
ステージクリアしている場合は3秒間動作を止めてパックマンとモンスターをもとの位置に戻し、餌もすべて食べられていない状態に戻してゲームを続行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
function OnStageClear() { stoping = true; setTimeout(AfterStageClear, 3000); } function AfterStageClear() { // パックマンとモンスターはゲーム開始の位置に戻す pacman.GoHome(); MonstersInit(); // 餌はすべて食べられていない状態に戻す dots.forEach(dot => dot.IsEaten = false); powerDots.forEach(dot => dot.IsEaten = false); // 動作の停止を解除 stoping = false; } |
次に当たり判定です。パックマンが移動するたびにCheckHitMonster関数が呼び出されます。ここではIsHitMonster関数を呼び出してそれぞれのモンスターと接触しているかどうかをチェックしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
function CheckHitMonster() { for (let i = 0; i < monsters.length; i++) { let monster = monsters[i]; if (IsHitMonster(monster)) { if (monster.IsIjike) { EatMonster(monster); } else { PacmanDead(); break; } } } } function IsHitMonster(monster: Monster): boolean { let x2 = (pacman.Position.CenterX - monster.Position.CenterX) ** 2; let y2 = (pacman.Position.CenterY - monster.Position.CenterY) ** 2; if (x2 + y2 < 1) { return true; } else return false; } |
モンスターと接触したとき、イジケ状態であればパックマンに食べられます。その場合は動作を1秒間とめます。そのあいだもパワー餌の有効時間は減っていくのでそのぶん追加の処理をしています。また連続で何体のモンスターを食べたかで点数計算もしています。
1 2 3 4 5 6 7 8 9 10 11 12 |
function EatMonster(monster: Monster) { stoping = true; CounterattackTime += 1000; // モンスターを食べたあと1秒間動作を止めるので、その分追加する eatMonsterCount++; score += (2 ** eatMonsterCount) * 100; setTimeout(AfterEatMonster, 1000, monster); } function AfterEatMonster(monster: Monster) { monster.GoHome(); stoping = false; } |
イジケ状態ではないモンスターと接触してしまった場合はミスとなります。残機がひとつ減らせて、動作を3秒間止めます。もし残機0になった場合はゲームオーバーです。これ以上は動作させる必要はないのでstopingはtrueのままです。isGameOverをtrueにすることで画面にはGame Overの文字が表示されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function PacmanDead() { stoping = true; setTimeout(AfterPacmanDead, 3000); } function AfterPacmanDead() { rest--; if (rest > 0) { pacman.GoHome(); MonstersInit(); stoping = false; } else { isGameOver = true; } } |