Create your AI Cum Slut -70% for Mother's Day
x

Ren'Py Practical usage of classes in Ren'py?

NesteroV

New Member
Dec 10, 2024
4
2
Beginner developer here. Two days ago I started learning lists, dictionaries and classes, mostly because of these two guides.




I find classes complex after using defaults, yet I figured out that defining everything at once is much better than doing it all over again for each character and action. Saves a lot of time, looks readable, and can be easily adjusted. But those are just basics. I wonder what else can be done with them. Maybe someone more experienced can share some wisdom? Because I have a feeling that I might overcomplicate things without knowing it.
 

Winterfire

Forum Fanatic
Respected User
Game Developer
Sep 27, 2018
5,829
8,483
There are simple variables like integers (ex. Money), floats (ex. Transparency), string (ex. locations) which can be used to make a full fledged visual novel with no issues at all.
However, when you need complex objects such as "Items" or "Characters" you need to define them in some way... What is an item, or rather, what does an item contain?

Typically a simple item would contain: ID (integer?), Name (String), Price (Integer), Description (string), etc. Not to mention that some items may require extra variables (Weapons would need an attack value) whereas a misc item wouldn't.
Without a class, a container, it would get dirty and messy rather quickly... Instead, you can create a class called "Item" and initialize all the items you need, or variants of it.

-edit-
Technically a simple item could be simply a list of strings... As long as the name is equal to the name of the image, you could display both name and icon of the item for a simple inventory. However, if you need anything more than that (such as description) then you'll need a class for it.
 

NesteroV

New Member
Dec 10, 2024
4
2
Can't say that I'm going for complex inventory systems. It's more like conditional, interaction based dating sims. A little bit of everything to be honest.
 

osanaiko

Engaged Member
Modder
Jul 4, 2017
2,930
5,597
Can't say that I'm going for complex inventory systems. It's more like conditional, interaction based dating sims. A little bit of everything to be honest.
Classes help for some stuff, such as situations that winterfire explained.

But they are simply not needed for a basic VN. Try making your game and see how far you can get just using regular variables.

BTW, for your first project it's highly likely that two things will happen:

- you will learn so much stuff that's helpful. Learning about all the different skills required, which includes but is absolutely not limited to programming. (Skills like: art creation, asset management, story plotting, dialogue writing, game design, music discovery and usage, sound effects, UI skinning, etc etc etc).

- you will make a bunch of mistakes and probably paint yourself into a corner.

The typical outcome is that you will abandon the first attempt and start a new one, but this time with all that knowledge, thus vastly increasing your ability to complete the work and the quality of your output.
 
  • Like
Reactions: NesteroV

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
11,811
18,243
But they are simply not needed for a basic VN. Try making your game and see how far you can get just using regular variables.
I disagree with this.

They are not needed for most basic VN, but the instant you starts to have a bit of counters to weight the character's reaction (love, lust, etc.), and/or if you want to keep track of some key choices regarding a character, they come handy since they permit to group everything.

There's too many games that have dozen of variables for each characters due to that, and lead to a mess because the dev lost track of them, or forget their syntax. It's, alas, not exceptional to have games with "amyLust", "amylust" and "amy_lust" variables. Something that obviously break the game since the values aren't accurate anymore, but that will throw no errors because the syntax is valid.
Same when the game have a lot of characters and the dev want short names for their variables. Then there's possible confusion between "v_lust" and "val_lust", the first one being possibly used in place of the second. Like there's the object, then the attribute, you're more pron to use longer names for the object itself. It will then become "ver.lust", "val.lust", what is less easy to confuse.


Like classes can do more than just storing values, they can also be used to avoid confusion. Something like this by example:
Python:
init python:

    #  Class where to store the characters stats.
    class CharStats( renpy.python.RevertableObject ):

        #  Initialize the object.
        def __init__( self ):

            #  Counters starts at 0.
            self.love = 0
            self.lust = 0

            #  Keep track of the important choices.
            self.flags = set( [] )

        #  Increase the love by /step/ points.
        def incLove( self, step ):
            self.love += step

        def incLust( self, step ):
            self.lust += step

        #  Decrease the love by /step/ points.
        def decLove( self, step ):
            self.love -= step

        def decLust( self, step ):
            self.lust -= step

        #  Add a key point.
        def addFlag( self. flag ):
             # Store the value in lowercase to limits syntax errors.
             self.flags.add( flag.lower() )

        def haveFlag( self, flag ):
             #  Check if the lowercase value is stored, and therefore if the flag exist.
            return flag.lower() in self.flags

default amy = CharStats()
permit to strengthen your code.

Instead of something like $ amyLove += 1 that wouldn't throw an error if the variable is in fact name "amylove", you would have $ amy.incLove( 1 ), that is a bit longer to type, but will throw an error if you write "amy.inclove( 1 )".
You check it as usual with if amy.love > 5, and like it's an object attritube, if you write "amy.Love", it will throw an error.
Bonus effect, like the "+=" and "-=" are wrote once, in the class method, no more risk to write "=-" or "=+", something that happen a bit too often and totally reset the value.


The last method also permit you to manage the key decisions without needing to have a tons of boolean variables. Something like:
Python:
label whatever:
    [...]
    menu:
        "Where do you want to go for your date with Amy?"
        "Something simple like a coffee shop":
            $ amyCoffee = True
            [...]
        "There's that movie I'm sure she would love to watch":
            $ amyMovie = True
            [...]
[...]
label somethingElse:
    [...]
    if amyCoffee:
        "I hope that this time we will not go to that lame coffee shop."
    else:
        "Another movie? I can't wait."
would them become something like:
Python:
label whatever:
    [...]
    menu:
        "Where do you want to go for your date with Amy?"
        "Something simple like a coffee shop":
            $ amy.addFlags( "coffee" )
            [...]
        "There's that movie I'm sure she would love to watch":
            $ amy.addFlags( "movie" )
            [...]
[...]
label somethingElse:
    [...]
    if amy.haveFlag( "coffee" ):
        "I hope that this time we will not go to that lame coffee shop."
    else:
        "Another movie? I can't wait."
And it doesn't matters if you wrote "Coffee" the first time, and "COFFEE" the second, the class transformed it into "coffee" both when you add the flag and when you check it. Therefore, here again you limits the risk of errors.



But classes permit way more than this. Not everything being usefull for a basic VN, but some can come handy.

By example a girl can agree to something if either she love the MC enough, or if she's lusty enough. In place of if amyLust > 5 or amyLove > 5: you can then have something like:
Python:
init python:
    class CharStats( renpy.python.RevertableObject ):
        [...]
        def loveOrLust( self, trigger ):
            #  Return True if one or the other of the value is greater than /trigger/.
            return self.love > trigger or self.lust > trigger
and then write if amy.loveOrLust( 5 ):

Starting there you can hide a lot of more or less complex computation behind something simple:
Python:
init python:
    class CharStats( renpy.python.RevertableObject ):
        [...]
        def average( self, trigger ):
            # True if the average between love and lust is greater than /trigger/.
            return ( self.love + self.lust ) / 2 > trigger

        def whole( self, trigger ):
            #  True if the sum of love and lust is greater than /trigger/.
            return self.love + self.lust > trigger
It's a bit more limited in use, but if you goes for a love Versus corruption approach, or something like that, you can even have something like:
Python:
init python:
    class CharStats( renpy.python.RevertableObject ):

        def __init__( self ):
            [...]
            #  What route is followed for this character.
            self.route = ""

        [...]

        #  Did she have enough points?
        def haveEnough( self, trigger ):
            #  If she's on the corruption route, it's her lust that decide.
            if self.route == "corruption":
                return self.lust > trigger
            #  Else, whatever if she's on the love route, or the route haven't been decided yet,
            # it will be her love that decide.
            else:
                return self.lust > trigger
Then, instead of having to constantly write something like if ( amyRoute == "corruption" and amyLust > 5 ) or ( not amyRoute == "corruption" and amyLove > 5), with all the possibility of spelling error that it imply, you just have to write if amy.haveEnough( 5 ).



And, but it's even more advanced, the class do not limits to storing and managing values.
Ren'Py permit to dynamically name an image, something that can by example be used for the classical "what swimsuit did you buy her for the mandatory beach scene?".

By default, it's done that way:
Python:
label whatever:
    menu:
        "what swimsuit do you choose?"
        "The blue one":
            $ amySwimsuit = "blue"
        "The sluty one":
            $ amySwimsuit = "sluty"

[...]

label theMandatoryBeachScene:
    #  Display either the "beach01_blue" or "beach01_sluty" image.
    scene expression "beach01_" + amySwimsuit
    [...]
    scene expression "beach02_" + amySwimsuit
    [...]
    scene expression "beach03_" + amySwimsuit
    [...]
But you can perfectly handle it through the class:
Python:
init python:
    class CharStats( renpy.python.RevertableObject ):

        def __init__( self ):
            [...]
            #  What swimsuit will she wear.
            self.swimsuit = ""

        [...]
        def beachScene( self, id ):
            return "beach{}_{}".format( id, self.swimsuit )

[...]

label whatever:
    menu:
        "what swimsuit do you choose?"
        "The blue one":
            $ amy.swimsuit = "blue"
        "The sluty one":
            $ amy.swimsuit = "sluty"

[...]

label theMandatoryBeachScene:
    #  Still display either the "beach01_blue" or "beach01_sluty" image.
    scene expression amy.beachScene( "01" )
    [...]
    scene expression amy.beachScene( "02" )
    [...]
    scene expression amy.beachScene( "03" )
    [...]
Or, but more limited in use:
Python:
init python:
    class CharStats( renpy.python.RevertableObject ):

        def __init__( self ):
            [...]
            #  What swimsuit will she wear.
            self.swimsuit = ""
            self.sceneCount = 0

        [...]
        def beachScene( self ):
            #  Pass to the next image.
            self.sceneCount += 1
            return "beach{}_{}".format( self.sceneCount, self.swimsuit )

[...]

label theMandatoryBeachScene:
    #  A small difference, will display either the "beach1_blue" or "beach1_sluty" image.
    scene expression amy.beachScene()
    [...]
    #  Will automatically pass to either "beach2_blue" or "beach2_sluty".
    scene expression amy.beachScene()
    [...]
    scene expression amy.beachScene()
    [...]
Be noted that once again, like you rely on a class method, it you misspell the name, it will throw an error that you'll immediately notice. But here it's less important since a missing image would normally also be noticed.


Will not mandatory for a basic VN, classes can still be interesting to use even in that case. It need a bit more of peparatory works, but it seriously limits the risk of errors and can sometimes simplify the rest of the development.


Edit: fixed the mess with the not closed "code" block.
 
Last edited:

Turning Tricks

Rendering Fantasies
Game Developer
Apr 9, 2022
1,680
3,131
I disagree with this.

<<snip>>

Thanks for this. I bookmarked it.

A year ago, this stuff would have flown right over my head. But now, I was actually able to follow along with it :LOL:

I'm getting closer to making my first Sandbox! :eek:
 

osanaiko

Engaged Member
Modder
Jul 4, 2017
2,930
5,597
anne O'nymous excellent examples, very good for anything remotely complex.

I did say this: "But they are simply not needed for a basic VN."

And your response was fine and showed how classes could be helpful.

But this part: "Try making your game and see how far you can get just using regular variables."

I stand by that statement in this specific case.

I don't think that this OP, who is a beginner, will benefit from the additional cognitive load of learning python object oriented classes while they are taking their baby steps with renpy.
 

Winterfire

Forum Fanatic
Respected User
Game Developer
Sep 27, 2018
5,829
8,483
anne O'nymous excellent examples, very good for anything remotely complex.

I did say this: "But they are simply not needed for a basic VN."

And your response was fine and showed how classes could be helpful.

But this part: "Try making your game and see how far you can get just using regular variables."

I stand by that statement in this specific case.

I don't think that this OP, who is a beginner, will benefit from the additional cognitive load of learning python object oriented classes while they are taking their baby steps with renpy.
I'm not sure if I agree with your statement, at least not completely.

Partially, yes, as long as you don't release your project and simply keep it to yourself as you learn, in that case failing is okay and there's no pressure, you can learn the basics (and much more, since we're talking about making a whole game which includes much more than "programming").

However, in a real project which you release to the public, you can't afford "try and see how far you get", it takes proper planning. Even when you want to code a system (say inventory) you can't just code it straight away, you need to plan it (not necessarily pseudocode) otherwise you'll be wasting time going back and forth with a lot of editing and risk of breaking stuff and compatibility with other systems.

You could also argue that while learning what a class is and how to make one the first time is complex, but once you do learn it, it will save you a lot of headaches and time in the long run.

That being said, I did make complete projects without the usage of classes, as long as you can plan in advance it's okay.
I'd say learning functions takes priority.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
11,811
18,243
I don't think that this OP, who is a beginner, will benefit from the additional cognitive load of learning python object oriented classes while they are taking their baby steps with renpy.
But, as implied by Winterfire, would he really benefit from not using classes in their most basic use, therefore as repository to groups variables?

I presented an overview of their possibilities since it's what OP wanted, but I haven't said "use classes at their fullest right from the starts".
Yet, building your first game, even one that you'll make to learn and will never release, using class in that most basic way, would be useful. Not only it will force him to be attentive to his variable names (since reading a missing attribute would throw an error), but it would also be a first approach with classes. All this with a really low cost since he would write "amy.love" in place of "amyLove".
At the opposite, a pure variable approach will give you the false idea that your game works, while it's in fact broken because you've half of the point stored in "amylove" and half in "amyLove". And, while I don't have stats, at first glance at least 40% of Ren'Py games have had this kind of error at least once.

It's not because classes offer more possibilities that one need to goes further for his first try.
 

NesteroV

New Member
Dec 10, 2024
4
2
Thanks to all your help and learning from your examples of how flexible classes can be, I am motivated to rewrite my current work in a much cleaner way. anne O'nymous code, kindly provided as a reference, opened my eyes to topics I had never considered before.

It was refreshing to see a new perspective after being stuck in development with my current project. A spark of hope was exactly what I needed. I will strive to keep my code neat and simple with new features to avoid getting lost in it again.