Dear TIGers, I am here to demonstrate you the means to create a base class in C++ and using the
luabind library, implement said class in your Lua, and enable you to create subclasses of such class. This will enable you to create a basic entity in your game engine code, and specify different logic for each of the subclasses.
First off, lets start with getting Lua, and luabind fired up:
// The Setup
lua_State* L = lua_open(); // create the Lua state
luaL_openlibs(L); // open all of the basic libraries so we can use them
luabind::open(L); // register luabind to our Lua state
I've omitted any error checking from above, but I assume you'll know how to implement those yourself. The code should be pretty self-explanatory.
Next, lets define our class:
class Foo {
public:
// The Constructor
// @self the reference to our Lua instance so we may reference to our Lua object in C++
// and call the Lua class methods via luabind::call_member<void>(m_Self, "function");
// @name an arbitrary test value, to identify our objects
Foo(luabind::object self, const char* name) : m_Self(self), m_Name(name) {
// Declare ourselves openly!
std::cout << m_Name << " created." << std::endl;
}
// The Destructor
~Foo(void) {
// Declare to the world that we no longer exist
std::cout << m_Name << " destroyed." << std::endl;
}
// This method will be overridden in Lua
void Think() {
std::cout << m_Name << " is thinking." << std::endl; // Output something generic
}
// This method will enable us to reference to another object, and alter it's values
// @target the reference to another Foo object
void Kill(luabind::object target) {
// Check if our target is a valid object
if (target && luabind::type(target) != LUA_TNIL) {
// Convert the luabind::object into our own class type
// notice the pointer form so we'll get just that, rather than a copy
Foo* target_foo = luabind::object_cast<Foo*>(target);
std::cout << m_Name << " attacks " << target_foo->m_Name << "!" << std::endl;
// Lets alter the target's properties
target_foo->m_Name = "Dead";
}
}
// Class attributes
luabind::object m_Self; // Reference to our own Lua object
std::string m_Name; // Our object's name
};
The code above is also pretty self-explanatory, as I attempted to explain everything in the comments. The things you need to know are the type
luabind::object, which is a wrapper for all of Lua's data types, so we can access those types in C++.
The basic constructor takes two parameters, a Lua object, and a string for a name. This default class constructor is called from Lua via the function
super(), which lets us access parent methods in classes. Next off, lets see how we can bind this class into Lua with the marvelous luabind:
luabind::module(L) [
luabind::class_<Foo>("Foo")
.def(luabind::constructor<luabind::object, const char*>())
.def("Think", &Foo::Think)
.def("Kill", &Foo::Kill)
.def_readwrite("name", &Foo::m_Name)
];
Now, this piece of code needs a little explaining to do. By
luabind::module(), we can define functions, classes and variables to Lua and it's namespaces. I highly recommend reading through
luabind's documentation to understand how all of this works.
But what we're doing now, is we first bind the class Foo into the Lua state L, via the
luabind::class_<>() template method, followed by a chain of
def() and
def_readwrite() calls to specify the classes functionality and variables. The important row to check out is the line defining the constructor, where you'll have to specify the exact same parameters into the template; I wasn't able to create multiple constructors in C++, and I'm not sure if this is even possible. The member attribute m_
Name will take the form of
name in Lua, and so-forth.
Now, onto the Lua code:
-- First, define the two new subclasses of Foo
class 'Bar' (Foo)
class 'Baz' (Foo)
-- Bar's constructor, notice the super() method on the first row
function Bar:__init(name)
super(self, name); -- Call for Foo::Foo(self, name)
print("A " .. self.name .. " a day keeps the doctor away.");
end
-- Bar's overloading method for Think()
function Bar:Think()
print(self.name .. " is cool.");
end
-- Baz's constructor, the same method for super() applies as to Bar
function Baz:__init(name)
super(self, name); -- Call for Foo::Foo(self, name)
print("A " .. self.name .. " is a wonderful thing, don't you think?");
end
-- Create a bar
bar = Bar("Barry");
bar:Think();
-- Create a baz
baz = Baz("Bazzy");
baz:Think();
-- Battle to the DEATH!
baz:Kill(bar);
bar:Think();
Now, this segment I'll explain in greater detail. First off, we declare our two new classes derived from
Foo, the class
Bar and
Baz. We then define a new constructor for Bar through
Bar:__init(), which calls for Foo's constructor through the function
super(), initializing the class in C++ and setting the name and whatnot. After this, we display a small message to see if our new derived constructor worked.
We also define a new
Think() method for Bar, by simply declaring an overloading function for it. The same is applied for Baz, minus the overloading
Think() method, which will cause Baz to call Foo's Think() method. Quite simple, really.
Now, you can run the Lua script with the function
luaL_dofile(), as such:
if (luaL_dofile(L, "foo.lua")) {
std::cout << "We hit a little snug: " << lua_tostring(g_LuaState, -1) << std::endl; // Print out the error message
}
Now, if everything is correct, when you execute the program built from these little snippets, your output would look something like this:
Barry created.
A Barry a day keeps the doctor away.
Barry is cool.
Bazzy created.
A Bazzy is a wonderful thing, don't you think?
Bazzy is thinking.
Bazzy attacks Barry!
Dead is cool.
Bazzy destroyed.
Dead destroyed.
So, we created our new Bar, "Barry", and our Baz, "Bazzy", then went on to have Bazzy attack Barry, resulting in Barry changing his name to "Dead."
It is important to notice, that the object destructors are called only during the garbage collection phase, so
make sure you close your Lua state before exiting the program, otherwise you'll encounter some memory loss and bad things will happen.
Now, that was all for now, I hope that was helpful to some of you and if you have any questions, suggestions for improvements or errors to correct, please do.