BGE: Loading values with configparser

Someone recently asked on the BA forums about loading variables from a file into the game engine. There are a number of ways to do this. The usual suggestions are Python’s built in IO – a simple, but messy solution to the problem. In this particular thread there was also the suggestion of saving/loading from your own custom module. And for completeness, there’s the pickle module. But there’s also the configparser module – which we’ll look at today.

It’s always struck me as odd that you never really see this module used in the BGE as I think it’s perfectly suited for dealing with user settings. The main downside is that it requires an external Python module, another thing you need to include as part of the final product. But I think it’s strengths more than make up for this. You can save and recall any variable you want by name, so unlike using built in IO you don’t need to know the order of things. You don’t even need to know how many variables are saved as variables are meaningfully grouped. For example, if you’re loading high scores and you don’t need know how many games have been played by the player as it wont effect any other variables or the order. Finally, the file produced is human friendly. This has the advantage of simplifying play testing. Say you have something that’s triggered by beating a certain score, it’s easy to open the file set a high score and test things out. Or you can test loaded control setups without having to produce the GUI for saving them. It’s magical stuff.

For the record, this tutorial uses Python 3.2. It should work with Python 2.x, I think the only change is the capitalisation in the module name.

INI style files

The configparser uses a file type based on the Windows .ini file style. This doesn’t mean that it makes registry edits like windows .ini files, nor does it mean that it’s restricted to the Windows system. It simply uses that style. Values are stored under a section header by option-value pairs (similar to a dictionary). So let’s have a look at one. Open up a text editor, notepad will do nicely, and enter the following:

[player]
# I am a comment, this section contains the player's details
name: Jay
Age: 24
location: UK

[highscores]
; I am also a comment
score1 = 1034
score2 = 5943
score3 = 2312

[settings]
lod: 3
mouse: 0.2
filters: yes

Lets save this file as settings.cfg (the file extension really doesn’t matter, make one up if you like). The first thing you see is a section header. This is the name of the section encased in square brackets: [section]. This is followed by a comment, which is started with either a hash symbol (#) or a semicolon (;). Comments exist on their own line and are optional. Under each section we have option-value pairs. First there’s the option (the variable’s name) followed by either a colon (:) or an equals sign (=) then the value. Values can even be multi-line. Pretty simple, right?

Reading the file with Python

You can either do this in pure Python, or open up Blender and create a new script and attach it to an object with an always sensor set to trigger once. All that matters is that the directory you save your .py or .blend is the same as settings.cfg (unless you specify the path to the settings file in the python script). What you do with these values once you’ve loaded them is up to you, for this tutorial we’re just going to print them. We shall start simple and loop through the file and print out the section headers and options:

import configparser

config = configparser.ConfigParser()
config.read('settings.cfg')

for section in config.sections():
    print('###',section,'###')
    for option in config.options(section):
        print(option)

After importing the configparser module we create an instance of the ConfigParser class and read settings.cfg into using the .read() function. We can return the names of the sections contained in settings.cgf with .sections() and returns a list that we can loop through. Looping through the list we then pass each section to .options() which returns a list of options under a section. We then loop through that list, printing out each option. Still with me?

Now, .read() can take a list of file names, and is used when loading multiple files. So, if the file does not exist then it will skip over it an wont return an error. This means that later down the line when we try and access something in the file we will get an error. This is where .read_file() comes in. This function only takes one file and returns an error if it doesn’t exist.

Error Handling

As I mentioned earlier, the INI style file format is very readable and so is easily edited, which means that when using it it’s good practice to protect your self against unexpected changes. In the next example we’ll look at using .read_file(), checking sections/options exist and some simple error handling.

import configparser

config = configparser.ConfigParser()

try:
    config.read_file(open('settings.cfg'))
except IOError:
    print('file could not be found')
    #Load some default settings here
    age = 30

#check a section exists
if config.has_section('player'):
    #check an option exists
    if config.has_option('player', 'age'):
        #get a specific option from a section
        age = config.get('player', 'age')

print(age)

.read_file() takes a file object, which we create by calling the built in method open(). In the above example if the specified file cannot be opened we can then move onto loading in some default variables so that our game does not crash. When we expect that something could go wrong we can use try/except to catch and handle exceptions. An exception is a syntactically correct statement, but one that might not always execute correctly.

Notice how the code captured by the try/except block is as minimal as possible. This means that only the exceptions we expect could possibly happen and have prepared for are being caught. Other errors will be generated as per usual. If we had not specified what error we’re looking for, or included all the code within try/expect, other errors/exceptions unrelated to a missing file could slip by and we’d have no idea they had occurred, creating harder to find problems. Try changing the file name in the above example to something that doesn’t exist and see what value age takes then.

We can use .has_section() and .has_option() to check for a section/option’s existence. These functions return true or false. Once we’re sure that the data we’re looking for exists we can grab it using .get(), which takes the section name followed by the option name and returns the value of the option in the form of a string. If a section or option doesn’t exist then we could use an else statement to assign a default value to age. Of course, you could avoid the need for extra else statements and multiple assignments by assigning age a value at the beginning of the script. Then if the file can be read and all the details that exist would get reassigned, otherwise it would remain unchanged. Another way to deal with missing sections/options, and perhaps the most straightforward, is with the fallback keyword argument:

gender = config.get('player', 'gender', fallback='male')
print(gender)

We never added the option gender, so if it doesn’t exist then a default value will be returned instead – in this case the string ‘male’.

There’s lots more to the configparser, such as saving, adding default values to your config file and dealing with data types. Not to mention a couple of different modes of operation. In my next tutorial on the subject we’ll do something a little more Blender specific and look at saving, doing things with the loaded data as well as defaults. But for now, this post is long enough and there’s plenty there to get started with. Any questions? Ask below.

Advertisements

~ by Jay on April 4, 2012.

3 Responses to “BGE: Loading values with configparser”

  1. Should be nice to show how to load it with default values

    Like

  2. […] my last tutorial, many moons ago, we took a look at using the configparser to load data into the BGE and printing […]

    Like

  3. […] can find the previous tutorials here: Introduction to the configparser Using data retrieved with the […]

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

 
%d bloggers like this: