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.
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
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
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.
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
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 will be called by the
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
A byte with value 1 should be drawn (foreground color) and a byte with value 0 should not be drawn (background color).
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.
It just set the
RenderTexture of the current target camera.
Chip8Sound.cs MonoBehaviour (ISound)
This is the
We just try to locate our AudioSource component that will be used to play the sound.
KeyboardChip8Input.cs MonoBehaviour (IInput)
This is the
IInput implementation for the keyboard.
First, we create the dictionary
_map to map the real keyboard keys to CHIP-8 keypad keys.
The only method we need to implement for
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
Debug and Error methods
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.
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.
We use the LateUpdate method from
MonoBehaviour to run the emulator
LateUpdate is called every frame after all Update functions have been called.
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.
- Get started with Unity
- Unity User Manual
- Double Buffer
- GL (Low-level graphics library).
- Input Manager
- Console Window