ARC-8: devlog #2 - Blazor
Some years ago I coded a CHIP-8 emulator in C# just for fun, that emulator was hibernating in a private repository that I never released. Some days ago I started to working on it again with the idea to release it running on Blazor and as a Unity asset where any game developer could drag its prefabs as easter eggs directly to their games.
In this post, I will talk about how I implemented the graphics, sound, input, and log systems for Blazor.
You can read the other ARC-8’s devlog posts.
The source code is not yet published on GitHub, I will notify in this series of posts about ARC-8 devlog and on my Twitter too when this happens.
Introduction
Blazor is a feature of ASP.NET that extends the .NET developer platform with tools and libraries for building web apps.
Blazor can run your client-side C# code directly in the browser, using WebAssembly. Because it’s real .NET running on WebAssembly, you can re-use code and libraries from server-side parts of your application.
This is why we can use our ARC-8 Core
, mentioned in the previous devlog, because it is a .NET Standard class library
and can run directly on Blazor web assembly.
For some components, like menu, inputs, and buttons I use the Blazorise library.
Online demo
You can test and play the CHIP-8’s games directly on your browser with our online demo: ARC-8 Blazor Online Demo.
Systems interfaces implementations
The system interfaces IGraphic
, ISound
, IInput
, and ILog
will be implemented as Blazor components.
A component is a self-contained chunk of user interface (UI), such as a page, dialog, or form. A component includes HTML markup and the processing logic required to inject data or respond to UI events. Components are flexible and lightweight. They can be nested, reused, and shared among projects.
Chip8Graphic.razor component (IGraphic)
This a simplified version of IGraphic
implementation (without of color selector that you can see in the demo).
C# side
JS side
OnAfterRenderAsync method
In the method OnAfterRenderAsync
we verify if it’s the component’s first render, then we call a JS method that will initialize a JS helper for Chip8Graphic.razor
that will return the size of the canvas to C# code, then we use this information to scale our 64 x 32 CHIP-8’s display.
OnAfterRenderAsync and OnAfterRender are called after a component has finished rendering. Element and component references are populated at this point. Use this stage to perform additional initialization steps using the rendered content, such as activating third-party JavaScript libraries that operate on the rendered DOM elements.
Draw method
This is one of the two methods needed to be implemented of IGraphic
interface. We received the array (64x32) of bytes representing the current state of CHIP-8 graphics and just update our local array variable _gfx
.
RenderAsync method
The method RenderAsync
will be called by the Chip8Loader
component during the Game Loop.
This method is used to draw the state of CHIP-8 graphics (_gfx array) to the HTML page.
We use a second array called _buffer
to implement a Double Buffer and reduce the screen flickering
.
A byte with value 1 should be drawn (foreground color) and a byte with value 0 should not be drawn (background color).
Invalidate method
This is the second of the two methods needed to be implemented of IGraphic
interface, but as we implemented a Double Buffer, this method does not need to perform any operation.
Resize method
Called by the JS side every time that the user resizes the browser window.
ClearCanvasAsync method
We use this one to invalidate our _buffer
and reset the canvas to the background color.
Chip8Sound.razor component (ISound)
This a simplified version of ISound
implementation.
C# side
JS side
OnInitialized method
We just use the information from NavigationManager
to set the audio file we want to play.
Play method
This is the only method we need to implement of ISound
interface and it just calls a JS method that will get the audio
tag on the component, set the AudioSource
, then load, and play it.
Chip8Input.razor component (IInput)
This is the IInput
implementation.
C# side
JS side
Key mapping
First we create the dictionary _map
to map the real keyboard keys to CHIP-8 keypad keys.
The second dictionary we create is _keyDown
. It will be used to map what keys the player is pressing.
OnAfterRenderAsync method
We call the JS method chip8Input.init
that will add two event listeners, one for keydown
and the other for keyup
that will call the C# methods HandleKeyDown
and HandleKeyUp
.
UpdateKeys method
The only method we need to implement for IInput
interface.
In this method, we set to 1
the CHIP-8’s keypad keys that were pressed by the player.
HandleKeyDown and HandleKeyUp methods
This method is responsible to set the _keyDown
dictionary by the keyboard keys that the player pressed.
Chip8Log.razor component (ILog)
This is a simplified version of the ILog
implementation.
C# side
Debug and Error methods
The two methods implemented for ILog
interface use the Microsoft.Extensions.Logging.ILogger<T>
to send log messages to the browser console.
Chip8Loader.razor
This is a simplified version of the component responsible to load all systems (IGraphic, ISound, IInput, and ILog), initialize the Chip8
class emulator, load the ROM and perform the game loop.
C# side
JS side
OnAfterRenderAsync method
Initializes the emulator with the systems, then calls the JS chip8Loader.init
function that will use the browser window.requestAnimationFrame to call the C# method RunCycle
.
The window.requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser calls a specified function to update an animation before the next repaint.
RunCycle method
This method is called by the JS (window.requestAnimationFrame
).
We implement a Game Loop and in the end, call the Chip8Graphic.RenderAsync
.
Next step
In the next ARC-8 devlog I will talk about the ARC-8’s implementation on Unity3D.
If you have any doubts about what I talk about above or any tip about the CHIP-8 emulator (or Blazor) and you like to share it, please let me know in the comments section below.
Further reading
- Blazor Tutorial - Build your first Blazor app
- Blazorise quick-start
- Create and use ASP.NET Core Razor components
- ASP.NET Core Blazor lifecycle
- Call JavaScript functions from .NET methods in ASP.NET Core Blazor
- ASP.NET Core Blazor logging
- Double Buffer
- Game Loop
- RequestAnimationFrame
Icons made by Freepik, Vignesh Oviyan and Eucalyp from www.flaticon.com is licensed by Creative Commons BY 3.0