PVG
7. Command Buffers and Debug Drawing — Program Video Games

7. Command Buffers and Debug Drawing

[[programvideogames]]We came up with a simple way of doing debug drawing, but it's not very flexible.

For example, we can't tell our game to draw something from the physics procedures.

In this lecture, we're going to use a new programming pattern called the Command Buffer.

This is not the same thing as the OOP Command pattern, though they server a similar function.

A Command Buffer is a fancy name for an array or queue in which we store some data about commands to be called later.

We will store all the debug drawing requests we want to make and then iterate over them at the end of our draw loop.

Why? We can use draw procedures anywhere in our code - not just between rl.BeginDrawing and rl.EndDrawing. This will be handy for the next lecture where we'll start working on enemy behaviors.

We've got a new language feature concept to cover first: The union.

union

A union in Odin is essentially a struct that could have one of a multitude types, depending on how you declare it.

Here are the actual types we're going to use, so you can go ahead and put these into a new file: `src/debug.odin.

package main

import rl "vendor:raylib"

Debug_Line :: struct {
    start, end: Vec2,
    thickness:  f32,
    color:      rl.Color,
}

Debug_Rect :: struct {
    pos, size: Vec2,
    thickness: f32,
    color:     rl.Color,
}

Debug_Circle :: struct {
    pos:    Vec2,
    radius: f32,
    color:  rl.Color,
}

Debug_Shape :: union {
    Debug_Line,
    Debug_Rect,
    Debug_Circle,
}

In our case, the Debug_Shape union may contain the data of either: Debug_Line, Debug_Rect, or Debug_Circle.

The cannot contain data of more than one.

The union's size will be the size required to fit the largest struct.

Now let's see why this is useful.

On our Game_State struct, we add a new field:

debug_shapes: [dynamic]Debug_Shape

Now, back in debug.odin, we create some procedures following the Raylib parameters:

debug_draw_line :: proc(start, end: Vec2, thickness: f32, color: rl.Color) {
    append(&gs.debug_shapes, Debug_Line{start, end, thickness, color})
}

debug_draw_rect :: proc(pos, size: Vec2, thickness: f32, color: rl.Color) {
    append(&gs.debug_shapes, Debug_Rect{pos, size, thickness, color})
}

debug_draw_circle :: proc(pos: Vec2, radius: f32, color: rl.Color) {
    append(&gs.debug_shapes, Debug_Circle{pos, radius, color})
}

We can append any Debug_Shape to the same array.

That means when we go to draw, which we'll now do at the end of the draw call (just before rl.DrawFPS)

        for s in gs.debug_shapes {
            switch v in s {
            case Debug_Line:
                rl.DrawLineEx(v.start, v.end, v.thickness, v.color)
            case Debug_Rect:
                rl.DrawRectangleLinesEx(
                    {v.pos.x, v.pos.y, v.size.x, v.size.y},
                    v.thickness,
                    v.color,
                )
            case Debug_Circle:
                rl.DrawCircleLinesV(v.pos, v.radius, v.color)
            }
        }

We can switch on the type by using this switch v in s syntax while iterating over one array.

Finally, after rl.EndDrawing, we want to clear this array:

clear(&gs.debug_shapes)

We could have achieved the same thing by adding unique arrays to Game_State, such as:

[dynamic]Debug_Line,
[dynamic]Debug_Rect,
[dynamic]Debug_Circle,

We'd have to clear each array and iterate over each one separately when drawing.

If we want to draw something more complex, like a debug UI element that has a panel with some text one it, then the order of drawing is important. Splitting them up in that case would add complexity.

To test out the debug drawing, let's add some calls - for now just before we iterate over them:

debug_draw_rect(20, 100, 4, rl.GREEN)
debug_draw_line(20, 100, 4, rl.YELLOW)
debug_draw_circle(20, 100, rl.RED)

You should end up with this:

![[Pasted image 20240904093243.png]]

Alright, now we are all set up to continue with Enemy Behavior!