ASP.NET Core版 マッピー(MAPPY)をつくる(4)の続きです。
クライアントサイドでの処理を考えます。
cshtmlファイル
Pages内にMappyフォルダを作り、このなかにgame.cshtmlというファイル名でcshtmlファイルをつくり、以下のように記述します。
Pages\Mappy\game.cshtml
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 |
@page @{ ViewData["Title"] = "鳩でもわかるマッピー(MAPPY)もどき"; Layout = "_Layout_none"; string baseurl = "https://lets-csharp.com/samples/2204/aspnetcore-app-zero"; } <div class = "display-none"> <img src = "@baseurl/mappy/images/players/player.png" id ="player"> <img src = "@baseurl/mappy/images/players/player-l.png" id ="player-l"> <img src = "@baseurl/mappy/images/players/player-r.png" id ="player-r"> <img src = "@baseurl/mappy/images/enemies/enemy-l.png" id ="enemy-l"> <img src = "@baseurl/mappy/images/enemies/enemy-r.png" id ="enemy-r"> <img src = "@baseurl/mappy/images/enemies/down-enemy-l.png" id ="down-enemy-l"> <img src = "@baseurl/mappy/images/enemies/down-enemy-r.png" id ="down-enemy-r"> <img src = "@baseurl/mappy/images/bullet.png" id ="bullet"> <img src = "@baseurl/mappy/images/doors/door.png" id ="door"> <img src = "@baseurl/mappy/images/doors/door-left.png" id ="door-left"> <img src = "@baseurl/mappy/images/doors/door-right.png" id ="door-right"> <img src = "@baseurl/mappy/images/doors/power-door-left.png" id ="power-door-left"> <img src = "@baseurl/mappy/images/doors/power-door-right.png" id ="power-door-right"> <img src = "@baseurl/mappy/images/floor.png" id ="floor"> <img src = "@baseurl/mappy/images/floor-over-left-open-door.png" id ="floor-over-left-open-door"> <img src = "@baseurl/mappy/images/floor-over-right-open-door.png" id ="floor-over-right-open-door"> <img src = "@baseurl/mappy/images/wall-r.png" id ="wall-r"> <img src = "@baseurl/mappy/images/wall-l.png" id ="wall-l"> <img src = "@baseurl/mappy/images/items/item1.png" id ="item1"> <img src = "@baseurl/mappy/images/items/item2.png" id ="item2"> <img src = "@baseurl/mappy/images/items/item3.png" id ="item3"> <img src = "@baseurl/mappy/images/items/item4.png" id ="item4"> <img src = "@baseurl/mappy/images/items/item5.png" id ="item5"> <img src = "@baseurl/mappy/images/sparks/spark1.png" id ="spark1"> <img src = "@baseurl/mappy/images/sparks/spark2.png" id ="spark2"> <img src = "@baseurl/mappy/images/sparks/spark3.png" id ="spark3"> <img src = "@baseurl/mappy/images/sparks/spark4.png" id ="spark4"> <img src = "@baseurl/mappy/images/sparks/spark5.png" id ="spark5"> <img src = "@baseurl/mappy/images/sparks/spark6.png" id ="spark6"> <img src = "@baseurl/mappy/images/trampoline1.png" id ="trampoline1"> <img src = "@baseurl/mappy/images/trampoline2.png" id ="trampoline2"> <img src = "@baseurl/mappy/images/trampoline3.png" id ="trampoline3"> <img src = "@baseurl/mappy/images/trampoline4.png" id ="trampoline4"> </div> <div style="position: relative; overflow: hidden; margin-left:20px;margin-top:20px;"> <canvas id="can"></canvas> <p>遊び方</p> <p>移動:← →キー<br> ドアの開閉:SPACEキー 遠隔操作で自分が向いている方向にある一番近くのドアを開閉できます。<br> トランポリンを使用して上昇または降下しているときは当たり判定はありません。<br> ただし同じトランポリンのうえで連続してジャンプできるのは4回までです。</p> <form name="form1"> <input type="checkbox" value="音を出す" id="sound-checkbox">音を出す <label>ハンドルネーム</label> <input type="text" id="player-name" maxlength='16' /><br> <input type="button" id="startButton1" value="ゲームスタート" onclick="GameStart()" style="margin-top:15px;margin-bottom:15px;"> </form> <input type="button" id="showTop30Button1" onclick="location.href='./hi-score'" value="上位30位をチェック"> <p id = "conect-result"></p> </div> <script> let connection = new signalR.HubConnectionBuilder().withUrl("@baseurl/MappyHub").build(); let base_url = "@baseurl"; </script> <script src="@baseurl/mappy/mappy.js"></script> |
画像ファイルは以下のようになっています。
player-l.png
player-r.png
enemy-l.png
enemy-r.png
down-enemy-l.png
down-enemy-r.png
bullet.png
door.png
door-left.png
door-right.png
power-door-left.png
power-door-right.png
floor.png
floor-over-left-open-door.png
floor-over-right-open-door.png
wall-l.png
wall-r.png
item1.png
item2.png
item3.png
item4.png
item5.png
spark1.png
spark2.png
spark3.png
spark4.png
spark5.png
spark6.png
trampoline1.png
trampoline2.png
trampoline3.png
trampoline4.png
mp3ファイルは以下のようになっています。
bgm.mp3
jump.mp3
door.mp3
get.mp3
enemy-down.mp3
bullet.mp3
explotion.mp3
clear.mp3
dead.mp3
gameover.mp3
グローバル変数と定数
次にJavaScriptですが、まずグローバル変数と定数の宣言部分を示します。
wwwroot\mappy\mappy.js
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 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
const CANVAS_WIDTH = 480; const CANVAS_HEIGHT = 420; let can = document.getElementById('can'); let ctx = can.getContext('2d'); // 現在プレイ中か? let isPlaying = false; // キーが押されているか? let isLeftKeyDown = false; let isRightKeyDown = false; let connectionID = ''; let startButton1 = document.getElementById('startButton1'); let showTop30Button1 = document.getElementById('showTop30Button1'); // プレイヤーのイメージ let playerImageL = document.getElementById('player-l'); let playerImageR = document.getElementById('player-r'); // 敵のイメージ let enemyImageL = document.getElementById('enemy-l'); let enemyImageR = document.getElementById('enemy-r'); let downEnemyImageL = document.getElementById('down-enemy-l'); let downEnemyImageR = document.getElementById('down-enemy-r'); // 弾丸のイメージ let bulletImage = document.getElementById('bullet'); // フロアのイメージ let floorImage = document.getElementById('floor'); let floorOverLeftOpenDoorImage = document.getElementById('floor-over-left-open-door'); let floorOverRightOpenDoorImage = document.getElementById('floor-over-right-open-door'); // ドアのイメージ let doorImage = document.getElementById('door'); let doorLeftImage = document.getElementById('door-left'); let doorRightImage = document.getElementById('door-right'); let powerDoorLeftImage = document.getElementById('power-door-left'); let powerDoorRightImage = document.getElementById('power-door-right'); // 外壁のイメージ let wallImageR = document.getElementById('wall-r'); let wallImageL = document.getElementById('wall-l'); // アイテムのイメージ let itemImage1 = document.getElementById('item1'); let itemImage2 = document.getElementById('item2'); let itemImage3 = document.getElementById('item3'); let itemImage4 = document.getElementById('item4'); let itemImage5 = document.getElementById('item5'); // トランポリンのイメージ let trampolineImage1 = document.getElementById('trampoline1'); let trampolineImage2 = document.getElementById('trampoline2'); let trampolineImage3 = document.getElementById('trampoline3'); let trampolineImage4 = document.getElementById('trampoline4'); // 火花のイメージ let sparkImage1 = document.getElementById('spark1'); let sparkImage2 = document.getElementById('spark2'); let sparkImage3 = document.getElementById('spark3'); let sparkImage4 = document.getElementById('spark4'); let sparkImage5 = document.getElementById('spark5'); let sparkImage6 = document.getElementById('spark6'); // BGM let bgm = new Audio(base_url + '/mappy/bgm.mp3'); // BGMは再生中か? let isPlayingBgm = false; // 効果音 // ジャンプ時 let jumpSound = new Audio(base_url + '/mappy/jump.mp3'); // ドア開閉時 let doorSound = new Audio(base_url + '/mappy/door.mp3'); // アイテム回収時 let getSound = new Audio(base_url + '/mappy/get.mp3'); // 敵が気絶した時 let enemyDownSound = new Audio(base_url + '/mappy/enemy-down.mp3'); // パワードア開放時 let bulletSound = new Audio(base_url + '/mappy/bullet.mp3'); // 敵死亡時 let explodeSound = new Audio(base_url + '/mappy/explotion.mp3'); // ステージクリア時 let clearSound = new Audio(base_url + '/mappy/clear.mp3'); // 自機死亡時 let deadSound = new Audio(base_url + '/mappy/dead.mp3'); // ゲームオーバー時 let gameoverSound = new Audio(base_url + '/mappy/gameover.mp3'); // プレイヤー名、スコア、残機数 let playerName; let score; let rest; // プレイヤーは死亡しているか? let isPlayerDead; // プレイヤーの向きと座標 let playerDirect = ""; let playerX; let playerY; // ステージの番号 let stageNumber = 0; // ステージのセルの幅 let xMax = 0; // フロアの座標 let floorXs = []; let floorYs = []; // フロアのうちドアの上部にあたる部分の座標 let floorOverLeftOpenDoorXs = []; let floorOverLeftOpenDoorYs = []; let floorOverRightOpenDoorXs = []; let floorOverRightOpenDoorYs = []; // 外壁の座標 let leftWallXs = []; let leftWallYs = []; let rightWallXs = []; let rightWallYs = []; // ドアの座標、開閉方向、開閉状態、パワードアかどうか? let doorXs = []; let doorYs = []; let doorDirects = []; let isDoorOpens = []; let isPowerDoors = []; // 敵の向きと座標、気絶しているかどうか? let enemyXs = []; let enemyYs = []; let enemyDirects = []; let isEnemyDowns = []; // 弾丸の向きと座標 let bulletXs = []; let bulletYs = []; let bulletDirects = []; // 火花の座標と寿命 let sparkXs = []; let sparkYs = []; let sparkLifes = []; // 追加される点数と表示位置(位置固定) let additionalPointXs = []; let additionalPointYs = []; let additionalPointValues = []; // 追加される点数と表示位置(水平方向に流れるように移動する) let additionalPointX = 0; let additionalPointY = 0; let additionalPointValue = 0; let isAdditionalPointFromLeft = true; // 流れる方向が左ならtrue、右ならfalse // 未回収のアイテムの座標と種類 let itemXs = []; let itemYs = []; let itemNums = []; // トランポリンの座標と寿命 let trampolineLifes = []; let trampolineXs = []; let trampolineYs = []; // 描画するときにどれだけX方向にずらすか? let shiftX = 0; // 描画処理がおこなわれた回数(トランポリンの描画処理で使う) let drawCount = 0; |
ページ読み込み完了時の処理
ページの読み込みが完了したときの処理を示します。canvasのサイズ設定と効果音のボリュームの設定、BGMをエンドレスで鳴らすための設定をおこなっています。
wwwroot\mappy\mappy.js
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 |
window.addEventListener('load', Init); function Init() { can.width = CANVAS_WIDTH; can.height = CANVAS_HEIGHT; SetVolumes(0.02); bgm.addEventListener("ended", function () { isPlayingBgm = false; }, false); setInterval(() => { if (IsSound()) { if (!isPlayingBgm) { bgm.currentTime = 0; bgm.play(); isPlayingBgm = true; } } else StopBgm(); }, 500); document.getElementById('sound-checkbox').checked = true; connection.start().catch(function (err) { document.getElementById("conect-result").innerHTML = '接続失敗'; }); } function SetVolumes(volume) { // 効果音の大きさに違いがあるので調整している let volume2 = volume * 3; if (volume2 > 1) volume2 = 1; explodeSound.volume = volume2; deadSound.volume = volume2; gameoverSound.volume = volume; enemyDownSound.volume = volume; bulletSound.volume = volume; doorSound.volume = volume; getSound.volume = volume; clearSound.volume = volume; jumpSound.volume = volume; bgm.volume = volume; } // チェックボックスにチェックがあり、プレイ中のときだけBGMと効果音を鳴らす function IsSound() { return isPlaying && document.getElementById('sound-checkbox').checked; } function StopBgm() { bgm.currentTime = 0; bgm.pause(); isPlayingBgm = false; } |
接続成功時の処理
Init関数の最後にconnection.start()を実行してサーバーサイドに接続しようとしていますが、接続に成功したときの処理を示します。
接続成功時にはサーバーサイドからSuccessfulConnectionToClientが送られてきます。これを受信したらページ下部にconnectionIDを表示させます。
1 2 3 4 |
connection.on("SuccessfulConnectionToClient", function (result, id) { connectionID = id; document.getElementById("conect-result").innerHTML = `conect-result ${result}:${id}`; }); |
接続成功時にはサーバーサイドからSendFloorPositionsToClientも送られてきます。これを受信したら床と壁を描画するために必要なデータを配列内に保存します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
connection.on("SendFloorPositionsToClient", function (xs1, ys1, xs2, ys2, xs3, ys3, max) { floorXs = xs1.split(','); floorYs = ys1.split(','); floorOverLeftOpenDoorXs = xs2.split(','); floorOverLeftOpenDoorYs = ys2.split(','); floorOverRightOpenDoorXs = xs3.split(','); floorOverRightOpenDoorYs = ys3.split(','); xMax = max; }); connection.on("SendWallPositionsToClient", function (xs, ys, xs2, ys2) { leftWallXs = xs.split(','); leftWallYs = ys.split(','); rightWallXs = xs2.split(','); rightWallYs = ys2.split(','); }); |
ゲーム開始時の処理
ゲーム開始ボタンを押したときの処理を示します。
この場合はサーバーサイドにGameStartを送信します。このときサーバーサイドでこれに対する処理が正常におこなわれた場合はEventGameStartToClientが送られてくるので、これを受信したらisPlayingフラグをセットして、ゲーム開始ボタンとランキングを見るボタンを非表示にします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function GameStart() { if (connectionID != '') { let playerName = document.getElementById('player-name').value; connection.invoke("GameStart", connectionID, playerName).catch(function (err) { return console.error(err.toString()); }); } } connection.on("EventGameStartToClient", function () { startButton1.style.display = 'none'; showTop30Button1.style.display = 'none'; isPlaying = true; }); |
キー操作時の処理
ゲーム中は方向キーを押したときのデフォルトの動作を抑制してキャラクターの移動しかできないようにします。そしてサーバーサイドにキーを送信します。
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 |
document.onkeydown = function (e) { // ゲーム開始以降はデフォルトの動作を抑制する if (isPlaying && (e.key == "ArrowUp" || e.key == "ArrowDown" || e.key == "ArrowLeft" || e.key == "ArrowRight" || e.key == " ")) e.preventDefault(); // キーが押しっぱなしになっているときの二重送信を防ぐ if (e.key == "ArrowLeft" && isLeftKeyDown) return; if (e.key == "ArrowRight" && isRightKeyDown) return; if (e.key == "ArrowLeft") isLeftKeyDown = true; if (e.key == "ArrowRight") isRightKeyDown = true; // ゲーム中にプレイヤー名を変更可能なのでキーが押されたらそのつどプレイヤー名も送信する // 接続されていない場合はなにもしない if (connectionID != '') { let playerName = document.getElementById('player-name').value; connection.invoke("DownKey", e.key, playerName).catch(function (err) { return console.error(err.toString()); }); } } document.onkeyup = function (e) { if (e.key == "ArrowLeft") isLeftKeyDown = false; if (e.key == "ArrowRight") isRightKeyDown = false; // 接続されていない場合はなにもしない if (connectionID != '') { connection.invoke("UpKey", e.key).catch(function (err) { return console.error(err.toString()); }); } } |
更新処理
サーバーサイドで更新処理がおこなわれたらUpdate…ToClientが送られてきます。これを受信したら配列または変数に更新処理用のデータを保存しておきます。
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 |
connection.on("UpdatePlayerToClient", function (x, y, direct, name, isdead, _score, _rest, stagenumber) { playerName = name; playerX = x; playerY = y; playerDirect = direct; isPlayerDead = isdead; score = _score; rest = _rest; stageNumber = stagenumber; }); connection.on("UpdateEnemiesToClient", function (xs, ys, directs, isDowns) { if (xs != '') { enemyXs = xs.split(','); enemyYs = ys.split(','); enemyDirects = directs.split(','); isEnemyDowns = isDowns.split(','); } else { enemyXs = []; enemyYs = []; enemyDirects = []; isEnemyDowns = []; } }); connection.on("UpdateItemsToClient", function (xs, ys, nums) { if (xs != '') { itemXs = xs.split(','); itemYs = ys.split(','); itemNums = nums.split(','); } else { itemXs = []; itemYs = []; itemNums = []; } }); connection.on("UpdateDoorsToClient", function (_doorXs, _doorYs, _isDoorOpens, _isPowerDoors, _doorDirects) { doorXs = _doorXs.split(','); doorYs = _doorYs.split(','); isDoorOpens = _isDoorOpens.split(','); isPowerDoors = _isPowerDoors.split(','); doorDirects = _doorDirects.split(','); }); connection.on("UpdateTrampolinesToClient", function (xs, ys, lefes) { if (xs != '') { trampolineXs = xs.split(','); trampolineYs = ys.split(','); trampolineLifes = lefes.split(','); } else { trampolineXs = []; trampolineYs = []; trampolineLifes = []; } }); connection.on("UpdateBulletsToClient", function (xs, ys, directs) { if (xs != '') { bulletXs = xs.split(','); bulletYs = ys.split(','); bulletDirects = directs.split(','); } else { bulletXs = []; bulletYs = []; bulletDirects = []; } }); connection.on("UpdateSparksToClient", function (xs, ys, lifes) { if (xs != '') { sparkXs = xs.split(','); sparkYs = ys.split(','); sparkLifes = lifes.split(','); } else { sparkXs = []; sparkYs = []; sparkLifes = []; } }); |
それから追加点が入ったときはサーバーサイドからPlayerAddScoreEvent1ToClientとPlayerAddScoreEvent2ToClientが送られてきます。前者の場合はこのデータを配列に格納しますが、一定期間が経過したらこれを削除します。これによってしばらくのあいだ追加点が画面に表示されるようになります。
1 2 3 4 5 6 7 8 9 10 11 |
connection.on("PlayerAddScoreEvent1ToClient", function (value, x, y) { additionalPointXs.push(x); additionalPointYs.push(y); additionalPointValues.push(value); setTimeout(() => { additionalPointXs.splice(0, 1); additionalPointYs.splice(0, 1); additionalPointValues.splice(0, 1); }, 2000); }); |
PlayerAddScoreEvent2ToClientが送られてきたら、このデータを配列に格納しますが、33ミリ秒ごとにこれを左右に移動させます。これによって追加点が水平方向にながれるように表示されるようになります。追加点を表示する座標が画面の端にきた場合や追加点が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 |
connection.on("PlayerAddScoreEvent2ToClient", function (value, y, isFromLeft) { additionalPointValue = value; additionalPointY = y; // 最初の表示するX座標は画面の端とし、タイマーでもう片方の端にむけて移動させる if (isFromLeft) additionalPointX = 0; else additionalPointX = xMax; isAdditionalPointFromLeft = isFromLeft; }); setInterval(() => { // 追加点が表示されるX座標を移動させ、画面端に到達したらadditionalPointValueに0を代入する // additionalPointValue == 0のときはなにも表示されない if (additionalPointValue == 0) return; if (additionalPointX < 0 || additionalPointX > xMax) additionalPointValue = 0; else { if (isAdditionalPointFromLeft) additionalPointX += 16; else additionalPointX -= 16; } }, 33); |
描画処理
サーバーサイドからEndUpdateToClientが送られてきたら描画処理を開始します。
プレイヤーのX座標によってスクロール処理をさせたいので、最初にGetShiftX関数でどれだけX方向に平行移動させた状態で描画するかを決めます。そのあとshiftX分ズラして描画処理をおこないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
connection.on("EndUpdateToClient", function () { ctx.fillStyle = "#000000"; ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); GetShiftX(); DrawFloors(); DrawBullets(); DrawItems(); DrawDoors(); DrawPlayer(); DrawEnemies(); DrawSparks(); }); function GetShiftX() { // プレイヤーが端のほうにいるとき以外はプレイヤーを中心に描画する if (playerX > CANVAS_WIDTH / 2) shiftX = playerX - CANVAS_WIDTH / 2; if (playerX > xMax - CANVAS_WIDTH / 2) shiftX = xMax - CANVAS_WIDTH; } |
床と外壁、トランポリンを描画する関数を示します。
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 |
function DrawFloors() { for (let i = 0; i < floorXs.length; i++) ctx.drawImage(floorImage, floorXs[i] - shiftX, floorYs[i]); for (let i = 0; i < floorOverLeftOpenDoorXs.length; i++) { ctx.drawImage(floorOverLeftOpenDoorImage, floorOverLeftOpenDoorXs[i] - shiftX, floorOverLeftOpenDoorYs[i] - 32); } for (let i = 0; i < floorOverRightOpenDoorXs.length; i++) { ctx.drawImage(floorOverRightOpenDoorImage, floorOverRightOpenDoorXs[i] - shiftX, floorOverRightOpenDoorYs[i] - 32); } for (let i = 0; i < leftWallXs.length; i++) { ctx.drawImage(wallImageL, leftWallXs[i] - shiftX, leftWallYs[i] - 0); } for (let i = 0; i < rightWallXs.length; i++) { ctx.drawImage(wallImageR, rightWallXs[i] - shiftX, rightWallYs[i] - 0); } // トランポリンの描画 // プレイヤーの連続使用回数によって色を変える(4回使用したら非表示)。 // 小刻みに上下に移動させる drawCount++; for (let i = 0; i < trampolineXs.length; i++) { let life = Number(trampolineLifes[i]); if (life == 4) ctx.drawImage(trampolineImage4, trampolineXs[i] - shiftX, trampolineYs[i] - 0 + 28 + drawCount % 4); if (life == 3) ctx.drawImage(trampolineImage3, trampolineXs[i] - shiftX, trampolineYs[i] - 0 + 28 + drawCount % 4); if (life == 2) ctx.drawImage(trampolineImage2, trampolineXs[i] - shiftX, trampolineYs[i] - 0 + 28 + drawCount % 4); if (life == 1) ctx.drawImage(trampolineImage1, trampolineXs[i] - shiftX, trampolineYs[i] - 0 + 28 + drawCount % 4); } } |
パワードアから放たれた弾丸を描画する関数を示します。
1 2 3 4 5 |
function DrawBullets() { for (let i = 0; i < bulletXs.length; i++) { ctx.drawImage(bulletImage, bulletXs[i] - shiftX, bulletYs[i]); } } |
未回収のアイテムを描画する関数を示します。アイテムの種類によって描画するイメージを変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function DrawItems() { for (let i = 0; i < itemXs.length; i++) { let itemImage = null; if (itemNums[i] == 1) itemImage = itemImage1; if (itemNums[i] == 2) itemImage = itemImage2; if (itemNums[i] == 3) itemImage = itemImage3; if (itemNums[i] == 4) itemImage = itemImage4; if (itemNums[i] == 5) itemImage = itemImage5; // 表示サイズを微調整している if (itemImage != null) ctx.drawImage(itemImage, itemXs[i] - shiftX + 6, itemYs[i] - 0 + 6, 32 - 8, 32 - 8); } } |
ドアを描画します。配列のデータを参照して開いているドアなのか閉まっているドアなのか?閉まっている場合どちら向きに開くドアなのか?それはパワードアなのかを調べて適切なイメージを使って描画しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function DrawDoors() { for (let i = 0; i < doorXs.length; i++) { let image = doorImage; if (isPowerDoors[i] == 'true') { if (doorDirects[i] == 'L') image = powerDoorLeftImage; else if (doorDirects[i] == 'R') image = powerDoorRightImage; } else { if (isDoorOpens[i] == 'true') image = doorImage; else if (doorDirects[i] == 'L') image = doorLeftImage; else if (doorDirects[i] == 'R') image = doorRightImage; } // 表示位置を微調整 ctx.drawImage(image, doorXs[i] - shiftX, doorYs[i] - 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 |
function DrawPlayer() { // プレイヤー死亡時は描画しない if (!isPlayerDead) { // 右向き左向き? if (playerDirect == 'L') ctx.drawImage(playerImageL, playerX - shiftX, playerY); else ctx.drawImage(playerImageR, playerX - shiftX, playerY); } // スコア、残機、ステージ数の描画 ctx.font = "24px Arial"; ctx.fillStyle = "#ffffff"; ctx.fillText(score + ' 残 ' + rest, 10, 24); ctx.font = "18px Arial"; ctx.fillText('STAGE ' + stageNumber, 280, 24); // 追加点の描画(位置固定) ctx.font = "16px Arial"; for (let i = 0; i < additionalPointXs.length; i++) ctx.fillText(additionalPointValues[i], additionalPointXs[i] - shiftX, additionalPointYs[i]); // 追加点の描画(水平方向に移動) if (additionalPointValue > 0) { ctx.font = "24px Arial"; ctx.fillText(additionalPointValue, additionalPointX - shiftX, additionalPointY); } } |
敵の描画をおこなう関数を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function DrawEnemies() { for (let i = 0; i < enemyXs.length; i++) { if (isEnemyDowns[i] == 'false') { if (enemyDirects[i] == 'L') ctx.drawImage(enemyImageL, enemyXs[i] - shiftX, enemyYs[i]); else ctx.drawImage(enemyImageR, enemyXs[i] - shiftX, enemyYs[i]); } else { if (enemyDirects[i] == 'L') ctx.drawImage(downEnemyImageL, enemyXs[i] - shiftX, enemyYs[i]); else ctx.drawImage(downEnemyImageR, enemyXs[i] - shiftX, enemyYs[i]); } } } |
火花の描画をおこなう関数を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function DrawSparks() { for (let i = 0; i < sparkXs.length; i++) { let sparkImage = null; if (sparkLifes[i] >= 11) sparkImage = sparkImage1; else if (sparkLifes[i] >= 9) sparkImage = sparkImage2; else if (sparkLifes[i] >= 7) sparkImage = sparkImage3; else if (sparkLifes[i] >= 5) sparkImage = sparkImage4; else if (sparkLifes[i] >= 3) sparkImage = sparkImage5; else if (sparkLifes[i] >= 1) sparkImage = sparkImage6; if (sparkImage != null) ctx.drawImage(sparkImage, sparkXs[i] - shiftX, sparkYs[i], 32, 32); } } |
効果音の再生
サーバーサイドでイベントが発生したら適切な効果音を鳴らします。
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 |
// ジャンプ時 connection.on("PlayerJumpEventToClient", function () { if (IsSound()) { jumpSound.currentTime = 0; jumpSound.play(); } }); // アイテム回収時 connection.on("GetItemEventToClient", function () { if (IsSound()) { getSound.currentTime = 0; getSound.play(); } }); // ドア開閉時 connection.on("OpenCloseDoorEventToClient", function () { if (IsSound()) { doorSound.currentTime = 0; doorSound.play(); } }); // 敵が気絶した時 connection.on("EnemyDownEventToClient", function () { if (IsSound()) { enemyDownSound.currentTime = 0; enemyDownSound.play(); } }); // パワードアを開放した時 connection.on("FireBulletEventToClient", function () { if (IsSound()) { bulletSound.currentTime = 0; bulletSound.play(); } }); // 敵が死亡した時 connection.on("EnemyDeadEventToClient", function () { if (IsSound()) { explodeSound.currentTime = 0; explodeSound.play(); } }); // ステージクリア時 connection.on("StageClearEventToClient", function () { StopBgm(); if (IsSound()) { clearSound.currentTime = 0; clearSound.play(); } }); // プレイヤー死亡時 connection.on("PlayerDeadEventToClient", function () { if (IsSound()) { deadSound.currentTime = 0; deadSound.play(); } }); |
ゲームオーバー時は効果音を鳴らすまえにBGMを停止してゲーム開始用のボタンとスコアランキングに移動するボタンを再表示させます。そのあとプレイ中であることを示すisPlayingフラグをクリアします。
1 2 3 4 5 6 7 8 9 10 11 12 |
connection.on("GameOverEventToClient", function () { startButton1.style.display = 'block'; showTop30Button1.style.display = 'block'; StopBgm(); if (IsSound()) { gameoverSound.currentTime = 0; gameoverSound.play(); } isPlaying = false; }); |