Monday, June 26, 2006

More Scripting in Lua

In the first LUA tutorial - we looked at how to call C# functions from LUA. All well and good ... but what about using classes? That's kinda of important isn't it! You want to interact with your NPC classes and all that kind of cool stuff. This is what we'll be covering here.

Let There Be Classes!



I assume you can set up a LUA enabled C# project - we're going to do another nice console window one.

First thing to note:

Classes, enumerations, structs, what have you, . . . are all "CTS types".

Q: "What does CTS stand for?"
A: "Shutup"

Right, so how do we use these CTS types inside LUA. How how how?

Well there are two important functions that we must use.

1. luanet.load_assembly
2. luanet.import_type

While you're commiting those to memory - let's start creating a demo, framework, example whatever! Let's make one. So we make a new console application.

When I type you . . . type. Just like that



Using the previous tutorial we created a LUA enabled C# program. You should end up with the following and should be able to compile it without errors (if you fail in this hang your head in shame and go read the first tutorial again)


using System;
using System.Collections.Generic;
using LuaInterface;

namespace LuaScriptingAdvanced
{
class Room
{
Lua lua = new Lua();

public Lua Lua
{
get { return lua; }
}

public Room()
{
}

static void Main(string[] args)
{
Room partyRoom = new Room();

}
}

}


Now we're going to add a class, a marvel of object orientated design and programming.


class Person
{
public string Description = "";
}


Amazing! Well let's say we want a few of these people in a room. We need the generic collections namespace. Luckily it's already at the top of the room class.


using System.Collections.Generic;


The class "Room" is where we'll be having the party so we need to add some code there. We'll have a list of people in the room (enjoying the party) and we'll have a function look, that will tells us what's going on.


class Room
{
// Our revellers.
List people = new List();

public void Look()
{
foreach (Person p in people)
{
Console.WriteLine(p.Description);
}
}


We need one last function then we can start our LUA stuff. Can you guess what function? No? Well it's an AddPersonToParty function, doesn't exactly roll off the tongue but it will do.


public void AddPersonToParty(Person p)
{
people.Add(p);
}


Making Use of Lua



Let's stick to tried and true ground first and register the above function. Well kind of tried and true - it has a type in it, Person, that's not basic to LUA! But if you're not afraid, I'm not afraid, let's continue! We're going to jazz up the room constructor like so.


public Room()
{
lua.RegisterFunction("JoinParty", this,
GetType().GetMethod("AddPersonToParty"));
}


Hmm sweet jazzy jazz. Here we register the room's function that will let us add people to the party from a Lua script (The room also contains a Lua reference). Imagine if we created more than one room object! We would have problems, we'd always register over the last join party function! Luckily we only ever want one room, so this is okay.

Also we're going to expand the main function so it actually does something.


static void Main(string[] args)
{
Room partyRoom = new Room();

partyRoom.Lua.DoFile("scripts/party.txt");
partyRoom.Look();

Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
}


Obviously the DoFile line of code jumps out. Scripts directory? "party.txt"? What's all this? You might wonder.

For now it's just filler in your bin directory create a directory called scripts. In the scripts directory create blank text file called "party.txt". Okay now you're all caught up.

So we call our [tag]script[/tag] and then call the room's look function - this will scope out the people at the party for us. Of course there are no people at the moment. Guess what the script might do! Now to write that script.

Coding in Lua



Ready? Okay go to the bin directory where the executable is being generated. You know where you dumped all those Lua dll files? Yeah? Good. Go there. Now you should have already created a text file named "party.txt". Open it and prepare to type!


--Grab the class info

luanet.load_assembly("LuaScriptingAdvanced");

Person = luanet.import_type("LuaScriptingAdvanced.Person");

--Steve
steve = Person();
steve.Description = "There's Steve drinking something brown and frothy from a shoe.\r\n";
JoinParty(steve);


For now Steve is a one man party. Okay now run the executable and try not to look alarmed. We should get the output:


There's Steve drinking something brown and frothy from a shoe

Press any key to exit.


And there you go. Remember what CTS stands for and you'll be fine! Now you can add classes to your Lua files - and you now what classes mean - prizes!

Let's Do Functions



We have to make use of the Lua libraries to do this. We're going to use the base library. Let's go back to our person class and add a few bits.


class Person
{
public string Description = "";
public string Name = "";

public void GiveName(string name)
{
Name = name;
}
}


Wow, talk about your contrived examples :D. Now let's check out that look function, in the room class.


public void Look()
{
foreach (Person p in people)
{
Console.WriteLine(p.Name + ": " + p.Description);
}
}


Let's alter the part of script dealing with Steve to include this new ground breaking function.


--Steve
steve = Person();
steve.Description = "There's Steve drinking something brown and frothy from a shoe.\r\n";
steve:GiveName("Steve");
JoinParty(steve);


Notice the little colon rather than a peroid. Be careful about that otherwise it will trip you up. Okay that should be enough to keep you going.

If you're going to use ints or numbers as function arguments remember all Lua's numbers are doubles therefore they need to be converted. This is done automatically yah! But in order to do it automatically you must include the math library. Using this: room.Lua.OpenMathLib(); will sort you out.

Next time I might do delegates, of course consider the amount of time that seems to pass between tutorials before you go betting your deadline on me. You may also be wondering about inheritence and all that ... the answer is yes you can but it's not something I think I'm going to need at the moment so I'm leaving it alone.

Source Code



Source Code

11 comments:

Anonymous said...

Exception at runtime:

./scripts/party.txt:8: attempt to call global 'Person' (a nil value)

I use lattest interface1.5.3, compiled with VS2005.

Could you help me check what's problem? I am a newbie in lua, thanks

balaam said...

Is this using the source coded provided?

I'm kind of moving everything over to a new site this week. So I'll defintely be looking at this code soon.

balaam said...

I just downloaded it and tested the source code with the latest LuaInterface library and it was fine - did you write the code out yourself?

If so download the code from the site and give that a go. Then try and see where the difference is!

Anonymous said...

I downloaded again, created a new project and copy everything and recompiled. Still got runtime exception.


Unhandled Exception: LuaInterface.LuaException: scripts/party.txt:8: attempt to call global 'Person'
(a nil value)
at LuaInterface.Lua.ThrowExceptionFromError(Int32 oldTop)
at LuaInterface.Lua.DoFile(String fileName)...


And I also find some old comments within your blog:


posted by Dan : 6:10 AM I've done everything in the tutorial and the comments but when I try to run it I get an exception:

"party.txt:9: attempt to call global `Person' (a nil value)"

Anyone know how to fix this?

# posted by Anonymous : 12:01 PM This may be a version conflict or something I also have the same issue. First time with LUA (obvsiously).

# posted by Anonymous : 4:51 AM Yes, that's more than likely considering I wrote this with the last version of Lua interface.

I'm going to scrapbook it in firefox now and I'll redo sometime during the week when I bored at school :D

Anonymous said...

You can find the comments at:
http://einfall.blogspot.com/2005/07/advanced-lua.html

And I've tested the code in "Scripting with Lua in C#":
http://einfall.blogspot.com/2006/05/scripting-with-lua-in-c.html

It works very well. I've done everything as you told. Copy latest luainterface.dll, lua51.dll, luanet.dll to my bin. I don't know what's the problem?

Anonymous said...

And I just test luainterface1.3.0.0, still got same exception. Anything wrong with my development environment? I use VS2005.

Katrina said...

I belive your namespace is ddifferent then the one used in the code

Anonymous said...

I made some tests. You get the exception at runtime if you have the class you want to import_type in the same namespace of where you call DoFile function.

Try making another namespace (for example MyLuaTypes) and put here only the Person class. Then in the file you have your main() add the namespace you made ( using MyLuaTypes; ) so that you'll not need to change the code.
In your script file change the first lines in:

luanet.load_assembly("MyLuaTypes");
Person = luanet.import_type("MyLuaTypes.Person");

and it should work.

I'm not an expert, so I hope i solved this problem without creating others :P

Noir707

Anonymous said...

I was getting that error too, "attempt to call global 'Person' (a nil value)" for me the problem was my .exe was called LUATest and the namespace the classes were in I was calling PeoplePlacesThings... based on the tutorial I thought the setup lines should both be using the namespace like this:

luanet.load_assembly("PeoplePlacesThings");
Person = luanet.import_type("PeoplePlacesThings.Person");

But this does not work. The first line needs to have the name of your .exe, and the class definition lines need the namespace. so for me changing the lines to this worked:

luanet.load_assembly("LUATest");
Person = luanet.import_type("PeoplePlacesThings.Person");

--Threnodi

Michael Francis said...

Hello,
I'm trying to implement scripting in a game engine I wrote. However, I'm having a problem registering functions and classes inside my main program. When I add the attribs to the function inside each class, and import system.reflection and such it only registers functions inside the main program. What should I do?

Thanks

ferhatcelik said...

Hi. I want to use a code like in the client side without defining any function in the client.

lua.DoFile ("myscript.lua");

But the methods must be defined in the client with RegisterFunction. How i can define the class methods in the script file like the code below:
----------------------------- myscript.lua-----------
-- Grab the person class
luanet.load_assembly ("LuaEntrance");

Person = luanet.import_type ("LuaEntrance.Person");
Room = luanet.import_type ("LuaEntrance.Room");

-- Jack London
jack = Person ('Jack London');
jack.Description = "Jack London, the writer. Sitting in the sofa";

-- Selahattin Eyyubi
sela = Person ('Selahattin');
sela.Description = "Sela is coming from the door. and says 'get out this room now!'";

Room.AddPerson (jack);
Room.AddPerson (sela);
Room.Look ();