It's been a while since I posted here. What have I been doing? Mostly working, and in my spare time trying to get my head around QtScript. What follows is a short introduction into QtScript. I'm sure that there are points of inaccuracy in this post - this is only to be expected, since I'm just starting out.
What I need for my game, is a single C++ class that can defer some of it's processing to a script, thereby changing the behavior of the class. For example, I want a "GameObject" class, that takes a script filename as a constructor parameter, and defers certain parts of the processing to the script. In the case of the game object, I may want to defer the following functionality to a script:
- Graphics loading (so different game objects look different).
- AI processing.
- Generic event handling.
The easiest way to do this is to have signals in the C++ class, that the script can choose to connect to (or not, as the case may be). As the C++ class is executed, it can emit these signals, and the corresponding slots in the script file can be called.
The Code
The C++ code is relatively simple. Consider the following header file:
This should all be fairly easy stuff. If it's not, you should probably look at the Qt tutorials before going any further.
#ifndef TESTSCRIPTOBJECT_H
#define TESTSCRIPTOBJECT_H
//
#include <qobject>
#include <qscriptvalue>
#include <qscriptable>
#include <qscriptengine>
#include <qmessagebox>
//
class QScriptEngine;
//
// Test script object class - used to demonstrate the fundamentals of QtScript.
//
class testScriptObject : public QObject
{
Q_OBJECT
public:
// ctor for this class - pass in the script engine to bind to.
testScriptObject( QScriptEngine *pEngine, QObject *parent =0);
// attach ourselves to a script file - this could be done inside the ctor. I
// have chosen to use a separate method instead.
void runScript(const QString &strProgram);
signals:
// test signal - we can emit this, and have some QtScript code run.
void signal1();
public slots:
// test slot - just displays a message box.
void slot1();
// second test slot - displays the string in a combo box.
void displayMsg(QString strMsg);
private:
// store a pointer to the script engine.
QScriptEngine *m_pEngine;
// store the "this" object, so we can manually call script functions if we need to.
QScriptValue m_thisObject;
};
Before we can use this class in any sensible manner, we need to do two things:
- Create a QScriptValue object that represents the "this" pointer. We do this so that we can call a script function, and pass it the C++ class object as "this" (we'll see this later).
- Open a script file and execute a "create" function, the contents of which will set up any signal / slot connections we require.
This is also pretty simple - the only line of any significance is the one where we create "m_thisObject".
testScriptObject::testScriptObject( QScriptEngine *pEngine, QObject *parent)
: QObject(parent),
m_pEngine(pEngine)
{
// create this object in the scripting land:
m_thisObject = m_pEngine->newQObject(this);
}
Now on to step 2 - running the script file. Let's take a look at the runScript method:
Again, this is all pretty simple stuff. This method does the following:
void testScriptObject::runScript(const QString &strAppName)
{
QFile file(strAppName + ".js");
if (! file.exists())
{
QMessageBox::critical(0, "Error", "Could not find program file!");
return;
}
if (! file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QMessageBox::critical(0, "Error", "Could not open program file!");
return;
}
QString strProgram = file.readAll();
// do static check so far of code:
if (! m_pEngine->canEvaluate(strProgram) )
{
QMessageBox::critical(0, "Error", "canEvaluate returned false!");
return;
}
// actually do the eval:
m_pEngine->evaluate(strProgram);
// uncaught exception?
if (m_pEngine->hasUncaughtException())
{
QScriptValue exception = m_pEngine->uncaughtException();
QMessageBox::critical(0, "Script error", QString("Script threw an uncaught exception: ") + exception.toString());
return;
}
QScriptValue createFunc = m_pEngine->evaluate("create");
if (m_pEngine->hasUncaughtException())
{
QScriptValue exception = m_pEngine->uncaughtException();
QMessageBox::critical(0, "Script error", QString("Script threw an uncaught exception while looking for create func: ") + exception.toString());
return;
}
if (!createFunc.isFunction())
{
QMessageBox::critical(0, "Script Error", "createFunc is not a function!");
}
createFunc.call(m_thisObject);
if (m_pEngine->hasUncaughtException())
{
QScriptValue exception = m_pEngine->uncaughtException();
QMessageBox::critical(0, "Script error", QString("Script threw an uncaught exception while looking for create func: ") + exception.toString());
return;
}
// now emit our test signal:
emit signal1();
}
- Looks for, and opens the script file specified.
- Does a static check of the code (makes sure that it's syntactically correct, but does not ensure that the script will run without error).
- Evaluates the script file.
- Retrieves the function named "create" from the script file.
- Executes this function, passing our previously created "m_thisObject" as the "this" object for the script to use. This function then binds our signals to slots in the script file.
- Finally, we emit our test signal.
...and that's it! The "create" method will first call the "test2" slot in our C++ class, then connect our test signal to a scripted function. When we emit the signal in our C++ class, the "testSlot" function will be called.
// set up the game object - the 'this' object will be an actual QObject
// derived class passed in from the application.
function create()
{
// test 1 - call a slot in the class:
this.slot1();
this.signal1.connect(this, testSlot);
}
// create our own slot:
function testSlot()
{
this.displayMsg("Testing testing.. 123");
}
This is just a starting point. The more I use the QtScript module, the more I marvel at the possibilities created with this tool. I shall post follow-up articles with more information and techniques as I see fit. In the meantime, go add scripting extension support to your faviourite project. With tools this easy to use, there's no excuse not to!