Using Python as a Scripting Language There is good documentation at - Why do you want to use a scripting language? - Allow designers to write "code" without worrying about them crashing the game - Also, easier to write code in! - Allow behavior modifications without having to recompile the game - A professional game can take over 6 hours to fully compile! - The three parts of using a scripting language in a game - "Embedding" -- From C, call the scripting language code and get a return value - "Extending" -- From the scripting language, call C code - "Querying" -- From the scripting language, know the context this code is being executed in - These three things would allow you to write the following as your FSM condition "game.CritterHealth( game.ActiveCritter() ) < 50" - ActiveCritter represents the context - CritterHealth is a C-code function - The result can get evaluated as true / false - Project Preliminaries - Project Setup -- In the python directory (on Windows, in C:/Python31/) there are "include" and "libs" directories. These directories are needed to build your game. These directories should be added to your "additional include dirs" and "additional link dirs", like before. - You will want to add #include to your project - If you get errors about not having "python31_d.lib", you should copy python31.lib to a python31_d.lib. - Initialization -- You need to call Py_Initialize() at program start-up, similar to SDL_Init(). - Python is garbage collected, but C is not. You need to tell Python when you are done using a Python object or if you are keeping an object around. - Python's implementation of garbage collection is called "Reference Counting". This means that each object keeps a count of how many other objects reference it. - In C, you must do this by hand by calling Py_XINCREF() and Py_XDECREF() - Py_XINCREF() will increment the refcount - Py_XDECREF() decrements the refcount -- if it hits zero, Python will free the memory. - Embedding -- Calling Python code from C - Simplest way is to call PyRun_SimpleString() - Returns if the call was successfully executed. - But this does not provide any way to get the result! - Can't see the return value, so not useful for FSM conditions - Better way -- PyRun_String() - More powerful call, it returns the value (or NULL if not successfully executed) - Because it returns a PyObject, you must Py_XDECREF() it. If you do not, you will leak memory. - Example -- Evaluate an expression and return a bool: bool PyRun_StringAsBool( const char* str ) { PyObject* pyResult; bool cResult; // Evaluate string in the __main__ module (what Python does by default) PyObject* mainMod = PyImport_AddModule( "__main__" ); PyObject* mainDict = PyModule_GetDict( mainMod ); pyResult = PyRun_String( str, Py_eval_input, mainDict, NULL ); // convert the result to C if( !pyResult || !PyBool_Check( pyResult )) { PyErr_Print(); printf( "Error when evaluating: %s -- Result is not a bool\n", str ); cResult = false; } else { cResult = (pyResult == Py_True); } // Free the result since it was allocated Py_XDECREF(pyResult); // Return the C result return cResult; } - Using Built In Python Types - Most scripting languages are NOT statically typed, so the object returned needs to have its type checked! - All built in types have a function bool Py*_Check( PyObject* ob ) that checks if ob is of the specified type. - PyBool_Check() - PyLong_Check() - PyFloat_Check() - and so on... - PyLong and PyFloat have conversion functions to get the underlying C value. - PyLong_FromLong() -- create a PyObject from a C long (int) - PyLong_AsLong() -- extract the C long from a PyObject that is a PyLong. - PyFloat_FromDouble() / PyFloat_AsDouble() -- same thing, for floating point numbers - PyBools are either the Py_True or Py_False object - You *NEED* to check that the type is returned as you expected before calling conversion functions. Otherwise, you my crash. - This is enough tools to write a PyRun_StringAsFloat() and PyRun_StringAsInt() - With this, you could run some extremely simple Python code as your conditions, like "2 * 3 < 5" - Can't yet call built in library functions - Modules -- Calling library functions - Python libraries are grouped into "modules" - You need to manually import the modules and add them by name if you want to have access to the module - Example: Importing and adding the "random" module to __main__ PyObject* mainMod = PyImport_AddModule( "__main__" ); // Import and add "random" PyObject* randomMod = PyImport_ImportModule( "random" ); PyModule_AddObject( mainMod, "random", randomMod ); - You want to do this *ONCE* at start up; there's no need to do this every time you execute Python code. - Now we can use built in modules in our conditions, like "random.randint( 0, 5 ) < 3" - Extending -- Calling back into the game engine. - You do this by creating a module in C that you add. You add it like any other module using PyModule_AddObject(). - By convention, I like to use the module name "game" - You have to create the module by hand, though. You create a module by writing a C function for each function you want to expose, building a list of these functions, and then calling PyModule_Create(). - Writing C functions to expose - Every C function looks like this: PyObject* MyFunction( PyObject* self, PyObject* args ); - Always should return SOME object. If you have nothing useful to return, return PyNone. - NULL means there was an error. - If you return PyNone, make sure you XINCREF it first! - SELF is used for writing Object Oriented code - ARGS is used for parameters passed - For now, covering no parameters - Example function -- Return the "player X pos" PyObject* Py_PlayerX( PyObject* self, PyObject* args ) { // playerX is a global, so I can access it return PyLong_FromLong( playerX ); } - Example function -- Kill the player PyObject* Py_KillPlayer( PyObject* self, PyObject* args ) { killPlayer(); // Nothing useful to return, so return Py_None. Py_XINCREF( Py_None ); return Py_None; } - Then, list each function in a ModuleDef - Two structures -- a "null terminated" array of MethodDefs, and a ModuleDef - Example: // PyMethodDef has four members: // 1. Python method name (a string) // 2. C function (a function) // 3. How to pass args, can be METH_NOARGS or METH_VARARGS // 4. Documentation string for the function. PyMethodDef gameModule[] = { { "PlayerX", Py_PlayerX, METH_NOARGS, "Return the player's X position" }, { "KillPlayer", Py_KillPlayer, METH_NOARGS, "Kill the player" } { 0 }}; // PyModuleDef has five members // 1. Must be PyModuleDef_HEAD_INIT // 2. Name of the module, I use "game" by convention // 3. Documentation string for the module. // 4. Extra memory to allocate. Not necesarry, just set to -1 // 5. The array defined above. PyModuleDef gameModuleDef = { PyModuleDef_HEAD_INIT, "game" "A collection of game functions", -1, gameModule }; // in some code PyModule* gameModule = PyModule_Create( &gameModuleDef ); - Now we can use simple game queries, like "game.PlayerX() < 50"