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/
-- 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
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.
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
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.
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?
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?
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
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.
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 :
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.
A lot of lua libraries declare their main table as local and return it at the end to avoid issues if a library use the same name as other global object in a code base.
So while doing so you just need to be sure you do not declare another variable called Underscore (or whatever is the name of the table that is returned for any given library)
This is great both for (a) confirming this isn't a dangerous hack, because I got one library to work this way but was not sure I'd done the right thing, and for (b) letting me know there's a version of Underscore for Lua! I use that all the time in javascript. Thanks!
Hmm, I kinda wonder if the Playdate's import function could have an optional param to automatically turn return values from imported files into into globals so we didn't have to modify a third-party library's code.
@matt I wasn't able to use the object-oriented style in underscore after importing this way. But I figured out that it works if I changed the last line of underscore.js to __ = Underscore:new()
At first, I set __ = Underscore after importing, but got the same results if I used Underscore in place of __. So in my main.lua, I had:
import 'underscore'
__ = Underscore
This worked for the standard invocation. So say self.points is a table of {x, y} points. I get the expected table of ints matching the x values if I run local xPlucked = __.pluck(self.points, 'x') ,
but if I try local xPlucked = __(self.points):pluck('x')
I get this error: attempt to call a table value (global '__')
Eventually I tracked this down to needing to assign the variable directly to Underscore:new(), rather than the return. Ultimately I just commented out the last line of underscore.lua, and set the var after importing, like this: