New Lua user here...with lots of questions :)

Started by
15 comments, last by DrEvil 18 years, 10 months ago
Hello :)

I'm one of the coders working on Mistlands and we use lua to script objects (entitys) in the games, gui and materials on models. A short description of how we do it might give you some ideas :).

Each entity in the game has it own .lua file (like dwarf.lua). Each lua file gets it's own lua_State that are used to load it. It's event based were the engine calls functions in the lua file when things happen.

Each time a function in the lua state for a entity is called the ID of that entity is stored so you know what entity to act on. All dwarfs use the same lua file but when you tell the dwarf to move somewere you call MoveTo(GetCurrentEntityID(), Position); so only the correct dwarf starts moving.

Variables for entitys are stored in the entitys and not in lua global variables. To set the name of the dwarf you do SetLocalString(GetCurrentEntityID(), "Gimli");. The name is then stored in the entity and you can get it back with myname=GetLocalString(GetCurrentEntityID(), "Gimli");.




@spinningcubes | Blog: Spinningcubes.com | Gamedev notes: GameDev Pensieve | Spinningcubes on Youtube

Advertisement
Ah, many thanks everyone. Now I just need to sit down with Visio and really think this through.
Quote:Original post by TheFallenKing
I'm not sure I follow the "execute one, then the next, then the next, etc." logic. I may be missing something in the lua doc, but I don't see anythign to stop a script besides the coroutine yield.


The example you gave of a 'script' was a function. When you call a function, you start at the top and come out somewhere at the end and it's over. In other words, it's executed. Now, if you're saying that you need things to be ongoing and deliberately yielded, that's a different question, but it's not what your example suggested.

As for lua_dofile, that's probably not the way you want to go about it. Instead you probably want to load individual scripts as functions and call them as necessary, or load in object definitions which other code creates when necessary. I don't even know if lua_dofile is exposed in the latest version. You probably want lua_load, often via luaL_loadfile.
Edit: wow this ended up long.

Here's an example of what one of my bot scripts look like.

When a bot is created, they optionally have a 'profile' assigned to them. This profile is basically just a reference to a script file. In my case its a game monkey script file, but it could be lua or whatever.

Among other things, the script allows a user to assign a custom goal function that the bot will use instead of its built in C++ goals. This allows a user to optionally take complete control of certain bots to script scenarios, machinima, tutorials, etc..


// Test Bot Script
goalFunc = function()
{
block(event.spawned);
this.GoTo(5.14, 392.77, 24.125);
if( block(event.goal_success, event.goal_failed) == event.goal_success )
{
this.BotCommand("say Wohoo, I made it!.");

dowhile(true)
{
closest = this.GetNearestAlly(cat.player,0);
if(closest != null)
{
info = this.GetTargetInfo(closest);
this.TurnToPosition(info.position);
} else
{
this.BotCommand("say No Target.");
}
yield();
}
}
else
{
this.BotCommand("say Sorry, couldn't make it.");
}
};
this.SetGoalFunction(goalFunc);


GM script allows the user to tell the scripting system what the value of 'this' is. When the c++ side executes this function, it sets the value of this to the bot pointer.

In this example, a function is declared, and at the bottom a function is called on the bot that sets the goal function to this script function. Part of the C++ implementation of this disables any default internal 'thinking' of the bot, creates a gmThread for the bot, and executes the function. Note that gmThreads aren't real threads like OS threads, they execute until the script yields/blocks/sleeps. GM Script has some nice built in functionality for these threads, the block(), yield(), and sleep() functions.

As you can see in the example, the script first blocks on an event.
block(event.spawned);
This stops execution of the script completely, this script will no longer execute until the contents of the value pass in to block() is signalled. With a simple hook in my event system in c++, when the bot spawns he will send the gmThread a signal(event.spawned), which will then unblock the thread and allow it to execute on the next update of the scripting system.

Ok, so as soon as the bot spawns, in this simple example a c++ bound function is called on the bot,
this.GoTo(5.14, 392.77, 24.125);

All this does it push a GoalGoTo on the bots goal queue, and the script immediately blocks again, this time defining several signals that can resume the scripts execution. It could take the bot several minutes to get somewhere, and he will do whatever it takes to get there. Part of the GoalGoTo is building a path from the bots current point to the goal location. Anything necessary to accomplish the goal will be automatically handled c++ side, based on the make-up of the goal. He may end up getting GoalNavigateElevator, GoalJumpGap, or any number of sub goals added based on the complexities of the path he is navigating. The important part is that the script is blocked at this point, and will not continue until the goal has succeeded or failed.

It's a simple example, but as soon as the bot succeeds in getting to the point, the bot will signal his goal thread to continue, say "Wohoo, I made it!." and then the script will go into a loop where he continuously checks his sensory system for any memory of the nearest ally. If there is one, he gets their target info, which is a c++ bound type that holds distance, position, velocity, orientation... and calls this.TurnToPosition(info.position); which causes the bot to turn to face the target over time based on the turn rate, spring stiffness/dampening aiming model.

Loops are potentially dangerous, so its important to notice that the yield() is in the loop, which means this loop will do 1 iteration each update and give control back to the bot dll. If the yield wasnt there the script would loop forever.

The top-down execution of this type of scripting system has many advantages.
1) It's easy to understand, and much less code than if you were to try and handle the asynchronous nature of most tasks you give a bot or game entity. Even non coders could probably create a tutorial or scripted bot relatively simply.
2) It spends very little time executing actual script code, meaning the speed penalty would be very little compared to running a script function every update that may or may not do something.

It's very easy to do most anything. The ability to stop execution of a script until the line before it completes is very useful in keeping scripts simple, small, and easy to debug. Especially dealing with AI, like this example, nothing happens instantaneously. It takes time for the bot to get somewhere, it takes time for the bot to turn to face a target. It takes time for a door to close, a treasure chest to open, an elevator to rise. All these would be insanely trivial to write a script for.

In the example above, I could replace the this.TurnToPosition(info.position);
with

if(this.TurnToPosition(info.position))
{
this.PressButton(FIRE);
}

since my TurnToPosition function returns true if the bots aim is within a small degree of error to the target, and false otherwise.

A door script could look like.

// Script for a locked door.
// When someone 'uses' this door, the script will set the activator, and
// signal the doors thread with "activated", allowing the door to do whatever
this.CloseDoor(); // we want this door to start closed.
dowhile(true)
{
block("activated");
activator = this.GetWhoeverActivatedMe();
if(activator != NULL) // paranoid error checking
{
if(activator.HasItem("bluekey"))
{
this.OpenDoor();
Sleep(this.GetDoorOpenDuration()); // stay open for however long
this.CloseDoor();
}
else
{
activator.SendMessage("The door is locked, find a key!");
}
}
}


With a very simple set of functionality on the C++ side, this would empower your designers/map makers to create incredibly diverse environments, by giving them the flexibility to do pretty much whatever they want. This script could trigger a trap, warp the character, etc. It could also be expanded easily. If the game has more actions than "activate" it could do something like this.


dowhile(true)
{
actionDone = block("activated", "inspected", "bashed");
if(actionDone == "activated")
{
// do the stuff above
}
else if(actionDone == "inspected")
{
activator.SendMessage("An old oak door. It looks pretty weak with age.");
}
else if(actionDone == "bashed")
{
if(activator.CanBash(this))
{
activator.SendMessage("You broke the doors lock!");
this.OpenDoor();
break; // the lock is busted, break out of the loop, the thread will end and the door will stay open
}
}
}


This type of thing could come in very handy in a MUD, where each action can be a command the user typed in, allowing anyone to implement custom keywords or actions at any time.

Ok anyways, apologies for the length of the post. Game Monkey script has very easy, and very well implemented support for this stuff out of the box. I'd recommend taking a look if you need this type of functionality. I'd take GM script over lua anyday, for these 'threads' the built in 'state' functionality, plus a more familiar syntax(I hate luas syntax). Alternately you could probably do something similar with lua co-routines but it wouldn't be very pretty from what I've read about them.

sigh, I hate this forum, that last post was me.

do anon posts disregard code tags? grr it looked fine in the preview.

Can a mod please fix my code blocks?
Anonymous posters can't use HTML. The links were completely stripped.
damn, not even ? That's not html

This topic is closed to new replies.

Advertisement