“ Holey cow. My #pygame book is being translated in to Japanese! Hoping I'm going to be big in Japan. ”0
I got a bad review of my book on Amazon.com. I wanted to post a comment to address his points, but Amazon told me that I must have purchased an item to post -- which I have (many times)! So I thought I would post my comment here (below).
I'm the author of Beginning Game Development with Python and Pygame. Let me take a moment to comment on your scathing review.
1) As the title of the book indicates, the book is intended for beginners. As such there is an introduction to Python in the first two chapters. A total of 30 pages out of approximately 300. I don't think this is unreasonable.
2) Some of the smaller listings aren't in the downloadable files because they weren't intended to run independently. A few small stand-alone listings in the early chapters don't have filenames by them. My fault entirely, but then the downloadable code is organized in to a folder for each chapter - making it simple to find a listing you are looking for. The downloadable files don't include the GameObjects library (or PyOpenGL). This is intentional -- it is better to get the up-to-date versions. However, full instructions on how to download and install these libraries are given in the README.txt file and the book.
3) I use the GameObjects vector class because it is simple to use and efficient. But I do talk about how to go about creating your own vector class, which is far more important than the choice of library. I don't use the Pygame sprite class, because it is more important that the reader knows how to manage sprites manually, before using the short-cut.
4) You must walk before you can run, young padawan. Pygame isn't a game creation kit, it is an API. You can't write a game if you don't know how to do the simple stuff. If I write about how to create tic-tac-toe, then all you can do is create tic-tac-toe games.
5) 3D games are important. I don't think you can find a clearer more accessible introduction to 3D anywhere, for any language.
There are topics that I would have have covered, if I had more time and space (although my book still covers more subjects than typical games books twice the size). Games development is a huge field. I chose to cover the essentials and topics that are poorly covered elsewhere. You are entitled to your one-star review Craig, but I would ask others to check out the free chapter on my blog before making a decision.http://tinyurl.com/3xdmyq
The following is a list of the chapter titles for Beginning Game Development with Python and Pygame, and a brief explanation of the contents.
Any questions about the book contents, let me know!
- Introducing Python
Introduction to basic Python.
- Exploring Python
Goes a little deeper in to Python, covers Classes.
- Introducing Pygame
Explains how import and use Pygame.
- Creating Visuals
Covers different ways of drawing to the screen.
- Making Things Move
Explains animation, vectors and time-based movement.
- Accepting User Input
Covers how to read input devices and connect them to in-game motion.
- Take Me to Your Leader
Covers artificial intelligence.
- Moving into the Third Dimension
Explains the basic 3D concepts, with samples.
- Exploring the Third Dimension
Covers 3D maths and introduces OpenGL.
- Making Things Go Boom
Sound and Music.
- Lights, Camera, Action!
Covers textures and reading 3D models from files.
- Setting the Scene with OpenGL
Covers lighting, blending, fog and other OpenGL features.
- A. GameObjects Reference
- B. How to package Pygame games
I chose chapter 7, which is on the subject of Artificial Intelligence. I selected this chapter because it is nicely self-contained and doesn't require much additional understanding of other game concepts. It is an unusual chapter, in that it contains the largest listing in the book. I tried to avoid really long listings, but in this case I think it was justified so that the code in the book actually runs; I hate leaving any code as an exercise for the reader.
Chapter 7 discusses how to build a simple AI simulation of an ants nest using a state machine, a technique which can be applied to virtually any simulation that you might want in a game. You can download the code for the book below, it isn't interactive, but I think it is entertaining to watch for a minute or two! Download Ant Simulation
The ant simulation uses the gameobjects library, which you can install with the following comand:
The book will be available in all good book shops, and some bad ones, on October 24th. Thanks to everyone at Apress for guiding me through the writing process! Update: I blogged the Table of Contents
I have many ideas in the course of any given day. They are dredged up from the depths of my subconscious and placed on my mental in-tray, to be sorted in to mental heaps. Most are placed on to the "that'll never work" heap, others are thrown on the "better not, that's probably illegal" heap or the "plutonium isn't available at every corner drug store you know" heap. Occasionally though, some make it to my mental out-tray.
One recent idea was about recording demos in a game written with PyGame, which is something that is not that hard to do, but requires a bit of forethought to reliably record and play back sequences of events. It would be nice if there were some way of transparently recording the on-screen action and playing it back like a movie. Recording a video file on the fly is possible, but it would probably be too processor intensive. What would be better, is the ability to store and time-stamp the function calls used to create the display/audio so that they could later be streamed back in from a file. There are actualy some commercial tools that do this with OpenGL, by providing a proxy DLL that hooks in to the GL function calls - but I figured it could be done in straight Python, given its highly dynamic nature.
A little coding convinced me that this is the case. I wrote a script that can be used to replace every callable in a module or class with a proxy callable that can record the name of the function that has been called and the parameters that it was called with. I tried it with one of my OpenGL demonstrations and it worked perfectly, the demo ran as normal, but it streamed details about every GL function call. All it took in the OpenGL code was replacing two imports with a
proxy_import function I had written (see below).
#from OpenGL.GL import * #from OpenGL.GLU import * import pystream pystream.proxy_import('OpenGL.GL', '*') pystream.proxy_import('OpenGL.GLU', '*')
I'm reasonably satisfied that recording the OpenGL output of a game is practical, but I'm in no hurry to implement it in full since I have plenty of work to do on other projects. The code I wrote to hook in to a module strikes me as something that could have a number of potential uses - beyond OpenGL and games. For instance, it could be used to turn a class in to a remotely callable object. The code is below.
class FunctionProxy(object): def __init__(self, name, function): self.__name = name self.__function = function def __call__(self, *args, **kwargs): print self.__name return self.__function(*args, **kwargs) class ObjectProxy(object): def __init__(self, my_object, name=None, scope=None): self.__object = my_object self.__dir = self.__object.__dict__.keys() self.__name = name for symbol in self.__dir: attr = getattr(self.__object, symbol) func_name = symbol if self.__name is not None: func_name = self.__name + '.' + func_name if hasattr(attr, '__call__'): function_proxy = FunctionProxy(func_name, attr) setattr(self, symbol, function_proxy) if scope is not None: scope[func_name] = function_proxy else: setattr(self, symbol, attr) if scope is not None and not func_name.startswith('__'): scope[func_name] = attr def proxy_import(module_name, from_list=None, scope=None): if from_list is not None: from_list = [f.strip() for f in from_list.split(',')] if scope is None: import sys scope = sys._getframe().f_back.f_globals module = __import__(module_name, scope, None, from_list) if from_list is None: proxy = ObjectProxy(module, name=module_name) scope[module_name] = proxy elif from_list and from_list=='*': proxy = ObjectProxy(module, scope=scope) else: proxy = ObjectProxy(module) for symbol in from_list: scope[symbol] = getattr(proxy, symbol, scope=scope) return proxy if __name__ == "__main__": #from math import * proxy_import('math', '*') print abs(sin(radians(60)))
FunctionProxyclass (never code after midnight).
A novice games programmer learns he can move a sprite by adding a constant to its position every frame. For a while he is happy and mocks the master games programmer for having wasted many years learning. But soon the novice realises his sprites are unpredictable and move at different rates on different machines. The novice is despondent and visits the master for advice.
"Master. Why do my sprites not obey my commands? They jump when I ask them to slide."
"Frame rate is like the wolf, it can not be tamed", replies the master.
The novice returns to his code and a revelation comes to him. "I must move my sprites with the frame rate!" The novice changes his sprite code to move a distance relative to the time since the previous frame, and his sprites become at one with the frame rate. "I must truly be a master", thinks the novice.
For a while the novice is happy, and produces many simple games. But as the novice's logic gets more complex he is faced with a problem. "My game updates are tied to the frame rate!", thinks the novice. He realises that his game will run differently on other machines and yearns for the simpler days when he thought frame rate was constant. Despondent, he visits the master for advice.
"Master. How can I make my game run predictably on all machines?"
"Time is not rigid as a tree. Time is supple, as a reed", replies the master.
The novice goes back to his code and ponders the master's advice. A revelation comes to him, "Game logic can run in it's own time!" With that thought in mind, he separates his game logic from the render code and soon his game logic runs independently of the frame rate.
The novice is humbled and thanks the master for his help.
"Truly, you are a master now", replies the master.
To become a master you can either meditate in a cave in Tibet or use the
GameClock class in the Game Objects library.
GameClock manages game time, which marches on at a perfectly constant frame rate, no matter how many frames you can actually render. The main advantage of this approach is that the game will run the same on a fast computer, as it does a slow computer, which is extremely useful for recording demos or adding network play. It also makes the math for moving sprites (2D or 3D) a little simpler, because you can just add constants each frame.
Here's a snippet from the example project that shows how to separate the render loop in to an update phase and a render phase. The call to
game_clock.update is an iterator that returns the frame count and time for each game logic update. It will return as many updates as necessary for game time to 'catch up' with the real time.
# Update phase for frame_count, game_time in game_clock.update(): for ball in balls: ball.update() # Render phase for ball in balls: ball.render(screen)
update function calculates the position of the sprite at the current update, and the position it will be at the next update. This is because the render phase will likely occur at some point between game logic updates, and the render position should be adjusted proportionally. The method
get_between_time returns a value between 0 and 1 that you can use to interpolate value between two game logic updates.
For example, if the sprite contains its current position (
self.position) and its position at the next update (
self.next_position), then you could find the render position with the following code (
lerp is a function in
x, y = lerp( self.position, self.next_position, game_clock.get_between_frame() )
So what is a good rate for the game logic update? In my experience 20 logic updates per second is about right for most games. You can increase this rate if you want finer control, but it is probably not a good idea to go much lower as it will reduce the responsiveness of the game.
An additional advantage of GameClock is that you can control the speed of time with little effort. If you call
game_clock.set_speed(0.5), then half as many game logic updates will be issued and the game will run at half speed. You can also make the game run faster by setting the speed to a value greater than 1.0. Alas, you can't set the speed to a negative value, as this would violate the laws of the cosmos.
You can experiment with the game logic update rate and time speed with the
GameClock sample code. You will need PyGame of course, and Game Objects 0.0.2. Game Objects can be easy installed with the command:
Here's a screenshot of the sample code running. I really must try to write samples that don't feature shiny spheres!
I've moved Game Objects to Google Code. It's new home is http://code.google.com/p/gameobjects/. There are source and Win32 packages available.
Game Objects is intended to be a collection of classes to assist with the creation of games, or other realtime applications. Currently there is a 2D and 3D Vector class, and a well optimized 4x4 Matrix class, but eventually Game Objects will contain code for general route finding, entity management, AI and other cool stuff. I'm happy to take suggestions, and if you would like to submit code - even better! All classes should following these commandments.
- Easy to use. Thou shalt be no more complex than absolutely necessary, and thou shalt protect the user from doing stupid things.
- Fast. Thou shalt be fast, unless it comes in to conflict with 1.
- Pure Python. Thou shalt be in pure Python. Optimized C versions may exist as long as the interface is identical.
- Few dependencies. Thou shalt not require external modules, other than the standard library. External modules may be optional if they increase performance.
Current version is 0.0.1, but don't let the low version scare you, the current classes are quite stable. Most of my previous little PyGame experiments have used old versions of Game Objects, so I have ironed out most of the kinks. I plan on putting together a collection of samples, so there is something to play with. Any suggestions for interesting projects that make use of Game Objects will be gratefully received.Update: I have created a Google Group for discussion of Game Objects (http://groups.google.com/group/gameobjects).
I've uploaded a simple example of how to use OpenGL with PyGame. It's a listing from my forthcoming book, Beginning Game Development with Python and PyGame, and demonstrates how to initialize OpenGL, create a light and draw a simple 3D 'world'. It also shows how to use the camera matrix to create a simple 'fly-cam'.
To run it you will need PyOpenGL, which you can download from the website, or from the command line if you have Easy Install (type
easy_install PyOpenGL). Use the cursor keys to look around, and the Q/A keys to move forward and back. You can also press Z and X to roll the camera.
Here's a screenshot.
I'm impressed by the ease of using OpenGL with PyGame. It's a one liner to create an OpenGL display, and the OpenGL bindings work well. With good use of display lists or vertex arrays, performance can be on par with commercial games. To put it in perspective, you could render an entire 3D object in less time it takes to draw a simple 2D sprite. I hope people take advantage of it, because the 'casual games' market is huge right now, and Python could give developers a serious advantage in getting games to market quickly!
I enjoy optimizing code. These days I don't get the opportunity to do it very often, since I prefer working in high level languages now. Which is why I have been particularly enthusiastic in optimizing the 3D math classes in Game Objects.
Whenever the subject of optimization comes up, someone inevitably regurgitates the quote "premature optimization is the root of all evil", which they take to mean that you should not do any optimization until you have a working application and are able to profile it. In general this is good advice - but if you know about the domain you are working in, occasionally early optimization is a good thing. 3D math for games is one of these situations. The code is a black box which doesn't need to be built upon. Speeding it up tends to have a direct impact on the user experience (i.e. a better frame-rate). There is also limited scope for algorithmic improvements (which should be considered first), so micro-optimization is justifiable in this case (IMHO).
When I started looking in to optimizing my Matrix class, I compared it against another matrix class in PyEuclid, which is a quite comprehensive set of classes for 2D and 3D euclidean geometry. When I looked at the Matrix3 implementation, I was surprised that it stored the values of the matrix as 16 attributes (self.a, self.b etc.), because attribute access can be a bottle-neck for serious number crunching code. So I suspected that the matrix multiplication in PyEuclid would be slower than the equivalent function in Game Objects, as I used a single list of 16 values, which should be faster. But when I timed it, I found that PyEuclid was fractionally faster. Naturally I had a look at the Matrix3.__imul__ method in PyEuclid, I noticed that It copied one of the matrices in to 16 local references, and that made it a little faster because the attribute access was done only once. So I tried doing the same thing for the self matrix, which turned out to speed it up my almost 20%, which made it faster than my version. I was happy to submit the optimization back to Alex Halkner, the author of PyEuclid, but naturally I was determined to speed up my version by at least the same amount! I used the updated Matrix__imul__ from PyEuclid (below) as my benchmark.
def __imul__(self, other): #assert isinstance(other, Matrix4) a = self.a b = self.b c = self.c d = self.d e = self.e f = self.f g = self.g h = self.h i = self.i j = self.j k = self.k l = self.l m = self.m n = self.n o = self.o p = self.p oa = other.a ob = other.b oc = other.c od = other.d oe = other.e of = other.f og = other.g oh = other.h oi = other.i oj = other.j ok = other.k ol = other.l om = other.m on = other.n oo = other.o op = other.p self.a = a * oa + b * oe + c * oi + d * om self.b = a * ob + b * of + c * oj + d * on self.c = a * oc + b * og + c * ok + d * oo self.d = a * od + b * oh + c * ol + d * op self.e = e * oa + f * oe + g * oi + h * om self.f = e * ob + f * of + g * oj + h * on self.g = e * oc + f * og + g * ok + h * oo self.h = e * od + f * oh + g * ol + h * op self.i = i * oa + j * oe + k * oi + l * om self.j = i * ob + j * of + k * oj + l * on self.k = i * oc + j * og + k * ok + l * oo self.l = i * od + j * oh + k * ol + l * op self.m = m * oa + n * oe + o * oi + p * om self.n = m * ob + n * of + o * oj + p * on self.o = m * oc + n * og + o * ok + p * oo self.p = m * od + n * oh + o * ol + p * op return self
It may appear that the 32 assignments would slow it down, but it was faster over all because they reduce the amount of attribute access in the function. Knowing that attribute access was the enemy, I tried the same trick with Matrix44.__imul__ in Game Objects. My 16 values of the matrix were stored in a list called self._m, and the individual values were accessed as self._m thru self self.m. Copying these in to local references turned out to be very easy, because I could use Python's list unpacking. Doing this for both matrices yielded the following code.
def __imul__(self, rhs): """Multiplies this Matrix44 by another, called by the *= operator.""" m1_0, m1_1, m1_2, m1_3, m1_4, m1_5, m1_6, m1_7, m1_8, m1_9, m1_10, m1_11, m1_12, m1_13, m1_14, m1_15 = self._m m2_0, m2_1, m2_2, m2_3, m2_4, m2_5, m2_6, m2_7, m2_8, m2_9, m2_10, m2_11, m2_12, m2_13, m2_14, m2_15 = rhs._m self._m = [ m2_0 * m1_0 + m2_1 * m1_4 + m2_2 * m1_8 + m2_3 * m1_12, m2_0 * m1_1 + m2_1 * m1_5 + m2_2 * m1_9 + m2_3 * m1_13, m2_0 * m1_2 + m2_1 * m1_6 + m2_2 * m1_10 + m2_3 * m1_14, m2_0 * m1_3 + m2_1 * m1_7 + m2_2 * m1_11 + m2_3 * m1_15, m2_4 * m1_0 + m2_5 * m1_4 + m2_6 * m1_8 + m2_7 * m1_12, m2_4 * m1_1 + m2_5 * m1_5 + m2_6 * m1_9 + m2_7 * m1_13, m2_4 * m1_2 + m2_5 * m1_6 + m2_6 * m1_10 + m2_7 * m1_14, m2_4 * m1_3 + m2_5 * m1_7 + m2_6 * m1_11 + m2_7 * m1_15, m2_8 * m1_0 + m2_9 * m1_4 + m2_10 * m1_8 + m2_11 * m1_12, m2_8 * m1_1 + m2_9 * m1_5 + m2_10 * m1_9 + m2_11 * m1_13, m2_8 * m1_2 + m2_9 * m1_6 + m2_10 * m1_10 + m2_11 * m1_14, m2_8 * m1_3 + m2_9 * m1_7 + m2_10 * m1_11 + m2_11 * m1_15, m2_12 * m1_0 + m2_13 * m1_4 + m2_14 * m1_8 + m2_15 * m1_12, m2_12 * m1_1 + m2_13 * m1_5 + m2_14 * m1_9 + m2_15 * m1_13, m2_12 * m1_2 + m2_13 * m1_6 + m2_14 * m1_10 + m2_15 * m1_14, m2_12 * m1_3 + m2_13 * m1_7 + m2_14 * m1_11 + m2_15 * m1_15 ] return self
This optimized version removed all the list index operations from the function and reduced attribute access dramatically, making it considerably quicker; it could do 60,502 matrix multiplies per second, compared to PyEuclid's 47,594 per second. I think this must be the quickest way to do a full matrix multiply in straight Python. Other functions benefited from this to, transforming a Vector3 improved by about 20%, as did other operations. I also applied the same technique to the Vector3 class, which was worthwhile, but not quite as good an improvement as the matrix code.
These experiments have lead me to the following rules of thumb for micro-optimizing Python code, with the disclaimer that you should only micro-optimize if you know it will produce tangible benefits in the user experience!
- Prefer lists of values, rather than attributes.
- If you use an attribute more than once, copy it in to a local reference.
I'd like to point out that I'm not claiming that this is a good reason to use Game Objects over PyEuclid. They are intended for different things, Game Objects will eventually be a collection of general algorithms for games and other realtime apps. The speed advantage is not stellar, so you should use which ever interface you prefer - or even both!