A list of helpful libraries and code

Today I finally cleaned up a library that I've been using since quite a while now and that I find very helpful to add animation.

sequence.lua.zip (2.5 KB)

It allows you to play a sequence of easings. It's so much easier to add more interesting or complex animations.

In our game I'm still using my own easing library (that I also used for quite some time) so I had to refactor a bit to use the playdate ones. Maybe that introduced some bugs. Let's hope not.

So has an example, this is how I animated out logo when you arrive on the main menu.

pick_anim_x = sequence.new():from(150):sleep(0.4):to(50, 0.3, "outCirc"):to(0, 0.5, "outExpo")
pick_anim_y = sequence.new():from(-240):sleep(0.4):to(0, 0.3, "inQuad"):to(-30, 0.2, "outBack"):to(0, 0.5, "outBounce")

pack_anim_x = sequence.new():from(150):sleep(0.2):to(50, 0.3, "outCirc"):to(0, 0.2, "outExpo"):start()
pack_anim_y = sequence.new():from(-240):sleep(0.2):to(0, 0.3, "inQuad"):to(-30, 0.2, "outBack"):to(0, 0.5, "outBounce")

pup_anim_x = sequence.new()
pup_anim_y = sequence.new():from(-240):to(0, 0.5, "outBack")

And this is the result
logo
SequenceExample.zip (27.9 KB)

In your code you just have to call the update function
sequence.update()

To trigger an animation just call
pick_anim_x:start()

And to get the value
pick_anim_x:get()

That's it :playdate_relaxed:

Update September 2022
it is on GitHub now GitHub - NicMagnier/PlaydateSequence: Create animations with simple sequences of easing functions in your playdate game

19 Likes

Super cool! I'll use it

Here a list of little functions I find handy. Nothing fancy or even technical but some good QoL.

Clamp

function math.clamp(a, min, max)
    if min > max then
        min, max = max, min
    end
    return math.max(min, math.min(max, a))
end

Not sure why clamp is still not part of lua but this one is a must have.

Ring

function math.ring(a, min, max)
    if min > max then
        min, max = max, min
    end
    return min + (a-min)%(max-min)
end

function math.ring_int(a, min, max)
    return math.ring(a, min, max+1)
end

Like clamp but instead of clamping it loop back to the start. Useful to cycle through values, for example an index in a menu
index = math.ring_int( index + 1, 1, 4)
-> 1, 2, 3, 4, 1, 2, 3, 4 etc.

Approach

function math.approach( value, target, step)
    if value==target then
        return value, true
    end

    local d = target-value
    if d>0 then
        value = value + step
        if value >= target then
            return target, true
        else
            return value, false
        end
    elseif d<0 then
        value = value - step
        if value <= target then
            return target, true
        else
            return value, false
        end
    else
        return value, true
    end
end

Got this one from the Celeste source code and it so simple but so useful. It just change a value toward a target. It returns a tuple, the new value and a boolean that says if it is on target.

Approach to infinity (but not beyond)

function math.infinite_approach(at_zero, at_infinite, x_halfway, x)
    return at_infinite - (at_infinite-at_zero)*0.5^(x/x_halfway)
end

Another approach function but with this one it never reaches the target. Useful for example to balance a game mode to always increase the difficulty but have a clear ceiling. You now the lowest and highest value (at_zero, at_infinite), you specify at which point you are midway and the rest is a nice natural curve.

So for example if you want to generate new enemies that get trickier as the playtime progress. The first enemies start with just 1 health, and eventually enemies can go up to 20. We can balance that we start to see enemies with 10 health after 5 minutes.
new_enemy.health = math.infinite_approach(1, 20, 5*60, playtime_in_seconds)

Random Element in a table

function table.random( t )
    if type(t)~="table" then return nil end
    return t[math.ceil(math.random(#t))]
end

Just return a random element in an array. Would be even better if lua had an even better RNG.

Call function for each array element

function table.each( t, fn )
	if type(fn)~="function" then return end
	for _, e in pairs(t) do
		fn(e)
	end
end

As I said, nothing special but useful.
What are your little functions you couldn't live without anymore?

8 Likes

This looks great! Looking forward to trying it out.

These are great! I have a couple functions like this, too, especially math.clamp.

I also use:

math.round and math.sign

-- from http://lua-users.org/wiki/SimpleRound
-- rounds v to the number of places in bracket, i.e. 0.01, 0.1, 1, 10, etc
function math.round(v, bracket)
  local bracket = bracket or 1
  return math.floor(v/bracket + math.sign(v) * 0.5) * bracket
end
-- round needs sign:
function math.sign(v)
  return (v >= 0 and 1) or -1
end

Deep Table Copy
Duplicates a table's contents into a new table, rather than just a reference to the original:

function deep_copy(obj)
  if type(obj) ~= 'table' then return obj end
  local res = {}
  for k, v in pairs(obj) do res[deep_copy(k)] = deep_copy(v) end
  return res
end

Random Unique UUID

function UUID()
  local fn = function(x)
    local r = math.random(16) - 1
    r = (x == "x") and (r + 1) or (r % 4) + 9
    return ("0123456789abcdef"):sub(r, r)
  end
  return (("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"):gsub("[xy]", fn))
end

And I use various functional helpers from lua-users wiki: Functional Library.

3 Likes

Kubrick
A library to help organize and execute logic for action sequences in your game. It sits on coroutines and is based on the ideas in this post by Elias Daler: Redirecting to https://edw.is/

kubrick.zip (701 Bytes)

Example project coming soon.

Here's a rough example:

-- cutscene.lua

class("StopPlayerAction").extends(kubrick.Action)
function StopPlayerAction:update(dt, player)
	local a <const> = player.acceleration * dt
	player.velocity.x += math.cos(player.rotation) * a
	player.velocity.y += math.sin(player.rotation) * a
	local velocity_magnitude <const> = player.velocity:magnitude()
	if velocity_magnitude <= a then
		ship.velocity.x = 0
		ship.velocity.y = 0
		self.finished = true -- Marks the action as finished.
	end
end

function cutscene_main(dt)
	kubrick.Wait(2.0) -- wait 2 seconds.
	StopPlayerAction(globals.player) -- stop player
	kubrick.Together(
		function()
			print("this function gets run")
			kubrick.Wait(1.0)
			print("in parallel the other functions.")
		end,
		function()
			kubrick.Wait(3.0)
			print("This function finishes last because it takes the longest.")
		end
	) -- run 2 or more routines in parallel. 
end


-- main.lua

import "cutscene"

local my_cutscene <const> = kubrick.create(cutscene_main)
local cutscene_started = false

function playdate.update()
	if cutscene_started then
		kubrick.update(my_cutscene, dt)
		if kubrick.finished(my_cutscene) then
			cutscene_started = false
		end
	end
end
12 Likes

FYI, the SDK has a similar function table.deepcopy(source) which, I believe, is implemented in C so should be even faster.
Copying table is handy but it is important to specify that most versions of such function can break if there is cross reference of element in the array which would lead in a stack overflow. There is versions of deep copy that make sure it doesn't happen and some also copy all metatable infos. Anyway is most cases a simple deepcopy works very well.

1 Like

Sorry to spam this thread.

I was a bit annoyed how I was managing my sprites Z index by putting number directly in them. It's always annoying since you have to kind of remember or check the Z Index of other sprites. And you always increment you Z Index by ten just in case you need to insert stuff in between without having to change manually all indexes.

enums would be perfect for that so I wrote a dump enum function:

function enum( t )
	local result = {}

	for index, name in pairs(t) do
		result[name] = index
	end

	return result
end

I just declare my layer list at launch:

layers = enum({
	"background",
	"enemies",
	"player",
	"clouds"
})

and just use to setup my sprites
sprite:setZIndex(layer.player)

12 Likes

Is it possible to add methods to the chain? Say if you want some logic to execute some custom logic somewhere in the sequence. Apologies, I haven't looked at the code yet.

ah! yeah it's on the list of things to add. :grinning:
I also wanted that feature to trigger sounds.

Not sure when I will add the feature but I will post it here when it's ready.

1 Like

New version for sequence
sequence.lua.zip (3.1 KB)

  • Removed few sneaky bugs
  • Added callbacks
local _crash = sequence.new()
	:from( 240)
	:to( 0, 2.0,"outQuad")
	:callback(function() print("reached high") end)
	:to( 240, 0.6,"outBounce")
	:callback(function() print("crash") end)
	:loop()
	:start()
9 Likes

If a callback function could return true or false whether it has completed or not, then you could use callbacks to handle actions unaccounted for by the library that take multiple frames. Perhaps that's going to far. Assume true if no bool is returned so it works as it does today?

I am not sure exactly in which context you want to use it.

A callback would pause the whole animation and be called every frame update until it returns true. Is that what you are thinking?

It would say allow me to add a custom animation or wait for input before the animation continues. I’m specifically thinking this could replace what I’m using for action sequences but I need custom logic to, say, turn the ship around and accelerate until velocity is zero. Though now that I look at this, I think this blocks until complete so it wouldn’t work for me anyway as I need to execute the rest of the game logic while this animation does its work each frame?

Perhaps just ignore me as is a good default. :laughing:

No I think It's great to explore how something might be use in a way I would not have expected.

While such feature could be implemented a massive refactoring would be necessary to enable it. Adding logic to it would also goes away from the simple idea of just playing animations.
The callback idea was a great suggestion because you want to be able to trigger sound for example. So I would not ignore you as a default :stuck_out_tongue_closed_eyes:

Just wanna pop in here and say that I've used both @dustin's Kubrick and @Nic's Sequence libraries to do some larger animation scenes in the past week, and they've been SO helpful. I'll try to share some example code and results once I get things looking right. Thank you both so much for sharing your work, it's been super helpful to this relative newcomer. :pray: :playdate_heart:

3 Likes

Here's one WIP scene that will wind up having different animations in each orb, with art by @ryan.splendorr, with timing coordinated by a combination of Kubrick functions and playdate.graphics.animation.loops :

project_bottle-alembic-WIP-01-min

10 Likes

Hey I don't know what this is but it looks fantastic. :playdate:

1 Like

Everyone here okay with me opening this thread up to the entire forum? I think the code sharing going on here could clearly be helpful most new to the platform.

6 Likes

I agree we should move the thread

1 Like