[Solved] Trying to use a Method to change variables based on Attributes of a Class Instance

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
LateWhiteRabbit
Eileen-Class Veteran
Posts: 1867
Joined: Sat Jan 19, 2008 2:47 pm
Projects: The Space Between
Contact:

[Solved] Trying to use a Method to change variables based on Attributes of a Class Instance

#1 Post by LateWhiteRabbit »

Okay, so first, I'm not a great coder. I know just enough to get myself into trouble.

I've been setting up a quite complicated game using NVL style dialogue and I've gotten nearly everything I want working, but I've been hung up on one thing for an entire week, and though I've been studying Python tutorials and help forums, I can't quite find the information to do what I want, especially as it relates to Ren'Py.

Basically, the game is going to have a LOT of clothing items for the player to wear, and each clothing item has attributes I want to be able to easily call in the dialogue script. As you can see from the Clothing custom class code below, those are the clothing's name, a description of it, a list of aspects (since the word 'properties' is reserved), the location on the player's body the item is worn, and the amount of the clothing item the player current possesses.


Now, I could easily just do the normal [raincoat.name] or [raincoat.description] in the Ren'Py script to display these attributes, but I want the player to be able to equip clothing into different 'slots', and I want to be able to separate out my logic, so I can call a method when the player equips an item, and everywhere else in the script just use the equipment slot variables - so [head_slot_name] or [head_slot_descrip] for example.

My game code is obviously a lot more than what I'm posting here, but these are all the relevant parts pulled out and presented. I have a .rpy file where I declare all my variables as defaults, and I have another .rpy file with my custom classes and their methods in a Python block. Then I have my .rpy file with the game script.

What I have below is a test to see if what I am trying even WORKS - the final equip method would obviously be more complicated with more equipment slots than just the head, and would ideally use the 'location' attribute in the Clothing class instances to assign the provided method argument into the proper location slot variables.

The code below doesn't produce any errors - the equip method just seems to do . . . nothing.

Code: Select all

default raincoat = Clothing("raincoat", "a slick yellow raincoat", ["waterproof"], "head", 1)
default head_slot_descrip = ""
default head_slot_name = ""
default player_inventory = Inventory("player")

init python:
    import renpy.store as store

    class Clothing(store.object):

        def __init__(self, name, description, aspects, location, amount = 1):
            self.name = name
            self.description = description
            self.aspects = []             
            self.location = []            
            self.amount = amount

    class Inventory(store.object):

        def __init__(self, name):
            self.name = name

        def equip(self, clothing_item):
            head_slot_name = getattr(clothing_item, 'name')
            head_slot_descrip = getattr(clothing_item, 'description')

label start:

    $ player_inventory.equip(raincoat)

    "The thing on your head slot is [head_slot_name]. It is [head_slot_descrip]."
When the code is run, all I get is just the default variable values for head_slot_name and head_slot_descrip.

Code: Select all

The thing on your head slot is . It is .
So, what am I doing wrong? I am on the completely wrong track? Is there a much easier way to do what I'm wanting than I am attempting?

I'm pretty frustrated. All the rest of my very customized game code and screens and menus are working great, and this is one of the last stumbling blocks to putting everything together.

Like I said, my Python skills are fairly weak - but I've done everything else with self study - some things I consider much more complicated than this in Python, and somehow getting an instance to change a variable using a method call is proving impossible for me.

Any help appreciated, including 'Dude, it would be so much easier to do what you want if you do x,y,z instead . . ." :oops:
Last edited by LateWhiteRabbit on Sat Nov 27, 2021 8:22 pm, edited 1 time 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: Trying to use a Method to change variables based on Attributes of a Class Instance

#2 Post by Ocelot »

Code: Select all

        def equip(self, clothing_item):
            head_slot_name = getattr(clothing_item, 'name')
            head_slot_descrip = getattr(clothing_item, 'description')

Code: Select all

        def equip(self, clothing_item):
            store.head_slot_name = clothing_item.name
            store.head_slot_descrip = clothing_item.description
< < insert Rick Cook quote here > >

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

Re: Trying to use a Method to change variables based on Attributes of a Class Instance

#3 Post by philat »

To provide some color: in general, you can't assign to global variables from a method (or more generally any function) unless you designate them global or refer to the store object. Either way is fine, but just so you understand why. viewtopic.php?t=29339#p347619

User avatar
LateWhiteRabbit
Eileen-Class Veteran
Posts: 1867
Joined: Sat Jan 19, 2008 2:47 pm
Projects: The Space Between
Contact:

Re: Trying to use a Method to change variables based on Attributes of a Class Instance

#4 Post by LateWhiteRabbit »

Ocelot wrote: Sat Nov 27, 2021 3:27 am

Code: Select all

        def equip(self, clothing_item):
            head_slot_name = getattr(clothing_item, 'name')
            head_slot_descrip = getattr(clothing_item, 'description')

Code: Select all

        def equip(self, clothing_item):
            store.head_slot_name = clothing_item.name
            store.head_slot_descrip = clothing_item.description
Thank you. That works perfectly for what I want to do, and I can now finishing designing my classes and methods knowing it is possible.

I knew it would be probably be something simple like you demonstrated - you wouldn't believe all the different attempts I made before I even attempted the wrong 'getattr' attempt I posted.

Thanks again.
philat wrote: Sat Nov 27, 2021 7:41 am To provide some color: in general, you can't assign to global variables from a method (or more generally any function) unless you designate them global or refer to the store object. Either way is fine, but just so you understand why. viewtopic.php?t=29339#p347619
I appreciate the further information. I knew assigning global variables from methods or functions isn't considered good practice, but in Ren'Py since I'm basically declaring all my variables using Default before the game starts anyway (I believe most variables in Ren'Py are by default 'global' by nature of how Ren'Py works with them?) it would be fine.

Thanks to you both again. You've saved me from more hair-pulling than you can imagine.

User avatar
zmook
Veteran
Posts: 421
Joined: Wed Aug 26, 2020 6:44 pm
Contact:

Re: [Solved] Trying to use a Method to change variables based on Attributes of a Class Instance

#5 Post by zmook »

If you want to take better advantage of python, consider this:

Code: Select all

init python:
    import renpy.store as store

    class Clothing(store.object):

        def __init__(self, name, description, aspects, location, amount = 1):
            self.name = name
            self.description = description
            self.aspects = []             
            if len(location) < 1: 
                raise ValueError("Clothing item %r needs at least one location it can be worn!" % name)
            self.location = location
            self.amount = amount
    
    
    class Outfit(object):
        
        def __init__(self, clothes):
            for item in clothes:
                self.equip(item)
        
        # declaring "@property" means you can use outfit.hat like a variable name, not a function call
        @property
        def hat(self):
            return self._hat if self._hat is not None else "nothing"
            
        # this is how you declare a setter method for the 'hat' property
        @hat.setter
        def hat(self, clothing_item)
            if isinstance(clothing_item, Clothing) and "head" in clothing_item.location:
                self._hat = clothing_item
            else:
                raise ValueError("Hm, can't put a %r on your head" % clothing_item)
        
        def equip(self, clothing_item, loc=None):
            if not loc:
                # use whatever is listed first as the default location
                loc = clothing_item.location[0]
            if (loc == 'head'):
                self.hat = clothing_item
            elif (loc == 'torso'):
                self.shirt = clothing_item
            elif (loc == 'legs'):
                self.trouser = clothing_item
            else
                raise ValueError("unknown clothing location %r for %" % (loc, clothing_item))


default p_outfit = Outfit([starting_shirt, starting_trouser])


label start:
    $ p_outfit.equip(raincoat)

    "The thing on your head slot is [p_outfit.hat.name]. It is [p_outfit.hat.description]."
There are ways to tighten this code up even more, but doing it right depends on knowing more about which functions you're actually going to use most often.
colin r
➔ if you're an artist and need a bit of help coding your game, feel free to send me a PM

User avatar
LateWhiteRabbit
Eileen-Class Veteran
Posts: 1867
Joined: Sat Jan 19, 2008 2:47 pm
Projects: The Space Between
Contact:

Re: [Solved] Trying to use a Method to change variables based on Attributes of a Class Instance

#6 Post by LateWhiteRabbit »

zmook wrote: Sun Nov 28, 2021 11:25 am If you want to take better advantage of python, consider this:
CODE
There are ways to tighten this code up even more, but doing it right depends on knowing more about which functions you're actually going to use most often.
I really appreciate it, and it does seem like a more elegant solution, but I can't make your code work.

I tried it out in a new project for a test (fixing some missing colons and making sure things like starting_shirt and starting_trouser were defined), but I can't get any of it to work. I keep getting missing value errors, attribute errors like Outfit object has no attribute '_hat', etc. and in general I'm just not experienced enough with Python to make any use of it.

I do appreciate that it taught me about setters and @property, but it's all probably too advanced for me at the moment. Ocelot's modification of my code perhaps isn't the most efficient way to do what I want, but I can understand and use it.

I'd love to tighten my code and my knowledge of Python more, but ease of use and readability of the code are my chief concerns at the moment. Hence why I'm not shy about long and descriptive variable names. :lol:

User avatar
zmook
Veteran
Posts: 421
Joined: Wed Aug 26, 2020 6:44 pm
Contact:

Re: [Solved] Trying to use a Method to change variables based on Attributes of a Class Instance

#7 Post by zmook »

LateWhiteRabbit wrote: Sun Nov 28, 2021 9:52 pm I tried it out in a new project for a test (fixing some missing colons and making sure things like starting_shirt and starting_trouser were defined), but I can't get any of it to work. I keep getting missing value errors, attribute errors like Outfit object has no attribute '_hat', etc. and in general I'm just not experienced enough with Python to make any use of it.
Yeah, I left a lot of declarations out. If you understand python classes well enough, they're easy to fill in, but it's more pseudocode than anything functional at the moment.

My main suggestion, then, is that your code will be easier to understand and modify later, if you put the code for tracking "what is the character currently wearing?" in a different place from Inventory, which is is "what is everything the character possesses?" The two collections are different, and if you mix them together you just make it harder to keep track. You might think, "oh no, I understand all this," but if you put the game down for a while and come back and try to modify something you did six months ago, you'll see what I mean.

The cleanest way to organize the code is to create separate Outfit and Inventory classes, to track their separate information. (This is what "classes" are *for*.) Outfit should have variables for all the clothing slots, and this is also the right place for an "equip" function. But if that seems too complicated now, you can just put comment lines, like " # ---- Player's outfit -----" and " # ---- Player's inventory ----" to mark out different parts of your script that manage them.
colin r
➔ if you're an artist and need a bit of help coding your game, feel free to send me a PM

Post Reply

Who is online

Users browsing this forum: Google [Bot]