ARC-8: devlog #3 - Unity
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 Unity 3D.
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
If you were out of the planet in the last decade, maybe you don’t know what is Unity: Unity is a cross-platform game engine developed in C++, but the games made on it are developed using .NET and C#.
Unity is a cross-platform game engine developed by Unity Technologies, first announced and released in June 2005 at Apple Inc.’s Worldwide Developers Conference as a Mac OS X-exclusive game engine. As of 2018, the engine had been extended to support more than 25 platforms. The engine can be used to create three-dimensional, two-dimensional, virtual reality, and augmented reality games, as well as simulations and other experiences. The engine has been adopted by industries outside video gaming, such as film, automotive, architecture, engineering and construction.
In older versions, Unity only supported a subset of .NET Framework (4.x), but nowadays it’s supporting .NET Standard 2.0.
This is why we can use our ARC-8 Core
, mentioned in the first devlog, because it is a .NET Standard class library
and can run directly on Unity.
The 3D models of the arcade cabinet and arcade room were created by my talented friend Giusepe Casagrande.
Systems interfaces implementations
The system interfaces IGraphic
, ISound
, IInput
, and ILog
will be implemented as MonoBehaviour.
The MonoBehaviour class is the base class from which every Unity script derives, by default. TPovides the framework which allows you to attach your script to a GameObject in the editor, as well as providing hooks into useful Events such as Start and Update.
Chip8Graphic.cs MonoBehaviour (IGraphic)
This is the IGraphic
implementation.
Intialize method
In the method Initialize
we verify if will render to the main camera or to a specific target camera, then we set some camera configuration, create the material we will use to render the CHIP-8 graphic, get the screen size, then we initialize our Double Buffer array.
In most cases, we don’t use the main camera, but instead, use a target camera that uses a RenderTexture, and then we can use that texture on any surface on our game, like a TV screen or an arcade cabinet.
Render Textures are special types of Textures that are created and updated at run time. To use them, you first create a new Render Texture and designate one of your Cameras to render into it. Then you can use the Render Texture in a Material just like a regular Texture.
Draw method
This is one of the two methods that 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
.
OnRenderObject
The method OnRenderObject is called after the camera has rendered the scene.
OnRenderObject can be used to render your own objects using Graphics.DrawMeshNow or other functions. This function is similar to OnPostRender, except OnRenderObject is called on any object that has a script with the function; no matter if it’s attached to a Camera or not.
Render method
The method Render
will be called by the OnRenderObject
method.
This method is used to draw the state of CHIP-8 graphics (_gfx
array) to the current camera using the GL (Low-level graphics library).
Use GL class to manipulate active transformation matrices, issue rendering commands similar to OpenGL’s immediate mode and do other low-level graphics tasks. GL immediate drawing functions use whatever is the “current material” set up right now (see Material.SetPass). The material controls how the rendering is done (blending, textures, etc.)
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.
SetRenderTexture method
It just set the RenderTexture
of the current target camera.
Chip8Sound.cs MonoBehaviour (ISound)
This is the ISound
implementation.
Awake method
We just try to locate our AudioSource component that will be used to play the sound.
Play method
This is the only method we need to implement of ISound
interface and it just calls the AudioSource’s PlayOneShot using the AudioClip defined on _beep
field.
KeyboardChip8Input.cs MonoBehaviour (IInput)
This is the IInput
implementation for the keyboard.
Key mapping
First, we create the dictionary _map
to map the real keyboard keys to CHIP-8 keypad keys.
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 using the Input.GetKey method.
Chip8Log.cs MonoBehaviour (ILog)
This is the ILog
implementation.
Debug and Error methods
The two methods implemented for ILog
interface use methods available on Unity Debug class to send log messages to the console window.
Chip8Loader.cs
This is a simplified version of the component responsible to load all systems (IGraphic, ISound, IInput, and ILog), initialize the Chip8
class emulator, and load the ROM.
Start method
Verifies if all the systems needed to run the emulator were configured in the editor, then sets the desired FPS and starts to run the emulator.
Run and Restart methods
These two methods have some overloads, but in the end, they will create a new instance of the Arc8.Chip8
class using the systems defined and will load the ROM.
LateUpdate method
We use the LateUpdate method from MonoBehaviour
to run the emulator EmulateCycle
method.
LateUpdate is called every frame after all Update functions have been called.
Next step
In the next ARC-8 devlog I will talk about how I put a CHIP-8 emulator to run inside the Unity editor inspector window.
If you have any doubts about what I talk about above or any tip about the CHIP-8 emulator (or Unity) and you like to share it, please let me know in the comments section below.
Further reading
- Get started with Unity
- Unity User Manual
- MonoBehaviour
- Double Buffer
- RenderTexture
- OnRenderObject
- GL (Low-level graphics library).
- AudioSource
- AudioClip
- Input Manager
- Debug
- Console Window
- TargetFrameRate
Icons made by Freepik, Vignesh Oviyan and Eucalyp from www.flaticon.com is licensed by Creative Commons BY 3.0