|
Scripting
languages allow rapid development of game behaviour without the pitfalls
that await the unwary C++ programmer. Using an existing scripting language
saves the time and cost of developing a custom language, and typically
gives you a far more powerful language than you could create on your own.
Python is
an excellent choice for a game scripting language because it is powerful,
easily embedded, can seamlessly be extended with C/C++ code, has most
of the advanced features that make scripting languages worthwhile, and
can also be used for automating production. Additionally, the books, development
tools and libraries available for Python provide great opportunities for
benefiting from the work of others.
This session
describes our experiences at Humongous Entertainment with integrating
Python into our new game engine. It explains why we chose Python, the
benefits we've seen, the problems we've encountered, and how we solved
them.
Why use
a scripting language?
C++ is a powerful language and an enormous improvement over C. However
it is not the best choice for all tasks. The emphasis in C++ is run-time
performance [Stroustrup94] and no feature will make it into the language
if it makes programs run slower. Thus, C++ programmers are burdened with
many restrictions and inconveniences.
A few of
the restrictions - that C++ programmers are so used to they usually don't
notice -- include:
- ·
Manual memory management: a huge amount of C++ programmer time is spent
making sure that delete gets called at the appropriate time.
- Link
phase: C++ modules are linked together so that function addresses don't
need to be resolved at run-time. This improves run-time performance,
but slows down the edit/test cycle.
- Lack
of introspection: C++ code has no way of knowing what members are in
a class. This makes writing code to load and save objects an enormous
task that in some scripting languages is a single call to a built-in
function.
C++ is static,
scripting languages are dynamic. Grossly oversimplified this means that
C++ runs fast, but scripting languages let you code faster.
Thus, C++
should only be used when you need to optimize for run-time performance.
On today's fast computers there are huge blocks of code where performance
is not an issue. If you develop these in C++ when a scripting language
would do then you are optimizing for the wrong thing.
What's wrong
with SCUMM?
Humongous has used SCUMM (Script Creation Utility for Maniac Mansion)
to create over 50 different games. SCUMM is a powerful adventure game
creation language, but it has some limitations. SCUMM was written more
than a decade ago and it is missing some of the features found in more
modern languages.
Despite
continual patching and maintenance, SCUMM will never be as full featured
and robust as the many other languages available.
Why Python?
We explored the idea of creating a new, more modern proprietary language,
but wisely discarded the idea. We are in the business of making games,
not languages.
After spending
millions of dollars over the years maintaining a suite of proprietary
tools we, really wanted to go with an existing scripting language rather
than creating one. Existing languages let us get started faster, have
lower maintenance costs, are generally better than a language we would
create, and tend to get better over the years, even if we don't work on
them.
Once we
decided that we wanted to use an existing scripting language we had to
choose one. We needed something clean that would support object oriented
programming and could be embedded in our games, without technical or licensing
problems.
We considered
Lua [Lua01] and Python [Python02], both of which had been used in several
games.
Lua is smaller
and probably easier to embed in an application, and has some nice language
constructs. However, at the time we were choosing a language the documentation
was a bit sketchy. This is probably because Lua is a newer language than
Python.
Python has
more extension modules than Lua, more books written about it, and stackless
Python [Tismer01], which was a promising way of creating micro-threads
for object AI. We ended up not using the stackless version of Python,
but we did start using Python for writing automated build scripts and
this gave us momentum. We knew the syntax, we liked it, and so we chose
Python.
Both languages
have been improved since we made our decision - Lua has become stackless
and Python has acquired generators, which allow some similar functionality.
Either language is a safe choice.
Who is using Python in games
Python has been used in a number of games, including
with numerous
other sightings that are difficult to verify, including at least one PS2
game.
Python is
also used in at least two game engines:
- Game
Blender - http://www.blender.nl/gameBlenderDoc/python.html
- PyGame
- http://www.pygame.org/
A build script
example
As an example
of Python code, here is a simple build script that recursively builds
all VC++ workspaces. Written in a fairly verbose fashion it still takes
just a dozen lines of code:
import os
def BuildAllWalker(unused,
dirname, nameList):
for entry in nameList:
if entry.endswith(".dsw"):
workspaceName =
os.path.join(dirname, entry)
resultFile = os.popen("msdev
%s /make all" % workspaceName)
resultLines = resultFile.readlines()
for line in resultLines:
print
line
if __name__ == '__main__':
os.path.walk(os.curdir, BuildAllWalker, None)
With a couple
of dozen more lines of code, this script [Dawson02] parses the output
and e-mails a summary report to everyone on the team. Unlike some scripting
languages, the code is quite readable. Using the same language for build
scripts and game scripting will save a lot of learning time.
This build
script example shows what is probably the most distressing aspect of Python
to new users. Flow control is indicated entirely by indenting. There are
no begin/end statements and no braces.
It took
me about five minutes to adjust to this idea and I have since found that
it is very effective. I've been in more than one heated argument about
where the braces in C/C++ should go and I'm sure that Python programmers
are 10% more productive just because of the time they don't spend arguing
about K&R indenting style versus others. When your code blocks are
defined by indenting there is no longer any possibility of the indenting
not matching the compilers view of the code's structure.
The one
time when indenting for flow control causes problems is when tabs and
spaces are mixed. Since most programmers use three or four space tabs
and the Python compiler internally uses eight space tabs, mixing tabs
and spaces causes confusing syntax errors. If you standardize on either
spaces or tabs and use an IDE that warns you when you are mixing them
then you will have no problems.
Game script
example
The following is a sample of some Python code from our first Python/C++
game. You can see from this small sample that the Python code is running
the main loop, calling out to modules that may be written in either language:
try:
#
Update the input
g_InputManager.DispatchInput()
#
Tick the menu code
g_MenuManager.Tick()
#
Tick the scene code
g_SceneManager.Tick()
g_SceneManager.SetScene()
except:
import traceback
traceback.print_exc(None,sys.stderr)
g_Clock.Stop()
#-endtry
Thus, our
game boots up in Python, and only calls out to C++ as needed.
How does
it work?
Python is based on modules. When one source file wants to use functions
defined in another source file it imports that file. For instance, if
gameai.py has a function
called UpdateAI then another
Python source file can go:
import gameai
gameai.UpdateAI()
The wonderful
thing about this from a game programmer's perspective is that if UpdateAI()
runs too slowly it can be rewritten in C++. To do this, the functions
and types defined in gameai.py are written in C++ and registered with
Python with the same module name. The Python code can continue to import
and use gameai without any changes.
Thus, Python
modules make it easy to prototype your entire game in Python and then
recode as needed in C++.
Glue code
Writing the glue code to make C++ code visible to Python is tedious if
you try to do it by hand. A system for generating glue code is essential.
Swig, Boost,
CXX and others [Abrahams01] are systems to help generate code to glue
together Python and C++. Another option is Fubi [Bilas01] which is a generic
system of making C++ functions and classes available to a scripting language.
Most of
these glue code systems work by parsing a C++ header file. Thus they are
restricted to what a C++ header can expose, and some of them did not support
having Python classes derive from C++ classes. They have been improved
substantially since then and are worth taking a look at.
We decided
to create a custom solution that generates glue code based on an IDL description
of the class or function to be exported. Our code name for this is Yaga,
which is a recursive acronym that means Yaga is A Game Architecture.
A typical
snippet of Yaga IDL code looks like this:
module yagagraphics
{
struct Rect
{
int32_t x;
int32_t y;
int32_t width;
int32_t height;
};
which then
generates the following glue code, and a few hundred lines more:
static int YAGAPYCALL
Rect_init(
YagaPyStruct<Rect>*
self,
PyObject*
args,
PyObject*
kwds)
{
if (!PyArg_ParseTuple(args, "|llll",
&self->structData.x,
&self->structData.y,
&self->structData.width,
&self->structData.height))
{
return
-1;
}
return
0;
}
This system
makes it very easy to export classes and functions, derive Python classes
from C++ classes, map C++ arrays and vectors to Python sequences, and
much more.
|