今回はクライアントサイドの処理をおこないます。
cshtmlファイルの作成
PagesフォルダのなかにCrashRoller.cshtmlという名前のファイルを作成して以下のように書きます。ただしGlobal.BaseUrlはこのようになっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class Global { public static string BaseUrl { get { if (IsDebug) return ""; else return "https://lets-csharp.com/samples/2204/aspnetcore-app-zero"; // 公開したいURL } } } |
Pages\Shared\_Layout_none.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 |
@{ string baseurl = Global.BaseUrl; } <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - 鳩でもわかるASP.NET Core</title> <style> body { background-color: #000; color: #fff; } #container { width: 100%; max-width: 960px; margin-right: auto; margin-left: auto; } .display-none { display:none; } </style> </head> <body> <div class="container"> @RenderBody() </div> @await RenderSectionAsync("Scripts", required: false) </body> </html> |
Pages\CrashRoller.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 |
@page @{ ViewData["Title"] = "鳩でもわかるクラッシュローラー"; Layout = "_Layout_none"; string baseurl = Global.BaseUrl; } <canvas id="can"></canvas> <div> <label for="player-name">ハンドルネーム (12文字まで):</label><br> <input type="text" id="player-name" name="player-name" maxlength="12" size="10"> <input type="button" value="ゲームスタート" onclick="GameStart()"><br> <input type="button" onclick="location.href='@baseurl/CrashRollerHiscore'" value="上位30位をチェック"> <!-- /CrashRollerHiscoreは次回作成する --> </div> <div class = "display-none"> <img src = "@baseurl/crash-roller/player.png" id ="player"> <img src = "@baseurl/crash-roller/enemy1.png" id ="enemy1"> <img src = "@baseurl/crash-roller/enemy2.png" id ="enemy2"> <img src = "@baseurl/crash-roller/roller_n.png" id ="roller_n"> <img src = "@baseurl/crash-roller/roller_s.png" id ="roller_s"> <img src = "@baseurl/crash-roller/roller_e.png" id ="roller_e"> <img src = "@baseurl/crash-roller/roller_w.png" id ="roller_w"> </div> <p id = "conect-result"></p> <script src="@baseurl/js/signalr.js"></script> <script> "use strict"; let connection = new signalR.HubConnectionBuilder().withUrl("@baseurl/CrashRollerHub").build(); </script> <script src="@baseurl/crash-roller/game-map.js"></script> <script src="@baseurl/crash-roller/game.js"></script> |
JavaScriptによる描画処理
wwwroot\crash-roller\game-map.jsは描画に必要な二次元配列です。ここからダウンロード可能です。
ではJavaScriptを示します。
通路の状態を示す列挙体(JavaScriptには列挙体はないけどそのように機能するもの)を示します。
wwwroot\crash-roller\game.js
1 2 3 4 5 6 7 8 9 10 |
const Road = { NONE: -1, ROAD: 1, // 普通の通路 BRIDGE_NS: 2, // 南北の橋 BRIDGE_WE: 3, // 東西の橋 BRIDGE_NS_CROSS: 4, // 東西の橋と通路が交差している点 BRIDGE_WE_CROSS: 5, // 東西の橋と通路が交差している点 BRIDGE_EDGE: 6, // 橋の開始地点 BRIDGE_NEAR_EDGE: 7, // 橋の開始地点の周辺 }; |
グローバル変数
次にグローバル変数を示します。
wwwroot\crash-roller\game.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 |
let leftMargin = 0; // Canvasの左側の余白 let topMargin = 10; // Canvasの上側の余白 let canvasWidth = 320; // Canvasのサイズ let canvasHeight = 480; let expansionRate = 1.6; // 拡大率(スマホでも表示できるギリギリのサイズ) let charactorSize = 20; // キャラクタの大きさ let borderWidth = 2; // 通路とそうでない部分の境界線の幅 let canvas = document.getElementById('can'); let ctx = canvas.getContext('2d'); let mapSourceHeight = map.length; // マップの幅と高さ let mapSourceWidth = map[0].length; let isVisits = []; // すでに通過した場所か? let playerX = 0; // プレイヤーの座標と移動方向 let playerY = 0; let playerDirect = 0; let enemy1X = 0; // 敵の座標と移動方向 let enemy1Y = 0; let enemy1Direct = 0; let enemy2X = 0; let enemy2Y = 0; let enemy2Direct = 0; let rollerNSX = 0; // ローラーの座標 let rollerNSY = 0; let rollerWEX = 0; let rollerWEY = 0; let isRollerHold = false; // ローラーはプレイヤーによって使用されているか? let positionHitEmemyX = 0; // 敵を撃退したときに加算される点数を表示する座標 let positionHitEmemyY = 0; // 両方とも0の場合は表示させる必要はない let addPointCrashEnemy = 0; // 表示する点数 let score = 0; // スコア let rest = 0; // 残機 let stageNumber = 1; // 現在のステージ let isUpKeyDown = false; // 方向キーは押されているか? let isDownKeyDown = false; let isLeftKeyDown = false; let isRightKeyDown = false; // 表示するイメージ let imgPlayer = document.getElementById('player'); let imgEnemy1 = document.getElementById('enemy1'); let imgEnemy2 = document.getElementById('enemy2'); // 東西南北に押されているローラーのイメージ let imgRollerN = document.getElementById('roller_n'); let imgRollerS = document.getElementById('roller_s'); let imgRollerE = document.getElementById('roller_e'); let imgRollerW = document.getElementById('roller_w'); // BGMと効果音を再生するためのオブジェクト let bgm = new Audio('./crash-roller/bgm.mp3'); let hitSound = new Audio('./crash-roller/hit.mp3'); let deadSound = new Audio('./crash-roller/dead.mp3'); let rollerSound = new Audio('./crash-roller/roller.mp3');; let stageClearSound = new Audio('./crash-roller/clear.mp3'); let gameOverSound = new Audio('./crash-roller/gameover.mp3'); // 現在効果音またはBGMは再生中か? let isPlayingRollerSound = false; let isPlayingBgm = false; // 現在プレイ中か? let isGaming = false; |
初期化
最初に表示させるキャンバスの幅と高さを設定します。
wwwroot\crash-roller\game.js
1 2 |
canvas.width = canvasWidth; canvas.height = canvasHeight; |
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 |
function GetCanvasSize() { const a = window.innerWidth / 2; const b = window.innerHeight / 3; if (a < b) { canvasWidth = a * 2 - canvasWidth / 32; canvasHeight = a * 3 - canvasWidth / 32 * 3 - 40; } else { canvasWidth = b * 2 - canvasWidth / 32; canvasHeight = b * 3 - canvasWidth / 32 * 3 - 40; } expansionRate = canvasWidth / 200; charactorSize = canvasWidth / 16; borderWidth = canvasWidth / 160; leftMargin = 0; topMargin = 10 * (canvasWidth / 320); } setInterval(() => { GetCanvasSize(); if (canvas != undefined && canvas != null && (canvas.width != canvasWidth || canvas.height != canvasHeight)) { canvas.width = canvasWidth; canvas.height = canvasHeight; Draw(); } }, 1000); |
ページが読み込まれたら上記のGetCanvasSize関数でキャンバスのサイズを設定して効果音を再生できるように準備をします。
1 2 3 4 5 6 7 8 |
window.addEventListener('load', function () { GetCanvasSize(); ClearVisits(); InitSound(); Draw(); // 後述 SetVolume(0.06); // 初期値は小さめの値に }); |
通路はプレイヤーが通過した部分とそうでない部分は色を変えます。プレイヤーが通過した部分なのかそうでない部分なのかは二次元配列isVisits で管理します。
1 2 3 4 5 6 7 8 9 10 |
function ClearVisits() { isVisits = []; for (let y = 0; y < mapSourceHeight; y++) { let arr = []; for (let x = 0; x < mapSourceWidth; x++) { arr.push(false); } isVisits.push(arr); } } |
効果音とBGMを初期化する処理を示します。効果音やBGMが再生されたらisPlayingRollerSoundとisPlayingBgmをfalseにして二重に再生されるのを防ぎます。
1 2 3 4 5 6 7 8 9 |
function InitSound() { rollerSound.addEventListener("ended", function () { isPlayingRollerSound = false; }, false); bgm.addEventListener("ended", function () { isPlayingBgm = false; }, false); } |
ボリュームを設定するための関数を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function SetVolume(volume) { if (volume < 0) volume = 0; if (volume > 1.0) volume = 1; bgm.volume = volume; hitSound.volume = volume; deadSound.volume = volume; rollerSound.volume = volume; stageClearSound.volume = volume; gameOverSound.volume = volume; } |
キー操作への対応
矢印キーが押されたらサーバサイドに送信します。またフラグをセットして離されたらクリアすることでキーを押しっぱなしにしているときに連続して送信されるのを防ぎます。
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 |
document.onkeydown = function (e) { if (isGaming && (e.key == "ArrowUp" || e.key == "ArrowDown" || e.key == "ArrowLeft" || e.key == "ArrowRight")) e.preventDefault(); if( (e.key == "ArrowUp" && isUpKeyDown) || (e.key == "ArrowDown" && isDownKeyDown) || (e.key == "ArrowLeft" && isLeftKeyDown) || (e.key == "ArrowRight" && isRightKeyDown) ) return; if(e.key == "ArrowUp") isUpKeyDown = true; if(e.key == "ArrowDown") isDownKeyDown = true; if(e.key == "ArrowLeft") isLeftKeyDown = true; if(e.key == "ArrowRight") isRightKeyDown = true; connection.invoke("DownKey", e.key) .catch(function (err) { return console.error(err.toString()); }); } document.onkeyup = function (e) { if (e.key == "ArrowUp") isUpKeyDown = false; if (e.key == "ArrowDown") isDownKeyDown = false; if (e.key == "ArrowLeft") isLeftKeyDown = false; if (e.key == "ArrowRight") isRightKeyDown = false; connection.invoke("UpKey", e.key) .catch(function (err) { return console.error(err.toString()); }); } |
通路の描画
DrawClear関数はキャンバス全体を黒で塗りつぶします。
1 2 3 4 5 |
function DrawClear() { ctx.fillStyle = "rgb(0, 0, 0)"; ctx.fillRect(0, 0, canvas.width, canvas.height); } |
GetVisitedColor関数はすでに通過した部分を塗りつぶすときの色を取得します。色はステージが進行するにつれて緑、ピンク、オレンジ、緑とかわっていきます。
1 2 3 4 5 6 7 8 9 |
function GetVisitedColor() { if (stageNumber % 3 == 1) return "rgb(0, 255, 0)"; if (stageNumber % 3 == 2) return "rgb(255, 20, 147)"; if (stageNumber % 3 == 0) return "rgb(255, 69, 0)"; } |
DrawRoad関数は通路を描画するための関数です。
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 |
function DrawRoad(){ ctx.fillStyle = "rgb(0, 0, 255)"; for (let y = 0; y < mapSourceHeight; y++) { for (let x = 0; x < mapSourceWidth; x++) { if (map[y][x] > 0) ctx.fillRect( x * expansionRate - borderWidth + leftMargin, y * expansionRate - borderWidth + topMargin, charactorSize + borderWidth * 2, charactorSize + borderWidth * 2); } } ctx.fillStyle = "rgb(255, 255, 255)"; for (let y = 0; y < mapSourceHeight; y++) { for (let x = 0; x < mapSourceWidth; x++) { if (map[y][x] > 0) ctx.fillRect( x * expansionRate + leftMargin, y * expansionRate + topMargin, charactorSize, charactorSize); } } ctx.fillStyle = GetVisitedColor(); for (let y = 0; y < mapSourceHeight; y++) { for (let x = 0; x < mapSourceWidth; x++) { if (map[y][x] > 0 && isVisits[y][x]) ctx.fillRect( x * expansionRate + leftMargin, y * expansionRate + topMargin, charactorSize, charactorSize); } } } |
DrawBridge関数は橋を描画する関数です。
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 |
function DrawBridgeWE(){ // 橋の輪郭を描画 ctx.fillStyle = "rgb(0, 0, 255)"; for (let y = 0; y < mapSourceHeight; y++) { for (let x = 0; x < mapSourceWidth; x++) { if (map[y][x] == Road.BRIDGE_WE_CROSS) { ctx.fillRect( x * expansionRate + leftMargin, y * expansionRate - borderWidth + topMargin, charactorSize, borderWidth); ctx.fillRect( x * expansionRate + leftMargin, y * expansionRate + charactorSize + topMargin, charactorSize, borderWidth); } } } // 橋の内部を描画 ctx.fillStyle = "rgb(255, 255, 255)"; for (let y = 0; y < mapSourceHeight; y++) { for (let x = 0; x < mapSourceWidth; x++) { if (map[y][x] == Road.BRIDGE_WE) ctx.fillRect( x * expansionRate + leftMargin, y * expansionRate + topMargin, charactorSize, charactorSize); } } // 橋の内部ですでにプレイヤーが通過している部分を描画 ctx.fillStyle = GetVisitedColor(); for (let y = 0; y < mapSourceHeight; y++) { for (let x = 0; x < mapSourceWidth; x++) { if ((map[y][x] == Road.BRIDGE_WE) && isVisits[y][x]) { ctx.fillRect( x * expansionRate + leftMargin, y * expansionRate + topMargin, charactorSize, charactorSize); } } } } function DrawBridgeNS(){ // 橋の輪郭を描画 ctx.fillStyle = "rgb(0, 0, 255)"; for (let y = 0; y < mapSourceHeight; y++) { for (let x = 0; x < mapSourceWidth; x++) { if (map[y][x] == Road.BRIDGE_NS_CROSS) { ctx.fillRect( x * expansionRate - borderWidth + leftMargin, y * expansionRate + topMargin, borderWidth, charactorSize); ctx.fillRect( x * expansionRate + charactorSize + leftMargin, y * expansionRate + topMargin, borderWidth, charactorSize); } } } // 橋の内部を描画 ctx.fillStyle = "rgb(255, 255, 255)"; for (let y = 0; y < mapSourceHeight; y++) { for (let x = 0; x < mapSourceWidth; x++) { if (map[y][x] == Road.BRIDGE_NS) ctx.fillRect( x * expansionRate + leftMargin, y * expansionRate + topMargin, charactorSize, charactorSize); } } // 橋の内部ですでにプレイヤーが通過している部分を描画 ctx.fillStyle = GetVisitedColor(); for (let y = 0; y < mapSourceHeight; y++) { for (let x = 0; x < mapSourceWidth; x++) { if ((map[y][x] == Road.BRIDGE_NS) && isVisits[y][x]) { ctx.fillRect( x * expansionRate + leftMargin, y * expansionRate + topMargin, charactorSize, charactorSize); } } } } |
ReDrawBridgeEdgeIfNeed関数は橋を描画することで通過した部分が白に戻ってしまったときに塗り直すためのものです。
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 |
let bridgeNearEdgeXs = []; let bridgeNearEdgeYs = []; function ReDrawBridgeEdgeIfNeed() { ctx.fillStyle = GetVisitedColor(); // 初回は配列bridgeNearEdgeXsが空なので該当する座標を取得して格納する // 2回目以降は格納されている値を使う if(bridgeNearEdgeXs.length == 0) { for(let y=0; y<mapSourceHeight; y++) { for(let x=0; x<mapSourceWidth; x++) { if(map[y][x] == Road.BRIDGE_EDGE) { bridgeNearEdgeXs.push(x); bridgeNearEdgeYs.push(y); } } } } for(let i=0; i<bridgeNearEdgeXs.length; i++) { if (isVisits[bridgeNearEdgeYs[i]][bridgeNearEdgeXs[i]]) ctx.fillRect( bridgeNearEdgeXs[i] * expansionRate + leftMargin, bridgeNearEdgeYs[i] * expansionRate + topMargin, charactorSize, charactorSize); } } |
プレイヤーと敵の描画
DrawPlayer関数とDrawEnemies関数はプレイヤーと敵を描画するための関数です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function DrawPlayer() { ctx.drawImage( imgPlayer, playerX * expansionRate + leftMargin - 5, playerY * expansionRate + topMargin - 5, charactorSize + 10, charactorSize + 10 ); } function DrawEnemies() { ctx.drawImage( imgEnemy1, enemy1X * expansionRate + leftMargin - 5, enemy1Y * expansionRate + topMargin - 5, charactorSize + 10, charactorSize + 10 ); ctx.drawImage( imgEnemy2, enemy2X * expansionRate + leftMargin - 5, enemy2Y * expansionRate + topMargin - 5, charactorSize + 10, charactorSize + 10 ); } |
橋を描画するとプレイヤーと敵が下に隠れてしまいます。もし橋の上にいるのであればもう一度描画しなおさなければなりません。IsNeedReDrawは再描画が必要かどうかを調べます。
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 |
// キャラクタが配列の範囲内にいることを確認するための関数 function IsInMap(y, x) { if (x < 0) return false; if (y < 0) return false; if (x >= mapSourceWidth) return false; if (y >= mapSourceHeight) return false; return true; } function IsNeedReDraw(x, y, direct) { // キャラクタが配列の範囲内にいることを確認したら・・・ if (IsInMap(y, x)) { // 橋の上と端の周辺にいる場合はキャラクタを再描画する if ( (map[y][x] == Road.BRIDGE_EDGE || map[y][x] == Road.BRIDGE_NEAR_EDGE || map[y][x] == Road.BRIDGE_NS || map[y][x] == Road.BRIDGE_WE)) { return true; } // キャラクタが橋とそうでない部分の交点にいる場合は // 南北にかかる橋の上を移動している場合(移動方向が北または南)、 // または東西にかかる橋の上を移動している場合(移動方向が東または西)のみ再描画する if (map[y][x] == Road.BRIDGE_NS_CROSS && (direct == 1 || direct == 3)) return true; if (map[y][x] == Road.BRIDGE_WE_CROSS && (direct == 2 || direct == 4)) return true; } return false; } |
必要であればプレイヤーと敵を再描画します。
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 |
function ReDrawPlayerIfNeed() { if (IsNeedReDraw(playerX, playerY, playerDirect)) DrawPlayer(); } function ReDrawEnemiesIfNeed() { if (IsNeedReDraw(enemy1X, enemy1Y, enemy1Direct)) { ctx.drawImage( imgEnemy1, enemy1X * expansionRate + leftMargin - 5, enemy1Y * expansionRate + topMargin - 5, charactorSize + 10, charactorSize + 10 ); } if (IsNeedReDraw(enemy2X, enemy2Y, enemy2Direct)) { ctx.drawImage( imgEnemy2, enemy2X * expansionRate + leftMargin - 5, enemy2Y * expansionRate + topMargin - 5, charactorSize + 10, charactorSize + 10 ); } } |
ローラーの描画
ローラーを描画するための関数を示します。ローラーが押されている向きに応じて適切なイメージを描画させます。
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 |
// 現在ローラーの描画用につかわれているイメージを格納しているグローバル変数 let imgRollerNS = imgRollerN; let imgRollerWE = imgRollerE; let lastRollerNSY = rollerNSY; let lastRollerWEX = rollerWEX; function DrawRollers() { if (lastRollerNSY != 0 && lastRollerNSY < rollerNSY) imgRollerNS = imgRollerS; if (lastRollerNSY > rollerNSY) imgRollerNS = imgRollerN; lastRollerNSY = rollerNSY; ctx.drawImage( imgRollerNS, rollerNSX * expansionRate + leftMargin - 2, rollerNSY * expansionRate + topMargin - 2, charactorSize + 4, charactorSize + 4 ); if (lastRollerWEX != 0 && lastRollerWEX < rollerWEX) imgRollerWE = imgRollerE; if (lastRollerWEX > rollerWEX) imgRollerWE = imgRollerW; lastRollerWEX = rollerWEX; ctx.drawImage( imgRollerWE, rollerWEX * expansionRate + leftMargin, rollerWEY * expansionRate + topMargin, charactorSize, charactorSize ); } |
スコア・残機の描画
スコアを示す文字列を描画するための関数を示します。また敵を倒したときはゲームが中断しているあいだ(2秒間)加算される点数を表示させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
function DrawScore() { let size = 18 * (canvasWidth / 320); ctx.font = `bold ${size}px MS ゴシック`; ctx.fillStyle = "rgb(255, 255, 222)"; // スコア表示用 let scoreText = 'SCORE ' + String(score).padStart(5, '0'); if (score >= 100000) scoreText = 'SCORE ' + score; ctx.fillText(scoreText, 10, topMargin + 380 * (canvasWidth / 320)); if (positionHitEmemyX != 0 || positionHitEmemyY != 0) { ctx.fillStyle = "#FF0000"; ctx.font = "20px Arial"; ctx.fillText( addPointCrashEnemy, positionHitEmemyX * expansionRate + leftMargin, positionHitEmemyY * expansionRate + topMargin + 20 ); } } |
残機を示す文字列を描画する関数を示します。
1 2 3 4 5 6 7 8 9 10 |
function DrawRest() { if (isGaming) { let size = 18 * (canvasWidth / 320); ctx.font = `bold ${size}px MS ゴシック`; ctx.fillStyle = "rgb(255, 255, 222)"; let scoreText = '残 ' + rest ctx.fillText(scoreText, 10, topMargin + 400 * (canvasWidth / 320)); } } |
DrawGameOverIfNeed関数はプレイ中ではないときは’GAME OVER’の文字を表示させます。
1 2 3 4 5 6 7 8 |
function DrawGameOverIfNeed() { if (!isGaming) { let size = 18 * (canvasWidth / 320); ctx.font = `bold ${size}px MS ゴシック`; ctx.fillStyle = "rgb(255, 0, 0)"; ctx.fillText('GAME OVER', 10, topMargin + 400 * (canvasWidth / 320)); } } |
描画処理の全体
描画処理をおこなう関数を示します。この関数は0.01秒おきに呼び出されます。
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 |
function Draw() { DrawClear(); DrawRoad(); DrawPlayer(); DrawEnemies(); DrawBridgeWE(); DrawBridgeNS(); ReDrawBridgeEdgeIfNeed(); DrawRollers(); if (isRollerHold) { ReDrawEnemiesIfNeed(); ReDrawPlayerIfNeed(); } else { ReDrawPlayerIfNeed(); ReDrawEnemiesIfNeed(); } DrawScore(); DrawRest(); DrawGameOverIfNeed(); } setInterval(() => { Draw(); PlaySoundsIfNeed(); }, 10); |
BGMと効果音の再生
BGMと効果音に関する処理をおこないます。BGMとローラーを押しているときに再生される効果音を再生する必要がある場合は再生します。
1 2 3 4 |
function PlaySoundsIfNeed() { PlayBGMIfNeed(); PlayRollerSoundIfNeed(); } |
BGMの再生開始と終了
BGMを開始し終了するための関数を示します。この関数はゲーム中でありBGMの再生が完全に終了したときに新たにBGMの再生を開始します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function PlayBGMIfNeed() { if (isGaming) { if (!isPlayingBgm) { isPlayingBgm = true; bgm.currentTime = 0; bgm.play(); } } } function StopBGM() { isPlayingBgm = false; bgm.currentTime = 0; bgm.pause(); } |
ボリュームの調整
ボリュームを上げたり下げたりする関数を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function VolumeUp() { let volume = bgm.volume; if (volume > 0.3) volume += 0.1; else volume += 0.01; SetVolume(volume); } function VolumeDown() { let volume = bgm.volume; if (volume > 0.3) volume -= 0.1; else volume -= 0.01; SetVolume(volume); } |
ローラー使用時の効果音の再生
プレイヤーがローラーを押しているときの効果音を再生する関数を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function PlayRollerSoundIfNeed() { if (isRollerHold && positionHitEmemyX == 0 && positionHitEmemyY == 0) { if (!isPlayingRollerSound) { isPlayingRollerSound = true; rollerSound.currentTime = 0; rollerSound.play(); } } else { isPlayingRollerSound = false; rollerSound.currentTime = 0; rollerSound.pause(); } } |
スコアランキングへの記録
ゲームオーバーになったときにスコアランキングに記録するための処理を示します。サーバサイドにプレイヤー名を送信します。スコアはサーバーサイドで管理しているのであとはサーバサイドにおいて処理がおこなわれます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function SendData() { const textbox1 = document.getElementById("player-name"); // ハンドルネームの欄になにも入力されていなければ「名無しさん」とする let name = textbox1.value; if (name == "") name = "名無しさん"; connection.invoke("SendData", name) .catch(function (err) { return console.error(err.toString()); }); } |
サーバーサイドからの通知の処理
サーバーサイドに接続する処理と接続成功を受信したときの処理を示します。ページの下部に接続した時刻とコネクションIDを表示させます。
1 2 3 4 5 6 7 |
connection.start().then(function () { console.log("connection.start()"); }); connection.on("ReceiveMessage", function (result, id, datetime) { document.getElementById("conect-result").innerHTML = `${result}:${datetime}:${id}`; }); |
ゲーム開始時
ゲームを開始させたいときに呼び出す関数を示します。サーバーサイドのGameStart関数を呼び出します。
1 2 3 4 5 6 |
function GameStart() { connection.invoke("GameStart") .catch(function (err) { return console.error(err.toString()); }); } |
キャラクタの座標の変化時の処理
サーバーサイドから送られてきたプレイヤーの座標を受信したときの処理を示します。受信した変数の値をグローバル変数に格納し、前述のDraw関数で描画できるようにします。
1 2 3 4 5 |
connection.on("ReceiveUpdatePlayer", function (_x, _y, _playerDirect) { playerX = _x; playerY = _y; playerDirect = _playerDirect; }); |
敵の座標を受信したときの処理を示します。やはり描画するときに必要になるデータをグローバル変数に格納します。
1 2 3 4 5 6 7 8 |
connection.on("ReceiveUpdateEnemy", function (_enemy1X, _enemy1Y, _enemy1Direct, _enemy2X, _enemy2Y, _enemy2Direct) { enemy1X = _enemy1X; enemy1Y = _enemy1Y; enemy1Direct = _enemy1Direct; enemy2X = _enemy2X; enemy2Y = _enemy2Y; enemy2Direct = _enemy2Direct; }); |
ローラーの座標を受信したときの処理を示します。やはり描画するときに必要になるデータをグローバル変数に格納します。
1 2 3 4 5 6 7 |
connection.on("ReceiveUpdateRoller", function (_rollerNSX, _rollerNSY, _rollerWEX, _rollerWEY, _isRollerHold) { rollerNSX = _rollerNSX; rollerNSY = _rollerNSY; rollerWEX = _rollerWEX; rollerWEY = _rollerWEY; isRollerHold = _isRollerHold; }); |
敵を倒したときの処理
敵を倒したことを受信したときの処理を示します。この場合は効果音を鳴らして追加される点数を描画します。また追加される点数を描画する必要がなくなったときもこの処理がおこなわれます。そのときはpositionHitEmemyXとpositionHitEmemyYに0が格納されます。
1 2 3 4 5 6 7 8 9 |
connection.on("ReceiveHitEmemy", function (_positionHitEmemyX, _positionHitEmemyY, _addPointCrashEnemy) { positionHitEmemyX = _positionHitEmemyX; positionHitEmemyY = _positionHitEmemyY; addPointCrashEnemy = _addPointCrashEnemy; if (positionHitEmemyX != 0 || positionHitEmemyY != 0) { hitSound.play(); } }); |
ステージクリア時の処理
ステージクリアしたことを受信したときにおこなわれる処理を示します。ステージクリア時はすべての通路を塗り残しがないように塗りつぶすためにisVisits[y][x]をすべてtrueにします。また新しいステージが開始されたら塗りつぶされている情報をすべてクリアしてすべて白で描画されるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 |
connection.on("ReceiveStageClear", function () { stageClearSound.play(); for (let y = 0; y < mapSourceHeight; y++) { for (let x = 0; x < mapSourceWidth; x++) { isVisits[y][x] = true; } } }); connection.on("ReceiveNextStage", function () { ClearVisits(); }); |
ミス時の処理
ミスをしたらReceiveDeadPlayer関数が呼び出されます。この場合は効果音を鳴らします。
1 2 3 |
connection.on("ReceiveDeadPlayer", function () { deadSound.play(); }); |
表示用のスコアと残機数を受信したときの処理
表示用のスコアと残機数を受信したときの処理を示します。
1 2 3 4 5 |
connection.on("ReceiveUpdateScore", function (_eatX, _eatY, _score, _rest) { isVisits[_eatY][_eatX] = true; score = _score; rest = _rest; }); |
ゲームスタートとゲームオーバーの処理
ゲームスタートとゲームオーバーの処理が行なわれたことを受信したときの処理を示します。
ゲーム開始時は通路の塗りつぶされている部分をクリアしてすべて白で描画されるようにします。ゲームオーバー時は効果音鳴らしてBGMの再生を停止します。
1 2 3 4 5 6 7 8 9 10 11 12 |
connection.on("ReceiveGameStarted", function () { ClearVisits(); isGaming = true; }); connection.on("ReceiveGameOvered", function () { isGaming = false; StopBGM(); gameOverSound.play(); SendData(); }); |