ASP.NET Core版 ボンバーマンのような対戦型ゲームをつくる(5)の続きですが、ここからは延長戦です。ゲームの進行に伴って壁が爆破されてなくなっていきますが、そのときに新しく壁を生成して追加する処理をおこないます。
壁が追加される条件
破壊できない壁が存在しない列で縦または横の壁がすべて無くなった場合、壁を追加します。ただしそれによってプレイヤーが動けなくなっては困るので以下の条件をつけます。
その列の前後にもプレイヤーは存在しない
その列には爆弾は設置されていない
以下は縦列で破壊できない壁が存在しない列で、その列だけでなくその列の前後にもプレイヤーは存在しない、しかもその列には爆弾は設置されていないという条件で追加すべき壁を第一引数のリストに格納するメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class BomberHub : Hub { static void GetColWallsToRestored(List<Wall> walls, int col) { if ( !Game.Walls.Any(wall => wall.Column == col) && !Game.Bombs.Any(bomb => bomb.Column == col) && !NPCs.Any(npc => col - 1 <= npc.CurrentColumn && npc.CurrentColumn <= col + 1) && !NPCs.Any(npc => col - 1 <= npc.NextColumn && npc.NextColumn <= col + 1) && !Players.Any(player => col - 1 <= player.Value.CurrentColumn && player.Value.CurrentColumn <= col + 1) && !Players.Any(player => col - 1 <= player.Value.NextColumn && player.Value.NextColumn <= col + 1) ) { for (int i = 1; i <= 13; i++) { if(!walls.Any(wall => wall.Column == col && wall.Row == i)) walls.Add(new Wall(col, i)); } } } } |
横列(というか行)も同様に追加すべき壁を第一引数のリストに格納するメソッドを定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class BomberHub : Hub { static void GetRowWallsToRestored(List<Wall> walls, int row) { if ( !Game.Walls.Any(wall => wall.Row == row) && !Game.Bombs.Any(bomb => bomb.Row == row) && !NPCs.Any(npc => row - 1 <= npc.CurrentRow && npc.CurrentRow <= row + 1) && !NPCs.Any(npc => row - 1 <= npc.NextRow && npc.NextRow <= row + 1) && !Players.Any(player => row - 1 <= player.Value.CurrentRow && player.Value.CurrentRow <= row + 1) && !Players.Any(player => row - 1 <= player.Value.NextRow && player.Value.NextRow <= row + 1) ) { for (int i = 1; i <= 13; i++) { if (!walls.Any(wall => wall.Column == i && wall.Row == row)) walls.Add(new Wall(i, row)); } } } } |
壁を追加する処理
Timer.Elapsedイベントが発生したら壁を追加する必要があるかを判断して、必要である場合は壁を追加します。
ただいきなり壁が出現するとユーザーがびっくりするので壁が出現する場合はその場所に壁を点滅させます。追加する壁をフィールド変数 WallsToAddedに格納してこの座標をクライアントサイドに送信します。その3秒後に壁をGame.Wallsに追加してSendWallsメソッドを実行し、クライアントサイドでも実際に存在する壁として描画させます。
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 |
public class BomberHub : Hub { static List<Wall> WallsToAdded = new List<Wall>(); static private void Timer_Elapsed(object? sender, ElapsedEventArgs e) { Task.Run(async () => { // 既存のコード } // 壁を追加する 送信する List<Wall> walls = new List<Wall>(); GetColWallsToRestored(walls, 3); GetColWallsToRestored(walls, 5); GetColWallsToRestored(walls, 9); GetColWallsToRestored(walls, 11); GetRowWallsToRestored(walls, 3); GetRowWallsToRestored(walls, 5); GetRowWallsToRestored(walls, 9); GetRowWallsToRestored(walls, 11); if (walls.Count > 0) { WallsToAdded.AddRange(walls); System.Timers.Timer timer = new System.Timers.Timer(); timer.Interval = 3000; timer.Elapsed += Timer_Elapsed1; timer.Start(); Task.Run(async () => { // 追加されようとしている壁をクライアントサイドに送信する string xs = String.Join(",", WallsToAdded.Select(wall => wall.X.ToString()).ToArray()); string ys = String.Join(",", WallsToAdded.Select(wall => wall.Y.ToString()).ToArray()); foreach (string key in ClientProxyMap.Keys) { if (ClientProxyMap.ContainsKey(key)) await ClientProxyMap[key].SendAsync("ReceiveWallsToAdded", xs, ys); } }); } static void Timer_Elapsed1(object? sender2, ElapsedEventArgs e2) { if (sender2 == null) return; System.Timers.Timer t = (System.Timers.Timer)sender2; t.Stop(); t.Dispose(); // 壁が出現しようとする座標の周囲にプレイヤーが存在する場合、壁は生成しない foreach (Player npc in NPCs) { WallsToAdded = WallsToAdded.Where(wall => !(npc.CurrentColumn - 1 <= wall.Column && wall.Column <= npc.CurrentColumn + 1 && npc.NextColumn - 1 <= wall.Column && wall.Column <= npc.NextColumn + 1 && npc.CurrentRow - 1 <= wall.Row && wall.Row <= npc.CurrentRow + 1 && npc.NextRow - 1 <= wall.Row && wall.Row <= npc.NextRow + 1) ).ToList(); } foreach (Player player in Players.Values) { WallsToAdded = WallsToAdded.Where(wall => !(player.CurrentColumn - 1 <= wall.Column && wall.Column <= player.CurrentColumn + 1 && player.NextColumn - 1 <= wall.Column && wall.Column <= player.NextColumn + 1 && player.CurrentRow - 1 <= wall.Row && wall.Row <= player.CurrentRow + 1 && player.NextRow - 1 <= wall.Row && wall.Row <= player.NextRow + 1) ).ToList(); } // 壁が出現しようとする座標に爆弾が設置されている場合、壁は生成しない foreach (Bomb bomb in Game.Bombs) { WallsToAdded = WallsToAdded.Where(wall => !(wall.Column == bomb.Column && wall.Row == bomb.Row) ).ToList(); } // 壁を追加する処理 foreach (Wall wall in WallsToAdded) { if (!Game.Walls.Any(_wall => _wall.Column == wall.Column && _wall.Row == wall.Row)) Game.Walls.Add(wall); } WallsToAdded.Clear(); // クライアントサイドに存在する壁を通知 Task.Run(async () => await SendWalls()); } } } |
クライアントサイドにおける処理
クライアントサイドにおける処理を示します。
まず出現しようとする壁のX座標とY座標を格納する配列を定義します。そしてReceiveWallsToAddedを受信したらこの配列に座標を格納すると同時に壁が出現するさいの効果音を鳴らします。
wallsToAddedXとwallsToAddedY内に要素が存在する場合はその壁を点滅させます。DrawWalls関数のなかでupdateCountの値をつかって描画したりしないことで点滅しているように見せます。
ReceiveWallsを受信したら壁が仮状態ではなく確定したことになるので配列はクリアします。
wwwroot\bomber\app.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 |
let wallsToAddedX = []; let wallsToAddedY = []; connection.on("ReceiveWallsToAdded", function (xs, ys) { if (xs != '') { wallsToAddedX = xs.split(','); wallsToAddedY = ys.split(','); if (isPlaying && document.getElementById('sound-checkbox').checked) { wallSound.currentTime = 0; wallSound.play(); } } }); function DrawWalls() { for (let i = 0; i < indestructibleWallXs.length; i++) ctx.drawImage(imgWall2, indestructibleWallXs[i], indestructibleWallYs[i], 32, 32); for (let i = 0; i < wallXs.length; i++) ctx.drawImage(imgWall1, wallXs[i], wallYs[i], 32, 32); if (updateCount % 4 < 2) { for (let i = 0; i < wallsToAddedX.length; i++) ctx.drawImage(imgWall1, wallsToAddedX[i], wallsToAddedY[i], 32, 32); } } connection.on("ReceiveWalls", function (xs, ys) { wallXs = xs.split(','); wallYs = ys.split(','); wallsToAddedX = []; wallsToAddedY = []; }); |