Log In  

Here is a series of functions that are designed to be easily inserted into any project!
Each should be well commented and has a working example. comment if you have any questions!

add a mouse function quickly!

move towards a target with a normalized speed (diagonals are not faster!)

updated gpio library for (significantly) less tokens. now available for commercial use.
enter the pin (0 to 127) and return the hex (0x5f80 to 0x5fff) with get and set functions
old version included for posterity

covers rect collisions and map flags; circ collisions are in lookatlib

grid based path finding using A*
credit to https://www.lexaloffle.com/bbs/?tid=3131 and Pico8 Fanzine #4!

P#85187 2020-12-09 10:27 ( Edited 2020-12-10 05:06)

here are the examples:

With the exception of A*, the CC4-BY-NC-SA license only applies to examples and assets. The functions are all licensed CC4-BY. Please credit back to this blog post and not just my name so more people can use and learn from them.
The pathfinder function is a derivative of the example by richy486 https://www.lexaloffle.com/bbs/?uid=5758
please contact them if you wish to use the pathfinding function commercially.

Cart #mouse-0 | 2020-12-09 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

--mouse library
--andy c
mouse = {
    init = function()
        poke(0x5f2d, 1) --activate devkit
    end,
    state = function()
        return stat(32),stat(33),stat(34)
    end,
    curs = function()
        spr(0,stat(32),stat(33))
    end
}

function _init()
    mouse.init()
    c = {x=0,y=0,b=0}
end

function _update60()
    c.x,c.y,c.b = mouse.state()
end

function _draw()
    cls()
    print(c.x..", "..c.y..", "..c.b)
    mouse.curs()
end

Cart #gpio2_0-0 | 2020-12-10 | Code ▽ | Embed ▽ | No License

--gpio library 2.0
--andy c
function tohex(pin)
    pin += 24448
    return sub(tostr(pin,true),1,6)
end

gpio = {
--[[
the pins are the 128 values
between 0x5f80..0x5fff,
the pin values g0 from 0 to 127
value may be 0..255

to access the pins in
javascript:
var pico8_gpio = new array(128);
--]]
    get = function(pin)
        address = tohex(pin)
        return peek(address),address
    end,
    set = function(pin,value)
        address = tohex(pin)
        poke(address,value)
    end
}

--example of use
function _init()
    gpio.set(16,255)
    x,y = gpio.get(16)
end

function _update60()

end

function _draw()
    cls()
    print(x..", "..y)
end

the GPIO library has been updated to save tokens and make the library safe for commercial use.
gpiolib1.0 is included in spoilers


Cart #gpio-0 | 2020-12-09 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

--gpio library
--andy c
function tohex(number)
--thanks to vitoralmeidasilva
                number += 128
    local base = 16
    local result = {}
    local resultstr = ""

    local digits = "0123456789abcdef"
    local quotient = flr(number / base)
    local remainder = number % base

    add(result, sub(digits, remainder + 1, remainder + 1))

  while (quotient > 0) do
    local old = quotient
    quotient /= base
    quotient = flr(quotient)
    remainder = old % base

         add(result, sub(digits, remainder + 1, remainder + 1))
  end

  for i = #result, 1, -1 do
    resultstr = resultstr..result[i]
  end

  return resultstr
end
gpio = {
--[[
the pins are the 128 values
between 0x5f80..0x5fff,
the pin values g0 from 0 to 127
value may be 0..255

to access the pins in
javascript:
var pico8_gpio = new array(128);
--]]
    get = function(pin)
        pinhex = tohex(pin)
        address = "0x5f"..pinhex
        return peek(address),address
    end,
    set = function(pin,value)
        pinhex = tohex(pin)
        address = "0x5f"..pinhex
        poke(address,value)
    end
}

--example of use
function _init()
    gpio.set(127,255)
    x,y = gpio.get(127)
end

function _update60()

end

function _draw()
    cls()
    print(x..", "..y)
end

Cart #lookat_asdfasdf-0 | 2020-12-09 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

--lookat library
--andy c
function atan(x1, y1, x2, y2)
--finds the angle between x & y
    local x = x2 - x1
    local y = y2 - y1
    return atan2(x, y)
end

function distance(x1, y1, x2, y2)
    return sqrt((x2 - x1) ^ 2 + (y2 - y1) ^ 2)
end

function lookat(x1,y1,x2,y2)
--steps x toward y
    local x = cos(atan(x1,y1,x2,y2))
 local y = sin(atan(x1,y1,x2,y2))
 return x,y
end

--example
function _init()
 obj = {x=63,y=63}
 trgt = {x=rnd(127),y=rnd(127)}
end

function _update60()
    vx,vy = lookat(obj.x,obj.y,trgt.x,trgt.y)
    obj.x += vx
    obj.y += vy
    if abs(distance(obj.x,obj.y,trgt.x,trgt.y)) <= 1 then
        run()
    end
end

function _draw()
    cls()
    spr(1,obj.x,obj.y)
    spr(2,trgt.x,trgt.y)
end

controls: x or o - refresh; arrow keys - move little dude

Cart #collisions-0 | 2020-12-09 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

--collision library
--andy c
function collision(r1x,r1y,r1w,r1h,r2x,r2y,r2w,r2h) --rectangular collisions
    if ((r1x < r2x + r2w) and (r1x + r1w > r2x) and (r1y < r2y + r2h) and (r1y + r1h > r2y)) then
        return true
    else
        return false
    end
end

--[[
    for circular collisions use: {where c is circle and r is radius}
    if distance(c1x,c1y,c2x,c2y) <= 1/2 * (c1r + c2r) then
        return true
    end
    distance can be found in my lookat library
--]]

function map_collision(r1x,r1y,r1w,r1h,flag)
    check = false
    for i=0,15,1 do
        for j=0,15,1 do
            if collision(r1x,r1y,r1w,r1h,i*8,j*8,8,8) and fget(mget(i,j),flag) then
                check = true
            end
        end
    end
    return check
end

--example
function _init()
    r1 = {x=rnd(97), y=rnd(33)}
    r2 = {x=rnd(97), y=rnd(33)}
    p = {x=0, y=72}
end

function _update60()
    if btnp(❎) or btnp(🅾️) then
        run()
    end
    if btn(➡️) then
        if p.x < 120 then
            p.x += 1
        end
    end
    if btn(⬅️) then
        if p.x > 0 then
            p.x -= 1
        end
    end
    if btn(⬇️) then
        if p.y < 120 then
            p.y += 1
        end
    end
    if btn(⬆️) then
        if p.y > 63 then
            p.y -= 1
        end
    end
end

function _draw()
    cls()
    rect(r1.x,r1.y,r1.x+30,r1.y+30,8)
    rect(r2.x,r2.y,r2.x+30,r2.y+30,12)
    map(0,0,0,0,16,16)
    spr(1,p.x,p.y)
    print(collision(r1.x,r1.y,30,30,r2.x,r2.y,30,30),0,0,7)
    print(map_collision(p.x,p.y,8,8,0),0,64)
end

Cart #a_star-0 | 2020-12-09 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

--a* library
--andy c
function pathfind(sx,sy,gx,gy) --thanks to picozine 4
showpath = false
showsearched = false
 start = {sx,sy}
 goal = {gx,gy}

 frontier = {}
 insert(frontier, start, 0)
 came_from = {}
 came_from[vectoindex(start)] = nil
 cost_so_far = {}
 cost_so_far[vectoindex(start)] = 0

 while (#frontier > 0 and #frontier < 1000) do
  current = popEnd(frontier)

  if vectoindex(current) == vectoindex(goal) then
   break
  end

  local neighbours = getNeighbours(current)
  for next in all(neighbours) do
   local nextIndex = vectoindex(next)

   local new_cost = cost_so_far[vectoindex(current)]  + 1 -- add extra costs here

   if (cost_so_far[nextIndex] == nil) or (new_cost < cost_so_far[nextIndex]) then
    cost_so_far[nextIndex] = new_cost
    local priority = new_cost + heuristic(goal, next)
    insert(frontier, next, priority)

    came_from[nextIndex] = current

    if (nextIndex != vectoindex(start)) and (nextIndex != vectoindex(goal)) then
      if showsearched then
        mset(next[1],next[2],19)
      end
    end
   end 
  end
 end

 current = came_from[vectoindex(goal)]
 path = {}
 local cindex = vectoindex(current)
 local sindex = vectoindex(start)

 while cindex != sindex do
  add(path, current)
  current = came_from[cindex]
  cindex = vectoindex(current)
 end
 reverse(path)

    for point in all(path) do
        if showpath then
            mset(point[1],point[2],18)
        end
    end
    reverse(path)
    for point in all(path) do
        targetx = point[1]
        targety = point[2]
    end
end

-- manhattan distance on a square grid
function heuristic(a, b)
 return abs(a[1] - b[1]) + abs(a[2] - b[2])
end

-- find all existing neighbours of a position that are not walls
function getNeighbours(pos)
 local neighbours={}
 local x = pos[1]
 local y = pos[2]
 if x > 0 and (mget(x-1,y) and not fget(mget(x-1,y),0)) then
  add(neighbours,{x-1,y})
 end
 if x < 15 and (mget(x+1,y) and not fget(mget(x+1,y),0)) then
  add(neighbours,{x+1,y})
 end
 if y > 0 and (mget(x,y-1) and not fget(mget(x,y-1),0)) then
  add(neighbours,{x,y-1})
 end
 if y < 15 and (mget(x,y+1) and not fget(mget(x,y+1),0)) then
  add(neighbours,{x,y+1})
 end

 ---[[ for making diagonals
 if (x+y) % 2 == 0 then
  reverse(neighbours)
 end--]]
 return neighbours
end

-- find the first location of a specific tile type
function getSpecialTile(tileid)
 for x=0,15 do
  for y=0,15 do
   local tile = mget(x,y)
   if tile == tileid then
    return {x,y}
   end
  end
 end
end

-- insert into start of table
function insert(t, val)
 for i=(#t+1),2,-1 do
  t[i] = t[i-1]
 end
 t[1] = val
end

-- insert into table and sort by priority
function insert(t, val, p)
 if #t >= 1 then
  add(t, {})
  for i=(#t),2,-1 do

   local next = t[i-1]
   if p < next[2] then
    t[i] = {val, p}
    return
   else
    t[i] = next
   end
  end
  t[1] = {val, p}
 else
  add(t, {val, p}) 
 end
end

-- pop the last element off a table
function popEnd(t)
 local top = t[#t]
 del(t,t[#t])
 return top[1]
end

function reverse(t)
 for i=1,(#t/2) do
  local temp = t[i]
  local oppindex = #t-(i-1)
  t[i] = t[oppindex]
  t[oppindex] = temp
 end
end

-- translate a 2d x,y coordinate to a 1d index and back again
function vectoindex(vec)
 return maptoindex(vec[1],vec[2])
end
function maptoindex(x, y)
 return ((x+1) * 16) + y
end
function indextomap(index)
 local x = (index-1)/16
 local y = index - (x*w)
 return {x,y}
end

function atan(x1, y1, x2, y2)
--finds the angle between x & y
    local x = x2 - x1
    local y = y2 - y1
    return atan2(x, y)
end

function distance(x1, y1, x2, y2)
    return sqrt((x2 - x1) ^ 2 + (y2 - y1) ^ 2)
end

function lookat(x1,y1,x2,y2)
--steps x toward y
    local x = cos(atan(x1,y1,x2,y2))
    local y = sin(atan(x1,y1,x2,y2))
    return x,y
end

--example
function _init()
  box = {x=1,y=1}
  coin = {x=rnd(13)+1,y=14}
  target = {x=0,y=0}
  step = 0
end

function _update60()
    pathfind(flr(box.x),flr(box.y),flr(coin.x),flr(coin.y))
    vx,vy = lookat(box.x,box.y,targetx,targety)
    if step >= 1 then
        box.x += vx
        box.y += vy
        step = 0
    end
    step += .05
    if distance(flr(box.x),flr(box.y),flr(coin.x),flr(coin.y)) <= 1 then
        run()
    end
end
function _draw()
 cls()
 spr(17,box.x*8,box.y*8)
 spr(16,coin.x*8,coin.y*8)
 mapdraw(0,0,0,0,16,16)
end
P#85188 2020-12-09 10:38 ( Edited 2020-12-10 05:24)
1

Cool!

P#85190 2020-12-09 13:29

Yo, so you can make some token and efficiency improvements to the collision library:

--collision library
--andy c
function collision(r1x,r1y,r1w,r1h,r2x,r2y,r2w,r2h) --rectangular collisions
    return r1x < r2x + r2w and
        r1x + r1w > r2x and
        r1y < r2y + r2h and
        r1y + r1h > r2y
end

For map collision, you can actually go much further:

function map_collision(r1x,r1y,r1w,r1h,flag)
    for x = max(0,r1x\8), min(15,(r1x+r1w-0x0.0001)\8) do
        for y = max(0,r1y\8), min(15,(r1y+r1h-0x0.0001)\8) do
            if (fget(mget(x,y),flag)) return true
        end
    end
end

You can calculate the map coordinates that a rectangle covers using integer division by 8 (the \ operator), which will save a very significant number of cycles in the long run, especially when dealing with many checks. We also don't even need the rectangle collision function this way.

Lua numeric for evaluates the expressions only once (unlike other languages), so we can inline this integer division with the clamping for extra token and cycle efficiency. The clamps here are based on the same 16x16 map size you targetted but could be easily adjusted to make them parameters.

We can also return true directly within the for loop, which will save cycles if a collision is detected. I've used Pico8's shorthand if form too to save 1 token. I've also omitted any explicit return false since it adds cycles and tokens; this version returns nil which is falsy anyway.

The last quirk is the -0x0.0001, which is a correction for sub-pixel-perfect collision: a rect with x=0,w=8 would evaluate as covering the map tiles from x=0 and x=1, when what we probably want is for it to only cover x=0. Thanks to the brilliant and amazing decision to use 16.16-bit fixed-point numbers, we can easily correct by subtracting 0x0.0001, the smallest possible value. This means that any x that is even a hair larger than x=0 will cover both map x=0 and x=1 correctly.

I actually wrote my platformer logic for Find Gold to be sub-pixel-perfect for both collision and rendering and I've been meaning to get around to cutting it out and making it available as a snippet, but I wanted to make it more complete as a platformer library. It's collision mechanism is also more advanced, as it does sweep tests to calculate how far an entity can move along the given direction, although they are not full vector sweep tests and treat the axes independently (I believe I did horizontal first in order to give the player leeway on landing jumps).

P#85884 2020-12-29 15:52 ( Edited 2020-12-29 16:05)

Thanks for that work !

P#121162 2022-11-22 12:52

@Ender42, I am looking at your mouse program. It shouldn't be that complex I think ...

Cart #movemouse-0 | 2022-11-22 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

Without comments it's quite a bit smaller.

function _init()
  poke(0x5f2d,1)
end
function _update()
  cls()
  x,y=stat(32),stat(33)
  spr(1,x,y)
end
P#121186 2022-11-22 18:15

[Please log in to post a comment]

Follow Lexaloffle:          
Generated 2024-03-28 09:57:07 | 0.028s | Q:38