The GoLang Fantasy Engine (GoLF Engine) is a retro game engine. It draws inspiration from fantasy console projects like pico-8, tic-80, and pyxle. Like those projects it is designed to be a retro-feeling game creation/playing tool. Unlike those projects GoLF is more minimal in scope and only provides an API and a small set of tools to help you create your games. Tools like an image editor and code editor are not built in. Despite this minimalism creating games in GoLF is still easy and should still maintain the retro game feel.
Installing the GoLF engine is simple. You can install the engine and required tools by running the following commands. From your terminal run
go get github.com/bjatkin/golf-engine/golf
Ignore any errors you get about syscall/js.
go get github.com/bjatkin/golf-engine/util
Then create a directory for your game. For example:
mkdir hello world
Navigte into this directory and run the golf_toolkit binary, located in the GoLF engine utils folder.
./Users/[your user name]/go/src/github.com/bjatkin/golf-engine/util/[windows,mac,linux]/golf_toolkit
This will start the golf_toolkit. which you'll need to help you develop GoLF engine games. Now you can init your new project.
This will create all the necessary files and folders for you to develop your game. You can edit the assets/spritesheet.png file to add sprites to your game. You can also edit assets/map.png to create a map for your game. Then you can start writing your GoLF code in the main.go file.
Once you've installed GoLF, you can try out some demo games. If you're not quite ready to start working on your own game but want to see what the GoLF engine can do you can find a few demo projects here.
Developing Your Game
Once you have your first GoLF project up and running, development is easy. Just write regular Go code using the GoLF API. Each time you reload localhost:8080, the golf_toolkit will automatically recompile your code so you can test out the changes. If you run into problems, you can also run the build command manually. This will run the build script as well as re-importing your sprite sheet and map file. If there are any errors compiling your Go code, the golf_toolkit will let you know.
It's important to remember that the GoLF engine only supports 8 colors on screen at a time. This means your sprite sheet can only contain 8 colors as well. When building your sprite sheet, be sure to pick 2 of the 16 color pallets and stick with those colors. Also remember that your sprites will be drawn based on the color pallets set by GoLF, so your sprites may be a different color on screen than they are on the sprite sheet.
The map file is another important aspect of your GoLF project. It's important to note that the map is just a 2D array of sprites to draw on screen. When creating your map file be sure that all the tiles you add line up with the 8x8 grid. Each tile should also match up with an 8x8 sprite on your sprite sheet.
Releasing Your Game
Making your GoLF games available for others to play can be done in two ways. If your friends have the golf_toolkit installed, you can simply send them a copy of the web folder generated by golf toolkit build command. You can rename this folder however you like. Anyone can then play your game using the
play command in the golf_toolkit.
The second option is to make your game playable online. Simply serve the 3 files in the web folder (project_name.wasm, index.html, and wasm_exec.js) using any server that supports the WASM file extension. You can also edit the html file to customize your game. Any part of the index.html can be changed, with the exception of the script tag and canvas tag.
GoLF Toolkit Commands
The golf_toolkit is a utility designed to help you develop games in the GoLF engine. Below is a list of commands which can be run inside the golf_toolkit along with an explanation of what they do.
- about: Displays some simple information about the golf_toolkit and why it exists.
- exit: Quits the golf_toolkit. Will stop the development server if it's running.
- map: Takes a map file location, a sprite file location, and an output file location. The map file should be a png file filled with 8x8 sprites from the specified sprite sheet. The result is saved to the output file.
- csvmap: Takes a csv map file location and an output file. This file should have a list of sprite indexes that correspond to the index of 8x8 sprites on the sprite sheet. The result is saved to the output file.
- sprite: Takes a sprite file location and an output file location. Converts the sprite sheet into GoLF data and saves it to the output file location. Sprite sheets must only use 2 pallets from the 16 GoLF pallets.
- flag: Takes a flag file location and an output file location. Contains a list of flags that correspond to the sprite sheet. Each flag should be 8 characters long and consist of 1's (flag is set) and 0's (flag is not set). This file need not contain all flags for all 512 sprites.
- startserver: Starts a development server. This will automatically open your default browser to localhost:8080 where you can play your game. Each time you reload your game your project will be rebuilt.
- stopserver: Stops the development/ play server if it's running. Otherwise, it does nothing.
- play:: Starts a play server. This will automatically open your default browser to localhost:8080 where you can play the game located in the specified folder.
- build: Builds the current project, creating a new WASM game file.
- init: Initializes a new game in the current directory. Creates the following files.
- map.png - An empty map file.
- spritesheet.png - An empty sprite sheet.
- index.html - Simple html file with a canvas to run your game.
- wasm_exec.js - The golang WASM glue file.
- main.go - Some boiler plate code to get you started.
- golf_config - Used by the golf_toolkit to compile your project.
- build.sh - Build file.
- config: Takes a golf_config property name and prints the current value. Valid config property names are listed below.
- name - Your project name.
- spriteFile - The sprite file to be converted when build is run.
- mapFile - The map file to be converted when build is run.
- flagFile - The flag file to be converted when build is run.
- outputSpriteFile - The Go file to write the converted sprite data to.
- outputMapFile - The Go file to write the converted map data to.
- outputFlagFile - The Go file to write the converted flag data to.
- setconfig: Takes a golf_config property name and a new value. The new value is assigned to that value in the golf_config file.
- clear: Clears the terminal screen.
- help: Displays all the golf_toolkit commands.
- !!: Re-run the last executed command.
- 192 x 192 screen size
- 64 total colors split into 16 pallets with 4 colors each
- 8 on screen colors consisting of any 2 of the 16 predefined pallets
- One 256 x 128 sprite sheet for a total of 512 8x8 sprites
- One 128 x 128 tile map
- 60 FPS
- Mouse support with 3 different cursor styles
- Keyboard support
The GoLF Color Pallet
GoLF uses a pallet of 64 colors split into 16 four-color pallets. You can mix and match these pallets however you want, but only 2 can be used at a time.
|Pallet||Color 1||Color 2||Color 3||Color 4|
|Pallet||Color 1||Color 2||Color 3||Color 4|
|0||(0, 0, 0)||(96, 96, 96)||(144, 144, 144)||(192, 192, 192)|
|1||(64, 64, 64)||(128, 128, 128)||(160, 160, 160)||(255, 255, 255)|
|2||(21, 3, 7)||(74, 18, 21)||(174, 48, 49)||(236, 126, 124)|
|3||(48, 11, 14)||(126, 33, 35)||(230, 63, 63)||(242, 189, 184)|
|4||(22, 6, 2)||(72, 44, 11)||(220, 155, 35)||(235, 185, 81)|
|5||(47, 25, 7)||(121, 81, 19)||(228, 170, 58)||(250, 215, 126)|
|6||(25, 21, 0)||(155, 90, 16)||(210, 118, 20)||(242, 178, 123)|
|7||(2, 24, 12)||(74, 102, 61)||(124, 153, 100)||(178, 203, 144)|
|8||(50, 76, 45)||(97, 128, 77)||(151, 178, 122)||(205, 227, 166)|
|9||(7, 13, 17)||(58, 119, 126)||(108, 225, 234)||(148, 232, 230)|
|10||(33, 66, 72)||(83, 172, 180)||(128, 229, 232)||(188, 238, 226)|
|11||(7, 1, 26)||(21, 34, 83)||(35, 66, 165)||(59, 104, 191)|
|12||(14, 18, 55)||(28, 50, 111)||(47, 85, 165)||(82, 141, 242)|
|13||(21, 34, 83)||(70, 135, 143)||(148, 227, 68)||(226, 243, 228)|
|14||(0, 48, 59)||(255, 119, 119)||(255, 206, 150)||(241, 242, 218)|
|15||(0, 0, 0)||(197, 17, 17)||(20, 58, 133)||(255, 255, 255)|
Note: Thanks to @Kirokaze for pallet 13 and @Polyducks for pallet 14.
There are 2 main GoLF structs that allow you to modify various GoLF draw functions.
golf.SOp: This structure is a list of options that can be passed to a sprite to change how it is drawn.
- FH: Flip the sprite horizontally.
- FV: Flip the sprite vertically.
- TCol: Set the sprite's transparency color.
- PFrom & PTo: Change the sprite's pallet. Colors number n in PFrom is converted to color number n in PTo.
- W: Width of the sprite in tiles to read from the spritesheet. (e.g. W: 2 is 16 pixels in width).
- H: Height of the sprite in tiles to read from the spritesheet. (e.g. H: 2 is 16 pixels tall).
- SW: The amount to scale the width of the sprite. Default value is 1 or no scaling.
- SH: The amount to scale the height of the sprite. Default value is 1 or no scaling.
- Fixed: If this is set to true then the sprite ignores the camera X & Y when drawing. Useful for UI.
golf.TOp: this structure is a list of options that can be passed to text functions to change how text is drawn.
- Col: The color to draw the text.
- Fixed: If this is set to true the text ignores the camera.
- SW: The amount to scale the width of the text.
- SH: The amount to scale the height of the text.
There are 2 GoLF types that will help you work with the GoLF color pallet.
golf.Col: A GoLF color. There are 8 colors ranging from Col0 to Col7. The first 4 colors map to pallet A and the last four map to pallet B.
golf.Pal: A GoLF pallet. There are 16 available pallets (Pal0 to Pal15). These can be used to give your game a unique feel/look.
The GoLF Engine
The GoLF Engine is the main object exposed by the GoLF package.
NewEngine(updateFunc func(), draw func()): Ereates a GoLF Engine instance and returns a pointer to the engine. The GoLF Engine is the main object used to perform most of the GoLF functions.
engine.Run(): Starts the game engine running. Once this is run, the update function will be called 60 times a second and the draw function will be called 60 times a second.
engine.Frames(): Returns the number of frames that have passed since the game engine was started. This count includes the startup animation frames. The startup animation is 254 frames, meaning the first frame that the update/draw function will be called is frame 255.
engine.DrawMouse(style int): Sets the draw style for the mouse indicator.
- 0 = No mouse cursor is drawn.
- 1 = A mouse arrow is drawn.
- 2 = A hand cursor is drawn.
- 3 = A cross cursor is drawn.
engine.Cls(col golf.Col): Fills the screen with the col color.
engine.Camera(x, y int): Changes the X, Y coordinates of the camera. This value is then subtracted from the X, Y coordinates of all future drawing calls. This is useful for panning the screen around.
engine.Clip(x, y, w, h int): Clips all future draw functions with upper left corner at point (x, y) and width w and heigh h.
engine.RClip(): Resets the screen clipping so that no screen pixels are clipped.
engine.PalA(pallet golf.Pal): Sets the first pallet.
engine.PalB(pallet golf.Pal): Sets the second pallet.
engine.PalGet(): Returns the first and second pallets that are currently set.
Using the following functions, you can draw various shapes on screen.
engine.Pset(x, y float64, col golf.Col): Sets the pixel on the screen at point (x, y) to the color col.
engine.Pget(x, y float64): Gets the color currently set at screen pixel (x, y).
engine.Rect(x, y, w, h float64, col golf.Col, fixed bool): Draw an empty rectangle outline with the specified draw color. fixed is an optional parameter. When set to true rect ignores the camera (useful for UI)
engine.RectFill(x, y, w, h float64, col golf.Col, fixed bool): Draw a filled rectangle with the specified draw color. fixed is an optional parameter. When set to true rect ignores the camera (useful for UI)
engine.Line(x1, y1, x2, y2 float64, col golf.Col, fixed bool): Draw a line from point (x1, y1) to (x2, y2). The line is drawn with the specified color. fixed is an optional parameter. When set to true rect ignores the camera (useful for UI)
engine.Circ(xc, yc, r float64, col golf.Col, fixed bool): Draw a circle outline with center at point (xc, yc) with radius r. The outline is drawn with the specified color. fixed is an optional parameter. When set to true rect ignores the camera (useful for UI)
engine.CircFill(xc, yc, r float64, col golf.Col, fixed bool): Draw a filled circle with center at point (xc, yc) with radius r. The circle is drawn with the specified color. fixed is an optional parameter. When set to true rect ignores the camera (useful for UI)
Using these functions, you can receive and handle user input for your game.
engine.Btn(key golf.Key): Returns true if the given key is being held on this frame.
engine.Btnp(key golf.Key): Returns true if the given key was first pressed on this frame.
engine.Btnr(key golf.Key): Returns true if the given key was released on this frame.
engine.Mbtn(key golf.MouseBtn): Returns true if the given mouse key is being held on this frame.
engine.Mbtnp(key golf.MouseBtn): Returns true if the given mouse key was first pressed on this frame.
engine.Mbtnr(key golf.MouseBtn): Returns true if the given mouse was released ont this frame.
The map functions allow you to draw a game map onto the screen easily and quickly.
engine.LoadMap(mapData [0x4800]byte): Load the map data into memory.
engine.Map(mx, my, mw, mh int, dx, dy float64, opts ...SOp): Draws the map data onto the screen witht he left coordinate at screen point (dx, dy). mx and my are the map coordinates in tiles and mw and mh are the map size in tiles. opts are optional and change how each individual map tile is drawn.
engine.Mset(x, y, t int): Sets the map tile to sprite number t at the map coordinate (x, y)
engine.Mget(x, y int): Returns the sprite index of the tile a the map coordinate (x, y)
These functions allow you to draw sprites on the screen and modify how they are drawn.
engine.LoadSprs(sheet [0x3000]byte): Load the sprite sheet data into memory.
engine.LoadFlags(flags [0x200]byte): Load the sprite flags into memory. Each sprite in the sprite sheet has 1 byte (or 8 flags) associated with it that can be set and then later checked. The meaning of each of these flags is entirely up to the needs of the programmer.
engine.Spr(n int, x, y float64, opts ...SOp): Draw sprite number n at screen position x, y. opts are optional and change how the sprite is drawn on screen. The sprite sheet is broken up into 8x8 areas that are then numbered from the top left to the bottom right. Usually the first 8x8 sprite is not used, as this sprite is drawn as a transparent tile when used on the map screen.
engine.SSpr(sx, sy, sw, sh int, dx, dy float64, opts ...SOp): A more general version of the spr function. It draws a sprite from an arbitrary spot on the sprite sheet with arbitrary size to the screen. sx and sy are the pixel coordinates of the upper left corner of the sprite on the sprite sheet. sw and sh are the sprites width and height respectively. dx and dy are the screen coordinates that the sprite is drawn to. opts is optional and changes how the sprite is drawn on screen.
engine.Fget(n, f int): Returns flag number f associated with sprite number n.
engine.Fset(n, f int, s bool): Sets the flag number f for sprite n to the same value as s.
engine.FgetByte(n int): Returns the full byte associated with sprite number n.
engine.FsetByte(n int, b byte): Sets the full byte associated with sprite number n to the value of b.
The GoLF Engine uses the custom-built font displayed below. It can be accessed using the following functions.
engine.Text(x, y float64, text string, opts ...TOp): Draws the text on screen at point (x, y). All text is converted to the GoLF Engine's internal font, which is all upper case. There are also several sequences that are converted into GoLF emojis. The escaped sequences are listed bellow. opts are optional and modify how the text is drawn.
- (<) left button
- (>) right button
- (^) up button
- (v) down button
- (x) x button
- (o) o button
- (l) l shoulder button
- (r) r shoulder button
- (+) + button
- (-) - button
- :) smiley face
- :( frowny face
- x( angry face
- :| meh face
- =[ boxy face
- |^ up arrow
- |v down arrow
- <- left arrow
- -> right arrow
- $$ pound symbol
- @@ small black dot
- <| speaker symbol
- <3 white heart
- <4 black heart
- +1 plus one symbol
- -1 minus one symbol
- ~~ the pi symbol
- () tall black dot
-  dark square
- :; dither pattern
- ** start symbol
Note: If you need to draw one of these patterns without it being drawn as an emoji, you can use the '^' symbol to escape the pattern. (e.g. ^** will be drawn as two asterisk characters rather than a star)
engine.TextL(text string, opts ...TOp): Draw text in the upper left hand corner of the screen. Each time TextL called a new line is added.
engine.TextR(text string, opts ...TOp): Draw text in the upper right hand corner of the screen. Each time TextR is called a new line is added.
Cart data functions allow you to store and retrieve persistent data (like game saves).
engine.Dset(name string, data byte): Stores persistent data to a user's browser as a cookie. Only 1024 bytes or less can be stored, and the name must be alpa numeric. The name is used to save the data so it can be retrieved later. Keep in mind this name should be unique, or it may get overwritten by other games.
engine.Dget(name string): Retrieves data stored with Dset. In addition to returning the data it returns a bool which is true if the saved data was successfully found.
The GoLF Memory Map
Another goal of GoLF is to be a 'hackable' engine. To achieve this, GoLF uses virtual RAM (stored in engine.RAM). This virtual RAM stores sprite data, map data, the screen buffer and much more. Below is a list of all the important memory addresses in the virtual RAM. This RAM is stored as a byte array and can be accessed through the RAM member variable of a GoLF Engine instance (you can also view memory addresses by looking at the memoryMap.go file)
- Screen Buffer: 0x0000 - 0x3600, This data is copied to the screen once per frame
- Screen Pallet: 0x3600, The two screen pallets (top 4 bits are pallet 1 and bottom 4 bits are pallet 2)
- Start Screen Length: 0x3601, The number of frames to play the startup animation. If you set this to 0 you can skip the startup animation. If you choose to do this, please credit the project some other way in your game.
- CameraX: 0x3602-0x3603, The 16 bit x coordinate of the camera.
- CameraY: 0x3604-0x3605, The 16 bit y coordinate of the camera.
- Frames: 0x3606-0x3608, The 24 bit number that counts the frames since the game engine was started.
- ClipX: 0x3609, The x coordinate of the clipping rect.
- ClipY: 0x360A, The Y coordinate of the clipping rect.
- ClipW: 0x360B, The width of the clipping rect.
- ClipH: 0x360C, The height of the clipping rect.
- MouseX: 0x360D, The x coordinate of the mouse.
- MouseY: 0x360E, The y coordinate of the mouse.
- Left Click: 0x360F, The click state of the left mouse button (00 - unclicked, 01 - click started, 10 - click ended, 11 - pressed).
- Middle Click: 0x360F, The click state of the middle mouse button (00 - unclicked, 01 - click started, 10 - click ended, 11 - pressed).
- Right Click: 0x360F, The click state of the right mouse button (00 - unclicked, 01 - click started, 10 - click ended, 11 - pressed).
- Mouse Style: 0x360F, The draw style of the mouse (00 - mouse cursor is not drawn, 01 - arrow, 10 - hand cursor, 11 - cross cursor).
- Keyboard Key State: 0x3601 - 0x3646, The pressed state of all the keys on the keyboard (00 - unpressed, 01 - press started, 10 - press ended, 11 - pressed). Keys are indexed in this array based on their js keyCode - 9 (backspace keycode).
- Internal Sprite Sheet: 0x3647 - 0x3F47, Sprite data for the GoLF font, emojis, logo and mouse sprites.
- Sprite Sheet: 0x3F48 - 0x6F48, The data for the user sprite sheet. This data is stored in the compressed format described below.
- Active Sprite Buff: 0x6F49 - 0x6F4A, 16 bit address that points to the memory location that will be used by the sprite functions. You can use this to swap to the internal sprite sheet or reindex sprites on the sprite sheet.
- Map Data: 0x6F4B - 0xB74B, The map data. This data is stored in the compressed format described below.
- Sprite Flag Data: 0xB74C - 0xB94C, The sprite flag data. Each sprite gets one byte of data which is 8 flags.
As mentioned above, the GoLF Engine uses simulated RAM in order to be more 'hackable' and to aid in the 'retro' feel. Using this memory efficiently is one of the ways GoLF emulates the retro feel. Because of this, some of the internal memory representations of data can get a little difficult to understand. In order to aid in this, I’ve included the following sections. Keep in mind that it is not strictly necessary to understand these concepts in order to use the GoLF Engine.
GoLF Pixel Data
GoLF supports up to 8 colors on screen at a time. Unfortunately, 8 is only a 3 bit number, which makes packing the color data efficiently into 8 bit bytes a little difficult (8 is not evenly divisible by 3). In order to resolve this the GoLF Engine splits a pixel's shade from its pallet. This leads to the following internal representation for pixel data in GoLF's simulated RAM. For each 3 byte set in any section of graphics data, there are 2 bytes with 4 pixel shades (1-4) followed by one byte with 8 color pallets (0 or 1). Each pallet bit corresponds to one of the previous 8 pixel shades. You can learn more about this by looking at the pget and pset functions in the GoLF engine or by looking at the graphic below.
GoLF Map Tile Data
The GoLF map supports indexing 512 8x8 sprites from the sprite sheet. 512 is a 9-bit number leading to a similar problem as the one we saw with sprite data (8 is not evenly divisible by 9). In order to deal with this, map tile data is packed using the following method. The sprite sheet is broken up into two halves, each with 256 sprites. The top half is the low half (low memory) and the bottom half is the high half (high memory). Each tile can then be indexed with an 8-bit integer 0-255. In addition to this index, the half of the sprite sheet where the tile is located must be stored (1 bit). This data is packed into the GoLF RAM as follows. 8 bytes with a 0-255 index for each tile, followed by 1 byte with 8 bits to map the previous 8 tiles to the high or low halves of the sprite sheet. You can learn more about this by looking at the Map function in the GoLF engine or by looking at the graphic below.
GoLF Graphics Memory Layout (Map and Sprite Sheet)
In the GoLF memory, sprite data and map data are placed next to each other. Sprite memory and map memory also grow in opposite directions. Sprite memory grows from low memory to high memory and the map memory grows from high memory to low memory. This allows for the sprite sheet or the map data to be expanded beyond their default size. In the case that the spritesheet ‘overgrows’ the map, keep in mind that the map will only index the first 512 tiles. Also the sprite flags will only apply to the first 512 sprites as the flag section of memory is fixed. For more info on this look at the LoadMap and LoadSprs functions in the GoLF Engine, or look at the graphic below.
contributing To This Project
If you are interesting in this project and would like to contribute to it please contact me at [email protected]
- Add the ability to play games natively using Electron rather than using a localhost server.
- Make the project more fantasy console-like.
- Simplify installation
- Add a GoLF terminal that runs in the browser rather than using GoLF toolkit
- Add sprite editor
- Add map editor
- Add in a sound API (use the last 20k of memory)
- Convert midi files
- Create a music editor
- Add a scripting language to make building games easier/more approachable
- Let the text function use multiple TOp arguments for different portions of the string.
- Add vertical/horizontal flipping for the text functions.