Introduction
ARIAT-HW is a spinoff of the main project I started for the Punk Jam and still haven’t released yet -thought secretly working on it.
Ultimately it’ll be a collection of various tiny games taking place in the same universe, and made with the amazing Pine-2K by @FManga.
I took interest into it because it’s great to prototype core gameplays.
This post also serves as a repository for various techniques I’m using.
Minigames & Experiments
Minigame - Highways
The intended game - it’s a prototype of the Highway gameplay from the main game.
It’ll be a sidescrolling road with a moving background and obstacles.
Status: Prototype-in-mind.
Minigame - Rumble
An arena where you’re in a tank and followed by a hoard of other tanks.
Status: In progress.
Techniques
Since 2K of code isn’t really much, I rely on the following techniques to help saving a bit of bytes.
Array & Pointer Arithmetics
Variables are great for 1-dimensional values, but I like to get 2 or 3-dimensional myself. Using more variables is just going to increase a lot the code, so how about packing them nicely into array instead?
const objectsCapacity = 5;
const objectSize = 3;
const objectsSize = objectsCapacity * objectSize * 4; // 32bits!
const objects = new Array(objectsCapacity * objectSize);
// objects' array is laid out like this:
// [update0, x0, y0, update1, x1, y1, ...];
var object0 = objects;
var object1 = objects + objectSize * 1;
// Iterating the objects.
for (var object = objects; object < objects + objectsSize; object++) {
var objectUpdate = object[0];
// Does this object have an update function?
if (objectUpdate)
objectUpdate(object);
}
...
function updateBarrel(object) {
// 1 & 2 are x and y.
sprite(object[1], object[2], BarrelSprite);
}
A collection of simple objects.
This allows me to have a dynamic number of objects. If you got only 2 objects, like the player and an enemy it’s probably not worth it.
Function Export / Import
An interesting technique which can help you pack a bit more code.
-
exec()
allows you to execute another script. -
save()
to save an array into a file. -
file()
to load a file into an array in RAM. - The proc doesn’t mind executing any code at any location in the RAM.
- Because Pine2K doesn’t check types, pointer arithmetics allows you to read any function’s bytes.
All of these combined mean you can use a script to export simple functions and load them from another script!
However, those functions come with a lot of limitations:
- You must know in advance how many bytes the function will take.
- What’s worst is that, while unlikely, code size may at time be greater in future versions of Pine2K.
- I couldn’t find a reliable way to calculate it.
- The function cannot call or use most symbols you’re declaring.
- No
function
norvar
. -
const
are probably ok, if they’re basic int or booleans. More testing to be done, but arrays are definitely out of the game.
- No
- The function can however call any standard Pine2K function, such as
sprite()
orexit()
. - Because it’s generated from a script, it will never get higher or equal to 2kB.
- What it won’t eat in PROGMEM, it’ll eat in RAM.
- While this is more abundant than PROGMEM, you might still need it for graphics & other data.
You can circumvent the call limitation by passing a function to be called (as a callback) or an array of functions.
I found the following uses for those:
- Generic pure functions, such as
abs
orsqrti
.- But don’t bother with
abs
/min
/max
, you’ll consume the same amount of PROGMEM at best.
- But don’t bother with
- Simplified functions which calls multiple system calls.
- One-time script-like functions, like setups.
- In my arena game, I’m going to have various positions for various objects - I could use an array of int and interpret it, or I could use this.
Also, there is a little trick due to Thumb-2 to make it work on the actual Pokitto (emulator is unaffected) (Thanks @FManga for the pointers!):
- When writing the function to an array, you have to start 1 byte before.
- Then, when loading it, you have to offset by +1 byte.
- This is due to how the proc recognize Thumb-2.
// Exports a function into a file.
function exportFunction(func, size, filename)
{
var arrayLength = (size + 3) / 4;
var funcArray = new Array(arrayLength);
func--; // Thumb-2 Trick.
for (var iI = 0; iI < arrayLength; iI++) {
funcArray[iI] = func[iI];
}
save(filename, funcArray);
// This assumes you're not using auto-newlines (io/FORMAT).
console("wrote "); console(arrayLength * 4); console(" into "); console(filename); console(" \n");
}
// Renders a sprite at the game position, with origin support and mirror on a sprite-basis, + recolor.
// position256 is assumed to be a pointer containing x, then y, whose value is actually a fixed24/8.
// spriteDef is a pointer containing the sprite, the origin in x, origin in y and a boolean of whether or not the sprite must be mirrored.
// recolor is the same recolor than sprite().
function drawSprite256(position256, spriteDef, recolor)
{
mirror(spriteDef[3]);
color(recolor);
sprite(position256[0] / 256 - spriteDef[1], position256[1] / 256 - spriteDef[2], spriteDef[0]);
}
// Using PROGMEM, I know this function takes 108 bytes.
// This is tricky, because if future versions of Pine2K somehow makes this function bigger, only parts of it will be exported.
// It's probably better to include a few more bytes just in case.
exportFunction(drawSprite256, 108, "drSp256.fun");
// After this call, drSp256.fun will contain the compiled drawSprite256.
// Imports drawSprite256 from the sprite.
const drawSprite256 = file("drSp256.fun", null) + 1;
// PROGMEM will be less affected, however the main RAM's consumption will increase.
...
// An object updated with updateObject(), at the middle of the screen.
var object = [updateObject, 110 * 256, 88 * 256];
// A debug circle whose origin is at 6, 6, and not mirrored.
const DebugCircleSprite = [file("DebugCircle", 0), 6, 6, false];
// X/Y starts at 4.
drawSprite256(object + 4, DebugCircleSprite, 6)