[Solved] Replace Call action with Function in Character Creator?

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
Hojoo
Newbie
Posts: 20
Joined: Wed Sep 28, 2022 7:26 pm
Contact:

[Solved] Replace Call action with Function in Character Creator?

#1 Post by Hojoo »

Calling labels gets wonky with screens for me.

Right now my imagemap has a Call statement at the end:

Code: Select all

		hotspot( 64,  33,  5, 7) action [SetVariable("body_color",If(body_color==1,6, body_color-1)), Call("set_tints")]
Which calls this label to set the tints for my player character:

Code: Select all

label set_tints:
	default body_color = 1
	default body_tint = "#d48c5f"
		if body_color == 1:
			$ body_tint = "#d48c5f"
		elif body_color == 2:
			$ body_tint = "#b09a93"
And it works, but with calling a label, the dialogue box under the character creator screen disappears so you can't continue the story after closing the screen.

So, I've been trying to replace the Call action with a Function action instead:

Code: Select all

		hotspot( 64,  33,  5, 7) action [SetVariable("body_color",If(body_color==1,6, body_color-1)), Function(set_tints)]

Code: Select all

init python:
	def set_tints():
		if body_color == 1:
			body_tint = "#d48c5f"
		elif body_color == 2:
			body_tint = "#b09a93"
But nothing happens, my character's tints don't change. I think the Function sets the variable, but doesn't apply it because it doesn't change the old variable? Maybe there's a hotspot action better than Call() that isn't Function(). Help would be greatly appreciated!
Last edited by Hojoo on Fri Jan 06, 2023 7:33 pm, edited 14 times in total.

User avatar
Ocelot
Lemma-Class Veteran
Posts: 2400
Joined: Tue Aug 23, 2016 10:35 am
Github: MiiNiPaa
Discord: MiiNiPaa#4384
Contact:

Re: Replace Call action with Function?

#2 Post by Ocelot »

body_tint variable is local to function.
https://docs.python.org/3/faq/programmi ... -in-python

You should either add global declaration, or access them as fields of store object like:
body_tint = "#d48c5f"store.body_tint = "#d48c5f"
Other places shoul be modified accordingly.
< < insert Rick Cook quote here > >

User avatar
_ticlock_
Miko-Class Veteran
Posts: 910
Joined: Mon Oct 26, 2020 5:41 pm
Contact:

Re: Replace Call action with Function?

#3 Post by _ticlock_ »

Some notes:
1) default statements should be outside label
2)
Hojoo wrote: Fri Nov 18, 2022 1:37 am so you can't return to the story.
Not sure what you meant, but when you call the label and want to return back you need to add return statement at the end of the label:

Code: Select all

label called_label:
    # ... 
    return
3) Ocelot explained what the problem was with Function, and using Function is quite a convenient option in this case. Anyway, alternatively, you can avoid using Function action directly. You can also do something like this:

Code: Select all

init python:
    def get_body_tint_color(i):
        body_tints = ["#d48c5f", "#b09a93", ... ]
        return body_tints[i]
default body_color = 0
default body_tint = get_body_tint_color(body_color)

Code: Select all

    hotspot( 64,  33,  5, 7) action SetVariable("body_color", If(body_color==1,6, body_color-1)), SetVariable("body_tint", get_body_tint_color(body_color))

Hojoo
Newbie
Posts: 20
Joined: Wed Sep 28, 2022 7:26 pm
Contact:

Re: Replace Call action with Function?

#4 Post by Hojoo »

Ocelot wrote: Fri Nov 18, 2022 4:19 am [...] either add global declaration, or access them as fields of store object [...]
Is this how you do it? I tried applying global to the original Function and added return body_tint:

Code: Select all

		hotspot( 64,  33,  5, 7) action [SetVariable("body_color",If(body_color==1,6, body_color-1)), Function(set_tints)]

Code: Select all

init python:
	def set_tints():
		global body_tint
		if body_color == 1:
			body_tint = "#d48c5f"
		elif body_color == 2:
			body_tint = "#b09a93"
		return body_tint
default body_color = 1
default body_tint = "#d48c5f"
But I broke a lot of things. I'm not familiar with global declarations or storing objects; how does this get formatted? Right now, (1) using the character creator screen seems to ignore modal True and interacts with the displayables below it, bringing the dialogue box to the front layer and continuing the story while ignoring the screen, and (2) the preview of my character in the character creator no longer changes colors, but the character that appears in the story does.
_ticlock_ wrote: Fri Nov 18, 2022 12:03 pm [...] alternatively, you can avoid using Function action directly.
I tried your code! It seems like a much cleaner alternative, but I seem to be getting the same problem as before (tints don't apply), even if I try making body_tint a global variable:

Code: Select all

		hotspot( 64,  33,  5, 7) action [SetVariable("body_color", If(body_color==1,6, body_color-1)), SetVariable("body_tint", get_body_tint_color(body_color))]

Code: Select all

init python:
	def get_body_tint_color(i):
	        global body_tint
		body_tints = ["#d48c5f", "#b09a93", "#99837b", "#7f665d", "#654a41", "#5f4747", "#554237"]
		return body_tints[i]
default body_color = 0
default body_tint = get_body_tint_color(body_color)

User avatar
_ticlock_
Miko-Class Veteran
Posts: 910
Joined: Mon Oct 26, 2020 5:41 pm
Contact:

Re: Replace Call action with Function?

#5 Post by _ticlock_ »

Hojoo wrote: Fri Nov 18, 2022 5:09 pm (1) using the character creator screen seems to ignore modal True and interacts
Note: don't return anything at the end of the function set_tints in this case return body_tint. That's why you have the issue (1) you mentioned.
You should do either:

Code: Select all

		hotspot( 64,  33,  5, 7) action [SetVariable("body_color",If(body_color==1,6, body_color-1)), Function(set_tints)]

Code: Select all

init python:
	def set_tints():
		global body_tint
		if body_color == 1:
			body_tint = "#d48c5f"
		elif body_color == 2:
			body_tint = "#b09a93"
Or

Code: Select all

		hotspot( 64,  33,  5, 7) action [SetVariable("body_color", If(body_color==1,6, body_color-1)), SetVariable("body_tint", get_body_tint_color(body_color))]

Code: Select all

init python:
	def get_body_tint_color(i):
		body_tints = ["#d48c5f", "#b09a93", "#99837b", "#7f665d", "#654a41", "#5f4747", "#554237"]
		return body_tints[i]	
Hojoo wrote: Fri Nov 18, 2022 5:09 pm (2) the preview of my character in the character creator no longer changes colors
Hojoo wrote: Fri Nov 18, 2022 5:09 pm the same problem as before (tints don't apply)
How do you apply tint in your character creator screen? Likely, your displayable(image) does not update, and not the variable body_tint

User avatar
_ticlock_
Miko-Class Veteran
Posts: 910
Joined: Mon Oct 26, 2020 5:41 pm
Contact:

Re: Replace Call action with Function?

#6 Post by _ticlock_ »

Hojoo wrote: Fri Nov 18, 2022 5:09 pm same problem as before (tints don't apply)
I actually made a mistake. If you are using SetVariable twice, you have to use the same if condition twice, because the value you put there is analyzed at the screen refresh, and not during actions:

Code: Select all

hotspot( 64,  33,  5, 7) action [SetVariable("body_color", If(body_color==1,6, body_color-1)), SetVariable("body_tint", get_body_tint_color(If(body_color==1,6, body_color-1)))]

Hojoo
Newbie
Posts: 20
Joined: Wed Sep 28, 2022 7:26 pm
Contact:

Re: Replace Call action with Function?

#7 Post by Hojoo »

You're definitely right about it being the image not updating. It used to work fine when I was just calling a label, but not with functions?
_ticlock_ wrote: Fri Nov 18, 2022 6:19 pm If you are using SetVariable twice, you have to use the same if condition twice [...]

Code: Select all

hotspot( 64,  33,  5, 7) action [SetVariable("body_color", If(body_color==1,6, body_color-1)), SetVariable("body_tint", get_body_tint_color(If(body_color==1,6, body_color-1)))]
I updated the hotspot with your code, but I don't see any change? The tints still don't apply until I close and reopen the screen.

As for how I apply tints in the character creator screen, I'm using a layeredimage with defined images using TintMatrix:

Code: Select all

image cc_base:
    "images/character_creator/cc_base.png"
    matrixcolor TintMatrix(body_tint)

Code: Select all

layeredimage Vagabond:
    always:
        "cc_base"
Then in the imagemap for my chargen screen, I add the player image, and my hotspots are arrows that cycle through colors which would update the player image.

Code: Select all

screen chargen():
    imagemap:
        ...
        add "Vagabond" pos (25,16)
        ...
        hotspot( 64,  33,  5, 7) action [SetVariable("body_color",If(body_color==1,6, body_color-1)), SetVariable("body_tint", get_body_tint_color(If(body_color==1,6, body_color-1)))]
        ...

User avatar
_ticlock_
Miko-Class Veteran
Posts: 910
Joined: Mon Oct 26, 2020 5:41 pm
Contact:

Re: Replace Call action with Function?

#8 Post by _ticlock_ »

Hojoo wrote: Fri Nov 18, 2022 6:48 pm

Code: Select all

image cc_base:
    "images/character_creator/cc_base.png"
    matrixcolor TintMatrix(body_tint)

Code: Select all

layeredimage Vagabond:
    always:
        "cc_base"
I see. From documentation - Layered Images - Advice:
Layered images shouldn't use data that changes at runtime. With the exception of the condition inside an if statement, all of the expressions in a layered images are run at init time. The layered image will not pick up changes in variables that occur after the game starts. (However, expressions in ATL transforms will be run each time the image is show, as with other ATL transforms.)
I am not sure what is the best solution, but here some possible solutions that comes to mind:

(1) You can hide-show the image in the character creation screen to update it. The player would not notice this hide-show. It would appear that the image is updated.
Something like this:

Code: Select all

screen charge():
    default old_body_color = body_color
  
        ...
        if old_body_color == body_color:
            add "Vagabond" pos (25,16)
        else
            $ old_body_color = body_color
            $ renpy.restart_interaction()
        ...
Simple, effective, but ugly

(2) You can use ConditionSwitch in the layeredimage. Since you are using set of body_tints, here is some compact example using function to create arguments for ConditionSwitch:

Code: Select all

init python:
    def gen_cs_arg(img):
        args = []
        body_tints = ["#d48c5f", "#b09a93", "#99837b", "#7f665d", "#654a41", "#5f4747", "#554237"]
        for i, body_tint in enumerate(body_tints):
            args.append(f"body_color == {i}")
            args.append(Transform(img, matrixcolor=TintMatrix(body_tint)))
        return args
        
default body_color = 0

layeredimage Vagabond:
    always:
        ConditionSwitch(*gen_cs_arg( "images/character_creator/cc_base.png"))
you don't need variable body_tint in this solution. Just change body_color. function gen_cs_arg would do the magic with body_tint

Code: Select all

hotspot( 64,  33,  5, 7) action SetVariable("body_color", If(body_color==0,6, body_color-1))
you don't need to change character creation screen

Possibly a bit less readable, but quite compact and no ugly workarounds with screen

Hojoo
Newbie
Posts: 20
Joined: Wed Sep 28, 2022 7:26 pm
Contact:

Re: Replace Call action with Function?

#9 Post by Hojoo »

_ticlock_ wrote: Sat Nov 19, 2022 12:27 am [...] From documentation - Layered Images - Advice:
[...] With the exception of the condition inside an if statement, all of the expressions in a layered images are run at init time. [...]
I set it up like you said and it works almost perfectly! I've applied it to the rest of my chargen images:

Code: Select all

init python:
    def gen_cs_arg_body(img):
        args = []
        body_tints = ["#d48c5f", "#b09a93", "#99837b", "#7f665d", "#654a41", "#5f4747"]
        for i, body_tint in enumerate(body_tints):
            args.append(f"body_color == {i}")
            args.append(Transform(img, matrixcolor=TintMatrix(body_tint)))
        return args
    def gen_cs_arg_hair(img):
        args = []
        hair_tints = ["#ffffff", "#eac5ae", "#A0A0A0", "#767676", "#494949", "#1C1C1C"]
        for i, hair_tint in enumerate(hair_tints):
            args.append(f"hair_color == {i}")
            args.append(Transform(img, matrixcolor=TintMatrix(hair_tint)))
        return args
    def gen_cs_arg_brows(img):
        args = []
        brows_tints = [(gen_cs_arg_hair(img)), "#554237"]
        for i, brows_tint in enumerate(brows_tints):
            args.append(f"brows_color == {i}")
            args.append(Transform(img, matrixcolor=TintMatrix(brows_tint)))
        return args
    def gen_cs_arg_horns(img):
        args = []
        horns_tints = [(gen_cs_arg_body(img)), "#554237"]
        for i, horns_tint in enumerate(horns_tints):
            args.append(f"horns_color == {i}")
            args.append(Transform(img, matrixcolor=TintMatrix(horns_tint)))
        return args
I'm working to fully understand it, but the code is so much more compact this way.
The only thing still breaking is tints that match other tints, because this is how I used to have it under the Call action:

Code: Select all

label set_tints:
    ...
    if brows_color == 1:
        $ brows_tint = hair_tint    ## <-- 
    elif brows_color == 2:
        $ brows_tint = "#554237"
    if horns_color == 1:
        $ horns_tint = body_tint    ## <--
    elif horns_color == 2:
        $ horns_tint = "#554237"
Is there a formatting for putting those back in?

User avatar
_ticlock_
Miko-Class Veteran
Posts: 910
Joined: Mon Oct 26, 2020 5:41 pm
Contact:

Re: Replace Call action with Function?

#10 Post by _ticlock_ »

Hojoo wrote: Sat Nov 19, 2022 6:40 pm I'm working to fully understand it, but the code is so much more compact this way.
The only thing still breaking is tints that match other tints, because this is how I used to have it under the Call action:

Code: Select all

label set_tints:
    ...
    if brows_color == 1:
        $ brows_tint = hair_tint    ## <-- 
    elif brows_color == 2:
        $ brows_tint = "#554237"
    if horns_color == 1:
        $ horns_tint = body_tint    ## <--
    elif horns_color == 2:
        $ horns_tint = "#554237"
Is there a formatting for putting those back in?
Basically the function returns list of arguments for ConditionSwitch, for example:
[“body_color == 0”, Transform(img, matrixcolor=TintMatrix(“#d48c5f”)), “body_color == 1”, Transform(img, matrixcolor=TintMatrix(“#b09a93”)), etc]


Possibly for matching colors you can use another ConditionSwitch, I guess that’s what you were trying to do in your code, but not sure if nested ConditionSwitch works:

Code: Select all

  
    def gen_cs_arg_brows(img):
        args = []
        brows_tints = [“hair”, "#554237"]
        for i, brows_tint in enumerate(brows_tints):
            args.append(f"brows_color == {i}")
            if brows_tints == “hair”:
                args.append(ConditionSwitch(*gen_cs_arg_hair(img)))
            else:
                args.append(Transform(img, matrixcolor=TintMatrix(brows_tint)))
        return args
Note, that I changed indexes for brows_color from 1,2 to 0,1.

Alternatively, you can combine conditions.

Code: Select all

def gen_cs_arg_brows(img):
        args = []
        hair_tints = ["#ffffff", "#eac5ae", "#A0A0A0", "#767676", "#494949", "#1C1C1C"]
        brows_tints = hair_tints + ["#554237"]
        brows_conditions = [f"brows_color == 0 and hair_color == {i}" for i in range(0, len(hair_tints))] + [“ brows_color == 1”]
        for i, brows_tint in enumerate(brows_tints):
            args.append(brows_conditions[i])
            args.append(Transform(img, matrixcolor=TintMatrix(brows_tint)))
        return args
You can also combine functions into one, so you don’t have to repeat some lines.

Hojoo
Newbie
Posts: 20
Joined: Wed Sep 28, 2022 7:26 pm
Contact:

Re: Replace Call action with Function?

#11 Post by Hojoo »

Yeah, nested ConditionSwitch doesn't work, but combining conditions definitely does!
_ticlock_ wrote: Sun Nov 20, 2022 4:04 am You can also combine functions into one, so you don’t have to repeat some lines.
Repeating lines are slightly annoying; how can these functions be combined, can it return multiple args?
Regardless, everything is otherwise in working order now. Thank you so much for all your help!

User avatar
_ticlock_
Miko-Class Veteran
Posts: 910
Joined: Mon Oct 26, 2020 5:41 pm
Contact:

Re: Replace Call action with Function?

#12 Post by _ticlock_ »

Hojoo wrote: Wed Nov 23, 2022 4:01 pm Repeating lines are slightly annoying; how can these functions be combined,
Well, due to matching colors it is a bit hard to combine them, but you can do something like this:

Code: Select all

init python:
    def gen_cs_arg(body_part, img):
        if body_part == "body" or body_part == "horns":
            body_tints = ["#d48c5f", "#b09a93", "#99837b", "#7f665d", "#654a41", "#5f4747"]
            if body_part == "body":
                # body_tints
                tints = body_tints
                conditions = [f"body_color == {i}" for i in range(0, len(body_tints))]
            else:
                # horns_tints
                tints = body_tints + ["#554237"]
                conditions = [f"horns_color == 0 and body_color == {i}" for i in range(0, len(body_tints))] + ["horns_color == 1"]
        elif body_part == "hair" or body_part == "brows":
            hair_tints = ["#ffffff", "#eac5ae", "#A0A0A0", "#767676", "#494949", "#1C1C1C"]
            if body_part == "hair":
                # hair_tints
                tints = hair_tints
                conditions = [f"hair_color == {i}" for i in range(0, len(hair_tints))]
            else:
                # brows_tints
                tints = hair_tints + ["#554237"]
                conditions = [f"brows_color == 0 and hair_color == {i}" for i in range(0, len(hair_tints))] + ["brows_color == 1"]

        args = []
        for i, tint in enumerate(tints):
            args.append(conditions[i])
            args.append(Transform(img, matrixcolor=TintMatrix(tint)))
        return args

Code: Select all

# body
ConditionSwitch(*gen_cs_arg("body", "images/character_creator/cc_base.png"))

Post Reply

Who is online

Users browsing this forum: Andredron, Bing [Bot]