Skip to main content

On Sale: GamesAssetsToolsTabletopComics
Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines
(+2)

thank u SO much, I've seen some of your work and it really inspired me to try animating some little things (working my way up to bigger ones!) I'll definitely check out the thread and ask more questions if I have them.

millie gave me a basic explanation that one way of animating in decker is basically layering canvases and timing them, and that the best way to draw on a canvas is to draw on the bg and then "copy to canvas"?  is this the logic behind zazz? 

if it's ok, can you also explain the benefits/mechanics of zazz vs puppeteer? millie also gave a basic intro to me saying zazz was better for looping bg animations and puppeteer was optimal for cutscenes? can u tell me more? 

(1 edit) (+3)

Well, gosh! I'll try!

Where to begin.... Yeah, if you're drawing directly in Decker you create the image data by drawing with the usual tools on the back of the card and then Copy -> (switch over to widget mode) - > Paste as new Canvas.

(And just in case anyone didn't know... turn on the Toolbars in the Decker menu... only one click to switch between modes and tools. Very handy.)

The best and most flexible place to draw is directly on the card but Canvases ("the image container widget") interact more easily with scripts and modules. And therefore, usually, animation. So, yep, move things into canvases when you're done drawing (or if you're ready to test something before you commit the time to finish your drawing! )

(The thread linked previous is an exception! That one was about making a looping animation while still being able to draw on the card.)

---

Depending on what I'm doing I will sometimes layer canvases on the card... it works just fine for small stuff. 

Basically, to do that, you change the visibility of the canvas widgets in a script -- hiding one and showing another in the same moment, and then creating a delay before doing the same thing again for the next image.

But... if you're making something super complicated... well, having several stacks of overlapping canvases can get a little unwieldy? 

Especially if you need to edit one single frame of it later. (Ask me how I know... no, actually don't....)

But there are other ways to do it! These days I often use copy[] and paste[] in a script instead. That is to say.... the image information is still all stored in canvases but instead of changing the visibility of lots of things in a big stack of widgets, I just change the image data on one single Canvas.

Soooo, examples:

Changing the visibility of three stacked canvases with .show (and sleep[] for the timing) might look like this:

frame1.show:"solid"
sleep[10]
frame1.show:"none"
frame2.show:"solid"
sleep[10]
frame2.show:"none"
frame3.show:"solid"
sleep[10]
frame3.show:"none"

While copy[] and paste[]-ing three images onto one canvas in sequence might look like this:

targetcanvas.show:"solid"
targetcanvas.paste[frame1.copy[]]
sleep[10]
targetcanvas.paste[frame2.copy[]]
sleep[10]
targetcanvas.paste[frame3.copy[]]
sleep[10]
targetcanvas.show:"none"

Personally, I find the second one easier to write and read but everyone is different and there's more than one way to make a sequence of images play. :)

A bonus visual example of what I mean.... take this animation:


In widget mode it's just one canvas and a button...


And over here (stored somewhere else) a sequence of images canvases that are 'played' on that one canvas when you click that button.

And this way it's still easy to copy the image back out of a canvas, edit it, and then paste it back in if I need to.

---

I tend to think of Zazz as a set of small tools for creating animated flourishes -- everything it does creates a loop but there's a few different kinds of loops.

And all of these things could be done by scripting it yourself! (but having it ready to go in a module makes it much simpler.)

Listing off a couple zazz options that I think are handy for the stuff I like to make:

.flipbook[] is like a normal looping gif where it plays a series of frames over and over on a canvas (also the thing in the previously linked thread is a secondary usage of flipbook -- to test how animations look, even if they're going to be handled by something else when they're done)

.bob[] makes a canvas bob up and down

.march[] makes a canvas move around different waypoints on the card.

And those are all really nice and useful on their own, but you can also mix them together, like this:


The butterfly's wings are a tiny .flipbook[] animation, the small up and down movement is from .bob[] and the back-and-forth is because the butterfly is .march[]-ing between waypoints (hidden widgets) on the two flowers.

Or, here's a case of using zazz.flipbook[] as a small part of a bigger interactive kind of thing:

In this example, a draggable canvas is being used with another module (called 'rect', from the 'draggable' example deck) that helps build certain kinds of interactions when you want your player to be able to drag things around.

When the rect module sees that the draggable canvas is overlapping the hidden 'rect'angle (lol) of the treasure map, it plays a zazz.flipbook[] animation on the canvas as if it's looking for something.... And then the thingy turns yellow if overlapping the 'rect'angle of the "X".

These are very silly little examples, but hopefully this helps explain my perspective on Zazz

It's a set of tools that are very helpful for certain kinds of looping effects and they generally combine well with other things.

---

So, for Puppeteer... the name is very well chosen. 

We're preparing Puppets and Props for a Decker stageplay. 

They move around positions on the card however we tell them to.

The main 'downside' is that there's a little more set-up involved here. It's really not bad at all but, for example, each Puppet needs it's own separate storage card (dressing room?) full of all the canvases for each of it's poses and expressions. It takes little longer to get everything put in the right places, but it's very powerful and flexible after that.

And planning a complicated scene with Puppeteer is a bit like going through rehearsals of a stage production. Tweaking the timing and positions and what expressions the actor-puppets should have, and so on.

(Obviously this is more of a 'vibes' explanation than a technical one....)

If zazz was the tool for looping animation, I usually think of Puppeteer as a tool for things that move forward in time. Cut scenes, visual novel conversations, interesting flourishes (sometimes).

This is one of the little examples from backstage in The Riddle:

You can see the little circles (tiny buttons) that are used to mark locations, and the directions for telling the bird puppet where to move around, how fast and when to change pose.

And technically Puppeteer does have some 'looping' traits such as the built-in blink, the mouth movement while talking with Dialogizer (you still have to draw them, but the coding side is handled) and fancy puppets -- which are probably a little too much for this conversation, but there's even more complexity available here for someone comfortable scripting in Lil.... 

But yeah, generally I would use Puppeteer for "scenes", rather than loops or idle animations.

---

And one more option for making moving pictures: Contraptions. 

These are really just basic widgets and Lil scripts pre-assembled together into a new kind of custom widget -- you can make your own but also there's a bunch of options already pre-made for your convenience in the contraption bazaar thread.

This small section of Desker has two small zazz.flipbook[] animations -- the cat's tail and the curtain. 

It also has three contraptions -- the eye contraption for the cat's eyes, the pop out contraption for the window opening and closing, and the rotor contraption for the inside of the plasma ball in the top right.

---

Anyway, this was a lot. Sorry if it's confusing! 

Basically, we have a lot of little tools available that have slightly different abilities and no one single "correct" workflow. 

It's completely fine to just use what makes sense to you for now, and then try new things later when you feel like it.

(And, of course, I'm happy to clarify anything I said here... !)

(+3)

thank you SOOOO so much for patiently explaining all of this! it's super helpful i'm going to digest it a bit and see what i can come up with but i think i understand the logic more! i really appreciate all your help!

(+2)

hi (covers face) It's Me Again 

thanks for the tip about the toolbars!

OK next question i got the animation running (used the canvas overlaying bc that's what made most immediate sense to my workflow) and i put the on view do / end frames script onto a canvas, but it seems like when the script is running the buttons and other widgets don't work while it's looping on view? i did some variants like only accessing the animation on click rather than looping, but while the animation is running on click it has to cycle completely before buttons become accessible again (and ofc the on click means it doesn't loop? but just cycles once)

im using millie's janky sequencer and jankytunes but even with her new trick to play continously across cards, it completely pauses on this card with the animation. and i added the retune button to try and start the music player manually, but it doesn't work, either.

pls lmk what i'm doing wrong lmao!! i've been staring at this for hours bc im so embarassed i don't understand

(4 edits) (+2)

Oh no! Don't worry, this was my bad. I didn't explain enough about 'sleep' -- it puts everything in Decker to sleep for the duration, including the ability to click buttons, or Jankytunes' ability to 'draw' music.

This is (usually) fine for the silly things I get up to but obviously you've run into one of the major downsides and I'm really sorry I didn't clarify this aspect of sleep[] in the previous post.

(I'm going to edit this shortly but I just wanted to assure you first that you're not doing anything wrong.)

Is your ideal outcome for this card a looping animation? And does it use consistent timing between frames?

EDIT: (I forgot about this one...) Inside of Wigglykit (there's a contraption which lets you paste a series of images inside of the properties menu that opens up from the widget. You can copy it from the Wigglykit deck or copy-paste this into your deck:

%%WGT0{"w":[{"name":"wp1","type":"contraption","size":[69,63],"pos":[115,92],"script":"on release pos do\n if snap.value me.pos:pos end\nend","def":"wigglyPlayer","widgets":{"c":{"size":[69,63],"draggable":1},"fr":{"size":[69,20],"value":{"text":["i","i","i"],"font":["","",""],"arg":["%%IMG2AEUAPwD/AP8A/wAsAQEAKwECABMBAgABAQMAKQELAAoBCAAhAQIABgEOAAEBAgACAQkAIAEEAAYBGwAgAQMACgEBAAMBDQADAQQAIAEDAA8BAgADAQUABQEDACEBAwAdAQMAIgEDAB0BBAAhAQMAHgEEACABAwAfAQMAIAEDAB4BAwAhAQMAHgEDACEBAwAeAQMAIQEDAB4BAwAhAQMAHgEDACEBAwAeAQMAIQEDAB4BAwAhAQMAHgEDACEBAwAdAQQAIQEDAB0BAwAiAQMAHQEDACIBAwAdAQMAIgEDAB0BAwAiAQMAHAEEACIBAwAcAQQAIgEDABwBBAAiAQMAHAEEACIBAwAdAQMAIgEDAB0BAwAhAQMAHQEEACEBAwAeAQQAIAEEAAoBAgACAQIADAEEACEBBQAGAQ4AAQEKACIBBQACARsAJAEfACUBCwAHAQEAAQECAAIBAgABAQEAAgEBACcBBgBAAQMA/wD/AP8AtA==","%%IMG2AEUAPwD/AP8A/wAuAQEAKgEBAAIBAwARAQUAKAEJAAMBAQAIAQgAIAEBAAgBHAAfAQMACAEEAAEBFQAfAQUACAECAAIBFAAhAQQADQECAAEBAwAHAQEAAgEDACEBAwAdAQMAIgEDAB0BAwAjAQMAHAEEACIBAwAcAQMAIgEEABwBBAAhAQMAHQEEACEBAwAdAQMAIgEDAB0BAwAiAQMAHQEDACIBAwAeAQMAIQEDAB4BAwAhAQMAHgEDACEBAwAeAQQAIAEDAB4BAwAhAQMAHgEDACEBAwAdAQQAIAEDAB8BAwAgAQMAHgEDACEBAwAeAQMAIQEDAB4BAwAhAQQAHQEDACIBAwAcAQUAIQEDABwBBQAhAQMAHQEEACEBAwAcAQQAIgEEAAoBAQADAQQABQECAAEBBgAiAQMACQEDAAEBEgAiAQcAAwEYACIBDwABAREAJQENAAMBAQADAQEABAEBAAQBAQAlAQwAOgEBAAEBAgD/AP8A/wC1","%%IMG2AEUAPwD/AP8A/wAsAQEALAEBABYBBAAgAQEACAEJAAEBAQACAQIABwEHAB4BAwAIARAAAgEJACABAwAHARoAIQEDAAgBAQACAQEAAgEGAAEBCAABAQMAIQEDAA8BAgABAQEABAECAAIBAQACAQMAIQEDAB8BAwAgAQMAHgEEACABAwAdAQQAIQEDAB0BAwAiAQMAHgEDACEBAwAeAQMAIQEDAB4BAwAhAQMAHgEDACEBAwAeAQMAIQEDAB4BAwAhAQMAHgEDACEBAwAeAQMAIQEDAB4BBAAgAQMAHgEDACEBAwAeAQMAIQEDAB0BAwAiAQMAHgEDACEBAwAeAQMAIQEDAB0BAwAiAQMAHAEEACIBAwAcAQQAIgEDABwBBAAiAQMAHAEEACIBAwAcAQQAIgEDABwBAwAjAQMAHAEEACMBAwAEAQIAAwEBAAEBCAACAQoAJAEDAAEBHgAiAR8AAQECACIBCAACAQYAAgEHAAEBAQABAQIAKAEFAEEBAgD/AP8A/wC1"]}},"or":{"size":[69,20],"value":"[0,1,2]"},"fl":{"size":[41,20],"pos":[0,111]},"em":{"pos":[78,-49]},"cbo":{"size":[100,13],"pos":[78,0]},"cst":{"size":[100,12],"pos":[78,20]},"cd":{"size":[100,13],"pos":[78,38],"value":1}}}],"d":{"wigglyPlayer":{"name":"wigglyPlayer","size":[100,100],"resizable":1,"margin":[1,1,1,1],"description":"A viewer and storage location for wiggly animations, which is also suitable as a \"Fancy Puppet\" for use with Puppeteer.","version":1.2,"script":"on frames do fr.images end\non order  do or.data end\n\non set_value x do\n if x.frames fr.images:x.frames..copy[] end\n or.data: (range count x.frames) unless x.order\nend\non get_value do\n r.frames:frames[]\n r.order :order[]\n r\nend\n\non view do\n f:frames[]\n o:order[]\n c.border:cbo.value\n c.show:card.show\n c.draggable:cd.value\n c.clear[]\n if count f\n  i:f[o[(count o)%sys.frame/5]]\n  i:if fl.value i.copy[].transform[\"horiz\"] else i end\n  if get_stretch[]\n   c.paste[i 0,0,card.size]\n  else\n   c.paste[i .5*c.size-i.size]\n  end\n end\nend\n\non get_border do cbo.value end\non set_border x do c.border:x cbo.value:c.border end\non get_stretch do cst.value end\non set_stretch x do cst.value:x end\non get_frames do fr.value end\non set_frames x do fr.value:x end\non get_order_text do \",\" fuse order[] end\non set_order_text x do or.data: 0+\",\" split x end\non get_animate do view end\non get_flip do fl.value end\non set_flip x do fl.value:x end\non get_emotes do em.data end\non set_emotes x do em.data: x end\non set_emote x do or.data:() unless get_emotes[][x] end\non get_draggable do cd.value end\non set_draggable x do cd.value:x end\n","template":"on click do\n \nend\n\non drag do\n \nend\n\non release do\n \nend","attributes":{"name":["border","draggable","stretch","frames","order_text"],"label":["Border","Draggable","Stretch to Fit","Frames","Frame Order"],"type":["bool","bool","bool","rich","string"]},"widgets":{"c":{"type":"canvas","size":[100,100],"pos":[0,0],"locked":1,"animated":1,"volatile":1,"script":"on click pos do\n card.event[\"click\" pos]\nend\non drag pos do\n if get_draggable[]\n  card.pos:pos+(pointer.pos-pointer.start)\n  me.pos:0,0\n  card.event[\"drag\" pos]\n end\nend\non release pos do\n if get_draggable[]\n  card.event[\"release\" pos]\n end\nend","border":1,"scale":1},"fr":{"type":"field","size":[100,20],"pos":[0,-77],"locked":1,"show":"none"},"or":{"type":"field","size":[100,20],"pos":[0,-49],"locked":1,"show":"none","style":"plain"},"fl":{"type":"button","size":[60,20],"pos":[0,148],"locked":1,"show":"none","style":"check"},"em":{"type":"field","size":[100,20],"pos":[109,-49],"locked":1,"show":"none","style":"plain"},"cbo":{"type":"button","size":[100,20],"pos":[109,0],"locked":1,"show":"none","style":"check","value":1},"cst":{"type":"button","size":[100,20],"pos":[109,31],"locked":1,"show":"none","style":"check","value":1},"cd":{"type":"button","size":[100,20],"pos":[109,61],"locked":1,"show":"none","style":"check","value":0}}}}}

This might also work for what you're doing.

( your project is looking amazing!)

thanks so much!!! also my friend figured out a workaround using Some Kind of Wizardry but it had to do with how I was implementing jankytunes? I want to both have looping bg animations And on-click animations. I think the workaround? was to use millies play across cards thing but to fix a few bugs I had in my implementation. 

that makes sense about sleep!! 

ty for ur support, I  hope to share this game at the end of the month for the decker jam! 

Hi, just want to check what the issue with jankytunes was here - was it just that it paused when the script was in "sleep" mode or was it something else?

There are some tricks you can do, like writing a little loop in the code where, instead of sleeping for 60 frames, it calls the animation bits and then sleeps for 1 frame, 60 times. I think in lil you'd use like an "each x in range 60" thing to do this? But you'll probably want to read the lil doco for more details.

It sounds like you got it sorted out though?

yes! that's the issue, it's paused when the script is in sleep mode. (actually the workaround didn't end up working, so i'd love some more help with it, i'll dm you on discord

(+2)

For the benefit of others in the thread, this is what I suggested

each x in range 60
 #do the thing that you need for the animation
 sleep[1]
end
(+3)

and it worked like a charm!! :3

(2 edits) (+2)

To de-mystify a little, when Dialogizer is displaying a dd.say[] modal, it's doing something similar to the following:

while !pointer.down
 blink_cursor[]
 deck.card.event["animate"]
 sleep[1]
end

The "animate" event which is being sent to the current card provides the opportunity to make other things happen while the dialog box is idling and waiting for user input. Puppeteer uses this hook to animate puppets, and it can be used to give other modules and contraptions a chance to do their thing as well.

Any "blocking" scripts you write yourself can use a similar approach; a synchronous animation script becomes responsible for explicitly routing the events to everything that needs to do work on every frame, whereas an asynchronous script allows Decker to handle some of that plumbing automatically.

(+1)

thanks!!! :0

(+2)

Just in case it helps, the Decker reference manual has a section about scripted animation that describes "synchronous" vs. "asynchronous" approaches and the tradeoffs therein. I also recently wrote an article about making action games in Decker that talks about similar issues through a more practical/applied lens.

thanks!!!!!