⇒ 動作確認はこちらからどうぞ。
これまで作成したゲームはデータが表のような形でしか描かれていません。これでは面白くないので地図を表示します。それから城のある位置に家紋を表示させます。これでどこがその城をおさえているのかがわかりやすくなります。
地図はこれを使います。
家紋はこれを使います。
Blazor WebAssemblyで描画処理をするのでBlazor.Extensions.Canvasを使います。NuGetでインストールしておきます。
index.htmlに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 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <title>信長の野望もどき</title> <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" /> <link href="css/app.css" rel="stylesheet" /> <link href="_WebApplicationNobunaga.styles.css" rel="stylesheet" /> </head> <body> <div id="app">Loading...</div> <div id="blazor-error-ui"> An unhandled error has occurred. <a href="" class="reload">Reload</a> <a class="dismiss">??</a> </div> <script src="_framework/blazor.webassembly.js"></script> <!-- 以下の1行を追加する あとはそのままでOK --> <script src="_content/Blazor.Extensions.Canvas/blazor.extensions.canvas.js"></script> </body> </html> |
それからIndex.razorは以下のようにします。
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 |
@page "/" @using Blazor.Extensions.Canvas @inject IJSRuntime JSRuntime @inject HttpClient HttpClient <img @ref="imageMap" hidden id="map" src="./images/map.png" /> <img @ref="imageDate" hidden id="i1" src="./images/date.png" /> <img @ref="imageUesugi" hidden id="i2" src="./images/uesugi.png" /> <img @ref="imageTakeda" hidden id="i3" src="./images/takeda.png" /> <img @ref="imageHojo" hidden id="i4" src="./images/hojo.png" /> <img @ref="imageTokugawa" hidden id="i5" src="./images/tokugawa.png" /> <img @ref="imageOda" hidden id="i6" src="./images/oda.png" /> <img @ref="imageAshikaga" hidden id="i7" src="./images/ashikaga.png" /> <img @ref="imageMori" hidden id="i8" src="./images/mori.png" /> <img @ref="imageChosokabe" hidden id="i9" src="./images/chosokabe.png" /> <img @ref="imageSimazu" hidden id="i10" src="./images/simazu.png" /> <button class="btn btn-primary" @onclick="btnGameStartClick" style="display:@btnStartDisplay">ゲーム開始</button> <p>@yearAtring</p> <p>@turn</p> <p>@message</p> <button class="btn btn-primary" @onclick="btnNextClick" style="display:@btnNextDisplay">次</button> <button class="btn btn-primary" @onclick="btnMarchClick" style="display:@btnMarchDisplay">進軍</button> <button class="btn btn-primary" @onclick="btnConscriptionClick" style="display:@btnConscriptionDisplay">徴兵</button> <button class="btn btn-primary" @onclick="btnTaxClick" style="display:@btnTaxDisplay">徴税</button> <button class="btn btn-primary" @onclick="btnDevelopmentClick" style="display:@btnDevelopmentDisplay">開発</button> <button class="btn btn-primary" @onclick="btnRushClick" style="display:@btnRushDisplay">突入</button> <button class="btn btn-primary" @onclick="btnCounterattackClick" style="display:@btnCounterattackDisplay">反撃</button> <button class="btn btn-primary" @onclick="btnWaitClick" style="display:@btnWaitDisplay">静観</button> <select name="area" @bind="@SelectedValue1"> @foreach (string str in StringChoices1) { <option value="@str">@str</option> } </select> <select name="area" @bind="@SelectedValue2"> @foreach (string str in StringChoices2) { <option value="@str">@str</option> } </select> <br> <BECanvas Width="800" Height="600" @ref="_canvasReference"></BECanvas> @if (CurentCastleStatusList == null) { <p>開始ボタンをクリックしてください</p> } |
Index.csの追加部分ですが、
フィールド変数部分
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 |
using Blazor.Extensions; using Blazor.Extensions.Canvas; using Blazor.Extensions.Canvas.Canvas2D; using System.Timers; using Microsoft.AspNetCore.Components; public partial class Index { private Canvas2DContext _context; protected BECanvasComponent _canvasReference; // ElementReferenceはMicrosoft.AspNetCore.Components名前空間 ElementReference imageMap; ElementReference imageDate; ElementReference imageUesugi; ElementReference imageTakeda; ElementReference imageHojo; ElementReference imageTokugawa; ElementReference imageOda; ElementReference imageAshikaga; ElementReference imageMori; ElementReference imageChosokabe; ElementReference imageSimazu; Timer Timer = new Timer(); } |
OnAfterRenderAsyncメソッドをオーバーライドしてこの中でタイマーを仕掛けて自作メソッド Updateを呼びます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public partial class Index { protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { this._context = await _canvasReference.CreateCanvas2DAsync(); Timer.Interval = 500; Timer.Elapsed += Timer_Tick; Timer.Start(); } await Update(); } } |
Updateメソッドの内容ですが、地図の城があるあたりの座標に城の名前と城主の家紋を描画し、地図の横に城の状態をしめす文字列を描画しているだけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class Index { async Task Update() { // 背景は白 await _context.SetFillStyleAsync(System.Drawing.Color.White.Name); await _context.FillRectAsync(0, 0, _canvasReference.Width, _canvasReference.Height); await _context.DrawImageAsync(imageMap, 0, 0); // 地図の城があるあたりの座標に城の名前と城主の家紋を描画する await DrawCastleImage(); // 地図の横に城の状態をしめす文字列を描画する await DrawCastleStatus(); } } |
地図の城があるあたりの座標に城の名前と城主の家紋を描画するDrawCastleImageメソッドを示します。城のイメージは城主が誰かしらべてその家紋を描画しています。GetImageFromCastleNameメソッドは城の名前から家紋のイメージを取得する自作メソッドです(後述)。
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 |
public partial class Index { async Task DrawCastleImage() { // 地図の城があるあたりの座標に城の名前と城主の家紋を描画する await _context.SetFillStyleAsync(System.Drawing.Color.Blue.Name); await _context.SetFontAsync("14px 'MS ゴシック'"); await _context.FillTextAsync("米沢城", 400, 80); await _context.DrawImageAsync(GetImageFromCastleName("米沢城"), 400, 85, 30, 30); await _context.FillTextAsync("春日山城", 350, 130); await _context.DrawImageAsync(GetImageFromCastleName("春日山城"), 350, 135, 30, 30); await _context.FillTextAsync("躑躅ヶ崎館", 330, 190); await _context.DrawImageAsync(GetImageFromCastleName("躑躅ヶ崎館"), 330, 195, 30, 30); await _context.FillTextAsync("小田原城", 370, 230); await _context.DrawImageAsync(GetImageFromCastleName("小田原城"), 370, 235, 30, 30); await _context.FillTextAsync("岡崎城", 294, 245); await _context.DrawImageAsync(GetImageFromCastleName("岡崎城"), 294, 250, 30, 30); await _context.FillTextAsync("岐阜城", 270, 190); await _context.DrawImageAsync(GetImageFromCastleName("岐阜城"), 280, 195, 30, 30); await _context.FillTextAsync("二条城", 230, 240); await _context.DrawImageAsync(GetImageFromCastleName("二条城"), 235, 245, 30, 30); await _context.FillTextAsync("吉田郡山城", 130, 240); await _context.DrawImageAsync(GetImageFromCastleName("吉田郡山城"), 140, 245, 30, 30); await _context.FillTextAsync("岡豊城", 162, 300); await _context.DrawImageAsync(GetImageFromCastleName("岡豊城"), 162, 305, 30, 30); await _context.FillTextAsync("内城", 59, 360); await _context.DrawImageAsync(GetImageFromCastleName("内城"), 59, 365, 30, 30); } } |
地図の横に城の状態をしめす文字列を描画するDrawCastleStatusメソッドを示します。
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 |
public partial class Index { async Task DrawCastleStatus() { // 地図の横に城の状態をしめす文字列を描画する await _context.SetFontAsync("16px 'MS ゴシック'"); await _context.SetFillStyleAsync(System.Drawing.Color.Black.Name); if (CurentCastleStatusList != null) { List<CastleStatus> statuses = CurentCastleStatusList.OrderBy(x => x.CastleID).ToList(); int i = 0; foreach (CastleStatus status in statuses) { if (status.CastleOwner == PlayerName) await _context.SetFontAsync("bold 16px 'MS ゴシック'"); await _context.FillTextAsync(status.CastleName, 500, 20 + 50 * i); await _context.SetFontAsync("bold 16px 'MS ゴシック'"); await _context.FillTextAsync(status.CastleOwner, 600, 20 + 50 * i); await _context.SetFontAsync("16px 'MS ゴシック'"); await _context.SetFillStyleAsync(System.Drawing.Color.Red.Name); if (status.AttackingCorps != null) await _context.FillTextAsync("攻込:" + status.AttackingCorps.AttackBaseOwner, 700, 20 + 50 * i); if (status.SiegingCorps != null) await _context.FillTextAsync("包囲:" + status.SiegingCorps.AttackBaseOwner, 700, 20 + 50 * i); await _context.SetFillStyleAsync(System.Drawing.Color.Black.Name); string str = String.Format("兵力:{0} 兵糧:{1} 収穫量:{2}", status.SoldierCount, status.MilitaryFood, status.Yield); await _context.FillTextAsync(str, 500, 40 + 50 * i); i++; } } } } |
城の名前から家紋のイメージを取得するGetImageFromCastleNameメソッドを示します。
ゲームが開始されるまえはCurentCastleStatusList == nullなので元の城主の家紋を返します。ゲームが進行しているときはCurentCastleStatusList.First(x => x.CastleName == name)とすることでその城のCastleStatusオブジェクトを取得してCastleOwnerプロパティから城主を調べ、これに対応するElementReferenceを返します。
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 |
public partial class Index { ElementReference GetImageFromCastleName(string name) { if (CurentCastleStatusList == null) { if (name == "米沢城") return imageDate; if (name == "春日山城") return imageUesugi; if (name == "躑躅ヶ崎館") return imageTakeda; if (name == "小田原城") return imageHojo; if (name == "岡崎城") return imageTokugawa; if (name == "岐阜城") return imageOda; if (name == "二条城") return imageAshikaga; if (name == "吉田郡山城") return imageMori; if (name == "岡豊城") return imageChosokabe; if (name == "内城") return imageSimazu; return imageSimazu; } CastleStatus status = CurentCastleStatusList.First(x => x.CastleName == name); if (status.CastleOwner == "伊達") return imageDate; if (status.CastleOwner == "上杉") return imageUesugi; if (status.CastleOwner == "武田") return imageTakeda; if (status.CastleOwner == "北条") return imageHojo; if (status.CastleOwner == "徳川") return imageTokugawa; if (status.CastleOwner == "織田") return imageOda; if (status.CastleOwner == "足利") return imageAshikaga; if (status.CastleOwner == "毛利") return imageMori; if (status.CastleOwner == "長宗我部") return imageChosokabe; if (status.CastleOwner == "島津") return imageSimazu; return imageSimazu; } } |
これだと最初に読み込まれたときになにも表示されないので(ページが読み込まれた直後ではフィールド変数 ElementReference imageMapなどに適切なデータが入っていないからか?)、しばらく待ってからもう一度Updateメソッドを呼び出しています。このあたりインチキ臭い方法ではなくちゃんと方法があるはずなのですが・・・。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public partial class Index { protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { this._context = await _canvasReference.CreateCanvas2DAsync(); Timer.Interval = 500; Timer.Elapsed += Timer_Tick; Timer.Start(); } await Update(); } private async void Timer_Tick(object sender, EventArgs e) { Timer.Stop(); await Update(); } } |