ASP.NET Core版 対人対戦できるぷよぷよをつくる(2)の続きです。
Contents
cshtmlファイル
cshtmlファイルをPagesフォルダの配下に作成し、以下のように記述します。
Pages\PuyoMatch\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 80 81 82 83 84 85 86 87 88 89 90 91 |
@page @{ Layout = ""; string baseurl = "https://lets-csharp.com/samples/2204/aspnetcore-app-zero"; // baseurlはアプリとして公開したいurl。ドメイントップで公開するのであれば空文字列でよい } <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>鳩でもわかるオンライン対戦型「ぴよぴよ」Ver 1.0 - 鳩でもわかるASP.NET Core</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script> <style> body { background-color: #000; color: #fff; font-family: "MS ゴシック"; } #container { width: 700px; } .display-none { display: none; } </style> </head> <body> <div id="container"> <div class="display-none"> @for (int i = 0; i < 5; i++) { @for (int k = 0; k < 20; k++) { if (k < 10) <img src="@(baseurl)/puyo-match/puyo-images/@(i+1)-0@(k).png" alt="" id="type@(i+1)-0@(k)" /> else <img src="@(baseurl)/puyo-match/puyo-images/@(i+1)-@(k).png" alt="" id="type@(i+1)-@(k)" /> } } <img src="@baseurl/puyo-match/puyo-images/wall.png" alt="" id="wall" /> </div> <!-- /.display-none --> <div style="margin-left:60px; color:#ff0000" id="how-to"> <p> [エントリーする]ボタンを押すとすでにエントリーしている人がいると対戦が始まります。 誰もエントリーしていないと誰かがエントリーした段階で対戦が始まります。 このページから別のページへ移動すると再度エントリーが必要です。 </p> </div> <div style="margin-left:60px; color:#ff0000" id="errer"> <p>エラー:通信が切れました。</p> </div> <div style="margin-left:60px"> <p id="notify"></p> </div> <canvas id="can"></canvas> <br> <div style="margin-left:60px;"> <input type="checkbox" id="sound-checkbox"><label for="sound-checkbox">音を出す</label> <div id="entry"> <label for="player-name" id="player-name-label">ハンドルネーム</label> <input type="text" id="player-name" maxlength='16' /> <input type="button" id="enteryButton" value="エントリーする" onclick="Entery()"><br> </div> <p id="notify1">あなた以外に <span id="player-count"></span> 人がエントリーしています。<br> 現在 <span id="game-count"></span> の対戦がおこなわれています。</p> <p>遊び方</p> <p> 左右の移動 LeftKey RightKey <span style="margin-left:20px;">落下 DownKey</span><br> 左回転 Z 右回転 X (回転2度押しでクイックターン:縦に並んでいる組ぷよを180度回せる) </p> <p id="conect-result"></p> </div> </div><!-- / #container --> <script> let connection = new signalR.HubConnectionBuilder().withUrl("@baseurl/PuyoMatchHub").build(); let base_url = '@baseurl'; </script> <script src="@baseurl/puyo-match/puyo-match.js"></script> </body> </html> |
それから表示させたい画像ファイルや再生したい音声ファイルをwwwrootフォルダ配下(ここではwwwroot\puyo-match\soundとwwwroot\puyo-match\sound-count)に配置します。
JavaScript部分
主なグローバル変数を示します。
wwwroot\puyo-match\puyo-match.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 |
// canvasのサイズ const CANVAS_WIDTH = 700; const CANVAS_HEIGHT = 480; let can = document.getElementById('can'); let ctx = can?.getContext('2d'); // 現在プレイ中か? let isPlaying = false; // BGMと効果音 let bgm = new Audio(base_url + '/puyo-match/sound/bgm.mp3'); let matchingSucceededSound = new Audio(base_url + '/puyo-match/sound/matching-succeeded.mp3'); let rotateSound = new Audio(base_url + '/puyo-match/sound/rotate.mp3'); let fixedSound = new Audio(base_url + '/puyo-match/sound/fixed.mp3'); let fallingSound1 = new Audio(base_url + '/puyo-match/sound/falling1.mp3'); let fallingSound2 = new Audio(base_url + '/puyo-match/sound/falling2.mp3'); let falledHeavySound = new Audio(base_url + '/puyo-match/sound/falled-heavy.mp3'); let offsetSound = new Audio(base_url + '/puyo-match/sound/offset.mp3'); let winSound = new Audio(base_url + '/puyo-match/sound/clear.mp3'); let lostSound = new Audio(base_url + '/puyo-match/sound/gameover.mp3'); // 連鎖発生時の効果音(自分) let soundCount0_1 = new Audio(base_url + '/puyo-match/sound-count/0-1.wav'); let soundCount0_2 = new Audio(base_url + '/puyo-match/sound-count/0-2.wav'); let soundCount0_3 = new Audio(base_url + '/puyo-match/sound-count/0-3.wav'); let soundCount0_4 = new Audio(base_url + '/puyo-match/sound-count/0-4.wav'); let soundCount0_5 = new Audio(base_url + '/puyo-match/sound-count/0-5.wav'); // 連鎖発生時の効果音(相手) let soundCount1_1 = new Audio(base_url + '/puyo-match/sound-count/1-1.wav'); let soundCount1_2 = new Audio(base_url + '/puyo-match/sound-count/1-2.wav'); let soundCount1_3 = new Audio(base_url + '/puyo-match/sound-count/1-3.wav'); let soundCount1_4 = new Audio(base_url + '/puyo-match/sound-count/1-4.wav'); let soundCount1_5 = new Audio(base_url + '/puyo-match/sound-count/1-5.wav'); let soundCount1_6 = new Audio(base_url + '/puyo-match/sound-count/1-6.wav'); let soundCount1_7 = new Audio(base_url + '/puyo-match/sound-count/1-7.wav'); let soundCount1_8 = new Audio(base_url + '/puyo-match/sound-count/1-8.wav'); let soundCount1_9 = new Audio(base_url + '/puyo-match/sound-count/1-9.wav'); let soundCount1_10 = new Audio(base_url + '/puyo-match/sound-count/1-10.wav'); let soundCount1_11 = new Audio(base_url + '/puyo-match/sound-count/1-11.wav'); let soundCount1_12 = new Audio(base_url + '/puyo-match/sound-count/1-12.wav'); let soundCount1_13 = new Audio(base_url + '/puyo-match/sound-count/1-13.wav'); let soundCount1_14 = new Audio(base_url + '/puyo-match/sound-count/1-14.wav'); let soundCount1_15 = new Audio(base_url + '/puyo-match/sound-count/1-15.wav'); let soundCount1_16 = new Audio(base_url + '/puyo-match/sound-count/1-16.wav'); let soundCount1_17 = new Audio(base_url + '/puyo-match/sound-count/1-17.wav'); let soundCount1_18 = new Audio(base_url + '/puyo-match/sound-count/1-18.wav'); let soundCount1_19 = new Audio(base_url + '/puyo-match/sound-count/1-19.wav'); let soundCount1_20 = new Audio(base_url + '/puyo-match/sound-count/1-20.wav'); // 各要素への文字列の設定用 let enteryButton = document.getElementById('enteryButton'); let notify = document.getElementById("notify"); let entery = document.getElementById('entry'); let gameCount = document.getElementById('game-count'); let playerCount = document.getElementById("player-count"); let conectResult = document.getElementById("conect-result"); let errer = document.getElementById("errer"); // ぷよを表示するためのイメージ let type1Images = []; for(let i = 0; i < 20; i++){ if(i < 10) type1Images.push(document.getElementById(`type1-0${i}`)); else type1Images.push(document.getElementById(`type1-${i}`)); } let type2Images = []; for(let i = 0; i < 20; i++){ if(i < 10) type2Images.push(document.getElementById(`type2-0${i}`)); else type2Images.push(document.getElementById(`type2-${i}`)); } let type3Images = []; for(let i = 0; i < 20; i++){ if(i < 10) type3Images.push(document.getElementById(`type3-0${i}`)); else type3Images.push(document.getElementById(`type3-${i}`)); } let type4Images = []; for(let i = 0; i < 20; i++){ if(i < 10) type4Images.push(document.getElementById(`type4-0${i}`)); else type4Images.push(document.getElementById(`type4-${i}`)); } let type5Images = []; for(let i = 0; i < 20; i++){ if(i < 10) type5Images.push(document.getElementById(`type5-0${i}`)); else type5Images.push(document.getElementById(`type5-${i}`)); } let wallImage = document.getElementById('wall'); |
ページが読み込まれたときの処理
ページが読み込まれたときの処理を示します。
HTMLに通信が切断されてしまったときのエラーメッセージが書かれていますが、最初は非表示にします。canvasのサイズを設定して黒で塗りつぶします。また効果音のボリューム設定や両プレイヤーのPing値を表示するためにPing送信するための初期化の処理をおこないます。
wwwroot\puyo-match\puyo-match.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 |
window.addEventListener('load', Init); function Init() { errer.style.display = 'none'; // 通信が切断されてしまったときのエラーメッセージ。最初は非表示にする can.width = CANVAS_WIDTH; can.height = CANVAS_HEIGHT; can.style.display = 'none'; ctx.fillStyle = '#000'; ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); // 効果音のボリューム設定 SetVolumes(0.02); // BGMをエンドレスで再生するための処理 InitBgm(); document.getElementById('sound-checkbox').checked = true; // Pingを打ち続けさせる connection.start().then(PingInit); } function SetVolumes(volume) { // 効果音の大きさに違いがあるので調整している let volume3 = volume * 3; let volume5 = volume * 5; if (volume3 > 1) volume3 = 1; if (volume5 > 1) volume5 = 1; matchingSucceededSound.volume = volume; bgm.volume = volume / 2; winSound.volume = volume; lostSound.volume = volume; fixedSound.volume = volume3; rotateSound.volume = volume; fallingSound1.volume = volume; fallingSound2.volume = volume; falledHeavySound.volume = volume5; offsetSound.volume = volume; } function InitBgm() { // BGMの末尾が変な終わり方をしているので適切な場所(79秒?)で最初から再生しなおす setInterval(() => { InitBgmCallBack(); }, 500); } function InitBgmCallBack() { if (IsSound()) { if (bgm.currentTime == 0) { // 最初は最初から再生する bgm.play(); } if (bgm.currentTime > 111) { // 再生開始から111秒経過したら6秒地点に戻って繰り返し再生する bgm.currentTime = 6; bgm.play(); } } else StopBgm(); } // ゲーム中で効果音を再生する設定がされているときだけ効果音とBGMを再生する function IsSound() { return isPlaying && document.getElementById('sound-checkbox').checked; } function StopBgm() { bgm.pause(); bgm.currentTime = 0; } // 3秒ごとにPingを打ち、反応が返ってくるまでの時間を計測する let intervalId = 0; function PingInit(){ intervalId = setInterval(Ping, 3 * 1000); } async function Ping() { let start = new Date(); await connection.invoke("Ping", connectionID).catch(PingErrer); let end = new Date(); let pingResult = end.getTime() - start.getTime(); await connection.invoke("PingResultToServer", connectionID, pingResult).catch(PingErrer); } function PingErrer(err) { clearInterval(intervalId); return console.error(err.toString()); } |
接続成功時の処理
AspNetCore.SignalRで接続に成功したらその旨をページ下部に表示します。また接続したときのIDとフィールドの最大行数と最大列数も送られてくるのでそれもグローバル変数に保存します。
wwwroot\puyo-match\puyo-match.js
1 2 3 4 5 6 7 8 9 10 11 |
let connectionID = ''; let colMax = 0; let rowMax = 0; connection.on("SuccessfulConnectionToClient", function (result, id, rowmax, colmax) { connectionID = id; rowMax = rowmax; colMax = colmax; document.getElementById("conect-result").innerHTML = `conect-result ${result}:${id}`; }); |
通信が切れてしまったときはエラーメッセージを表示させます。またゲーム続行不能になっているのにBGMが流れ続けるのはおかしいのでBGMも止めます。
1 2 3 4 5 6 |
connection.onclose(async () => { errer.style.display = 'block'; conectResult.innerText = "エラー:通信が切断されました"; isPlaying = false; StopBgm(); }); |
エントリー開始からゲーム終了になるまでのあいだ通知が送られてくるのでそれをページ上部に表示させます。
1 2 3 |
connection.on("NotifyToClient", function (str) { notify.innerHTML = str; }); |
サーバー側から生存確認用のデータが送られてくるので受け取ります。ここではとくに処理はおこないません。
1 2 |
connection.on("SendToClient", function () { }); |
エントリーしている人数(実質的には0または1)や現在対戦中のゲームの数が変更されたら、それを表示します。
1 2 3 4 5 6 7 |
connection.on("PlayerCountChangedToClient", function (count) { playerCount.innerHTML = count; }); connection.on("GameCountChangedToClient", function (count) { gameCount.innerHTML = count; }); |
登録完了時の処理
現在の仕様ではゲームに参加したいプレイヤーは事前にエントリーボタンをクリックしてエントリーする必要があります。ボタンをクリックされたときにおこなわれる処理を示します。
1 2 3 4 5 6 7 8 |
function Entery(){ if (connectionID != '') { let playerName = document.getElementById('player-name').value; connection.invoke("Entery", connectionID, playerName).catch(function (err) { return console.error(err.toString()); }); } } |
サーバーサイドでエントリーの処理が正常におこなわれたら、クライアントサイドには”EnteredToClient”が送信されます。これを受信したときの処理を示します。
ここではエントリーボタンを非表示にしてゲームの方法を記述した部分を非表示にしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
connection.on("EnteredToClient", function (str) { notify.innerHTML = str; entery.style.display = 'none'; let how_to = document.getElementById('how-to'); if (how_to != null) how_to.style.display = 'none'; // エントリーできたら効果音を鳴らす(ただし鳴らす設定をしている場合のみ) if (document.getElementById('sound-checkbox').checked) { rotateSound.currentTime = 0; rotateSound.play(); } }); |
マッチングが成功したら成功したらその旨を表示するとともに効果音を鳴らします。
1 2 3 4 5 6 7 8 |
connection.on("MatchingSucceededToClient", function (str) { notify.innerHTML = str; if(document.getElementById('sound-checkbox').checked){ matchingSucceededSound.currentTime = 0; matchingSucceededSound.play(); } }); |
ゲーム開始時の処理
ゲーム開始処理が正常におこなわれたときはサーバーサイドから”EventGameStartToClient”が送信されます。
マッチングが成功するとPuyoMatchGameクラスにプレイヤー情報が登録されますが、ゲーム開始時に0番で登録された場合は引数としてtrueが送られてきます。これによってどちらであっても自分自身が常に左側に表示されるようにします。
1 2 3 4 5 6 7 8 |
let isThisFirstPlayer = false; connection.on("EventGameStartToClient", function (isFirstPlayer) { can.style.display = 'block'; isPlaying = true; isThisFirstPlayer = isFirstPlayer; }); |
キー操作時の処理
キー操作がおこなわれたときはサーバーサイドに押下されたキーを送信するのですが、キーが押下されているあいだサーバーサイドに連続して送信がおこなわれないようにフラグで制御しています。
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 |
let isLeftKeyDown = false; let isRightKeyDown = false; let isDownKeyDown = false; let isZKeyDown = false; let isXKeyDown = false; let isSpaceKeyDown = false; document.onkeydown = OnKeyDown; function OnKeyDown(e) { // プレイ中でないときはデフォルトの動作のみでなにもしない if (!isPlaying) return; if (e.code == 'ArrowLeft' && !isLeftKeyDown) { isLeftKeyDown = true; SendKeyToServer(e.code); } else if (e.code == 'ArrowRight' && !isRightKeyDown) { isRightKeyDown = true; SendKeyToServer(e.code); } else if (e.code == 'ArrowDown' && !isDownKeyDown) { isDownKeyDown = true; SendKeyToServer(e.code); } else if (e.code == 'KeyZ' && !isZKeyDown) { isZKeyDown = true; SendKeyToServer(e.code); } else if (e.code == 'KeyX' && !isXKeyDown) { isXKeyDown = true; SendKeyToServer(e.code); } // プレイ中はデフォルトの動作を抑制する if (isPlaying && (e.code == 'ArrowUp' || e.code == 'ArrowDown' || e.code == 'ArrowLeft' || e.code == 'ArrowRight')) return false; } function SendKeyToServer(key){ if(!isPlaying) return; connection.invoke("SendKeyDown", key).catch(function (err) { return console.error(err.toString()); }); } document.onkeyup = OnKeyUp; function OnKeyUp(e) { if (e.code == 'ArrowLeft') { isLeftKeyDown = false; } if (e.code == 'ArrowRight') { isRightKeyDown = false; } if (e.code == 'ArrowDown') { isDownKeyDown = false; } if (e.code == 'KeyZ') { isZKeyDown = false; } if (e.code == 'KeyX') { isXKeyDown = false; } let key = e.code; if (connectionID != '') { connection.invoke("SendKeyUp", key).catch(function (err) { return console.error(err.toString()); }); } } |
更新と描画
描画処理に必要なデータを受信したときの処理を示します。
描画処理に必要なデータを受信したときはグローバル変数にデータを保存します。そして”EndUpdatedToClient”を受信したら保存されたデータを使って描画処理をおこないます。
まずはグローバル変数を示します。
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 |
// プレイヤー0の固定または落下中のぷよの位置、種類、連鎖数が格納された配列 let fixedRows0 = []; let fixedCols0 = []; let fixedTypes0 = []; let fixedRensas0 = []; let fallingRows0 = []; let fallingCols0 = []; let fallingTypes0 = []; // プレイヤー1の固定または落下中のぷよの位置、種類、連鎖数が格納された配列 let fixedRows1 = []; let fixedCols1 = []; let fixedTypes1 = []; let fixedRensas1 = []; let fallingRows1 = []; let fallingCols1 = []; let fallingTypes1 = []; let nextMainSub0 = []; let nextNextMainSub0 = []; //let cpuMainSub = []; let nextMainSub1 = []; let nextNextMainSub1 = []; // スコア let score0 = 0; let score1 = 0; // これから落下しようとしているおじゃまぷよの数 let yokoku0 = 0; let yokoku1 = 0; let playerName0 = ''; let playerName1 = ''; // 相殺されたおじゃまぷよの数 let offsetCount0 = 0; let offsetCount1 = 0; // Ping値 let ping0 = 0; let ping1 = 0; |
落下中のぷよの情報はサーバーサイドから”FallingPuyoUpdatedToClient”で送られてきます。受信したらグローバル変数に格納します。このとき引数isFirstPlayerとグローバル変数isThisFirstPlayerの状態をみてどちらを~0側に格納するかを切り分けています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
connection.on("FallingPuyoUpdatedToClient", FallingPuyoUpdatedToClientCallBack); function FallingPuyoUpdatedToClientCallBack(isFirstPlayer, rowsText, colsText, typesText) { if ((isFirstPlayer && isThisFirstPlayer) || (!isFirstPlayer && !isThisFirstPlayer)) { fallingRows0 = rowsText.split(','); fallingCols0 = colsText.split(','); fallingTypes0 = typesText.split(','); } else { fallingRows1 = rowsText.split(','); fallingCols1 = colsText.split(','); fallingTypes1 = typesText.split(','); } } |
それ以外のデータが送られてきたときも同様に処理します。
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 |
connection.on("FixedPuyoUpdatedToClient", FixedPuyoUpdatedToClientCallBack); function FixedPuyoUpdatedToClientCallBack(isFirstPlayer, rowsText, colsText, typesText, rensasText) { if ((isFirstPlayer && isThisFirstPlayer) || (!isFirstPlayer && !isThisFirstPlayer)) { fixedRows0 = rowsText.split(','); fixedCols0 = colsText.split(','); fixedTypes0 = typesText.split(','); fixedRensas0 = rensasText.split(','); } else { fixedRows1 = rowsText.split(','); fixedCols1 = colsText.split(','); fixedTypes1 = typesText.split(','); fixedRensas1 = rensasText.split(','); } } connection.on("SendNextToClient", SendNextToClientCallBack); function SendNextToClientCallBack(isFirstPlayer, nextMainSub, nextNextMainSub) { if ((isFirstPlayer && isThisFirstPlayer) || (!isFirstPlayer && !isThisFirstPlayer)) { nextMainSub0 = nextMainSub.split(','); nextNextMainSub0 = nextNextMainSub.split(','); } else { nextMainSub1 = nextMainSub.split(','); nextNextMainSub1 = nextNextMainSub.split(','); } } connection.on("ScoreUpdatedToClient", ScoreUpdatedToClientCallBack); function ScoreUpdatedToClientCallBack(isFirstPlayer, score, yokoku, playerName) { if ((isFirstPlayer && isThisFirstPlayer) || (!isFirstPlayer && !isThisFirstPlayer)) { score0 = score; yokoku0 = yokoku; playerName0 = playerName; } else { score1 = score; yokoku1 = yokoku; playerName1 = playerName; } } connection.on("PingUpdatedToClient", function (isFirstPlayer, pingValue) { if ((isFirstPlayer && isThisFirstPlayer) || (!isFirstPlayer && !isThisFirstPlayer)) ping0 = pingValue; else ping1 = pingValue; }); |
相殺が発生したときは効果音を鳴らしたあと、1秒間だけ相殺されたおじゃまぷよの数を表示させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
connection.on("OffsetToClient", OffsetToClientCallBack); function OffsetToClientCallBack(isFirstPlayer, offsetCount) { if (IsSound()) { offsetSound.currentTime = 0; offsetSound.play(); } if (offsetCount > 0) { if ((isFirstPlayer && isThisFirstPlayer) || (!isFirstPlayer && !isThisFirstPlayer)) { offsetCount0 = offsetCount; setTimeout(() => { offsetCount0 = 0; }, 1000); } else { offsetCount1 = offsetCount; setTimeout(() => { offsetCount1 = 0; }, 1000); } } } |
回転したときや組ぷよが着地したときも効果音を鳴らしますが、鳴らすのは自分のぷよのみが対象です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
connection.on("RotatedToClient", RotatedToClientCallBack); function RotatedToClientCallBack(isFirstPlayer) { if (IsSound() && (isThisFirstPlayer && isFirstPlayer || !isThisFirstPlayer && !isFirstPlayer)) { rotateSound.currentTime = 0; rotateSound.play(); } } connection.on("FixedToClient", FixedToClientCallBack); function FixedToClientCallBack(isFirstPlayer) { if (IsSound() && (isThisFirstPlayer && isFirstPlayer || !isThisFirstPlayer && !isFirstPlayer)) { fixedSound.currentTime = 0; fixedSound.play(); } } |
連鎖発生時は連鎖数に応じて効果音を変えます。
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 |
connection.on("RensaToClient", RensaToClientCallBack); function RensaToClientCallBack(isFirstPlayer, rensa) { if (IsSound()) { if (isThisFirstPlayer && isFirstPlayer || !isThisFirstPlayer && !isFirstPlayer) { PlayRensaSound1(rensa); } else { PlayRensaSound2(rensa); } } } function PlayRensaSound1(rensa){ if (IsSound()) { let sound = null; if(rensa == 1) sound = soundCount0_1; else if(rensa == 2) sound = soundCount0_2; else if(rensa == 3) sound = soundCount0_3; else if(rensa == 4) sound = soundCount0_4; else sound = soundCount0_5; if(sound != null){ sound.currentTime = 0; sound.play(); } } } function PlayRensaSound2(rensa){ if (IsSound()) { let sound = null; if(rensa == 1) sound = soundCount1_1; else if(rensa == 2) sound = soundCount1_2; else if(rensa == 3) sound = soundCount1_3; else if(rensa == 4) sound = soundCount1_4; else if(rensa == 5) sound = soundCount1_5; else if(rensa == 6) sound = soundCount1_6; else if(rensa == 7) sound = soundCount1_7; else if(rensa == 8) sound = soundCount1_8; else if(rensa == 9) sound = soundCount1_9; else if(rensa == 10) sound = soundCount1_10; else if(rensa == 11) sound = soundCount1_11; else if(rensa == 12) sound = soundCount1_12; else if(rensa == 13) sound = soundCount1_13; else if(rensa == 14) sound = soundCount1_14; else if(rensa == 15) sound = soundCount1_15; else if(rensa == 16) sound = soundCount1_16; else if(rensa == 17) sound = soundCount1_17; else if(rensa == 18) sound = soundCount1_18; else if(rensa == 19) sound = soundCount1_19; else sound = soundCount1_20; if(sound != null){ sound.currentTime = 0; sound.play(); } } } |
おじゃまぷよが落ちてくるときも効果音を鳴らします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
connection.on("OjamaFallingToClient", OjamaFallingToClientCallBack); function OjamaFallingToClientCallBack(isFirstPlayer, ojamaCount) { if (IsSound()) { if (IsSound()) { if (isThisFirstPlayer && isFirstPlayer || !isThisFirstPlayer && !isFirstPlayer) { if (ojamaCount > 6) { falledHeavySound.currentTime = 0; falledHeavySound.play(); } else { fallingSound1.currentTime = 0; fallingSound1.play(); } } else { fallingSound2.currentTime = 0; fallingSound2.play(); } } } } |
サーバーサイドから”EndUpdatedToClient”が送られてきたらUpdate関数(後述)を呼び出して描画処理をおこないます。
1 |
connection.on("EndUpdatedToClient", Update); |
ぷよの描画をするときにどのイメージを使用するのかを取得する関数を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function GetImageFromPuyo(puyoType, rensaCount){ let index = 0; if(rensaCount == undefined) index = 0; else index = rensaCount; if(puyoType == 1) return type1Images[index]; else if(puyoType == 2) return type2Images[index]; else if(puyoType == 3) return type3Images[index]; else if(puyoType == 4) return type4Images[index]; else if (puyoType == -1) return type5Images[index]; else return null; } |
あとは保存されているグローバル変数を用いて描画処理をおこなうだけです。
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 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
// Col == 0 Row == 0 にあるぷよを描画するとき、左または上からこれだけ離れた座標に描画する const marginLeftPlayer1 = 50; const marginTopPlayer1 = 20; const marginLeftPlayer2 = 400; const marginTopPlayer2 = 20; function DrawFallingPuyo(isFirstPlayer){ let marginLeft; let marginTop; let fallingTypes; let fallingRows; let fallingCols; if ((isFirstPlayer && isThisFirstPlayer) || (!isFirstPlayer && !isThisFirstPlayer)) { marginLeft = marginLeftPlayer1; marginTop = marginTopPlayer1; fallingTypes = fallingTypes0; fallingRows = fallingRows0; fallingCols = fallingCols0; } else { marginLeft = marginLeftPlayer2; marginTop = marginTopPlayer2; fallingTypes = fallingTypes1; fallingRows = fallingRows1; fallingCols = fallingCols1; } if(fallingRows.length <= 1) return; for (let i = 0; i < fallingRows.length; i++) { if(Number(fallingCols[i]) < 0) continue; let x = (Number(fallingCols[i]) + 1) * 28 + marginLeft; let y = (Number(fallingRows[i]) + 1) * 28 + marginTop; let img = GetImageFromPuyo(fallingTypes[i]); if (img != undefined) { if (Number(fallingRows[i]) > 0) ctx.drawImage(img, x, y); else if (Number(fallingRows[i]) == 0) { ctx.drawImage(img, x, y, 28, 28); ctx.fillRect(x, y - 10, 28, 28); } } } } function DrawFixedPuyo(isFirstPlayer) { let marginLeft; let marginTop; let fixedPuyoRows; let fixedPuyoCols; let fixedPuyoTypes; let fixedRensas; if ((isFirstPlayer && isThisFirstPlayer) || (!isFirstPlayer && !isThisFirstPlayer)) { marginLeft = marginLeftPlayer1; marginTop = marginTopPlayer1; fixedPuyoRows = fixedRows0; fixedPuyoCols = fixedCols0; fixedPuyoTypes = fixedTypes0; fixedRensas = fixedRensas0; } else { marginLeft = marginLeftPlayer2; marginTop = marginTopPlayer2; fixedPuyoRows = fixedRows1; fixedPuyoCols = fixedCols1; fixedPuyoTypes = fixedTypes1; fixedRensas = fixedRensas1; } if(fixedPuyoTypes.length <= 1) return; for (let i = 0; i < fixedPuyoRows.length; i++) { let img = GetImageFromPuyo(fixedPuyoTypes[i], fixedRensas[i]); let x = (Number(fixedPuyoCols[i]) + 1) * 28 + marginLeft; let y = (Number(fixedPuyoRows[i]) + 1) * 28 + marginTop; if (img != null) { if (fixedPuyoRows[i] > 0) ctx.drawImage(img, x, y); else if (fixedPuyoRows[i] == 0) { ctx.drawImage(img, x, y, 28, 28); ctx.fillRect(x, y - 10, 28, 28); } } } } function DrawWalls(isPlayer){ let marginLeft = marginLeftPlayer1; let marginTop = marginTopPlayer1; if (!isPlayer){ marginLeft = marginLeftPlayer2; marginTop = marginTopPlayer2; } for(let i = 0; i<=colMax + 1; i++){ let y = (rowMax + 1) * 28 + marginTop; let x = i * 28 + marginLeft; ctx.drawImage(wallImage, x, y, 28, 28); } for(let i = 1; i< rowMax + 1; i++){ let y = (i + 1) * 28 + marginTop; let x = marginLeft; ctx.drawImage(wallImage, x, y, 28, 28); } for(let i = 1; i< rowMax + 1; i++){ let y = (i + 1) * 28 + marginTop; let x = (colMax + 1) * 28 + marginLeft; ctx.drawImage(wallImage, x, y, 28, 28); } } function DrawNextPuyo(isFirstPlayer){ let marginLeft; let marginTop; let nextMainSub; let nextNextMainSub; if ((isFirstPlayer && isThisFirstPlayer) || (!isFirstPlayer && !isThisFirstPlayer)) { marginLeft = marginLeftPlayer1; marginTop = marginTopPlayer1; nextMainSub = nextMainSub0; nextNextMainSub = nextNextMainSub0; } else { marginLeft = marginLeftPlayer2; marginTop = marginTopPlayer2; nextMainSub = nextMainSub1; nextNextMainSub = nextNextMainSub1; } if(nextMainSub.length <= 1) return; let y = 64; let x = marginLeft + 250; let image = GetImageFromPuyo(nextMainSub[1]); ctx.drawImage(image, x, y); y += 28; image = GetImageFromPuyo(nextMainSub[0]); ctx.drawImage(image, x, y); y += 48; image = GetImageFromPuyo(nextNextMainSub[1]); ctx.drawImage(image, x, y); y += 28; image = GetImageFromPuyo(nextNextMainSub[0]); ctx.drawImage(image, x, y); } function DrawScore(isFirstPlayer) { let marginLeft; let marginTop; let score; let yokoku; let offsetCount; let playerName; let ping; if ((isFirstPlayer && isThisFirstPlayer) || (!isFirstPlayer && !isThisFirstPlayer)) { marginLeft = marginLeftPlayer1; marginTop = marginTopPlayer1; score = score0; yokoku = yokoku0; offsetCount = offsetCount0; playerName = playerName0; ping = ping0; } else { marginLeft = marginLeftPlayer2; marginTop = marginTopPlayer2; score = score1; yokoku = yokoku1; offsetCount = offsetCount1; playerName = playerName1; ping = ping1; } let text = '(' + yokoku + ')'; ctx.fillStyle = '#ff0'; ctx.font = 'bold 16px MS ゴシック'; ctx.fillText(playerName, marginLeft + 20, marginTop + 0); ctx.fillStyle = '#0ff'; ctx.font = 'bold 32px MS ゴシック'; ctx.fillText(score, marginLeft + 20, marginTop + 40); ctx.fillStyle = '#f00'; ctx.font = 'bold 20px MS ゴシック'; ctx.fillText(text, marginLeft + 160, marginTop + 30); if(offsetCount != 0) { let offsetText = '(相殺:' + offsetCount + ')'; ctx.fillStyle = '#ff0'; ctx.font = 'bold 20px MS ゴシック'; ctx.fillText(offsetText, marginLeft + 160, marginTop + 55); } ctx.fillStyle = '#fff'; ctx.font = 'bold 16px MS ゴシック'; ctx.fillText(`ping = ${ping} ms`, marginLeft + 120, marginTop + 440); ctx.fillStyle = '#000'; } |
最後に上記の関数を呼び出して描画処理をおこないます。
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 |
function Update(){ ctx.fillStyle = '#000'; ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); DrawWalls(true); DrawFixedPuyo(true); DrawFallingPuyo(true); DrawNextPuyo(true); DrawWalls(false); DrawFixedPuyo(false); DrawFallingPuyo(false); DrawNextPuyo(false); DrawScore(true); DrawScore(false); } <h3>ゲーム終了時の処理</h3> ゲームが終了したら勝敗に対応した効果音を鳴らして、非表示になっていたエントリー用のボタンを再表示させます。 <pre class="lang:default decode:true " > connection.on("LostGameToClient", function () { if (IsSound()) { lostSound.currentTime = 0; lostSound.play(); } isPlaying = false; StopBgm(); entery.style.display = 'block'; enteryButton.value = 'もう一度エントリーする'; }); connection.on("WonGameToClient", function () { if (IsSound()) { winSound.currentTime = 0; winSound.play(); } isPlaying = false; StopBgm(); entery.style.display = 'block'; enteryButton.value = 'もう一度エントリーする'; }); |