Ren'Py Gripes: Picker Functions

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
User avatar
Arowana
Miko-Class Veteran
Posts: 531
Joined: Thu May 31, 2012 11:17 pm
Completed: a2 ~a due~
Projects: AXIOM.01, The Pirate Mermaid
Organization: Variable X, Navigame
Tumblr: navigame-media
itch: navigame
Contact:

Ren'Py Gripes: Picker Functions

#1 Post by Arowana » Fri Feb 08, 2019 9:04 pm

So I’ve had this gripe with LayeredImages that I’ve been trying to deal with for a while. But it’s gotten frustrating to the point where I’m considering abandoning LayeredImages if I can’t find a better solution. :cry:

My sprites have a lot of different layers, which I mix and match to show different emotions. So if I want to show the “angry” emotion, for example, I need to change a bunch of different layers (eyes, mouth, eyebrows, arms, etc.).

In the olden days of LiveComposite, I would just write a function to set a bunch of variables. So something like:

Code: Select all

init python:
    def set_emotion(emotion):
	if emotion == “angry”:
	    $ eyes = “narrow”
	    $ mouth = “yell”
	    $ eyebrows = “down”
	    $ right_arm = “down”
	    $ left_arm = “punch”
	    # and so forth
	elif emotion == “sad”:
	    $ eyes = “narrow”
	    $ mouth = “frown”
	    $ eyebrows = “up”
	    $ right_arm = “down”
	    $ left_arm = “down”
	    # and so forth
    # and so on for other emotions

$ set_emotion(“angry”)
char “I’m angry!”
$ set_emotion(“sad”)
char “I’m sad.”
$ mouth = “yell”
char “I’m still sad but yelling now!”
Now with LayeredImages, I have to set a bunch of different attributes to get the same effect. So something like:

Code: Select all

char eyes_narrow mouth_yell eyebrows_down rightarm_up leftarm_punch “I’m angry!”
Writing out all those attributes for every emotion change gets really crazy, so I tried to define sets of attributes using an adapted version of the "Picker" attribute_function from PyTom’s Patreon. Something like:

Code: Select all

init python:
    class Picker(object):
        def __init__(self, options):
            self.options =  [ i.split() for i in options ]

        def __call__(self, attributes):
            rv = set(attributes)

            for i in self.options:
                if i[0] in attributes:
                    rv.update(i[1:])

            return rv 
            
layeredimage char:
    attribute_function Picker([
    "angry eyes_narrow mouth_yell eyebrows_down rightarm_up leftarm_punch", 
    "sad eyes_narrow mouth_frown eyebrows_up rightarm_down leftarm_down",
    "sad_yell eyes_narrow mouth_yell eyebrows_up rightarm_down leftarm_down",
    # and so on for other emotions
    ])
    
    group combos:
        attribute angry null
        attribute sad null
        attribute sad_yell null
        # and so on for other emotions
        
     # add groups for all the attributes below
   
char sad "I'm sad."
char sad_yell "I'm sad but yelling now!"  
Unfortunately, this has problems too. Basically, I have to define a new combo for every unique set of attributes I use. But because I use so many different combos, defining every single one gets really crazy. :(

What I really want is to use sets of attributes with the ability to change individual attributes without defining a completely new set. Something like this would be perfect:

Code: Select all

char sad “I’m sad.” 
#where “sad” is a set of eyes, mouth, eyebrows, etc. attributes I defined

char mouth_yell “I’m still sad but yelling now!” 
#swaps the mouth attribute to “yell,” but keeps the rest of the attributes in the “sad” set

char angry mouth_smile “I’m angry but smiling.” 
#shows all the attributes in the “angry” set, but swaps the mouth attribute to “smile”
Is it possible to have this kind of functionality in LayeredImages? I hope so, because I really do like using LayeredImages outside of this one issue.
Complete: a2 ~a due~ (music, language, love)
In progress: The Pirate Mermaid (fairytale otome)
On hold: AXIOM.01 (girl detective game)

Image

User avatar
ComputerArt.Club
Veteran
Posts: 409
Joined: Mon May 22, 2017 8:12 am
Completed: Famous Fables, BoPoMoFo: Learn Chinese, Santa's workshop, Cat's Bath, Computer Art Club
Location: Taiwan
Contact:

Re: Ren'Py Gripes

#2 Post by ComputerArt.Club » Sat Feb 09, 2019 5:11 am

Update: Check the tutorial here, maybe the attribute picker: https://patreon.renpy.org/layeredimage-conversion.html
Arowana wrote:
Fri Feb 08, 2019 9:04 pm
So I’ve had this gripe with LayeredImages that I’ve been trying to deal with for a while. But it’s gotten frustrating to the point where I’m considering abandoning LayeredImages if I can’t find a better solution. :cry:

My sprites have a lot of different layers, which I mix and match to show different emotions. So if I want to show the “angry” emotion, for example, I need to change a bunch of different layers (eyes, mouth, eyebrows, arms, etc.).

In the olden days of LiveComposite, I would just write a function to set a bunch of variables. So something like:

Code: Select all

init python:
    def set_emotion(emotion):
	if emotion == “angry”:
	    $ eyes = “narrow”
	    $ mouth = “yell”
	    $ eyebrows = “down”
	    $ right_arm = “down”
	    $ left_arm = “punch”
	    # and so forth
	elif emotion == “sad”:
	    $ eyes = “narrow”
	    $ mouth = “frown”
	    $ eyebrows = “up”
	    $ right_arm = “down”
	    $ left_arm = “down”
	    # and so forth
    # and so on for other emotions

$ set_emotion(“angry”)
char “I’m angry!”
$ set_emotion(“sad”)
char “I’m sad.”
$ mouth = “yell”
char “I’m still sad but yelling now!”
Now with LayeredImages, I have to set a bunch of different attributes to get the same effect. So something like:

Code: Select all

char eyes_narrow mouth_yell eyebrows_down rightarm_up leftarm_punch “I’m angry!”
Writing out all those attributes for every emotion change gets really crazy, so I tried to define sets of attributes using an adapted version of the "Picker" attribute_function from PyTom’s Patreon. Something like:

Code: Select all

init python:
    class Picker(object):
        def __init__(self, options):
            self.options =  [ i.split() for i in options ]

        def __call__(self, attributes):
            rv = set(attributes)

            for i in self.options:
                if i[0] in attributes:
                    rv.update(i[1:])

            return rv 
            
layeredimage char:
    attribute_function Picker([
    "angry eyes_narrow mouth_yell eyebrows_down rightarm_up leftarm_punch", 
    "sad eyes_narrow mouth_frown eyebrows_up rightarm_down leftarm_down",
    "sad_yell eyes_narrow mouth_yell eyebrows_up rightarm_down leftarm_down",
    # and so on for other emotions
    ])
    
    group combos:
        attribute angry null
        attribute sad null
        attribute sad_yell null
        # and so on for other emotions
        
     # add groups for all the attributes below
   
char sad "I'm sad."
char sad_yell "I'm sad but yelling now!"  
Unfortunately, this has problems too. Basically, I have to define a new combo for every unique set of attributes I use. But because I use so many different combos, defining every single one gets really crazy. :(

What I really want is to use sets of attributes with the ability to change individual attributes without defining a completely new set. Something like this would be perfect:

Code: Select all

char sad “I’m sad.” 
#where “sad” is a set of eyes, mouth, eyebrows, etc. attributes I defined

char mouth_yell “I’m still sad but yelling now!” 
#swaps the mouth attribute to “yell,” but keeps the rest of the attributes in the “sad” set

char angry mouth_smile “I’m angry but smiling.” 
#shows all the attributes in the “angry” set, but swaps the mouth attribute to “smile”
Is it possible to have this kind of functionality in LayeredImages? I hope so, because I really do like using LayeredImages outside of this one issue.
You could probably do what you are talking about in a hacked kind of way, personally I found it very useful to change individual parts of the characters in my most recent game, eg changing eye directions and hand positions and mouths frequently.

I am pretty sure you could define one grouping as an image
e.g.
image ben_angry = “ ben mouth_angry eyes_angry”
Id say you would run into problems when switching if you use atl transformations though.
you might have to do something like:

Code: Select all

show ben:
    “ben_angry”

show ben: 
    “ben_sad”
    
Still clunky though. You have defaults set up, right? I found that reduced the amount of work required, and from their I welcomed being able to change individual layers.

There might be an easier more direct way of doing it but I am on my phone right now, so it is difficult for me to look for the patreon tutorial.

User avatar
Imperf3kt
Lemma-Class Veteran
Posts: 2726
Joined: Mon Dec 14, 2015 5:05 am
Location: Your monitor
Contact:

Re: Ren'Py Gripes

#3 Post by Imperf3kt » Sat Feb 09, 2019 5:40 am

I believe Pytom is looking into a suggestion that might be of interest to you:
viewtopic.php?p=504780#p504755
Warning: May contain trace amounts of gratuitous plot.
pro·gram·mer (noun) An organism capable of converting caffeine into code.

Twitter

User avatar
Arowana
Miko-Class Veteran
Posts: 531
Joined: Thu May 31, 2012 11:17 pm
Completed: a2 ~a due~
Projects: AXIOM.01, The Pirate Mermaid
Organization: Variable X, Navigame
Tumblr: navigame-media
itch: navigame
Contact:

Re: Ren'Py Gripes

#4 Post by Arowana » Sat Feb 09, 2019 2:35 pm

ComputerArt.Club wrote:
Sat Feb 09, 2019 5:11 am
Update: Check the tutorial here, maybe the attribute picker: https://patreon.renpy.org/layeredimage-conversion.html
Thanks for checking, but that's actually the second method I listed in my post above. The problem with that method is that, although you can define and use combos of attributes, you cannot modify individual attributes in that combo on the fly. This means you have to explicitly define every single combo you use (even if they they vary only in a single attribute). This isn't realistic for me because I have so many different combos. :oops:
Imperf3kt wrote:
Sat Feb 09, 2019 5:40 am
I believe Pytom is looking into a suggestion that might be of interest to you:
viewtopic.php?p=504780#p504755
This is actually what inspired me to make my post above. :lol: Because at first, I also thought it would solve my issue, but then I realized it was a different problem. :( In that person's case, it looks like each emotion ("surprised", "idle", etc.) is actually a different pose image, not a combo of multiple attributes. So this issue of defining and modifying combos wouldn't apply to them.
Complete: a2 ~a due~ (music, language, love)
In progress: The Pirate Mermaid (fairytale otome)
On hold: AXIOM.01 (girl detective game)

Image

User avatar
PyTom
Ren'Py Creator
Posts: 15437
Joined: Mon Feb 02, 2004 10:58 am
Completed: Moonlight Walks
Projects: Ren'Py
IRC Nick: renpytom
Github: renpytom
itch: renpytom
Location: Kings Park, NY
Contact:

Re: Ren'Py Gripes: Picker Attribute Functions

#5 Post by PyTom » Sun Feb 10, 2019 2:06 pm

So, I think the problem here is that you're considering the attribute function to be a lot smarter than it actually is. There are basically two sets of attributes in play here. The attribute_function doesn't really affect the attributes that the core of Ren'Py thinks the layeredimage has. Ren'Py maintains the model of the current attributes the image knows about - given the attributes you show it with - and then passes that into a function that gets how the layeredimage is to be displayed.

It's this function that calls your attribute_function, with the current set of attributes. The function then returns a list of attributes, which then become the layers that layeredimage shows.

It seems like a function that works would be pretty easy:

Code: Select all

define mygroups = { "happy" : [ "head_happy", "feet_happy" ] , sad: [ "head_sad", "feet_sad" ] }

def mypicker(attributes):
     rv = [ ] 
     
     for i in attributes:
          if i in mygroups:
               rv.extend(mygroups[i])

     for i in attributes:
           prefix, _, suffix = i.partition("_")
           if suffix:
                rv = [ i for i in attributes if not i.startswith(prefix + "_") ] 
           rv.append(i)

     return i

layeredimage eileen:
     attribute_function mypicker

     group expression:
           happy null
           sad null

     group head:
          attribute head_happy
          attribute head_sad

     group feet:
          attribute feet_happy
          attribute feet_sad

The only issue with this is that a self-defined attribute would be 'sticky' - so you'd have to 'shut off' any attributes you manually add.

Code: Select all

e happy feet_sad "Happy face sad feet."
e happy "No change - happy face, sad feet."
e -feet_sad "Now I have happy feet."
But that's better than the python version, where you'd have to manage the feet anyway through a variable.
Supporting creators since 2004
(When was the last time you backed up your game?)
"Do good work." - Virgil Ivan "Gus" Grissom
"Silly and fun things are important." - Elon Musk
Software > Drama • https://www.patreon.com/renpytom

User avatar
Arowana
Miko-Class Veteran
Posts: 531
Joined: Thu May 31, 2012 11:17 pm
Completed: a2 ~a due~
Projects: AXIOM.01, The Pirate Mermaid
Organization: Variable X, Navigame
Tumblr: navigame-media
itch: navigame
Contact:

Re: Ren'Py Gripes: Picker Functions

#6 Post by Arowana » Sun Feb 10, 2019 9:04 pm

Thanks so much for the info and sample code, PyTom! The concept sounds great to me, but when I try to use the code, it doesn't work as I expect. Maybe I'm misunderstanding how to use it correctly? :(

Here is the picker function I'm using:

Code: Select all

init python:
    def mypicker(attributes):
         rv = [ ]

         for i in attributes:
              if i in mygroups:
                   rv.extend(mygroups[i])

         for i in attributes:
               prefix, _, suffix = i.partition("_")
               if suffix:
                    rv = [ i for i in attributes if not i.startswith(prefix + "_") ]
               rv.append(i)

         return rv
Here is a sample LayeredImage:

Code: Select all

layeredimage palette:
    attribute_function mypicker

    group combos:
        attribute pastel null
        attribute neon null

    group blue prefix "blue":
        attribute sky:
            Solid("#BFEFFF", xysize=(100,100))
        attribute cyan:
            Solid("#00ffff", xysize=(100,100))

    group red prefix "red":
        pos (100, 0)
        attribute rose:
            Solid("#ffe4e1", xysize=(100,100))
        attribute magenta:
            Solid("#FF00FF", xysize=(100,100))

    group green prefix "green":
        pos (200, 0)
        attribute grass:
            Solid("#77dd77", xysize=(100,100))
        attribute lime:
            Solid("#39ff14", xysize=(100,100))
            
 init python:           
     mygroups = {
        "pastel": [ "blue_sky", "red_rose", "green_grass" ],
        "neon": [ "blue_cyan", "red_magenta", "green_lime" ]
        }
And here is a test script. At the end, multiple layers of the image disappear, and I'm not sure why.

Code: Select all

label start:
    show palette pastel:
        align (0.5, 0.5)
    "All layers are pastel, as expected."
    show palette neon
    "All layers are neon, as expected."
    show palette blue_sky
    "I expect this to look the same as above, just with a sky blue layer."
    "Though the blue layer changes correctly, all the other layers disappear."
    end

I am also having issues getting this to work with LayeredImages that have "default" attributes. Here another sample LayeredImage (same as the one above, but now I've assigned defaults for each group):

Code: Select all

layeredimage palette2:
    attribute_function mypicker

    group combos:
        attribute pastel null
        attribute neon null

    group blue prefix "blue":
        attribute sky default:
            Solid("#BFEFFF", xysize=(100,100))
        attribute cyan:
            Solid("#00ffff", xysize=(100,100))

    group red prefix "red":
        pos (100, 0)
        attribute rose:
            Solid("#ffe4e1", xysize=(100,100))
        attribute magenta default:
            Solid("#FF00FF", xysize=(100,100))

    group green prefix "green":
        pos (200, 0)
        attribute grass default:
            Solid("#77dd77", xysize=(100,100))
        attribute lime:
            Solid("#39ff14", xysize=(100,100))
And here is another test script. When I try to use the preset groups (in this case, "neon"), they appear to be overriden by the default attributes.

Code: Select all

    
 label start:
    show palette2:
        align (0.5, 0.5)
    "Here is another image that has defaults for each layer."
    show palette2 neon
    "I expect this to switch all layers to neon, but nothing changes."
    show palette2 red_rose
    "This does switch the red layer to rose correctly though."
    return
Let me know if you have any ideas on how to fix these issues.
Complete: a2 ~a due~ (music, language, love)
In progress: The Pirate Mermaid (fairytale otome)
On hold: AXIOM.01 (girl detective game)

Image

philat
Eileen-Class Veteran
Posts: 1656
Joined: Wed Dec 04, 2013 12:33 pm
Contact:

Re: Ren'Py Gripes: Picker Functions

#7 Post by philat » Mon Feb 11, 2019 1:13 am

Not sure what's up with the second problem (default), but for the first one, the function seems to be behaving a bit weirdly.

Code: Select all

init python:
    def mypicker(attributes):
         rv = [ ]

         for i in attributes:
              if i in mygroups:
                   rv.extend(mygroups[i])

         for i in attributes:
               prefix, _, suffix = i.partition("_")
               if suffix:
                    rv = [ a for a in rv if not a.startswith(prefix + "_") ] # was originally i for i in attributes if not i.startswith(prefix + "_")
               rv.append(i)

         return rv
Regarding the line I changed, I'm not a python guru but I would have expected that using i in this generator would not affect the outside i (for i in attributes) but it does. Weird. Also, attributes (in your example) is ["neon", "blue_sky"] rather than ["blue_cyan", "red_magenta", "green_lime"], which is rather rv. That said, I don't know how this would affect anything else because I don't really work with layeredimages much. Still, the first example works when running it as you posted.

User avatar
Arowana
Miko-Class Veteran
Posts: 531
Joined: Thu May 31, 2012 11:17 pm
Completed: a2 ~a due~
Projects: AXIOM.01, The Pirate Mermaid
Organization: Variable X, Navigame
Tumblr: navigame-media
itch: navigame
Contact:

Re: Ren'Py Gripes: Picker Functions

#8 Post by Arowana » Mon Feb 11, 2019 10:47 am

philat wrote:
Mon Feb 11, 2019 1:13 am
NAlso, attributes (in your example) is ["neon", "blue_sky"] rather than ["blue_cyan", "red_magenta", "green_lime"], which is rather rv.
Ohh you are totally right, it makes more sense to use the rv list. Thanks so much for catching that! :D

For the default thing, I think I actually need to put the default on the combo group rather than on the individual attribute groups. I will try that tonight and report back later if it works!
Complete: a2 ~a due~ (music, language, love)
In progress: The Pirate Mermaid (fairytale otome)
On hold: AXIOM.01 (girl detective game)

Image

Post Reply

Who is online

Users browsing this forum: Poorman65