ZxSim Code
API (C#) for simulating certain aspects of a ZX spectrum for porting.
Status: Pre-Alpha
Brought to you by:
luny
Git repo for ZX projects. ========================= The ZX project acts as a simulator for ease of porting Z80 Spectrum games to C#. This is a collection of libraries which handle colours, drawing and raw game data using a generic interface that can be plugged into a modern front-end such as Godot. Pyjamarama (WIP) has been included as an example of a game specific library and a Godot project. The idea is that the libraries allow the original binary data to be used to create the graphics, rooms etc. Any Z80 which puts these items together can then be ported into C#. Over time other libraries will eventually exist to aid converting this raw info into bitmaps and JSON for quicker use. All portable to different UI platforms. Architect ========= ------------- ---------------------- | ZX Util | Builder | ------------- ---------------------- | ZX | Bindings | Logging | ------------- ------------------ | | ZX Platform | ZX Game | | ------------------------------------ | ZX Drawing | ------------------------------------ | Godot project / Platform | ------------------------------------ Builder ------- Builder uses the Composite and Builder patterns to create a simple form of dependency injection. Each 'module' or scope can have a composite object (base on IComposition) to own any concrete classes belonging to the module. The composite object can also implement the IBuilder class allowing it to expose any of it's instances (as Interfaces) and request other instances from other modules. By using interfaces the modules can stay detached and have either a loose dependency or better still use dependency reversion. As an example, ZX.Plaform defines the ISurface and IView interfaces which are used by the ZX.Drawing and other modules. Whereas the actual project (Godot project and relating modules) will create the concrete surface using platform specific image objects and implement the ISurface in order to allow other modules to use it with a loose coupling. IComposite objects are automatically instantiated when calling 'BuildAll' on the Builder.Creator class. The builder will own all composites. From here any composites based on the IBuilder interface will then run through the builder process by calling - IBuilder.CreateBuildables to get a list of any child classes that are also IBuildable. - IBuilder.RegisterObjects to allow the instance to register any child classes for dependency injection. - IBuilder.AskForDependents to get a list of other instances the object wants injected. Once all this information has been collected for all instances, the creator will call IBuilder.DependenciesMet for all instances. It is at this point that each object can ask for the dependent instances it requested. Bindings -------- Bindings is a way to share values across different scopes and to report when a value changes. Any module can get the shared BindingsManager and create a bound object. Bound Objects (IBoundObject) are generic so any value type can be defined during creation. Other modules may then bind to the object through the manager, if they wish to know when a value has changed. Additionally the object can be gotten through the manager if another scope wishes to change the value as well. ZX.Drawing ---------- This holds classes to aid converting 8 bit bits into onscreen graphics. Graphic drawing is made up of ILayers, ISurfaces and IDrawers. The IDrawers takes the raw bytes from the original game and converts then into colour pixels on the ISurface. The ISurfaces acts as a generic interface between the ZX drawing and the platform (Windows / Godot etc). ILayers can be created to handle any amount of drawers and blend them onto the final screen surface. This can be done automatically by adding a layer to the IScreen. Each layer has a Z order as set by ILayer.Z, and is used by the screen to determine the order of blending. [Optional] -------------- --------- -------- | GFX / IChunk | --> | IDrawer | >---> | | --------- -------------- --------- | | | | [Update() - Blends all layers in order.] ---------- | ILayer | >------> | IScreen | | ISurface | ----------------------> | | | | ---------- | | --------- -------- [Blend() - Blends surface to another (normally screen)] [Update() - Uses IDrawers to draw onto surface.] The Animation layer (ZX.Drawing.IAnimationLayer), is a surface of a given size which will hold a list of animations (IAnimation) which all use the same IDrawer object to be displayed and updated for each game cycle. There are the standard components, which can be used or the user can create their own IAnimation and IDrawer. ZX.Game ------- The game module holds the main state machine and game loop. It holds an IGameProvider which keeps track of the current game state (Titles, game loop, game over etc) and is called by the main game loop. Objects based on the IGameItem (and any derived interfaces) can be added to the provider and will in turn be called when a game starts, a new level and the main game frame cycle. Setting up a new project ======================== Godot ----- Create a new scene 'main' and make it the default. Create a scene called 'view' Add a child TextureRect node and call it 'view'. Copy folder, and its contents, ./..../Platform into the project. Include libraries in projects makefile. Create a script based on a Node. Override the void _Ready() method. This will be ran at the start. Call into you main static Initialisation code from here and any initial loading required. Make the script run immediately using: Project Settings -> Globals -> Autoload Attach the script 'View.cs' to node 'view'. Main.cs This should be a godot Node, IComposition and an IBuildable. This sould be added as an autostart, where it can invoke the build functionality. Main should instantiate the view and create the surface for it. This will be the main screen. The composition should expose the main view (IView) as "Platform.Main.View". Sub projects are class libraries dotnet new classlib -n <NAME> Make sure new projects build to the output folder. In the project build file update the main property group: <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> <OutputPath>../output/$(Configuration)</OutputPath> </PropertyGroup> Example node use ---------------- Node inst = _commandScene.Instantiate(); AddChild(inst); FurnitureDisplayNode view = inst.GetNode<FurnitureDisplayNode>("Panel/Window/View"); if(view is not IView) { throw new InvalidOperationException($"Node without IView when trying to create command '{name}'"); } return view; Where: . FurnitureDisplayNode is a script based on a TextureRect and IView. . "Panel/Window/View" is the tree path for the physical node added to the Godot scene. The IView will then give you access to the surface which gets created in the factory.