Draw Regions (Split-Screen)

Draw regions let you restrict all drawing to a sub-rectangle of the screen. This is how you implement split-screen multiplayer - each player gets their own viewport where coordinates start at (0, 0).

Basic usage

// Draw player 1's view in the left half
gosprite64.SetDrawRegion(0, 0, 144, 216)
drawGameForPlayer(0)
gosprite64.ResetDrawRegion()

// Draw player 2's view in the right half
gosprite64.SetDrawRegion(144, 0, 144, 216)
drawGameForPlayer(1)
gosprite64.ResetDrawRegion()

Inside SetDrawRegion, all coordinates are local to the region. Drawing at (0, 0) puts pixels at the region's top-left corner, not the screen's.

On N64 hardware, SetDrawRegion also sets the RDP scissor rectangle, so any drawing that falls outside the region is clipped at the hardware level.

Two-player layout

The 288x216 canvas divides naturally for split-screen:

2-player side-by-side:  SetDrawRegion(0, 0, 144, 216)  // left
                        SetDrawRegion(144, 0, 144, 216) // right

2-player top-bottom:    SetDrawRegion(0, 0, 288, 108)   // top
                        SetDrawRegion(0, 108, 288, 108) // bottom

4-player quadrants:     SetDrawRegion(0, 0, 144, 108)     // top-left
                        SetDrawRegion(144, 0, 144, 108)   // top-right
                        SetDrawRegion(0, 108, 144, 108)   // bottom-left
                        SetDrawRegion(144, 108, 144, 108) // bottom-right

Drawing a divider

Draw region doesn't prevent drawing outside it after ResetDrawRegion. Draw divider lines after resetting:

gosprite64.SetDrawRegion(0, 0, 144, 216)
drawPlayer1()
gosprite64.ResetDrawRegion()

gosprite64.SetDrawRegion(144, 0, 144, 216)
drawPlayer2()
gosprite64.ResetDrawRegion()

// Divider line in full-screen space
gosprite64.DrawLine(144, 0, 144, 216, gosprite64.White)

Nesting

Calls can be nested. Each SetDrawRegion pushes onto a stack, and ResetDrawRegion pops back to the previous region:

gosprite64.SetDrawRegion(0, 0, 144, 216)    // player 1 half
gosprite64.SetDrawRegion(10, 10, 124, 90)   // HUD sub-area within player 1
drawHUD()
gosprite64.ResetDrawRegion()                 // back to player 1 full half
drawGameplay()
gosprite64.ResetDrawRegion()                 // back to full screen

DrawRegion type

You can also work with DrawRegion values directly for coordinate math:

region := gosprite64.DrawRegion{X: 50, Y: 30, W: 100, H: 80}

// Convert local coords to screen coords
screenX, screenY := region.Offset(10, 20)  // (60, 50)

// Check if a local point is within the region
region.ContainsPoint(10, 20)  // true
region.ContainsPoint(200, 0)  // false

// Clip and offset a rectangle
x1, y1, x2, y2, ok := region.Clip(0, 0, 150, 150)
// ok=true, coords clamped to region bounds

// Check if region is active (non-zero size)
region.Active()  // true
gosprite64.DrawRegion{}.Active()  // false

Dr. Mario example

Dr. Mario 64 shows 2-4 game boards. With draw regions, each board draws at local coordinates:

func drawBoard(playerIndex int, board *GameBoard) {
    // Each board is 64x136 pixels (8 cols x 17 rows of 8x8 cells)
    boardW := 8 * 8
    boardH := 17 * 8

    // Position boards across the screen
    positions := []struct{ x, y int }{
        {20, 20}, {152, 20},           // 2 players
    }

    pos := positions[playerIndex]
    gosprite64.SetDrawRegion(pos.x, pos.y, boardW, boardH)

    // All drawing is now local to the board
    for row := 0; row < 17; row++ {
        for col := 0; col < 8; col++ {
            cell := board.Get(col, row)
            if cell != Empty {
                drawCell(col*8, row*8, cell)
            }
        }
    }

    gosprite64.ResetDrawRegion()
}

Complete example

See examples/splitscreen_demo for a working 1P/2P split-screen game using draw regions, timers, and menus together.