cadin / panels

Build interactive comics for the Playdate console.
Creative Commons Attribution 4.0 International
158 stars 8 forks source link

Add ability to set & read global variables #14

Closed cadin closed 1 year ago

cadin commented 1 year ago

Can there be some way to use variables within the comic without needing to implement custom functions?

Example use case:

ledbetter-art commented 1 year ago

that's pretty much exactly what i'm looking for. the ability to add interaction (explicit or non-explicit) to panels that then affect branching path options later on.

alberteddu commented 1 year ago

Edit: I realize this depends on the developer using custom functions. The goal was to avoid that. I failed to notice this detail in the description of the issue. I still think dynamic sequences can be useful but ignore that suggestion for the purpose of this specific problem.

Hey! First of all, thanks for creating this. I have looked for a way to have some sort of dynamic behaviour with panels.

As far as I can understand the code, it seems to me that once the comic data is given, it's fixed and can never be changed later.

So maybe a possible way to do what is suggested (global variables) is to simply allow the dynamic creation of sequences. What I mean is, that when panels expects the next sequence to go to (such as after a branching decision) a sequence can be returned instead of an index.

This will unfortunately make other features harder or impossible (such as having a complete table of contents) but will open the door to basically any scenario, since even completely dynamic sequences can be built on the fly by the code using the panels library.

At that point setting and retrieving global variables is automatically possible by simply using Lua variables.

I was planning on doing a change like this in a fork of panels for my own project. I can share that if and when that is available, but @cadin has maybe a different idea on how to tackle this.

cadin commented 1 year ago

It's unlikely that I'll be able to make this happen without still needing custom functions. Right now I'm thinking the best solution might be to just make it easier to work with custom functions for things like this. For example maybe having an update function that you can use to set variables or do other logic without needing to take over rendering of the entire panel. Still thinking about it...

@alberteddu Can you say more about your exact use case? You want to dynamically change the content of the sequences? Is it because there are too many variations to use the existing branching pattern? Or are you wanting to insert fully dynamic elements (like showing the user's name in the comic)? Or something else?

If you've already thought about how you might implement what you need I'd be interested to hear.

alberteddu commented 1 year ago

Yes, that's correct. What I am working on cannot be described with a branching narrative, as it's completely dynamic with some exceptions. However, the way panels works otherwise is just perfect, and that's why I thought of simply adapting it to my needs.

I think you are right about custom functions. At the moment you either use panels without any customisation (by simply passing comic data as expected) or by using custom functions. With the second option you can do basically anything, but you also lose all the other stuff that panels does for free. And furthermore, you're still trapped in the original list of sequences that were passed during setup.

So what I was planning to do was to change panels so that it would offer increasingly generic escape routes that would allow me to more or less depend on how panels works out of the box. I can be more specific. At the extremities the options that are already available (no custom functions, or custom functions). In between, I had in mind two different possibilities.

A) (very strong customisation but still using panels' drawing) Pass sequences to panels on the fly. The idea was to change targetSequenceFunction so that it could return a table instead of an index (the sequence to go to next). I already thought that this sort of breaks the flow of the comic (even more than branching narratives already do), since this new sequence is outside of the original list, which means there is no obvious "next sequence". This sequence would be forced to specify a "next sequence" and probably cannot appear in the index. This problem was fine for me, since I would only use dynamically provided "next sequences". Also, this option would require changing heavily how the comic data is stored internally (I think).

B) (slightly less customisation but still very useful, still using panels' drawing) Have sequences not be static. At the moment once a table representing a sequence is created, it simply stays that way. I thought I would change the code (specifically drawLayers) so that it would check if (for example), panel.layers is a function instead of a table. If it was, it would then call it to get the layers dynamically, passing all the other information as arguments (x or y offset, etc.). After that, drawLayers can simply draw whatever layers it has received in that frame. This would allow the developer to still use the panels' rendering system but you can already achieve quite a lot with this. A possible problem with this approach is that if the code that is being called to get the layers is not very optimised, the performance will suffer.

(An extra change I had in mind was the possibility of passing image objects to panels instead of paths. Now the keys image, images, etc. only accept paths. This way if a layer that appears in a sequence is totally dynamic (instead of being one of X choices; think, for example, showing the PIN code of a safe in a game where the PIN code is different for each playthrough) you can still do that without custom functions).

These two (and a half) things together with advanceFunction + auto-advance (for those panels in which I wanted some extra interaction/puzzle solving/other) would fix every problem for me.

I would like to point out that I considered my use case an edge case, and for this reason I was ok with A and B being quite heavy modifications of the way panels works. I don't know if this is the right way to go for the implementation in general (some might argue that it is in the nature of comics (even branching comics) to retain some inflexibility) so I didn't really put a lot of thought into other consequences. However since you asked I thought it useful to share (and sharing doesn't cause any harm anyway).

However this problem is tackled, anyway, I am of course available to help (testing changes, contributing).

alberteddu commented 1 year ago

For example maybe having an update function that you can use to set variables or do other logic without needing to take over rendering of the entire panel. Still thinking about it...

This sounds interesting. Of course there's comic navigation to take into account, if this update function is using user input as well. I mean if you're using this extra update function together with input handlers, that means comic advancement is probably handled by the game itself in those panels (I think this compromise is worth it though).

cadin commented 1 year ago

@alberteddu Thanks. that's helpful. It sounds like this change wouldn't completely satisfy your needs, so probably best to keep hacking on your own fork.

One option you could consider would be to build your entire game using the cutscene feature. You might not have any "game" elements outside of Panels, but it would give you the opportunity to run logic between each sequence and potentially dynamically create sequences, since they can be passed to Panels one at a time.

Pseudocode:

local sequenceNumber = 1

function cutsceneDidFinish(target)
   sequenceNumber = sequenceNumber + 1
    local comicData = dynamicallyCreateComicDataForSequenceBasedOnUserChoice(sequenceNumber, target)
    Panels.startCutscene(comicData, cutsceneDidFinish)
end

You'd lose the title screen and chapter menu stuff, but it sounds like that won't be much use to you anyway.

alberteddu commented 1 year ago

I see, that's interesting. I think I will actually go that way. Thanks a lot for your help!

cadin commented 1 year ago

A robust solution for this would require pretty major changes to how Panels works. As you say @alberteddu, you'd probably need to be able to pass executable functions to panels and layers. I'm worried that even if I could make that work, it wouldn't get much use because it would be so complex.

In lieu of that, I've made a few simple changes to hopefully make it easier to accomplish this through custom functions:

  1. Global Variables You can now read and write to global variables by sticking them in Panels.vars. Technically, you can stick them anywhere, but variables in Panels.vars get saved to disk and restored when the user comes back to the game. Neat!

  2. Update Function As mentioned above, I added an updateFunction property that lets you run custom logic for a panel without having to take over rendering everything yourself.

  3. Layer Rendering To really do things like conditional rendering or dynamic layer updates, you'll need to use a custom render function. I made that a little bit easier. Now you can pass a layer to Panels.renderLayerInPanel() to have Panels draw the layer for you. This means you can choose which layers to render, and even modify their properties in real time, but still have Panels calculate parallax and draw them to the screen.

All of this is available if you pull down the latest Panels. Documentation is a bit rough for now. I'll be working on that soon.

In the meantime, here's an example project that shows everything in action: 📦 https://github.com/cadin/panels-item-example

And a walkthrough video of the project where I say everything above over a much longer period of time: 📺 https://youtu.be/VNswT0y0VP8

I hope y'all can make that work. Let me know if you need any help.