Blazor WebAssemblyでテトリスを作ってみようと考えました。「Blazor WebAssembly テトリス」で検索してもそれっぽい情報は出てきません。そもそもゲームをつくるのであればもっと適切な方法を使うべきかもしれません。いまからやることは邪道なのかも。できあがったものも動作が遅くちょっと残念な結果になってしまいました。
ゲームをつくるのであれば描画処理とキーがおされたときの処理が必要です。Blazor WebAssemblyで描画処理をするにはどうすればいいのでしょうか? 調べてみたところ、Blazor.Extensions.Canvasがみつかりました。また落下してくるテトリミノを操作するためにはキーが押されたときのイベントを処理しなければなりません。これに対してはToolbelt.Blazor.HotKeysを使います。
まずBlazor.Extensions.Canvasの使い方から。パッケージマネージャーでBlazor.Extensions.Canvasをインストールしましょう。
そしてwwwroot.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  | 
						<!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>BlazorAppXXX</title>     <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />     <link href="css/app.css" rel="stylesheet" /> </head> <body>     <app>Loading...</app>     <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 >  | 
					
そしてこのようにすれば矩形を描画することができます。
| 
					 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  | 
						@page "/" <BECanvas Width="500" Height="500" @ref="_canvasReference"></BECanvas> @code {     @using Blazor.Extensions     @using Blazor.Extensions.Canvas     @using Blazor.Extensions.Canvas.Canvas2D     private Canvas2DContext _context;     protected BECanvasComponent _canvasReference;     protected override async Task OnAfterRenderAsync(bool firstRender)     {         if (firstRender)         {             this._context = await _canvasReference.CreateCanvas2DAsync(); 			// 背景を黒に             await _context.SetFillStyleAsync(System.Drawing.Color.Black.Name);             await _context.FillRectAsync(0, 0, _canvasReference.Width, _canvasReference.Height); 			// 赤で左上の座標は(0,0)、幅、高さ100の矩形を描画             await _context.SetFillStyleAsync(System.Drawing.Color.Red.Name);             await _context.FillRectAsync(0, 0, 100, 100);         }     } }  | 
					
タイマーを使えば移動もできそうです。
| 
					 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  | 
						@code { 	// タイマーを追加     @using System.Timers;     Timer Timer = new Timer();     protected override async Task OnAfterRenderAsync(bool firstRender)     {         if (firstRender)         {             this._context = await _canvasReference.CreateCanvas2DAsync(); 			// 1000ミリ秒(1秒ごとにOnElapsedを呼び出す)             Timer.Elapsed += OnElapsed;             Timer.Interval = 1000;             Timer.Start();         }     } 	// X座標を10ずつ増やしながら矩形を移動させる     int x = 0;     async void OnElapsed(object sender, EventArgs args)     {         await _context.SetFillStyleAsync(System.Drawing.Color.Black.Name);         await _context.FillRectAsync(0, 0, _canvasReference.Width, _canvasReference.Height);         await _context.SetFillStyleAsync(System.Drawing.Color.Red.Name);         await _context.FillRectAsync(x, 0, 100, 100);         x += 10;     } }  | 
					
ところがこれでは他のページに移動するときにエラーが発生します。
さらに以下を追加します。
| 
					 1 2 3 4 5 6 7 8  | 
						@code {     @implements IDisposable     public void Dispose()     {         Timer.Dispose();     } }  | 
					
ではキー操作に関する処理はどうすればいいのでしょうか?
まずはパッケージマネージャーでToolbelt.Blazor.HotKeysをインストールしましょう。
そしてProgram.csに以下の2行をを追加します。ほかはそのままで問題ありません。
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  | 
						using Toolbelt.Blazor.Extensions.DependencyInjection; // 追加 namespace BlazorAppXXX {     public class Program     {         public static async Task Main(string[] args)         {             var builder = WebAssemblyHostBuilder.CreateDefault(args);             builder.RootComponents.Add<App>("app");             builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });             builder.Services.AddHotKeys(); // 追加             await builder.Build().RunAsync();         }     } }  | 
					
OnInitialized()メソッドのなかでHotKeysContext の作成と、キーの打鍵で呼び出すコールバックの登録をおこないます。左右のカーソルキーをおすと表示されている数字が増減します。おわったらDispose()メソッドでHotKeysContext オブジェクトも破棄しましょう。
| 
					 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  | 
						@page "/" <p>@value</p> @code {     @using Toolbelt.Blazor.HotKeys;     @inject HotKeys HotKeys     @implements IDisposable     private HotKeysContext HotKeysContext;     int value = 0;     protected override void OnInitialized()     {         //  以下、HotKeysContext の作成と、         //  左右のカーソルキーの打鍵で呼び出すコールバックの登録         this.HotKeysContext = this.HotKeys.CreateContext();         this.HotKeysContext.Add(ModKeys.None, Keys.Left, () =>         { 			// これで左キーをおしたらOLeftLey()が実行される             OLeftLey();         });         this.HotKeysContext.Add(ModKeys.None, Keys.Right, () =>         { 			// こんな書き方もOK             value++;             this.StateHasChanged();         });     }     void OLeftLey()     {         value--;         this.StateHasChanged();     }     public void Dispose()     {         // HotKeysContext オブジェクトも破棄する         this.HotKeysContext.Dispose();     } }  | 
					
次に音を鳴らす方法を考えます。
うまい方法があるのかもしれませんが、あまり情報がありません。調べてみた結果、このような方法が見つかりました。
効果音の再生はJavaScriptにやらせて、JavaScriptの関数をJS.InvokeAsyncで呼び出そうというわけです。
wwwroot/index.html
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21  | 
						<!DOCTYPE html> <html> <head> 	<!-- 省略 --> </head> <body> 	<!-- 再生したいmp3を書く -->     <audio id="foo" src="./audio/foo.mp3" />     <audio id="bar" src="./audio/bar.mp3" /> 	<!-- razorに書くことができないのでここに書く -->     <script>         function mysound(str) {             document.getElementById(str).play();         }     </script> </body> </html>  | 
					
XXX.razor
| 
					 1 2 3 4 5 6 7 8 9  | 
						@code {     @inject IJSRuntime JS;     async void PlayFoo()     {         await JS.InvokeAsync<string>("mysound", "foo");     } }  | 
					
この方法ではmp3ファイルの再生中に新しい再生がはじまっても対応できません。そこで
wwwroot/index.html
| 
					 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> 	<!-- 省略 --> </head> <body> 	<!-- 再生したいmp3を書く --> 	<audio id="foo0" src="./audio/foo.mp3" /> 	<audio id="foo1" src="./audio/foo.mp3" /> 	<audio id="foo2" src="./audio/foo.mp3" /> 	<audio id="foo3" src="./audio/foo.mp3" /> 	<audio id="foo4" src="./audio/foo.mp3" /> 	<audio id="foo5" src="./audio/foo.mp3" /> 	<audio id="foo6" src="./audio/foo.mp3" /> 	<audio id="foo7" src="./audio/foo.mp3" /> 	<!-- razorに書くことができないのでここに書く -->     <script>         function mysound(str) {             document.getElementById(str).play();         }     </script> </body> </html>  | 
					
XXX.razor
| 
					 1 2 3 4 5 6 7 8 9 10 11 12  | 
						@code {     @inject IJSRuntime JS;     int count = 0;     async void PlayFoo()     {         count++;         string para = "foo" + (count % 8).ToString();         await JS.InvokeAsync<string>("mysound", para);     } }  | 
					
ただこの方法でも限界があり、再生が終わる前に新たに再生できるのは5回くらいまでです。
また任意の位置に画像を描画したいときはこのようにすればできます。
| 
					 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  | 
						@page "/" <BECanvas Width="500" Height="530" @ref="_canvasReference"></BECanvas> <img @ref="myimage" hidden id="i" src="./images/myimage.png" /> @code {     ElementReference myimage;     @using Blazor.Extensions     @using Blazor.Extensions.Canvas     @using Blazor.Extensions.Canvas.Canvas2D     private Canvas2DContext _context;     protected BECanvasComponent _canvasReference;     protected override async Task OnAfterRenderAsync(bool firstRender)     {         if (firstRender)         {             this._context = await _canvasReference.CreateCanvas2DAsync();         }     }     async void DrawImage()     { 		// (20,10)の位置に描画         await _context.DrawImageAsync(myimage, 20, 10);     } }  | 
					
さてこれらを使えばBlazor WebAssemblyでTETRISをつくることも可能かもしれません。次回は実際につくってみることにします。
