A list of helpful libraries and code

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

Often you will come across Lua modules/libraries that expect to be included through use of require, but this does not exist in the Playdate SDK world.

Here is an easy way to make these libraries compatible. I'm interested to hear if there is a better/more correct way to do this!

Example: the Underscore library

  1. in the module: remove local declaration

    @underscore.lua: line 27
    local Underscore = { funcs = {} }
    to
    Underscore = { funcs = {} }

  2. in your code:

    import "underscore"
    __ = Underscore:new() -- double underscore
    
  3. example use, two ways to print each element of a table:

    __.each({1, 2, 3}, print)
    __({1, 2, 3}):each(print)
    

Underscore.lua is a Lua library that provides a set of utility functions for dealing with iterators, arrays, tables, and functions.

3 Likes

Yes this is the correct way to do it right now.

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)

2 Likes

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. :playdate_question:

2 Likes

@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:

import 'underscore'
__ = Underscore:new()
1 Like