Game entity system: Interactions between Lua and C++

Started by
7 comments, last by Flyverse 8 years, 8 months ago

Hey guys!

Most of my previous posts are already questions on the plugin-system I'm trying to implement in my current game. Thanks to this community I've now set on using Lua for plugins and C++ for the game(-engine). The simplest thing to implement, and on what I'm already pretty clear about, is some kind of event-system. It is simple:

- C++ keeps track of listeners for each event

- An event is simply some kind of ID

- You can hook into an event trough Lua and C++, by some simple function (Someting like hookInto(id, callback))

- Events can be triggered through Lua and C++, by some simple function (Something like triggerEvent(id, arguments))

- If triggered, all listeners of this event are called: A listener simply is a callback function.

- Maybe I could make it a bit more complex and add some kind of constraints, but it seems pretty ok as it is now.

(It is basically just some callback system)

So, what will a standard plugin do? Simply hook into some events, and do stuff in the callbacks (With the exposed variables. Seems simple enough. I was also told I shouldn't directly expose structs and variables and just implement a method for each thing I want to change.)

The thing is, I want each entity, respectively each entity type, be implemented in Lua. But... How?! I thought quite a bit about it now, and still can't wrap my head around it. I've had some little ideas here and there, but not enough to really put something together. Let's put together what I already have:

Each entity consists of:

- A drawable

- A physics object (+ hitbox)

- Position

- An Init-Function or Constructor or something

Plus

- Local variables only used by a specific type of entity

- Local functions only used by a specific type of entity

(By the way, local variables are all only used in the local functions (for example flags or something), and local functions are nothing more than a callback of some event or a function used in a callback of some event. Please keep this in mind for the rest of the post.)

Only Lua needs to know about those last two.

My problems are, that I don't know how to link this all together. Of course, I don't want to:

- Create or load the drawable in Lua for performance reasons. So I'll need to expose the C++ method, which will load the drawable on C++ side

- Render the entity in Lua for performance reasons. So I'll need to use the previously loaded drawable in C++ too, which doesnt seem like a problem.

- Let C++ know about all those local variables and functions. They should stay where they are, in Lua (Unless I would somehow convert the whole Lua script into something C++-ish on load-time, but I guess that is way too complex.)

I also want to:

- Update the position accordingly to the physics object. I guess I'll just let it be a part of the physics object, problem solved.

The thing is, how do I keep tracks of entities on the C++ Side?

I actually don't want to make dozens of copies of the same entity-type where everything stays the same but the values of the variables.

But I do need to keep entities in some kind of list. So I do need to let the drawable, physic object, and init-function-reference to be stored on C++ Side. And I also need to map all that stuff to one type of entity, maybe an entity ID. But the problems stays: How do I actually create entities now?

I don't want to load the same Lua script multiple times or something. I somehow have to copy some variables while letting other variables just be references, and everything coming from the same base entity... I know this reads confusing, but this is exactly the point where I get confused.

(Another small problem is that, since all local functions are event-system callbacks, I need some kinds of constraints. For example if I have a collide event, I don't want to trigger the callback in entity X for every collision in the game. Only for collisions involving Entity X.)

I hope you guys can understand what I mean and maybe help me :)

Kind regards,

Flyverse

Advertisement

The RPG I'm working on uses a custom C++ engine and Lua for scripting. It's open source, well commented, and has a decent amount of documentation, so maybe you could snoop around what we've done there and get some ideas.

I've been thinking about putting a video together on our youtube channel to explain how the map exploration code in the game works and how you build a script with it, but unfortunately won't get around to that until next month at the earliest. Here's some general designs that we've implemented.

First, we use Luabind to connect our C++ and Lua code. Luabind allows us to specify which classes and functions we want to be available within our Lua files. Most of the code that processes what happens on a map happens on the C++ side (or Lua calling into the C++ functions). For example, path-finding, animations, and drawing all happen in C++. The things that happen in Lua for a map are some large functions that populate the map with sprites, objects (like treasures), zones (defined areas of the map used for different purposes), and events (things that happen on a map). Lua will also have a number of small custom functions to do specialized things that we need, for example shaking the screen during an earthquake scene, moving the camera to a different location, or telling a sprite to move to a specific destination.

Most of the class objects we create in Lua we send a call to the corresponding manager class in C++ to handle that object. So every sprite we create is sent to a sprite manager, for instance, so that the sprite manager can figure out which sprites need to be updated, drawn to the screen, and handle collision detection between sprites. When we do this, we also tell C++ "I created this object in memory. I'm now giving it to you with the responsibility of destroying it when you are done". This isn't something you have to do, but it helps us to keep all object destruction contained in a single place rather than rely on Lua's garbage collector. (We use special arguments in our Luabind binding code to make this happen).

Now one thing I want to describe in detail is our event management system. Our maps all have an event manager, which holds event objects of various types. You may have an event that moves a sprite to a destination, an event to display a dialogue between characters, an event that causes a battle to occur, an event that plays an audio file, and so on. We also have custom events that contain pointers to Lua functions to run. Events can be chained together so that after one event finishes, another will begin automatically. This is useful for sequences where we want to have several things happen one right after another. You can specify how long to wait after an event finishes before the next one to begin, or you can start another event at the same time that the current event begins. Every event has two functions: a "Start" function that runs when the event begins, and an "Update" function that updates the event as necessary, and returns true when the event is finished. So a sprite move event would specify the desired location for the sprite in "Start" and the "Update" function wouldn't return true until the sprite reaches that destination. This is a simple system that offers great flexibility, which is what you need to build maps that are interesting.

The image below hopefully helps illustrate this idea of event chaining. We use Lua code to setup an event chain and to check for the conditions when an event chain should begin (for example, when a sprite walks into a certain area of the map). Lua tells the event manager to start the first event in the chain, and from there the C++ code handles the rest, processing each event until the sequence is complete (or processing it infinitely if there's a loop in the event chain).

MapEventFlow.png

I know that's a lot of ideas and words I'm throwing around, but hopefully some of what I said makes sense and helps you get a better idea of how C++ and Lua can co-exist. This was one of the most difficult problems for me to figure out myself, so my best advice to you is to experiment and figure out what works best for your situation. Maybe it's something like we've done, or maybe it's something entirely different. There's really not a right or wrong answer to how to mix Lua and C++ together.

Hero of Allacrost - A free, open-source 2D RPG in development.
Latest release June, 2015 - GameDev annoucement

Thanks for the answer (And sorry for my late reply - I was doing an internship at Wolfram smile.png)

I really like your idea/concept, but unfortunately I don't think that I can apply it to my game, since the current problem I'm working on, is letting the user implement any kind of entity by Lua scripting - I will still be able to use your idea later on though, for "regular" add-ons.

After a bit of thinking I might have a hunch at how I want to achieve it; Please give me feedback on this idea. I'll first just write example code to demonstrate it and then I'll try to explain. It should be noted that I'm using Box2D as physics-engine and won't write an abstraction layer for it, so some of the constraints the code has comes from Box2D.

(I also did not implement it yet, it is just an idea.)

In Lua, for entity "PlatformX":


-- Some local variables used by those local functions
PlatformX_Flag1 = false
PlatformX_Counter = 0
PlatformX_Useless = "Useless"

-- Now the local functions; Those are the callbacks I talked about in my original post.
function PlatformX_CollisionCallback(CppEvent, EntityID, Shape, position, angle, mass, velocity, angleVelocity, gravityScale, bodyType, density, friction, restitution)
    PlatformX_Flag1 = !PlatformX_Flag1
    PlatformX_Counter++
    Cpp_Entity_SetMass(EntityID, PlatformX_Counter) -- It is stupid but it is just an example
end

-- Now the registering stuff
typeId = 1
Cpp_NewEntityType(typeId) -- Creates space for a new entity type in C++
Cpp_RegisterDefaultEntityParameters(typeId, default stuff like shape mass velocity etc)
Cpp_HookEntityIntoEvent(typeId, EventType::COLLISION, PlatformX_CollisionCallback) -- Yes the first argument is a C++ enum but I don't know how to do that in Lua yet. Second argument is a function reference

Then you could have a "Level" file with contant such as:


<UsedEntityTypes>
    1, etc
</UsedEntityTypes>
<entity type=1>
    <override
        mass=333
        position=0,0
    />
</entity>

Which will then be loaded in C++:


struct EntityValues {
    Position, Mass, Shape, All_that_stuff
}
...
foreach(entityType){
    runLuaScript(entityType);
}
foreach(entity){
    entityVector.add(new EntityValues(all those values));
}

Then, somewhere in, for example the physics code:


//Ok, so there was a collision between two Box2D-Bodies
TRIGGER_EVENT(Events::Collision, EntityValues1, EntityValues2)

//And TRIGGER_EVENT somehow needs to forward the call to all "normal" registered listeners
//AND all entities registered. So if EntityValues1 or EntityValues2' ID equals the ID that registered into the event, call that Lua callback method.

vector<EntityValues> entities;
map<EventType, vector<functionPointer>> callbacks;
map<EntityType, DefaultEntityValues> ...

Also, I'd

have to create all those C++ API functions for Lua; For example:


void Cpp_NewEntityType(int id){
    Actually no idea what I should do here
}

void Cpp_RegisterDefaultEntityParameters(id, all those params){
    EntityValues vals = build from params
    entityType_DefaultEntityValues_Map.insert(id, vals);
}

void Cpp_HookEntityIntoEvent(int id, EventType t, FunctionPointer c){
   somehowRegisterIntoEvent, with the constraint of "id"
}

Basically, what I want to do, is keep track of some values every entity has independently of their behaviour. Their behaviour will then be defined by event-callbacks which are the same for each entity type: Thus the changing values like position etc need to be given to that callback function.

(Edit: Oh, and then there is another problem... The local variables used in Lua need to be different for each entity too... No idea how to achieve that yet though... Maybe add something like Cpp_RegisterLocalVar(entityTypeId, defaultValue) and Cpp_ChangeLocalVar(entityId, varId, newVal) and Cpp_GetLocalVar(entityId, varId))

I hope that I could make myself clear. Please comment on this, as I am still a bit lost sad.png ...

Kind regards

The RPG I'm working on uses a custom C++ engine and Lua for scripting. It's open source, well commented, and has a decent amount of documentation, so maybe you could snoop around what we've done there and get some ideas.

I've been thinking about putting a video together on our youtube channel to explain how the map exploration code in the game works and how you build a script with it, but unfortunately won't get around to that until next month at the earliest. Here's some general designs that we've implemented.

First, we use Luabind to connect our C++ and Lua code. Luabind allows us to specify which classes and functions we want to be available within our Lua files. Most of the code that processes what happens on a map happens on the C++ side (or Lua calling into the C++ functions). For example, path-finding, animations, and drawing all happen in C++. The things that happen in Lua for a map are some large functions that populate the map with sprites, objects (like treasures), zones (defined areas of the map used for different purposes), and events (things that happen on a map). Lua will also have a number of small custom functions to do specialized things that we need, for example shaking the screen during an earthquake scene, moving the camera to a different location, or telling a sprite to move to a specific destination.

Most of the class objects we create in Lua we send a call to the corresponding manager class in C++ to handle that object. So every sprite we create is sent to a sprite manager, for instance, so that the sprite manager can figure out which sprites need to be updated, drawn to the screen, and handle collision detection between sprites. When we do this, we also tell C++ "I created this object in memory. I'm now giving it to you with the responsibility of destroying it when you are done". This isn't something you have to do, but it helps us to keep all object destruction contained in a single place rather than rely on Lua's garbage collector. (We use special arguments in our Luabind binding code to make this happen).

Now one thing I want to describe in detail is our event management system. Our maps all have an event manager, which holds event objects of various types. You may have an event that moves a sprite to a destination, an event to display a dialogue between characters, an event that causes a battle to occur, an event that plays an audio file, and so on. We also have custom events that contain pointers to Lua functions to run. Events can be chained together so that after one event finishes, another will begin automatically. This is useful for sequences where we want to have several things happen one right after another. You can specify how long to wait after an event finishes before the next one to begin, or you can start another event at the same time that the current event begins. Every event has two functions: a "Start" function that runs when the event begins, and an "Update" function that updates the event as necessary, and returns true when the event is finished. So a sprite move event would specify the desired location for the sprite in "Start" and the "Update" function wouldn't return true until the sprite reaches that destination. This is a simple system that offers great flexibility, which is what you need to build maps that are interesting.

The image below hopefully helps illustrate this idea of event chaining. We use Lua code to setup an event chain and to check for the conditions when an event chain should begin (for example, when a sprite walks into a certain area of the map). Lua tells the event manager to start the first event in the chain, and from there the C++ code handles the rest, processing each event until the sequence is complete (or processing it infinitely if there's a loop in the event chain).

MapEventFlow.png

I know that's a lot of ideas and words I'm throwing around, but hopefully some of what I said makes sense and helps you get a better idea of how C++ and Lua can co-exist. This was one of the most difficult problems for me to figure out myself, so my best advice to you is to experiment and figure out what works best for your situation. Maybe it's something like we've done, or maybe it's something entirely different. There's really not a right or wrong answer to how to mix Lua and C++ together.

Hi Roots, I found your answer very useful. I was looking in your game code, and I have notice that you have different lua states for every script.

It is recommended to do this? I have read many people don't.

In my engine, I have the problem that the variables declared in the script functions are shared between the instances of the same type. I want to avoid that. Implementing your solution, will solve my problem?

Thanks in advance and sorry for my english.

I think you're confusing lua states with our usage of the metatable to provide a shared space. Something like the following is at the top of most of our Lua scripts:


local ns = {};
setmetatable(ns, {__index = _G});
harrvah_underground_river_cave = ns;
setfenv(1, ns);

What this does is similar to how a namespace works in C++. Any variables declared within this "namespace" (which in this case we name harrvah_underground_river_cave) are protected within this metatable so that a variable with the same name in another script doesn't collide and overwrite these values.

For example, say we have two Lua files, each which represent a map. Let's call them a_map and b_map. Our engine starts a single Lua state for running the game. This state is shared among all Lua files we load into it. We want to have the same variable names in a_map and b_map (ex: a table called "sprites", a number called "tileset_count", and so on). Without these metatable namespace guards at the top of the file, we could only ever have one map loaded into the Lua state at any time. Otherwise a_map and b_map would be sharing the same variables, and running code on a_map could overwrite the stored data for those matching name variables on b_map.

That's why we do that. We do not use seperate Lua states. We use metatables to essentially wrap the file in a namespace to avoid collisions with the same variable names in other Lua files that we may load into our single Lua state. (Disclaimer: this was added to our Lua standards long ago and I wasn't involved, so I don't know how or why this little trick works. All I know is that it does work).

Hero of Allacrost - A free, open-source 2D RPG in development.
Latest release June, 2015 - GameDev annoucement

Sorry for bringing this thread back to life again, but I still have questions on this. I've postponed the actual coding & designing to later in order to finish the "base-game" first, but I still want to code it with the scripting system in mind.

I've thought a little bit about "platforms"/"entities" that could be scripted, and tried to find some that won't be able to be scripted: For example, platforms/entities requiring to be updated on every gametick. This just isn't doable, since the overhead to make all the calls to Lua would probably be way too much. I tried to figure out a way around it but didn't realy manage to do so. For example: Let's assume a "Magnet" Platform, which will apply some kind of force on the player if he enters a certain area. This force needs to be applied every gametick, but the "Magnet" Platform is coded in the scripting system, which has no access to the update-function. Of course, I could create some kind of C++-Register-Method that registers that specific platform to be notified when the player enters a specific zone on the screen, but even that would probably cost too much performance, since the entity-callback will still be called every gametick as long as the player stands in that area. Does anyone have an idea on how to bypass this kind of restriction? I really liked this idea of having a totally event-driven behaviour, but it seems to be a little bit too restricting because of that ONE update-function...

Kind regards,

Flyverse

Are you sure it is going to cost you too much performance? My game calls several hundred Lua scripts and a few quite complex ones per tick, and the Lua VM itself have never cause me any performance trouble.

My metric is if some operations happen less then 1 million times per second, then even considering to optimize or not is often not worth it.

In my experience, how you write your Lua script is just as important as the c++ code behind the scenes.

If you create a lot of garbage, the Lua VM HAS to spend time dealing with it.

This can be very, very slow. I have seen cases where bad Lua code has generated a 4 mS spike every two seconds. The garbage just built up faster than the VM could deal with it and eventually it had to down tools and sort it out.

So please keep that in your head when thinking about scripting.

@AzureBlaze

Wow, if it really is that fast that would be really awesome. Makes my game-design so much easier.

@Stainless

I'll keep that in mind, thanks =)

This topic is closed to new replies.

Advertisement