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.