- Jun 14, 2018
- 1,607
- 2,256
Newbie introduction to Lists, Dictionaries and Classes in RenPy
And by newbie, I mean me obviously.
This can be considered Chapter 2 continuation of Newbie guide to basic variables in RenPy.
This is not a short guide and even now, there is still a lot potentially missing.
I take some liberties for the sake of keeping things simple (Yes, I know
I'm going to be mixing RenPy and python statements here without specifically using the RenPy statements
Lists. See :
Just arrays in my mind. Collection of other stuff.
I'm going to do into a lot of detail with lists here, but only because most of it is true for the other stuff I will talk about later.
It's the square brackets that make python recognise a list.
Each part of the list can be referenced by a number. Those numbers start at ZERO. It's known as it's index number.
So
If you try to access an item beyond the end of the list, you get an "index out of bounds" error.
So
There's also special case where index number -1 (minus one) lets you access the last item in the last. (-2, -3, also work going backwards - though why bother?).
So
The contents of a List can be any other data type. So far I've only used strings.
But this would be fine too...
Which is a list of integer numbers.
You can also get part of a list by using ranges of index numbers.
A range is specified as
So...
Reminder:
You can also do open ended ranges...
You can also mix any other data types too.
Though programming tends to work towards fixed, predictable structures... so something as random as this is very unlikely (at least in something as simple as a RenPy game).
That said, it could be a persons name, age, phone number, height, etc.
You can also have lists of lists.
Though RenPy (maybe python too) stores strings as Unicode, so when you display things like "Bob", it actually shows as
Much more common though is to just go straight to the creation of
Though something that unwieldy can be made a bit easier to read by spreading it across multiple lines....
Again, in theory, each of those lists within the main list could be different lengths and have different data types in there. But it just wouldn't make sense for most programming needs with a simple RenPy game.
Beyond that, you can have lists within lists within lists within lists within lists.
Things are only limited by your imagination. Though keeping it simple will keep you sane.
Working with lists.
You can figure out how long a specific list is by using the
You can check if a given value is part of this list using
You can add to the end of a list using
You can add into the middle of a list using
You can remove something from a list by value using
If there is more than one "banana", it will remove the first...
You can also remove something from a list by it's index number using
One other aspect of
If you don't give it an index number, it will just remove the last item in the list...
Finally, you can empty the list completely using
Looping through lists.
You can loop through a list's values using
But since you can put python code into RenPy code, you can still do this...
Why "x" ? ... Why not?
It can be any variable name that is unique.
Though in RenPy games, where characters are known as
I'm using
Here's a more complex example... though seriously, don't ever do this...
You can also loop through a list's indexes using
If I'm honest, I can't really see why you'd want to. But if the index numbers matter to you more than the data... I guess this is your solution.
A note of *** WARNING ***
What it does is create a pointer (I believe it's called a reference) to the first list.
This isn't just true of lists, but (I believe) most objects within python.
So be careful anytime you're typing anything that looks like it could be
The solution is either
Everything else...
Details of what else can be done with lists can be read here...
Like
Dictionaries. See :
Dictionaries are just a variation on a theme. They are stored as key and data pairs.
So something like:
It's the curly brackets and that extra colon that make python recognise a dictionary.
Each data element of the dict can be referenced by it's key, by putting the key within square brackets.
Which feels kinda limiting... with only one bit of data per entry.
Except, in the same way you can have lists within lists... You can have lists within dictionaries too (or even dictionaries within dictionaries).
Maybe something like:
Where the person's first name is used as a key to reference the other information held about them.
Which has the downside that you can only have one person called "Bob" or one person called "Alice". But this is only a poor example, so don't sweat it. I trust you to pick better values for keys.
With this dict/list combo, you would access stuff as a combination of both the way you access dictionaries and lists.
Working with dictionaries.
You can check if a given value is a key of this dict using
You can also check if a given value matches the data of this dict using
To use our more complicated "people info" example...
In this example, we've needed to use a mix of both our dict() and list() references. Specifically, we've used
The simplest way to add an item to a dictionary is to just update the dictionary using a new key.
The key for dictionary can be any simple object. It just has to be possible to "hash" it, which I've no clue about. From my perspective it just means that the key should be a string, an integer, a float or even a boolean (True/False). You can't use a list() as a key. I did try using an image as a key and it did technically work, except I couldn't figure out how make practical use of it. So far, in any game I've seen, I've only ever seen strings used as keys.
Updating a value is done in the same way, expect by using a key name that already exists.
Though there is a dictionary method called
Though that kinda feels like the hard way to do it.
Next, there's removing an entry from the dictionary using
Again, like with lists,
Finally, like lists... dictionaries can be emptied by using
Looping through dictionaries.
Looping around a dictionary comes in three flavors. You can either loop around the key values or you can loop around the data values or both.
Everything else...
Details of what else can be done with lists can be read here...
Like
Sets and Tuples. See :
I've never had cause to really look into Tuples or Sets. They are the other two types of array in addition to lists and dictionaries.
From what little I've read, tuples are just lists that can't be changed without recreating them. They use parentheses
Sets seem to be lists that can't be accessed using either an index or a key. From what little I can see, you can only loop around the values in set. Though there does seem to be a lot of extra functions for comparing two sets and some other shit.
I'm sure there's a good reason for using either. I just haven't a clue what it might be.
... and no, I really don't need to be told their usage. Feel free to write your own guide - I may even read it.
Classes. See :
For me, Classes are just a variation on lists. At least, to the level I use them at the moment.
Classes are a template used to create variables. Though I suppose all data is an "object" rather than variable. I use the two names interchangeably.
You're already using the string and integer classes without even realising it (although they are treated someone differently when you code python, so it's understandable... they are sort of implied rather than explicit).
But
* don't shoot me.
Creating a class.
Classes have two components. "properties" and "methods". Properties are the data. Methods are the code that is runs when you tell it to.
I would use "function" and "method" interchangeable. But that maybe just shows how much I really don't understand what I'm talking about.
There is a special function/method name called
Taking it step by step...
So matching stuff up.
"self" is the first parameter and is the name of the new variable (in this case
After that, all the values within the Person() brackets are passed in sequence as the rest of the parameters.
So...
fname is "Bob", sname is "Parker", age is 35, etc.
The convention is that class names should have each word within it Capitalized and to not use any separators except those capital letters. In this case
Not everyone follows that convention, especially RenPy devs who've barely read the documentation (like me).
But at the moment, these are only the values passed to the function. Now we need to do something with them.
That would look like:
You may notice I've deliberately used different identifiers for forename and surname.
Most people would use the same name for both parameter and property though. After all, why make life more difficult for yourself by using two different names (like I have).
Likewise
Here's what it would look like if I didn't use
Everything after the
But for
The data types are inherited from the parameters that are passed to the function.
So since "Bob" is a string,
Finally (at least for __init__), functions allow for default values to be assumed if no other value is passed to the function.
So...
I don't think it makes much sense to assume a first name. To be honest, it doesn't make much sense to assume a family name either, but "Smith" is a good catch all.
By adding those default values, you can now do stuff like:
Which would create
If you wanted to create his older sister Zoe, but using all those other defaults... then...
Which would create
Even though
In fact, you can put everything out of sequence if you are that sort of warped person...
Accessing the values within a class.
Each time you use a class to create a new variable, all those
Since we know that
So
Classes based upon other classes (inheritance).
You can also create new classes based upon other classes. If you don't override anything, all the functions will be copied from the old class to the new class. Since one of those functions is
All you need to do is put the name of the parent class in the brackets of the the
But the point to inheritance is to override things. Maybe you want to add a photo the to Person() class and call it Person2().... Yeah, I know... I've zero imagination.
You'd code something like:
Honestly, the idea of inheritance is well beyond "newbie" level - so if you really care about that sort of stuff... go read...
define -vs- default.
So far, I've done all my examples as
Simple rule of thumb is if the values are not going to change while the game is running, use
Or put another way, stuff created with
You might use
Or use
Think carefully about this sort of stuff. Data saved is also loaded back into the game. If you have 10 achievements stored in a way that will be saved, then add another 5 in chapter 2 of your game.... those 10 will still be loaded by existing players and those extra 5 will be lost (unless you make other arrangements). Likewise, my example of a shop and inventory. Obviously you want the inventory to be saved by the player, but what if you change the prices in the shop and you didn't use
Custom functions.
The convention is that function names be all lowercase, with underscores used to separate words for readability. Like
Again, not everyone follows the conventions. So you might see code that only uses mixedCase. Like
Generally speaking... pick a style of naming and stick to it. But the underscores are the recommended style.
See :
Function names that begin with double underscore (
The same should be consider true for RenPy too which uses a single underscore for it's internal stuff.
So we've used one "method", called
Okay. That looks pretty pointless when we can just as easily use:
But perhaps you want to add some validation, to avoid unrealistic ages.
Some functions are used to return a value back to the calling code.
But programmers are always looking for shortcuts, and there's one here...
It will still return True or False, just using slightly less code to do it.
Beyond that, there's only your imagination (and learning from experience) to decide what other things work well as functions.
Other special functions.
There are some other special function names I've come across.
So for example, the
* please don't shoot me.
So even though
In our Person() example above, we could perhaps use it to return the character's full name.
Honestly, I'm really unsure on this one.
But using our Person() example again, I can imagine something like:
What this is doing is returning False if the other variable being compared isn't actually a
But as long as it is a
You'd use it something like:
In this case, even though not all the data is the same, because both names match, that is enough for the code to say the objects match.
Overriding
I've somewhat glossed over
Practically everything is a class.
So way back near the start, I pointed out that lists can contain lists and dictionaries can contain lists.
But a list is a class and so is a dictionary. And so is a string, an integer, a float and a boolean.
So in the same way we have lists made up from strings and integers as well as lists made up from strings, integers and other lists... we can pretty much mix things up as much as we like.
We could have a list of people. Using a list which contains a Class.
In this case,
Everything beyond this point is just a variation on that theme.
Dictionaries using classes.
Classes using other classes.
Classes using lists.
... and anything else you can keep straight in your head.
I recently saw a piece of code intended to be used as a phone text message system for a RenPy game.
That looked a bit like this:
(I've edited it to remove the other functions)
In this case, each character within the game was given it's own
Then functions were used to add messages (using the
In addition, as
Whilst the meat of the code is missing, I hope it shows the potential for classes which store data using other classes and lists (and dictionaries, strings, integers, etc, etc.).
Anyway. that's it. Well done if you reaches this far. Please accept a sticker from the nurse on the way out.
And by newbie, I mean me obviously.
This can be considered Chapter 2 continuation of Newbie guide to basic variables in RenPy.
This is not a short guide and even now, there is still a lot potentially missing.
I take some liberties for the sake of keeping things simple (Yes, I know
Character()
is a function not a class, but it's easier to explain the familiar than be 100% accurate).I'm going to be mixing RenPy and python statements here without specifically using the RenPy statements
$
or python:
. It's only to make it easier to read here at the beginning. As I need to introduce the full code, I will. The actual syntax in a RenPy game will vary, depending on how you use each statement.Lists. See :
You must be registered to see the links
Just arrays in my mind. Collection of other stuff.
I'm going to do into a lot of detail with lists here, but only because most of it is true for the other stuff I will talk about later.
Python:
mylist = ["apple", "banana", "cherry"]
It's the square brackets that make python recognise a list.
[ ]
.Each part of the list can be referenced by a number. Those numbers start at ZERO. It's known as it's index number.
So
mylist[0]
is "apple"If you try to access an item beyond the end of the list, you get an "index out of bounds" error.
So
mylist[3]
wouldn't work with this data.There's also special case where index number -1 (minus one) lets you access the last item in the last. (-2, -3, also work going backwards - though why bother?).
So
mylist[-1]
would be "cherry"The contents of a List can be any other data type. So far I've only used strings.
But this would be fine too...
Python:
mylist = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]
Which is a list of integer numbers.
You can also get part of a list by using ranges of index numbers.
A range is specified as
[x:y]
, where x is the start index and y is the end index. The "end index" item is NOT part of the returned values, so I find it easier to think of it as "end index + 1".So...
mylist[0:4]
is [2, 3, 5, 7].mylist[3:4]
is [7].mylist[3:8]
is [7, 11, 13, 17, 19]Reminder:
mylist[8]
is 23.You can also do open ended ranges...
mylist[:5]
is [2, 3, 5, 7, 11].mylist[5:]
is [13, 17, 19, 23, 29, 31, 37]You can also mix any other data types too.
Python:
mylist = [2, "banana", "cherry", 3.14159265, 11, "42", 17, 19, 23, 29, 31, 37]
# [0] = 2 (integer aka int)
# [1] = "banana" (string aka str)
# [2] = "cherry" (str)
# [3] = pi (floating point number aka float)
# [4] = 11 (int)
# [5] = "42" (str) !!!
# etc, etc.
Though programming tends to work towards fixed, predictable structures... so something as random as this is very unlikely (at least in something as simple as a RenPy game).
That said, it could be a persons name, age, phone number, height, etc.
Python:
list_bob = ["Bob", "Parker", 35, "1-800-fuc-kyou", 1.77]
list_alice = ["Alice", "Taylor", 28, "1-212-555-1234", 1.57]
list_john = ["John", "Doe", 65, "", 1.9] # John doesn't have a phone number.
You can also have lists of lists.
Python:
list_people = [list_bob, list_alice, list_john]
# list_people[0] is ["Bob", "Parker", 35, "1-800-fuc-kyou", 1.77]
# list_people[0][0] is "Bob"
# list_people[2][1] is "Doe"
# list_people[1][4] is 1.57
Though RenPy (maybe python too) stores strings as Unicode, so when you display things like "Bob", it actually shows as
u'Bob'
.Much more common though is to just go straight to the creation of
list_people
, without creating the containing lists separately.
Python:
list_people = [ ["Bob", "Parker", 35, "1-800-fuc-kyou", 1.77], ["Alice", "Taylor", 28, "1-212-555-1234", 1.57], ["John", "Doe", 65, "", 1.9] ]
Though something that unwieldy can be made a bit easier to read by spreading it across multiple lines....
Python:
list_people = [
["Bob", "Parker", 35, "1-800-fuc-kyou", 1.77],
["Alice", "Taylor", 28, "1-212-555-1234", 1.57],
["John", "Doe", 65, "", 1.9]
]
Again, in theory, each of those lists within the main list could be different lengths and have different data types in there. But it just wouldn't make sense for most programming needs with a simple RenPy game.
Beyond that, you can have lists within lists within lists within lists within lists.
Things are only limited by your imagination. Though keeping it simple will keep you sane.
Working with lists.
You can figure out how long a specific list is by using the
len()
function...len(list_people)
is 3.len(list_people[0])
is 5.You can check if a given value is part of this list using
if ... in ...
Python:
default mylist = ["apple", "banana", "cherry"]
label start:
if "apple" in mylist:
"There is an apple in the list."
else:
"Sorry, no apples today."
return
You can add to the end of a list using
.append()
...
Python:
mylist = ["apple", "banana", "cherry"]
mylist.append("orange")
# mylist is now ["apple", "banana", "cherry", "orange"]
You can add into the middle of a list using
.insert()
...
Python:
mylist = ["apple", "banana", "cherry"]
mylist.insert(2, "orange")
# mylist is now ["apple", "banana", "orange", "cherry"]
You can remove something from a list by value using
.remove()
...
Python:
mylist = ["apple", "banana", "cherry"]
mylist.remove("banana")
# mylist is now ["apple", "cherry"]
If there is more than one "banana", it will remove the first...
Python:
thislist = ["apple", "banana", "cherry", "orange", "banana"]
thislist.remove("banana")
# mylist is now ["apple", "cherry", "orange", "banana"]
You can also remove something from a list by it's index number using
.pop()
...
Python:
mylist = ["apple", "banana", "cherry"]
mylist.pop(1)
# mylist is now ["apple", "cherry"]
One other aspect of
.pop()
is that the data that is removed is passed back to the invoking code. It might be that .remove()
does the same, I haven't tested it.
Python:
mylist = ["apple", "banana", "cherry"]
removed_item = mylist.pop(1)
# mylist is now ["apple", "cherry"]
# removed_item is now "banana"
If you don't give it an index number, it will just remove the last item in the list...
Python:
mylist = ["apple", "banana", "cherry"]
mylist.pop()
# mylist is now ["apple", "banana"]
Finally, you can empty the list completely using
.clear()
.
Python:
mylist = ["apple", "banana", "cherry"]
mylist.clear()
# mylist is now []
# Though honestly... this will work just as well, and is quicker to just type...
mylist = []
Looping through lists.
You can loop through a list's values using
for:
...for:
is a python command. There is no RenPy equivalent, except when writing screen code.But since you can put python code into RenPy code, you can still do this...
Python:
mylist = ["apple", "banana", "cherry"]
for x in mylist:
renpy.say(None, x)
# Will show...
# "apple"
# "banana"
# "cherry"
list_people = [
["Bob", "Parker", 35, "1-800-fuc-kyou", 1.77],
["Alice", "Taylor", 28, "1-212-555-1234", 1.57],
["John", "Doe", 65, "", 1.9]
]
for x in list_people:
renpy.say(None, x)
# Will show...
# ["Bob", "Parker", 35, "1-800-fuc-kyou", 1.77]
# ["Alice", "Taylor", 28, "1-212-555-1234", 1.57]
# ["John", "Doe", 65, "", 1.9]
Why "x" ? ... Why not?
It can be any variable name that is unique.
Though in RenPy games, where characters are known as
a
for Character("Anne") and e
for Character("Eileen")... don't use a
or e
. I guess all I'm saying is that if "x" is used for something specific, don't use "x".I'm using
renpy.say()
here to have the RenPy game display something as dialogue. None
is used because no specific character is speaking.Here's a more complex example... though seriously, don't ever do this...
Python:
list_people = [
["Bob", "Parker", 35, "1-800-fuc-kyou", 1.77],
["Alice", "Taylor", 28, "1-212-555-1234", 1.57],
["John", "Doe", 65, "", 1.9]
]
for x in list_people:
for y in x
renpy.say(None, y)
# Will show...
# ... well, everything... one value at a time. It's slow and I'm not typing all that shit here.
You can also loop through a list's indexes using
for ... in range():
in combination with len()
...
Python:
list_people = [
["Bob", "Parker", 35, "1-800-fuc-kyou", 1.77],
["Alice", "Taylor", 28, "1-212-555-1234", 1.57],
["John", "Doe", 65, "", 1.9]
]
for i in range(len(list_people)):
renpy.say(None, list_people[i])
If I'm honest, I can't really see why you'd want to. But if the index numbers matter to you more than the data... I guess this is your solution.
A note of *** WARNING ***
mylist2 = mylist
DOES NOT CREATE A NEW LIST called mylist2
.What it does is create a pointer (I believe it's called a reference) to the first list.
This isn't just true of lists, but (I believe) most objects within python.
Python:
mylist = ["apple", "banana", "cherry"]
mylist2 = mylist
mylist.append("orange")
# mylist is now ["apple", "banana", "cherry", "orange"]
# mylist2 is also ["apple", "banana", "cherry", "orange"] !!!
So be careful anytime you're typing anything that looks like it could be
object2 = object
.The solution is either
.copy()
or list()
. Both work to create a new list, completely independent of the first.
Python:
mylist = ["apple", "banana", "cherry"]
mylist2 = mylist.copy()
mylist3 = list(mylist)
mylist.append("orange")
# mylist is now ["apple", "banana", "cherry", "orange"]
# mylist2 is still ["apple", "banana", "cherry"]
# mylist3 is also ["apple", "banana", "cherry"]
Everything else...
Details of what else can be done with lists can be read here...
You must be registered to see the links
Like
.sort()
or .count()
.Dictionaries. See :
You must be registered to see the links
Dictionaries are just a variation on a theme. They are stored as key and data pairs.
So something like:
Python:
mydict = {
"apple": "red",
"banana": "yellow",
"cherry": "red"
}
It's the curly brackets and that extra colon that make python recognise a dictionary.
{ }
.Each data element of the dict can be referenced by it's key, by putting the key within square brackets.
Python:
temp = mydict["banana"]
# temp would be "yellow"
Which feels kinda limiting... with only one bit of data per entry.
Except, in the same way you can have lists within lists... You can have lists within dictionaries too (or even dictionaries within dictionaries).
Maybe something like:
Python:
dict_people = {
"Bob": ["Parker", 35, "1-800-fuc-kyou", 1.77],
"Alice": ["Taylor", 28, "1-212-555-1234", 1.57],
"John": ["Doe", 65, "", 1.9]
}
Where the person's first name is used as a key to reference the other information held about them.
Which has the downside that you can only have one person called "Bob" or one person called "Alice". But this is only a poor example, so don't sweat it. I trust you to pick better values for keys.
With this dict/list combo, you would access stuff as a combination of both the way you access dictionaries and lists.
Python:
dict_people = {
"Bob": ["Parker", 35, "1-800-fuc-kyou", 1.77],
"Alice": ["Taylor", 28, "1-212-555-1234", 1.57],
"John": ["Doe", 65, "", 1.9]
}
v_temp_age = dict_people["Alice"][1]
# my v_temp_age variable would be 28.
Working with dictionaries.
You can check if a given value is a key of this dict using
if ... in ...
Python:
mydict = {
"apple": "red",
"banana": "yellow",
"cherry": "red"
}
label start:
if "apple" in mydict:
"Yes, there is an apple."
else:
"Sorry, no apples today."
return
You can also check if a given value matches the data of this dict using
if ... in ...
too
Python:
mydict = {
"apple": "red",
"banana": "yellow",
"cherry": "red"
}
label start:
if "red" in mydict.values():
"Yes, there is a red fruit."
else:
"Sorry, no red fruit today."
return
To use our more complicated "people info" example...
Python:
dict_people = {
"Bob": ["Parker", 35, "1-800-fuc-kyou", 1.77],
"Alice": ["Taylor", 28, "1-212-555-1234", 1.57],
"John": ["Doe", 65, "", 1.9]
}
label start:
if "Parker" in dict_people.values()[0]:
"Yes, we have someone called Parker."
else:
"Sorry, nobody from the Parker family here today."
return
In this example, we've needed to use a mix of both our dict() and list() references. Specifically, we've used
.values()
to access the actual data rather than the keys and [0]
to access the first element of the list, which stores the surname.The simplest way to add an item to a dictionary is to just update the dictionary using a new key.
Python:
mydict = {
"apple": "red",
"banana": "yellow",
"cherry": "red"
}
mydict["orange"] = "orange"
# -or-
dict_people = {
"Bob": ["Parker", 35, "1-800-fuc-kyou", 1.77],
"Alice": ["Taylor", 28, "1-212-555-1234", 1.57],
"John": ["Doe", 65, "", 1.9]
}
dict_people["Helen"] = ["Johnson", 30, "1-212-555-0000", 1.52]
The key for dictionary can be any simple object. It just has to be possible to "hash" it, which I've no clue about. From my perspective it just means that the key should be a string, an integer, a float or even a boolean (True/False). You can't use a list() as a key. I did try using an image as a key and it did technically work, except I couldn't figure out how make practical use of it. So far, in any game I've seen, I've only ever seen strings used as keys.
Updating a value is done in the same way, expect by using a key name that already exists.
Python:
mydict = {
"apple": "red",
"banana": "yellow",
"cherry": "red"
}
mydict["apple"] = "bright red"
Though there is a dictionary method called
.update
, that can be used like this:
Python:
mydict = {
"apple": "red",
"banana": "yellow",
"cherry": "red"
}
mydict.update({"apple"}: "bright red")
Though that kinda feels like the hard way to do it.
Next, there's removing an entry from the dictionary using
.pop()
Python:
dict_people = {
"Bob": ["Parker", 35, "1-800-fuc-kyou", 1.77],
"Alice": ["Taylor", 28, "1-212-555-1234", 1.57],
"John": ["Doe", 65, "", 1.9]
}
dict_people.pop("Bob"]
# removes Bob's data from the dictionary.
Again, like with lists,
.pop()
passes the removed data back to the invoking code. In the case of a dictionary though, only the data is passed back and not the actual key. So when we do dict_people.pop("Bob"]
, it returns ["Parker", 35, "1-800-fuc-kyou", 1.77]
.Finally, like lists... dictionaries can be emptied by using
.clear()
Python:
mydict = {
"apple": "red",
"banana": "yellow",
"cherry": "red"
}
mydict.clear()
# mydict is now {}
# Though again, I think it's just easier to type:
mydict = {}
Looping through dictionaries.
Looping around a dictionary comes in three flavors. You can either loop around the key values or you can loop around the data values or both.
Python:
for x in dict_people: #keys
renpy.say(None, "[x]"]
# would show a list of the dictionary keys. "Bob", "Alice", "John".
for x in dict_people.keys(): #keys
renpy.say(None, "[x]"]
# is another way to do the same thing. (a loop through the keys).
for x in dict_people.values(): #data
renpy.say(None, "[x]"]
# Would show ["Parker", 35, "1-800-fuc-kyou", 1.77] then ["Taylor", 28, "1-212-555-1234", 1.57] then ["Doe", 65, "", 1.9]
for x, y in dict_people.items(): #both
renpy.say(None, "Key : [x] has data value [y]"]
# hopefully things make enough sense by now to figure out for yourself what the output would look like.
Everything else...
Details of what else can be done with lists can be read here...
You must be registered to see the links
Like
.copy()
or .setdefault()
.Sets and Tuples. See :
You must be registered to see the links
I've never had cause to really look into Tuples or Sets. They are the other two types of array in addition to lists and dictionaries.
From what little I've read, tuples are just lists that can't be changed without recreating them. They use parentheses
( )
instead of square brackets.Sets seem to be lists that can't be accessed using either an index or a key. From what little I can see, you can only loop around the values in set. Though there does seem to be a lot of extra functions for comparing two sets and some other shit.
I'm sure there's a good reason for using either. I just haven't a clue what it might be.
... and no, I really don't need to be told their usage. Feel free to write your own guide - I may even read it.
Classes. See :
You must be registered to see the links
For me, Classes are just a variation on lists. At least, to the level I use them at the moment.
Classes are a template used to create variables. Though I suppose all data is an "object" rather than variable. I use the two names interchangeably.
You're already using the string and integer classes without even realising it (although they are treated someone differently when you code python, so it's understandable... they are sort of implied rather than explicit).
But
Character()
is a class* in RenPy. So is Image()
.* don't shoot me.
Creating a class.
Classes have two components. "properties" and "methods". Properties are the data. Methods are the code that is runs when you tell it to.
I would use "function" and "method" interchangeable. But that maybe just shows how much I really don't understand what I'm talking about.
There is a special function/method name called
__init__
that runs when a new object is created. It can be used to do anything that normal code will do. But mainly it's used to add a list of the properties available to this class. In a lot of ways, it's very similar to the keys of a dictionary.Taking it step by step...
Python:
init python:
class Person():
def __init__ (self, fname, sname, age, phone, height):
# some other shit
define bob_object = Person("Bob", "Parker", 35, "1-800-fuc-kyou", 1.77)
So matching stuff up.
"self" is the first parameter and is the name of the new variable (in this case
bob_object
).After that, all the values within the Person() brackets are passed in sequence as the rest of the parameters.
So...
fname is "Bob", sname is "Parker", age is 35, etc.
The convention is that class names should have each word within it Capitalized and to not use any separators except those capital letters. In this case
class Person()
. But taking it to an extreme class WeatherReportsAtNight()
.Not everyone follows that convention, especially RenPy devs who've barely read the documentation (like me).
But at the moment, these are only the values passed to the function. Now we need to do something with them.
That would look like:
Python:
init python:
class Person():
def __init__ (self, fname, sname, age, phone, height):
self.forename = fname
self.surname = sname
self.age = age
self.phone = phone
self.height = height
define bob_object = Person("Bob", "Parker", 35, "1-800-fuc-kyou", 1.77)
You may notice I've deliberately used different identifiers for forename and surname.
fname
is the name of the parameter passed to the function, whereas forename
is the name of the class's property. Same with sname
and surname
.Most people would use the same name for both parameter and property though. After all, why make life more difficult for yourself by using two different names (like I have).
Likewise
self
is just a name. In theory it could be anything, but everyone uses self
so it makes sense to do the same.Here's what it would look like if I didn't use
self
. It's still fine, but don't do it.
Python:
init python:
class Person():
def __init__ (wibble, forename, surname, age, phone, height):
wibble.forename = forename
wibble.surname = surname
wibble.age = age
wibble.phone = phone
wibble.height = height
define bob_object = Person("Bob", "Parker", 35, "1-800-fuc-kyou", 1.77)
Everything after the
def:
line is just python code. So if you wanted to invoke another function or do something clever... sure... why not.But for
__init__
maybe keep it simple and just use it to set up the variables and nothing else.The data types are inherited from the parameters that are passed to the function.
So since "Bob" is a string,
bob_object.forename
is also a string. And since 35 is an integer number, bob_object.age
is an integer number.Finally (at least for __init__), functions allow for default values to be assumed if no other value is passed to the function.
So...
Python:
init python:
class Person():
def __init__ (self, forename, surname="Smith", age=18, phone="", height=1.75):
self.forename = forename
self.surname = surname
self.age = age
self.phone = phone
self.height = height
I don't think it makes much sense to assume a first name. To be honest, it doesn't make much sense to assume a family name either, but "Smith" is a good catch all.
By adding those default values, you can now do stuff like:
Python:
define bob_object = Person("Bob")
Which would create
bob_object
as Bob Smith, aged 18 with no phone number and of average height.If you wanted to create his older sister Zoe, but using all those other defaults... then...
Python:
define zoe_object = Person("Zoe", age=22)
Which would create
zoe_object
as Zoe Smith, aged 22 with no phone number and of the same average height as this version of Bob.Even though
age
isn't the next parameter after forename, you can place it out of sequence by naming it explicitly.In fact, you can put everything out of sequence if you are that sort of warped person...
Python:
define alice_object = Person(age=28, height=1.57, forename="Alice", Surname="Winters", phone="1-212-555-1234")
Accessing the values within a class.
Each time you use a class to create a new variable, all those
def:
statements will carried forward to the new variable.Since we know that
self
is just another name for that variable, and the initialization created properties called things like self.forename
or self.age
- those carry forward too.So
default john = Person("John", "Barker", 22)
will let you access stuff as:john.forename
, john.surname
, john.age
or any of the properties regardless of whether you set the value explicitly or let it use the default values (if you've set those).Classes based upon other classes (inheritance).
You can also create new classes based upon other classes. If you don't override anything, all the functions will be copied from the old class to the new class. Since one of those functions is
__init__
that also means the same properties too.All you need to do is put the name of the parent class in the brackets of the the
class ()
statement.But the point to inheritance is to override things. Maybe you want to add a photo the to Person() class and call it Person2().... Yeah, I know... I've zero imagination.
You'd code something like:
Python:
class Person2(Person):
def __init__ (self, forename, surname="Smith", age=18, phone="", height=1.75, photo=None):
super().__init__(forename, surname, age, phone, height)
self.photo = photo
You must be registered to see the links
define -vs- default.
So far, I've done all my examples as
define
.Simple rule of thumb is if the values are not going to change while the game is running, use
define
. If they are going to change, use default
.Or put another way, stuff created with
default
is saved as part of the save game file, stuff created with define
is not.You might use
define
for a list of possible achievements. Then use default
to store which ones have been unlocked.Or use
define
for a list of items that can be purchased and default
to store the inventory of those items.Think carefully about this sort of stuff. Data saved is also loaded back into the game. If you have 10 achievements stored in a way that will be saved, then add another 5 in chapter 2 of your game.... those 10 will still be loaded by existing players and those extra 5 will be lost (unless you make other arrangements). Likewise, my example of a shop and inventory. Obviously you want the inventory to be saved by the player, but what if you change the prices in the shop and you didn't use
define
? Sometimes it's better to split something into two variables rather than risk permanently saving something which you don't expect to change (while the game is running).Custom functions.
The convention is that function names be all lowercase, with underscores used to separate words for readability. Like
def set_age():
.Again, not everyone follows the conventions. So you might see code that only uses mixedCase. Like
def setAge():
.Generally speaking... pick a style of naming and stick to it. But the underscores are the recommended style.
See :
You must be registered to see the links
Function names that begin with double underscore (
__
) are reserved internal python names. Don't create any yourself, unless they are already in the python documentation. If you add one in error and a future python release happens to add something using that exact same name... things will go... badly.The same should be consider true for RenPy too which uses a single underscore for it's internal stuff.
So we've used one "method", called
__init__
, but we can create as many new ones as we like (or none).
Python:
init python:
class Person():
def __init__ (self, forename, surname, age, phone, height):
self.forename = forename
self.surname = surname
self.age = age
self.phone = phone
self.height = height
def set_age(self, newage):
self.age = newage
default bob = Person("Bob", "Parker", 35, "1-800-fuc-kyou", 1.77)
label start:
$ bob.set_age(42)
"Bob is now [bob.age]."
"*** THE END ***"
return
Okay. That looks pretty pointless when we can just as easily use:
$ bob.age = 42
But perhaps you want to add some validation, to avoid unrealistic ages.
Python:
init python:
class Person():
def __init__ (self, forename, surname, age, phone, height):
self.forename = forename
self.surname = surname
self.age = age
self.phone = phone
self.height = height
def set_age(self, newage=18): # if no age is specified, use 18.
if newage < 18:
self.age = 18
elif newage > 99:
self.age = 99
else:
self.age = int(newage) # force an integer, just in case some idiot passed a floating point number as an age.
default bob = Person("Bob", "Parker", 35, "1-800-fuc-kyou", 1.77)
label start:
$ bob.set_age(6)
"Bob is now [bob.age]." # Bob is actually 18, not 6.
"*** THE END ***"
return
Some functions are used to return a value back to the calling code.
Python:
init python:
class Person():
def __init__ (self, forename, surname, age, phone, height):
self.forename = forename
self.surname = surname
self.age = age
self.phone = phone
self.height = height
def set_age(self, newage=18): # if no age is specified, use 18.
if newage < 18:
self.age = 18
elif newage > 99:
self.age = 99
else:
self.age = newage
def is_old(self):
if self.age >= 50:
return True
else:
return False
default bob = Person("Bob", "Parker", 35, "1-800-fuc-kyou", 1.77)
label start:
if bob.is_old():
"Bob is old."
else:
"Bob is young."
"*** THE END ***"
return
But programmers are always looking for shortcuts, and there's one here...
Python:
def is_old(self):
return self.age >= 50
It will still return True or False, just using slightly less code to do it.
Beyond that, there's only your imagination (and learning from experience) to decide what other things work well as functions.
Other special functions.
There are some other special function names I've come across.
__str__
can be used whenever the code expects a string.So for example, the
Character()
class* has a __str__
function that I presume looks something like:* please don't shoot me.
Python:
class Character():
def __init__ (self, name, magic, more_magic, some_shit_i_dont_understand):
# some clever shit.
def __str__ (self):
return self.name
define e = Character("Eileen", color="#04B486")
label start:
"Hello, my name is [e]."
"*** THE END ***"
return
So even though
e
is a character object, when printed - it merely shows "Eileen", a string.In our Person() example above, we could perhaps use it to return the character's full name.
Python:
init python:
class Person():
def __init__ (self, forename, surname, age, phone, height):
self.forename = forename
self.surname = surname
self.age = age
self.phone = phone
self.height = height
def __str__ (self):
return self.forename + " " + self.surname
__eq__
can be used whenever one instance of the class is compared with another.Honestly, I'm really unsure on this one.
But using our Person() example again, I can imagine something like:
Python:
def __eq__ (self, other):
if isinstance(other, Person):
return self.forename == other.forename and self.surname == other.surname
else:
return False
What this is doing is returning False if the other variable being compared isn't actually a
Person()
class.But as long as it is a
Person()
then it compares the forename and the surname and if they match, it returns True. If they don't match, it returns False.You'd use it something like:
Python:
define bob_object = Person("Bob", "Parker", 35, "1-800-fuc-kyou", 1.77)
define zoe_object = Person("Bob", "Parker", 42, "1-212-555-0001", 1.75)
label start:
if bob_object == zoe_object:
"Bob has the same name as Zoe."
else:
"Bob and Zoe are different."
In this case, even though not all the data is the same, because both names match, that is enough for the code to say the objects match.
Overriding
__eq__
is probably a bad idea in most cases. But since I saw it recently, I'm including it here under my list of special known functions for classes.I've somewhat glossed over
isinstance()
. If you want to see what that's all about, check this out:
You must be registered to see the links
Practically everything is a class.
So way back near the start, I pointed out that lists can contain lists and dictionaries can contain lists.
But a list is a class and so is a dictionary. And so is a string, an integer, a float and a boolean.
So in the same way we have lists made up from strings and integers as well as lists made up from strings, integers and other lists... we can pretty much mix things up as much as we like.
We could have a list of people. Using a list which contains a Class.
Python:
init python:
class Person(): # yes, yes, I keep forgetting to use the default values.
def __init__ (self, forename, surname, age, phone, height):
self.forename = forename
self.surname = surname
self.age = age
self.phone = phone
self.height = height
default people = [
Person("Bob","Parker", 35, "1-800-fuc-kyou", 1.77),
Person("Zoe", "Smith", 22, "1-212-555-0000", 1.75),
Person("Alice", "Taylor", 28, "1-212-555-1234", 1.57),
Person("John", "Doe", 65, "", 1.9)
]
people[0].surname
would be "Parker".Everything beyond this point is just a variation on that theme.
Dictionaries using classes.
Classes using other classes.
Classes using lists.
... and anything else you can keep straight in your head.
I recently saw a piece of code intended to be used as a phone text message system for a RenPy game.
That looked a bit like this:
(I've edited it to remove the other functions)
Python:
class Contact:
def __init__(self, name, profilePicture, newMessages=False, locked=True):
self.name = name
self.profilePicture = im.Scale(profilePicture, 60, 60)
self.newMessages = newMessages
self.locked = locked
self.messages = []
class Message:
def __init__(self, contact, msg):
self.contact = contact
self.msg = msg
self.replies = []
self.reply = False
class ImageMessage(Message):
def __init__(self, contact, image):
self.contact = contact
self.image = image
self.replies = []
self.reply = False
In this case, each character within the game was given it's own
default charname = Contact( ... blah, blah...)
.Then functions were used to add messages (using the
class Message()
or class ImageMessage
to the list of messages stored for that person.In addition, as
list()
was used to store a list of possible replies to each incoming message until the player picked on. At which point the chosen reply was store in self.message.reply
and self.message.replies
was set back to being an empty list.Whilst the meat of the code is missing, I hope it shows the potential for classes which store data using other classes and lists (and dictionaries, strings, integers, etc, etc.).
Anyway. that's it. Well done if you reaches this far. Please accept a sticker from the nurse on the way out.
Last edited: