Jump to content
  • Advertisement
Sign in to follow this  
Zane528

Cutscenes In Lua

This topic is 4785 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I've been using lua for a while, and I've intergrated it into my engine. I can use it to setup the maps, player and npcs, and I have already added trigger events, but my problem is that I'm having a hard time making functions for the cutscenes. The problem im having is when i call these functions the there are drawing problems because the engine has to wait for the function to end. So I was wondering how you could make functions like this progressive, so they happen over many frames. If you want to see some of my code I'll put it up later since im in class right now.

Share this post


Link to post
Share on other sites
Advertisement
You want to look into Lua coroutines. Google the phrase for many tutorials.

Basically, once you wrap your head around them, you should be able to write cutscenes like this:

function discoMarioCutsceneUpdate()
local mario = spawn("Mario")

mario:playAnimation("Do a little dance")
while mario:isPlayingAnimation() do coroutine.yield() end

mario:playAnimation("Make a little love")
while mario:isPlayingAnimation() do coroutine.yield() end

mario:playAnimation("Get down tonight!")
while mario:isPlayingAnimation() do coroutine.yield() end

mario:despawn()
end



In call discoMarioCutsceneUpdate() every frame through the "resume" function and it will trigger the 3 animations in succession without blocking.

[Edited by - corysama on September 14, 2005 8:28:02 PM]

Share this post


Link to post
Share on other sites
Thanks for the post. I looked up the coroutines and I think I understand how it works. One other things that I'm not sure on is that in my engine I have different states for various things like the intro screen which has the new game, load game options, a state for walking around maps, battle and so on. The problem is that I'm not too sure on how to handle the script while walking around the maps, like where I should call the scripts or if I should have a function if a cutscene is in progress to loop the script. Also im not sure how I would resume the coroutines and make it so that it'll resume any coroutine that might be suspended. Again if you need me to put any code up I can, thanks again.

Share this post


Link to post
Share on other sites
I've actually been thinking about this myself lately. I skipped the chapter on co-routines in the Lua book because it didn't seem that useful, but I'll definitely go back and re-read it now. [smile] I'll share some thoughts I've had related to map scripting for my game and contrast it with what I think you are suggesting.


1. Don't define such small granularity events
What I mean by this is don't have a script dictate every single frame move that your sprite/object does. Instead, write an interal C/C++ function that processes a sprite moving from one tile to the next (assuming you have a tile-based game). Then, write another function which can take a destination/target tile, perform a pathfinding algorithm, and then have the Lua script call the pathfinding function with a destination.

So basically your Lua script would look something like:


sprite.move(-3x);
sprite.move(+2y);
sprite.stop();


Which is much better than trying to move the sprite one pixel at a time. [wink]


2. Define an event list for each map sprite
An event list is simply a table full of functions for "events" to perform on the sprite. You can have an index/pointer to the next event to process, and then define the behavoior of how your event list is processed (most of the time I'd imagine that you'd do a circular loop of your events to show the sprite walking back and forth). The C/C++ code would process each event and be able to detect when it should fetch the next event from Lua.



3. Define 'triggers' on your maps that call large events
So lets say when the player steps onto tile X, I want all the sprites on the screen to start running away from the player's sprite. Lets call this a global map event (GME) for lack of a better term. Basically, a GME is processed by the map just like events are processed by sprites, but the GME can set specific events on any map object that wouldn't normally be processed by the individual object or sprite.


What I was planning to do in my game was create three ways that can cause a GME. First is to step on a tile. Second is to walk off a tile. Third is to press the "confirm/check" button when facing a tile. You may also want GMEs to be registered to more than one tile (say, an entire row of tiles). The way I represent this in my code is I have a bit set in the properties mask for each affected tile, and when I detect an event on a tile occured, I look up the event that corresponds to that tile in a vector of stored GMEs and process it.


You may also want to have GMEs to occur only once or multiple times, and you should specify that in the Lua data.




Blegh, anyway those are just some of my ideas right now. Hopefully they made sense. I'm kind of tired so my brain isn't working at full capacity right now. [smile]

Share this post


Link to post
Share on other sites
Quote:
Original post by Zane528
Thanks for the post. I looked up the coroutines and I think I understand how it works. One other things that I'm not sure on is that in my engine I have different states for various things like the intro screen which has the new game, load game options, a state for walking around maps, battle and so on. The problem is that I'm not too sure on how to handle the script while walking around the maps, like where I should call the scripts or if I should have a function if a cutscene is in progress to loop the script. Also im not sure how I would resume the coroutines and make it so that it'll resume any coroutine that might be suspended. Again if you need me to put any code up I can, thanks again.


What I would recommend is that you make a table in Lua to contain all of the coroutines that are currently active. Whenever a new cooroutine starts, add it to the table. Every frame call a Lua function that loops through the table calling couroutine.resume() on each entry in succession. Also call coroutine.status() to see if the thread is dead and therefore should be removed from the table.

A setup like that would probably be generic enough to be used by pretty much all of your "Lua-threaded" systems simultaneously.

If you require a specific ordering to your updates (like cinemas always update before the player) then you might need design a structured container using more than 1 table.

Share this post


Link to post
Share on other sites
Thanks for all the replies and ideas. I wrote some code based on what corysama said, I haven't had time to test it but maybe someone can tell me if im going in the right direction with it.

CPP Code:

// While the cutscene is going, update lua coroutines
while(cutscene) {
lua_coroutine_table_update();
}

// Calls lua to update the table of coroutines
void lua_coroutine_table_update() {
lua_getglobal(luaVM, "coroutine_table_update");
lua_pcall(luaVM, 0);
}

// Calls lua to setup the table
void lua_coroutine_table_setup() {
lua_getglobal(luaVM, "coroutine_table_setup");
lua_pcall(luaVM, 0);
}



Lua Code:

// Sets up the table
function coroutine_table_setup()
coroutine_table = {};
current_table_index = 0;
end

// Adds an entry in the coroutine table
function coroutine_table_add_entry(function)
current_table_index = current_table_index + 1;
coroutine_table[current_table_index] = function;
}

// Deletes an entry in the coroutine table
function coroutine_table_delete_entry(number)
for i = number, current_table_index - 1 do
coroutine_table = coroutine_table[i + 1];
end

current_table_index = current_table_index - 1;
end

// Loops through the coroutine table, checks if a function is not dead.
// If it is, its deleted from the table, if not its resumed
function coroutine_table_update()
for i = 1, current_table_index do
if(coroutine.status(coroutine_table) not dead) then
coroutine.resume(coroutine_table);
else
coroutine_table_delete_entry(i);
end
end
end

Share this post


Link to post
Share on other sites
That's the idea.

Your current coroutine_table_update() has a bug. When a coroutine is removed from the table, the next entry (whose index just became i) will be skipped. Also, "function" is a keyword so it doesn't work as a parameter name.

I would suggest the following implementations:

function coroutine_table_add_entry(thread)
table.insert(coroutine_table, thread)
end

function coroutine_table_delete_entry(number)
table.remove(coroutine_table, number)
end

function coroutine_table_update()
for index, thread in ipairs(coroutine_table) do
if coroutine.status(thread) ~= "dead" then
coroutine.resume(thread)
else
coroutine_table_delete_entry(index)
end
end
end


I'm not sure if you can safely for-loop over a table while modifying that table... You might have to do something like:

function coroutine_table_update()
local index = 0
while index < table.getn(coroutine_table) do
if coroutine.status(coroutine_table[index]) ~= "dead" then
coroutine.resume(coroutine_table[index])
index = index+1
else
coroutine_table_delete_entry(index)
end
end
end


or maybe even...

function coroutine_table_update()
local deadthreads = {}
for index, thread in ipairs(coroutine_table) do
if coroutine.status(thread) == "dead" then
deadthreads[index] = true
end
end
for index, ignore in pairs(deadthreads) do
coroutine_table_delete_entry(index)
end
for index, thread in ipairs(coroutine_table) do
coroutine.resume(thread)
end
end

Share this post


Link to post
Share on other sites
Depending on your CPU load and the manner in which you want to use coroutines in lua you may not want/need to resume each coroutine each frame/tick/whatever.

An alternate idea is to move the logic that determines if the lua script needs to move onto the next command from the script itself into you C++ native code. This can be handled by having operations that act on some game object send a message to the C++ object that handles each lua coroutine (or the C++ object that handles each game object).

For example, suppose that you have a sprite controller that can animate\move a sprite object in your game. A lua script for a cutscene could have the form:

spriteController:addCommand_moveTo(soldier1, 100, 50, self);
coroutine.yield();
spriteController:addCommand_playAnim(soldier1, "wave", self);
coroutine.yield();

The sprite controller is a C++ object that registers each command you pass it and processes it each frame until the command is complete. Also passed to the sprite controller is an object to be notified when the command is completed (this notification occurs within native C++ code). This notification object could simply wrap resuming the associated coroutine or you could introduce more logic allowing for things such as:

spriteController:addCommand_moveTo(soldier1, 100, 50, self);
spriteController:addCommand_moveTo(soldier2, 50, 25, self);
self:waitForCommands(2);
coroutine.yield();
spriteController:addCommand_playAnim(soldier1, "dance", self);
spriteController:addCommand_playAnim(soldier2, "dance", self);
self:waitForCommands(2);
coroutine.yield();

(This would initially wait for both soldiers to move into position and then make them both dance together. waitForCommands(X) tells the C++ object handling the coroutine to only resume the lua coroutine after it has recieved completion notifications from X commands. You can make this more complex of course and allow waiting for specific commands to complete but you get the idea.)

Which method you choose really depends on your existing game architecture (a message based architecture makes implementation of the method above slightly easier) and your needs.

One advantage of this method is that you do not dip into lua each tick/frame. You only dip into lua to resume a coroutine when required.

Corysama's method is simpler though, and no doubt quicker to get working.

Swings and roundabouts I guess!

(for some reason the forum won't display CPlusPlus using plus symbols, so where I have said 'C' I mean 'C Plus Plus')

Mike.

Share this post


Link to post
Share on other sites
Im doing it the way Corysama suggested and have one question:

How do I stop/abort any of the coroutines from the host program?

Share this post


Link to post
Share on other sites
I would guess that you would use the function to delete that thread, then right after you tell the coroutine to yield, taking you out of that coroutine. Then when the engine checks the lua table it wont find it, and will stop it.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!