For all the great things Lua has done for my current game development endeavour, it is now proving to be a royal pain in the ass.
The problems began when I added a background loading system to load new scenes in a separate thread without interrupting play. This all worked until it began loading Lua scripts. At the time, there was a single Lua state (virtual machine) for all of the scripts. This was awesome because communicating between scripts was extremely easy. When the loading thread started adding new scripts to the Lua state which was currently in use by the main thread, however, things began to go horribly wrong.
This problem was easily solved by having one Lua state per script. This also fixed problems with naming, as Lua has no notion of namespace. Lua states are designed to execute in isolation, so communicating between them becomes extremely difficult. Implementing game logic without entity communication is neigh on impossible.
There are a few potential solutions. The first would be to construct some kind of message passing system on the C++ side. When an actor wants to tell another actor something, it dispatches a message which is received by the actor on the next update and interpreted. Another option would be to use Luabind’s call_function() template function. This approach would require custom wrapper functions for each different function signature you’d need.
Spoonty | 23-Feb-08 at 11:34 pm | Permalink
William miller you need to link your blog and Fics on the homepage. I only found it today!
u dumb
Justin Fic | 26-Feb-08 at 12:51 am | Permalink
Fuck Lua™.
When I had the misfortune of working with it, the Lua scripts weren’t used at all for communication between objects- that was still done in C++. Lua was more for stuff that either didn’t need communication with other objects (for instance AI, which only referenced a global perception state, or scripts for cutscenes.)
wmiller | 26-Feb-08 at 8:07 am | Permalink
Yeah, that’s pretty much the solution I’ve come up with. I’ve written this class called PersistentData that each Actor has. The PersistentData object has two hash tables, one of strings and the other of doubles, each one keyed to a string for easy lookup. The class lets you add, retrieve, and edit arbitrary data to an Actor from script - essentially extending the class at runtime. It can also be saved to disk as binary and read back (hence the name). This, in conjunction with a script callback function for collision which gets passed two Actor pointers (the two Actors that collided with each other), solves most of the problems. For instance:
-- actorB has collided with actorA
-- x, y, and z is the average of the contact points found
-- for the two actors in the collision manifold
state["OnCollision"] = function(actorA, actorB, x, y, z)
if actorA:GetName() == "Hero" then
if actorB:GetPersistentData():GetNumber("IsEnemey") then
damage = actorB:GetPersistentData():GetNumber("DamageCaused")
actorA:GetPersistentData():SetNumber("Damage", totalDamage + damage)
end
end
end
The idea of a “perception state” is pretty cool. What I’ve set up is a state-machine system for callback functions. Each function is attached to a state. The actor’s state is stored on the C++ side, and can be changed from script. The example above is a collision callback attached to a state called “state”. An enemies OnCollision method might be different in its Attack state than in its Patrol state. Here’s an example:
Patrol = {}
Attack = {}
Dead = {}
-- Some variables
target = nil
totalDamage = 0
MaxHealth = 0
-- A helper function
CheckForBulletHit = function(actorA, actorB)
-- Check if I've collided with a bullet
if actorB:GetPersistentData():GetString("Type") == "bullet" then
totalDamage = totalDamage + actorB:GetPersistentData():GetNumber("Damage")
actorA:GetPersistentData():SetNumber("Damage", totalDamage);
-- Check if we're dead
if totalDamage >= MaxHealth then
actorA:GetStateMachine():ChangeState(Dead)
end
end
end
------------- Patrol State -------------
-- Called when the actor is first loaded
Patrol["OnLoad"] = function(actor)
MaxHealth = actor:GetPersistentData():GetNumber("Health")
end
-- Called when the state is entered
Patrol["OnEnter"] = function(actor)
print("I've entered the patrol state. Looking for dudes to pwn.")
end
-- Called every frame
Patrol["OnUpdate"] = function(actor)
print("Still looking...")
end
-- Called when the state is exited
Patrol["OnExit"] = function(actor)
printf("Found yo ass!")
end
-- Called when we've collided with something
Patrol["OnCollision"] = function(actorA, actorB, x, y, z)
-- Check if I've collided with a bullet
CheckForBulletHit(actorA, actorB)
if actorB:GetName() == "Hero" then
target = actorB
actorA:GetStateMachine():ChangeState(Attack)
end
end
------------- Attack State -------------
Attack["OnEnter"] = function(actor)
print("I've entered the attack state. Prepaire to die!")
end
Attack["OnUpdate"] = function(actor)
-- Shoot at the target
-- Check if the target is dead
if target:GetPersistentData():GetNumber("Health") <= 0 then
-- He's dead. Going back to patrol
actor:GetStateMachine():ChangeState(Patrol)
end
end
Attack["OnExit"] = function(actor)
print("Exiting attack state. My work here is done.")
end
Attack["OnCollision"] = function(actorA, actorB, x, y, z)
-- Check if I've collided with a bullet
CheckForBulletHit(actorA, actorB)
end
------------- Dead State -------------
-- ... You get the idea