Update time again...
lua -
I've spent a while integrating lua scripting, using the
luabind library to... bind lua to C++. The library is really good and is fully native C++, so there's no need to run any tools to grab data from your header files or anything.
I wrote a prototype for a template function that I specialize to initialize various areas of my engine:
namespace luabind{
template <class T>
void registerClass( lua_State* );
} // luabind
For every class I then specialize this to fill the passed 'lua_state' with the luabind info, like so:
namespace luabind{
template <>
void registerClass<gdk::Object>( lua_State* state )
{
luabind::module( state )
[
luabind::class_<gdk::Object>("Object")
.enum_("PrimType")
[
value("PRIM_CUBE", gdk::Object::PRIM_CUBE),
value("PRIM_SPHERE", gdk::Object::PRIM_SPHERE),
value("PRIM_PLANE", gdk::Object::PRIM_PLANE),
value("PRIM_CUSTOM", gdk::Object::PRIM_CUSTOM)
]
.enum_("TransparencyType")
[
value("ALPHA_NONE", gdk::Object::ALPHA_NONE),
value("ALPHA_TEST", gdk::Object::ALPHA_TEST),
value("ALPHA_BLEND", gdk::Object::ALPHA_BLEND)
]
.def(luabind::constructor<gdk::Object::PrimType>())
.def(luabind::constructor<const std::string&, bool>())
.def("createInstace", &gdk::Object::createInstace)
.def("createCopy", &gdk::Object::createCopy)
.def("setImage", &gdk::Object::setImage)
.def("setShader", &gdk::Object::setShader)
.def("setExcluded", &gdk::Object::setExcluded)
.def("setPosition", (void(gdk::Object::*)(f32, f32, f32))&gdk::Object::setPosition)
.def("setPosition", (void(gdk::Object::*)(const Vec3f32&))&gdk::Object::setPosition)
.def("setRotation", (void(gdk::Object::*)(f32, f32, f32))&gdk::Object::setRotation)
.def("setRotation", (void(gdk::Object::*)(const Vec3f32&))&gdk::Object::setRotation)
.def("setScale", (void(gdk::Object::*)(f32, f32, f32))&gdk::Object::setScale)
.def("setScale", (void(gdk::Object::*)(const Vec3f32&))&gdk::Object::setScale)
.def("setTransparency", &gdk::Object::setTransparency)
.def("getPath", &gdk::Object::getPath)
.def("getId", &gdk::Object::getId)
.def("getPosition", (Vec3f32(gdk::Object::*)())&gdk::Object::getPosition)
.def("getRotation", (Vec3f32(gdk::Object::*)())&gdk::Object::getRotation)
.def("getScale", (Vec3f32(gdk::Object::*)())&gdk::Object::getScale)
//.def("getTransform", (Mat4(gdk::Object::*)())&gdk::Object::getTransform)
.def("associate", &gdk::Object::associate)
];
}
} // luabind
That may look really confusing, but the luabind lib overloads operators in crazy ways to give you pretty elegant syntax to bind classes/enums/methods/etc to lua very easily. That particular one binds my 3D Object class(wraps GDK's objects) which allows me to instance objects and alter them from lua, like so:
local obj = Object( Object.PRIM_SPHERE )
obj:setScale( 5.0, 10.0, 15.0 )
Resources -
I don't know why, but I seem to be unable to write code that prevents possible future additions from being implemented. I noticed the way media was being loaded was very unfriendly to mods(if I ever support them), so I wrote a very basic resource file manager, whereby I can pass it a bunch of file paths and whenever I attempt to load a media file(image, shader, etc) it searches all the resource paths until it finds a match, if not then it raises an exception. This is all easy stuff, but it means my application.lua(first auto-run script) can cache media from every mod, like so:
function loadMod( name )
getResourceManager():addResourcePath( name )
setWorkingDirectory( name )
dofile( "mod.lua" )
setWorkingDirectory( ".." )
end
getResourceManager():addResourcePath( "Common" )
-- Load all mods
loadMod( "Default" )
So eventually I'll write code to search for all valid mods and load them too, but that's not important right now. The 'Common' dir I add in is for the GUI media, as I wasn't sure how a mod would add to the GUI without replacing it. The mod.lua file just caches all planet/enemy/satellite/etc templates.
GUI -
I changed how I handle the GUI as well, it now uses lua for everything so I only have one 'Menu' class that has about 5 methods. This means the root menu is really cool:
local cursor = Quad( getImageManager():findImage( "DefaultCursor" ) )
local this = getCurrentMenu()
local gui = this:getGUI()
this:setOnUpdate( function( elapsed )
mousePos = this:getMousePosition()
mouseLoc = Loc2D( Vec2i32( mousePos.x, mousePos.y ) )
cursor:draw( gui, mouseLoc, Loc2D.AlignXYCentre, BGRA( BGRA.WHITE ), true )
end )
-- Spawn initial menu
this:createChild( "GUI\\title.lua" )
Children get updated before their parents, so having the root menu draw the cursor causes it to always be on top. You may notice I use a lot of local variables in my scripts, this is because I only use 2 lua states across the whole engine(application(creeps, planets etc) and GUI), so storing the instance of a class in a 'this' variable would a very bad idea without it being local. I use very few states because binding the C++ classes to lua probably isn't the fastest thing in the world(I haven't tested it), you saw a snippet of one specialization to bind my gdk:
bject class, well this is the main function I call to bind them all:
void registerAllLuaClasses( lua_State* state )
{
luabind::registerClass<Vec2i32>( state );
luabind::registerClass<Vec3i32>( state );
luabind::registerClass<Vec4i32>( state );
luabind::registerClass<Vec2f32>( state );
luabind::registerClass<Vec3f32>( state );
luabind::registerClass<Vec4f32>( state );
luabind::registerClass<Vec2f64>( state );
luabind::registerClass<Vec3f64>( state );
luabind::registerClass<Vec4f64>( state );
luabind::registerClass<Mat4>( state );
luabind::registerClass<BGRA>( state );
luabind::registerClass<gdk::ResourceManager>( state );
luabind::registerClass<gdk::CameraManager>( state );
luabind::registerClass<gdk::Camera>( state );
luabind::registerClass<gdk::Object>( state );
luabind::registerClass<gdk::ObjectManager>( state );
luabind::registerClass<gdk::Shader>( state );
luabind::registerClass<gdk::ShaderManager>( state );
luabind::registerClass<gdk::Image>( state );
luabind::registerClass<gdk::ImageManager>( state );
luabind::registerClass<gdk::Rotation>( state );
luabind::registerClass<gdk::Input>( state );
luabind::registerClass<gdk::Std>( state );
luabind::registerClass<gdk::gui::Quad>( state );
luabind::registerClass<gdk::gui::FontSheet>( state );
luabind::registerClass<gdk::gui::GUI>( state );
luabind::registerClass<gdk::gui::Loc2D>( state );
luabind::registerClass<AOPos>( state );
luabind::registerClass<Entity>( state );
luabind::registerClass<Building>( state );
luabind::registerClass<BuildingTemplate>( state );
luabind::registerClass<BuildingTemplateManager>( state );
luabind::registerClass<BuildingSlot>( state );
luabind::registerClass<BuildingSlotTemplate>( state );
luabind::registerClass<Planet>( state );
luabind::registerClass<PlanetManager>( state );
luabind::registerClass<PlanetTemplate>( state );
luabind::registerClass<PlanetTemplateManager>( state );
luabind::registerClass<Player>( state );
luabind::registerClass<Satellite>( state );
luabind::registerClass<SatelliteTemplate>( state );
luabind::registerClass<SatelliteTemplateManager>( state );
luabind::registerClass<Creep>( state );
luabind::registerClass<CreepManager>( state );
luabind::registerClass<CreepTemplate>( state );
luabind::registerClass<CreepTemplateManager>( state );
luabind::registerClass<Menu>( state );
luabind::registerClass<SceneManager>( state );
}
Yeah...
2D coordinates -
If you read my root GUI code you may notice I use some mystery Loc2D class(
mouseLoc = Loc2D( Vec2i32( mousePos.x, mousePos.y ) ) ), this class stores 2 2D vectors, an integer and a float one. The float one signifies a percentage across the screen, so a coordinate of [0.5, 0.5], would be right in the centre. The integer vector is a pixel offset from this, in this case the mousePos.x/y values are pixel offsets, so I pass them to Loc2D's constructor, lua isn't very strict about typing so I had to stuff them in an integer vector so it knew which constructor to call.
Using such a coordinate system is vital for GUIs if you plan to support more resolutions than 1, because if you use only pixel positions then changing resolution will cause GUI elements to not fit. A very cheap way to get around this is to use a purely window based GUI, that way, changing the resolution just means there's more room to move the GUI elements around, another similar way is to position things relative to markers, like the left/right of the screen or something. But that won't work for all instances, thus this system easily allows you to create a GUI once, of course if you position and scale everything based on percentages then your GUI may look really strange on widescreen displays, and handling the text may not be so easy. But we'll cross that bridge when we come to it!
Last notes -
Most of these new additions have been purely
behind the scenes, thus there's no point in me releasing a new video. However I did add in one creep that the satellites can now damage, currently they just drain its health when in range because I haven't written any bullet code. They also can't be destroyed, as handling their deallocation will be very fun, because the lua code can reference my C++ objects, so I can't just randomly delete them.
Anyway, input is still encouraged! I don't mind making this game up as I go along, but the whole point was that the community tells me what crap to add in, though because I've added so much scripting this may be easier? Once I finish the framework(very soon) I'll work on wrapping the game together and releasing it, I intend to finish the game code by the 24th, and then spend 1 week writing scripts, making media and finally pouring insane amounts of polish over it so I can release it on Feb 1st.