Python Function for displaying text using renpy.say

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Post Reply
Message
Author
Peach
Regular
Posts: 41
Joined: Tue Apr 01, 2014 2:40 pm
Contact:

Python Function for displaying text using renpy.say

#1 Post by Peach »

Okay, I give up, I´m so frustrated right now... Screw figuring things out on my own, I just can´t wrap my head around this.

Goal: An imagebutton that changes variables and displays the say window with a character talking

What I´ve tried:

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    
    add = renpy.curry(MyFunction)

screen obj_paper:
        if bucketstate == False:        
            imagebutton:
                (...)
                action MyFunction("a","b", "objectstate", "name", "description")
The screen is called in a label.

Problem:
No matter how I wrote the renpy.say (I also tried renpy.display_say and others) I just get errors, with this code its "Have you forgotten to use ui.close() somewhere?" So I added that under the function too, but it didn´t work.
The variables just don´t do anything, not even an error. They just stay False and in their default state. the renpy.say works perfect if I use it in a label, just not when I try using it in the button.

I´m sorry for asking so much, I try not to be the typical programming noob, but I can´t find anything that could help me... maybe I´m just searching wrong, but arghhh.... my head hurts.

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Python Function for displaying text using renpy.say

#2 Post by xela »

Could you be a bit more specific about your goal? I cannot understand what you're trying to do here.
Like what we're doing? Support us at:
Image

Peach
Regular
Posts: 41
Joined: Tue Apr 01, 2014 2:40 pm
Contact:

Re: Python Function for displaying text using renpy.say

#3 Post by Peach »

I want a button, that, upon clicking, executes a function that includes setting a few variables and displaying the "say" window with a character, like as if I wrote

Code: Select all

    Character "Characters Text"
Buttons only take Simple Actions or a list of them, so I can´t use this line. Thats why I tried defining my own function (also for reusability since I´ll need this kind of button a few times), but can not seem to get the Python code to be executed.

Asceai
Eileen-Class Veteran
Posts: 1258
Joined: Fri Sep 21, 2007 7:13 am
Projects: a battle engine
Contact:

Re: Python Function for displaying text using renpy.say

#4 Post by Asceai »

Why did you write this?

Code: Select all

add = renpy.curry(MyFunction)
You're quite right that you need to curry that function to be able to use it with parameters as an action, but you never use it, and you called it 'add' which is a little odd.

Asceai
Eileen-Class Veteran
Posts: 1258
Joined: Fri Sep 21, 2007 7:13 am
Projects: a battle engine
Contact:

Re: Python Function for displaying text using renpy.say

#5 Post by Asceai »

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:

Code: Select all

c = MyFunction(5)
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)

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Python Function for displaying text using renpy.say

#6 Post by xela »

I still don't understand what it all does... Either I've never required the functionality it offers or am doing the same thing in a different way.
Like what we're doing? Support us at:
Image

Asceai
Eileen-Class Veteran
Posts: 1258
Joined: Fri Sep 21, 2007 7:13 am
Projects: a battle engine
Contact:

Re: Python Function for displaying text using renpy.say

#7 Post by Asceai »

xela wrote:I still don't understand what it all does... Either I've never required the functionality it offers or am doing the same thing in a different way.
What do you do when you want to call a function with arguments in an action? Currying might make this easier to do.

Peach
Regular
Posts: 41
Joined: Tue Apr 01, 2014 2:40 pm
Contact:

Re: Python Function for displaying text using renpy.say

#8 Post by Peach »

Wow thank you Asceai, for the in-depth explanation!!
the add was from an old, probably outdated part in the Wiki. I basically read somewhere that you need to "curry" a function to use it, then googled my way through for code snippets and tried around with them. This is really useful!

-On my Question:
YES!!! It opens up the dialogue just fine!! Now I´ll just have to do some fine-tuning for showing a side image etc, but I think I can do that. THANK YOU!!!

One more thing though: The variables stay the same :/ does invoke_in_new_context ignore variables that are set at the start of the game?
I think I can just use SetVariable outside of the function, so it isn´t a big deal (for now, who knows what errors I´ll run in to), but this interests me for completion of the topic :)

Asceai
Eileen-Class Veteran
Posts: 1258
Joined: Fri Sep 21, 2007 7:13 am
Projects: a battle engine
Contact:

Re: Python Function for displaying text using renpy.say

#9 Post by Asceai »

Peach wrote:One more thing though: The variables stay the same :/ does invoke_in_new_context ignore variables that are set at the start of the game?
No. It's to do with Python scoping rules- that function doesn't know about those variables, so it creates new copies that disappear once the function ends. So the function itself is a problem, not the fact that it was called with invoke_in_new_context.
Here's two possible ways you could rewrite the function.

Code: Select all

    def MyFunction (a, b, objectstate, name, description):
        global objectstate, obj_name, obj_descr
        renpy.say(a, b, interact=True)
        objectstate = True
        obj_name = name
        obj_descr = description    

Code: Select all

    def MyFunction (a, b, objectstate, name, description):
        renpy.say(a, b, interact=True)
        renpy.store.objectstate = True
        renpy.store.obj_name = name
        renpy.store.obj_descr = description    
I don't know which approach is better or the implications of either, but in my own code I tend to favour the renpy.store approach.

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Python Function for displaying text using renpy.say

#10 Post by xela »

Asceai wrote:
xela wrote:I still don't understand what it all does... Either I've never required the functionality it offers or am doing the same thing in a different way.
What do you do when you want to call a function with arguments in an action? Currying might make this easier to do.
I do this (been doing it for a while):

Code: Select all

    class Execute(Action):
        """
        Well... it executes... :)
        """
        def __init__(self, func, *args, **kwargs):
            self.func = func
            self.args = args
            self.kwargs = kwargs
        
        def __call__(self):
            self.func(*self.args, **self.kwargs)
            return "__"
Like what we're doing? Support us at:
Image

Asceai
Eileen-Class Veteran
Posts: 1258
Joined: Fri Sep 21, 2007 7:13 am
Projects: a battle engine
Contact:

Re: Python Function for displaying text using renpy.say

#11 Post by Asceai »

Yeah, you effectively created renpy.curry() but with the curried_invoke_in_new_context() calling style (function then args). That's perfectly reasonable.

Peach
Regular
Posts: 41
Joined: Tue Apr 01, 2014 2:40 pm
Contact:

Re: Python Function for displaying text using renpy.say

#12 Post by Peach »

Well, as a newbie all of that function doesn´t make sense to me so i really prefer the already "assembled" piece of code... Even if it took me a while to find what I was looking for. I´m sure your way allows for more flexibility though, right?
Also, another few love points to Asceai. I´ll probably have to mention him in the credits of my game if this keeps up ;_; (my pride - it wants to figure out all these things alone) *bows deeply*

User avatar
Kia
Eileen-Class Veteran
Posts: 1040
Joined: Fri Aug 01, 2014 7:49 am
Deviantart: KiaAzad
Discord: Kia#6810
Contact:

Re: Python Function for displaying text using renpy.say

#13 Post by Kia »

xela can you explain more about your Execute class and how to use it please. I can't get my head around it.

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Python Function for displaying text using renpy.say

#14 Post by xela »

I gave you an example in the other thread, you use it inside of a screen to run a function with arguments/key work arguments.

Code: Select all

screen battle:
    textbutton "Attack":
        action Execute(attack, "Argus", damage=25)

init python:
    def attack(name, damage=0):
        ...
Like what we're doing? Support us at:
Image

Post Reply

Who is online

Users browsing this forum: Majestic-12 [Bot]