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

post image

The system interfaces IGraphic, ISound, IInput, and ILog will be implemented as MonoBehaviour.

post image

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)

post image

This is the IGraphic implementation.

post image

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)

post image

This is the ISound implementation.

post image

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)

post image

This is the IInput implementation for the keyboard.

post image

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)

post image

This is the ILog implementation.

post image

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

post image

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.

post image

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

post image

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.

post image

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

post image




Icons made by Freepik, Vignesh Oviyan and Eucalyp from www.flaticon.com is licensed by Creative Commons BY 3.0

Loading comments...
Tutorials

Articles

Labs