You know what, this is a common problem people have, so I'll just explain this once and link it in the future,
EXPLANATION OF SCREEN ACTIONS AND CURRYING
When you want a button on a screen to do something, or an effect to occur when you move your mouse over something, or when you want a certain keypress to trigger something, you need to use an 'action'.
Code: Select all
textbutton "My Button" action SomeAction
The documentation
explains what an action is:
Actions are invoked when a button (including imagebuttons, textbuttons, and hotspots) is activated, hovered, or unhovered. Actions may determine when a button is selected or insensitive.
Along with these actions, an action may be a function that does not take any arguments. The function is called when the action is invoked. If the action returns a value, then the value is returned from an interaction.
An action may also be a list of actions, in which case the actions in the list are run in order.
Now, that same page of the documentation lists a whole lot of predefined actions that are available, but if you want to do something more advanced, you want to have a python function be called. (actually, if you want to do something a lot more advanced, you want to define your own Action class, but using a function is a good intermediary step)
As the documentation says:
Code: Select all
an action may be a function that does not take any arguments.
More specifically, that means you can do this:
Code: Select all
init python:
def MyFunction():
renpy.notify("My Function!")
screen MyScreen:
textbutton "My Button" action MyFunction
Note that you supply the function as an action- you don't call it. And, as the documentation say, it can't take any parameters! This is because there is no way for the action to know which params you wanted to pass it.
And you can't call it:
Code: Select all
init python:
def MyFunction(num):
renpy.notify("My Function was called with %d!" % num)
screen MyScreen:
textbutton "My Button" action MyFunction(5)
This would,
during the execution of the screen itself, call MyFunction(5), take its return value and that return value becomes the action. When the action actually executes you'll probably get an error about NoneType or something, because it didn't actually return anything. This is because that code doesn't supply MyFunction as an action- it just calls MyFunction in the process of evaluating actions.
This is not what you want, and it's why ren'py requires that you pass a function that doesn't take arguments - because it has no idea what to supply the function!
"But that's stupid! You mean if I have 5 actions that do nearly the same thing, I need to create 5 different functions that take no arguments?" Actually thankfully no.
Ren'Py has this useful little function called
renpy.curry. It's not documented in the current documentation (there is a page on the wiki, however). The explanation of it in the docstring is:
Takes a callable, and returns something that, when called, returns something that when called again, calls the function. So basically, the thing returned from here when called twice does the same thing as the function called once.
That might take a bit to wrap your head around, but uh, basically:
Code: Select all
a = renpy.curry(MyFunction)
b = a(5)
c = b()
is the same as:
However, the c = b() line is worth looking at, because that is where MyFunction is actually called. Since you supply the argument on the previous invocation (the b = a(5) line), the c = b() line doesn't need to be called with any arguments! This means you can use it in an action.
Code: Select all
init python:
def MyFunction(num):
renpy.notify("My Function was called with %d!" % num)
MyCurriedFunction = renpy.curry(MyFunction)
screen MyScreen:
textbutton "My Button" action MyCurriedFunction(5)
Now, MyCurriedFunction(5) is executed when the screen is displayed, but rather than running MyFunction, it returns a new function that, when called, is the same as calling MyFunction with the same arguments. So the returned function is then supplied to the action and everything works as planned.
Lecture over, back to OP's question. The problems are that:
1) You need to supply the curried form of the function, not the function itself, as the argument to 'action'.
2) Screen actions take place mid-interaction, so you can't do something like renpy.say that starts a new interaction, so even fixed this code won't work. You need to break into a new context to do this- since you want to call a function, you need
renpy.invoke_in_new_context.
Amazingly, ren'py actually has a function that includes both the curry and the invoke in a new context in one go. It's called renpy.curried_invoke_in_new_context, and it's what you want here. The syntax is the same as renpy.invoke_in_new_context, it's just that instead of running instantly, it returns a function that, when called, invokes a new context with the parameters passed.
So, here's a fixed version of this code:
Code: Select all
init python:
def MyFunction (a, b, objectstate, name, description):
renpy.say(a, b, interact=True)
objectstate = True
obj_name = name
obj_descr = description
screen obj_paper:
if bucketstate == False:
imagebutton:
(...)
action renpy.curried_invoke_in_new_context(MyFunction, "a","b", "objectstate", "name", "description")
It looks a little more horrifying now, but it works! (probably)