Daniel Keast

Pico 8: Mode 7

Programming

I’m playing around with Pico 8, making little games and experiments. For this cart I’ve made a working SNES Mode 7 style floor. The trick is in the function tline, which draws a line between two points, sampling from the map data along the way. You give it the map position to start from, and a dx and dy value it should increment by after each pixel. The other main thing is setting a depth value per scanline, the closer to the bottom of the screen the nearer to the players view. The rest is just trigonometry, applying angles to vectors using sin and cos.

Click here to try it out.

Here’s the code, which I’ve tried to comment as much as possible for future reference:

function _init()
    --horizontal map loop size
    poke(0x5f38,8)
    --vertical map loop size
    poke(0x5f39,8)

    --constants
    hrzn=63 --y pos ground starts
    spd=0.1
    trn_spd=0.01

    --camera
    cam={h=128,x=5,y=5}
    update_a(0)
end

function _update()
    if (btn(⬆️)) move(spd)
    if (btn(⬇️)) move(spd*-1)
    if (btn(⬅️)) turn_left()
    if (btn(➡️)) turn_right()

    if btn(❎) then
        cam.h=min(500,cam.h+1)
    end
    if btn(🅾️) then
        cam.h=max(1,cam.h-1)
    end
end

function _draw()
    cls(12)
    for y=hrzn,128 do
        --start sampling the map from
        --the top, no matter where the
        --horizon is. adding 1 to stop div/0
        --division by zero on the next
        --line
        local ln=y-hrzn+1
        --since line increases down
        --the screen, depth decreases
        --higher line = further away
        local dpth=cam.h/ln

        --map tile coords of the left
        --and right side of the screen
        local lx=cam.x+dpth*cam.l.x
        local ly=cam.y+dpth*cam.l.y
        local rx=cam.x+dpth*cam.r.x
        local ry=cam.y+dpth*cam.r.y

        local ln_wth=rx-lx
        local ln_hgt=ry-ly

        tline(
            --line coords, full scanline
            0,y,128,y,
            --map start coords
            lx,ly,
            --width/pixels=h step
            ln_wth/128,
            --height/pixels=v step
            ln_hgt/128)
    end

    print("h  :"..cam.h)
    print("a  :"..cam.a)
    print("x,y:"..cam.x..","..cam.y)
end

function update_a(a)
    local c=cos(a)
    local s=sin(a)

    cam.a=a%1
    cam.c=c
    cam.s=s

    --left and right edges of
    --the screen, 90 fov
    --
    --(x=s,y=-c) == right vector
    --(x=0,y=-1) == forwards
    --(x=1,y=0)  == right
    cam.l={x=c-s,y=s+c}
    cam.r={x=c+s,y=s-c}
end

function turn_left()
    update_a(cam.a-trn_spd)
end

function turn_right()
    update_a(cam.a+trn_spd)
end

function move(spd)
    --cosine of angle ==
    --x offset of unit circle
    cam.x+=cam.c*spd
    --sine of angle ==
    --y offset of unit circle
    cam.y+=cam.s*spd
end