Skip to content

Instantly share code, notes, and snippets.

@giacomelli
Last active November 4, 2020 17:41
using UnityEngine;
namespace Arc8
{
public class Chip8Graphic : MonoBehaviour, IGraphic
{
Material _mat;
byte[,] _buffer;
byte[,] _gfx;
[SerializeField]
Camera _targetCamera;
[SerializeField]
Color _backgroundColor = Color.black;
[SerializeField]
Color _foregroundColor = Color.green;
[SerializeField]
Shader _shader;
int _screenWidth;
int _screenHeight;
public bool HasRenderTexture { get => _targetCamera.targetTexture; }
public Shader Shader { get => _shader; }
public void Initialize()
{
if (_targetCamera == null)
_targetCamera = Camera.main;
_targetCamera.cullingMask = 0;
_targetCamera.clearFlags = Application.isMobilePlatform ? CameraClearFlags.Depth : CameraClearFlags.Nothing;
if (HasRenderTexture)
{
_screenWidth = _targetCamera.targetTexture.width;
_screenHeight = _targetCamera.targetTexture.height;
}
_mat = new Material(_shader);
_mat.SetPass(0);
_buffer = new byte[64, 32];
for (int x = 0; x < 64; x++)
for (int y = 0; y < 32; y++)
_buffer[x, y] = 2;
}
public void Draw(byte[,] gfx)
{
_gfx = gfx;
}
private void OnRenderObject()
{
if (Camera.current == _targetCamera)
Render();
}
public void Render()
{
if (_targetCamera.targetTexture == null)
Render(true, Screen.width, Screen.height, true);
else
Render(false, _screenWidth, _screenHeight, true);
}
public void Render(bool clearBuffer, int screenWidth, int screenHeight, bool yInverted)
{
if (_gfx == null)
return;
GL.PushMatrix();
if (clearBuffer)
GL.Clear(true, false, _backgroundColor);
_mat.SetPass(0);
GL.LoadPixelMatrix();
GL.Begin(GL.QUADS);
var pixelWidth = screenWidth / 64f;
var pixelHeight = screenHeight / 32f;
float xScaled;
float yScaled;
for (int x = 0; x < 64; x++)
{
xScaled = x * pixelWidth;
for (int y = 0; y < 32; y++)
{
if (!clearBuffer && _buffer[x, y] == _gfx[x, y])
continue;
_buffer[x, y] = _gfx[x, y];
if (_gfx[x, y] == 1)
GL.Color(_foregroundColor);
else
GL.Color(_backgroundColor);
if (yInverted)
yScaled = (31 - y) * pixelHeight;
else
yScaled = y * pixelHeight;
GL.Vertex3(xScaled, yScaled, 0); // Top left.
GL.Vertex3(xScaled + pixelWidth, yScaled, 0); // Top right.
GL.Vertex3(xScaled + pixelWidth, yScaled + pixelHeight, 0); // Bottom right.
GL.Vertex3(xScaled, yScaled + pixelHeight, 0); // Bottom left.
}
}
GL.End();
GL.PopMatrix();
}
public void Invalidate()
{
}
public void SetRenderTexture(RenderTexture renderTexture)
{
_targetCamera.targetTexture = renderTexture;
}
}
}
using System;
using UnityEngine;
using UnityEngine.Serialization;
namespace Arc8
{
[DefaultExecutionOrder(1000)]
public class Chip8Loader : MonoBehaviour
{
Chip8 _chip8;
TextAsset _romLoaded;
[SerializeField]
int _FPS = 60;
[SerializeField]
int _opcodesPerCycle = 10;
[SerializeField]
Chip8Graphic _graphic;
[SerializeField]
Chip8Sound _sound;
[SerializeField]
KeyboardChip8Input _input;
[SerializeField]
Chip8Log _log;
[SerializeField]
[FormerlySerializedAs("Rom")]
TextAsset _rom;
public Chip8Graphic Graphics { get => _graphic; }
public TextAsset Rom { get => _rom;}
private void Start()
{
if (_rom == null)
LogError("Rom should be defined");
if (_graphic == null)
LogError("Graphic should be defined");
if (_sound == null)
LogError("Sound should be defined");
if (_input == null)
LogError("Input should be defined");
if (_log == null)
LogError("Log should be defined");
Application.targetFrameRate = _FPS;
Run();
}
public void Run()
{
Run(_graphic);
}
public void Run(TextAsset rom)
{
_rom = rom;
Run(_graphic);
}
public void Run(IGraphic graphics)
{
Restart(graphics, _input);
}
public void Run(IInput input)
{
Restart(_graphic, input);
}
public void Restart()
{
Restart(_graphic, _input);
}
public void Restart(IGraphic graphics, IInput input)
{
if (Rom == null)
LogError("Rom should be defined.");
_log.Debug($"Rom size: {_rom.bytes.Length}");
_chip8 = new Chip8(_log, graphics, _sound, input);
_graphic.Initialize();
_chip8.LoadRom(Chip8Rom.FromData(_rom.name, _rom.bytes));
_romLoaded = _rom;
}
public void LateUpdate()
{
_chip8.EmulateCycle(_opcodesPerCycle);
}
[System.Diagnostics.Conditional("DEBUG")]
void LogError(string msg)
{
Debug.LogError($"[{name}] {msg}");
}
}
}
using UnityEngine;
namespace Arc8
{
public class Chip8Log : MonoBehaviour, ILog
{
private void Start()
{
}
public void Debug(string message, params object[] args)
{
if (enabled)
UnityEngine.Debug.LogFormat($"[CHIP8] {message}", args);
}
public void Error(string message, params object[] args)
{
UnityEngine.Debug.LogErrorFormat($"[CHIP8] {message}", args);
}
}
}
using UnityEngine;
namespace Arc8
{
[RequireComponent(typeof(AudioSource))]
[ExecuteInEditMode]
public class Chip8Sound : MonoBehaviour, ISound
{
[SerializeField]
AudioClip _beep;
AudioSource _source;
private void Awake()
{
_source = GetComponent<AudioSource>();
if (_source == null)
Debug.LogError("Chip8Sound did not find any AudioSource");
}
public void Play()
{
if(enabled)
_source.PlayOneShot(_beep);
}
}
}
using System.Collections.Generic;
using UnityEngine;
namespace Arc8
{
public class KeyboardChip8Input : MonoBehaviour, IInput
{
static readonly Dictionary<KeyCode, ushort> _map = new Dictionary<KeyCode, ushort>
{
{ KeyCode.Alpha1, Keypad.Key1 },
{ KeyCode.Alpha2, Keypad.Key2 },
{ KeyCode.Alpha3, Keypad.Key3 },
{ KeyCode.Alpha4, Keypad.KeyC },
{ KeyCode.Q, Keypad.Key4 },
{ KeyCode.W, Keypad.Key5 },
{ KeyCode.E, Keypad.Key6 },
{ KeyCode.R, Keypad.KeyD },
{ KeyCode.A, Keypad.Key7 },
{ KeyCode.S, Keypad.Key8 },
{ KeyCode.D, Keypad.Key9 },
{ KeyCode.F, Keypad.KeyE },
{ KeyCode.Z, Keypad.KeyA },
{ KeyCode.X, Keypad.Key0 },
{ KeyCode.C, Keypad.KeyB },
{ KeyCode.V, Keypad.KeyF }
};
public void UpdateKeys(byte[] keys)
{
foreach (var m in _map)
{
if (Input.GetKey(m.Key))
keys[m.Value] = 1;
else
keys[m.Value] = 0;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment