The frame and the controller
From here on the tutorial writes the square example, one part at a time. Start a new module as setting up did, or reuse that one. This page covers the parts of it that draw the box and move it; the cube and the music are left for the pages after.
The screen and the box#
The program's state is package-level variables, as the first program's screen was. The screen is set up a little differently:
// The screen, 320 by 240 at the console's own frame rate, with ordering
// tables of 1<<4 depths, enough for the cube's faces to be sorted by
// distance.
var screen = yaroze.Screen{
Background: yaroze.CVECTOR{R: 40, G: 60, B: 100},
Depth: 4,
}
Depth is the ordering table's size, as a power of two: 16 entries, one for each depth a frame's drawing can be sorted to. The first program sorted nothing, so it did not matter; here the cube's faces will be sorted by how far away they are, and the box drawn over all of them. The rest of the screen's settings are left at their defaults, 320 by 240 at the console's own frame rate, sixty frames a second on an American or Japanese console and fifty on a European one.
The box is a GsBOXF, the library's filled rectangle: a position, a size and a color. A second one, thin and white, will be the bar that shows how far loading has got:
var box = yaroze.GsBOXF{W: 40, H: 40, R: 255, G: 160, B: 32}
var bar = yaroze.GsBOXF{H: 4, R: 255, G: 255, B: 255}
The controller is a Pad, which is read once a frame:
var pad yaroze.Pad
The frame#
This is the whole of main:
func main() {
screen.Open()
// The 3D library puts the origin in the middle of the screen, for 2D
// as well, so the text and the box are placed from the corner Bounds
// gives.
yaroze.GsInit3D()
b := screen.Bounds()
screen.Font(int32(b.X)+16, int32(b.Y)+16, 256, 64, 512)
box.X, box.Y = b.X+24, b.Y+b.H-64
bar.X, bar.Y = b.X+b.W/2-100, b.Y+b.H/2
loader.Start(300, files...)
for ot := range screen.Frames() {
pad.Update()
dx, dy := pad.Dir()
box.X, box.Y = b.Clamp(box.X+int16(dx*speed), box.Y+int16(dy*speed), box.W, box.H)
if pad.Pressed(yaroze.PADstart) {
pauseMusic()
}
// The box is sorted at depth 0, in front of everything, so it
// passes over the cube.
box.Sort(ot, 0)
yaroze.Print("the directions move the box\nstart pauses the music\n")
switch {
case !loader.Done():
if done, failed := loader.Step(); done && !failed {
setUpCube()
startMusic()
}
bar.W = uint16(200 * loader.Loaded() / len(files))
bar.Sort(ot, 0)
case loader.Failed() != nil:
yaroze.Print("could not read ")
yaroze.Print(loader.Failed().Name)
default:
// The cube turns with the frames, so that where it is is the
// same at the same frame however long the disc took.
frame := screen.Frame()
cube.Rotation.Vx = int16(frame * 8)
cube.Rotation.Vy = int16(frame * 20)
cube.Draw(ot)
}
}
}
Where the screen is#
main starts by opening the screen, as before, and then calls GsInit3D, which sets up the library's 3D and, with it, moves the origin of the screen's coordinates from the top left corner to the middle, which is where a 3D picture wants it. That applies to the box as well, so the program asks the screen where its corners are now. Bounds is the screen as the frame's coordinates see it, here from -160, -120 to 160, 120, so the text, the box and the bar are each placed from its corner or its middle and land where they would without GsInit3D. The box starts near the bottom left; the bar is a line across the middle that grows from no width as the files arrive.
Each frame#
Each time round, pad.Update reads the controller. Dir turns the directional buttons into steps of -1, 0 or 1 across and down, with opposite buttons held together cancelling, and speed makes those two pixels a frame:
// How far a held direction moves the box each frame.
const speed = 2
Clamp moves the box back inside the screen's bounds if a step would take any of it outside. Pressed is true on the one frame a button goes down, not on every frame it is held, which is what a button that toggles something wants: Start pauses the music, and holding it should not pause and resume it sixty times a second. Held is the other kind, true for as long as the button is down.
box.Sort(ot, 0) sorts the box into the frame's ordering table at depth 0. The GPU draws the table from its deepest entry to its shallowest, so what is at 0 is drawn last, over everything else: the box always passes in front of the cube, whatever depth the cube's faces are sorted to. Print adds two lines of text to the font's window, which the frame draws after the table.
The switch that follows is the rest of the program: loading the files, saying so if they could not be read, and drawing the cube once they have been. The next page is about the first two.