tags:

views:

101

answers:

2

My current problem is that I have several enemies share the same A.I. script, and one other object that does something different. The function in the script is called AILogic. I want these enemies to move independently, but this is proving to be an issue. Here is what I've tried.

1) Calling dofile in the enemy's constructor, and then calling its script function in its Update function which happens in every game loop. The problem with this is that Lua just uses the script of the last enemy constructed, so all of the enemies are running the same script in the Update function. Thus, the object I described above that doesn't use the same script for it's A.I. is using the other enemies' script.

2) Calling dofile in the Update function, and then calling its script function immediately after. The problem with this is that dofile is called in every object's update function, so after the AILogic function runs and data for that script is updated, the whole thing just gets reset when dofile is called again for another enemy. My biggest question here is whether there is some way to retain the values in the script, even when I switch to running a different one.

I've read about function environments in Lua, but I'm not quite sure how to implement them correctly. Is this the right direction? Any advice is appreciated, thanks.

Edit: I've also considered creating a separate place to store that data rather than doing it in the Lua script.

Edit2: Added some sample code. (Just a test to get the functionality working).

-- Slime's Script

local count = 0;

function AILogic( Slime )
  --Make the slime move in circles(err a square)
  if count < 4 then
    Slime:MoveDir( 0 );
  elseif count < 8 then
    Slime:MoveDir( 2 );
  elseif count < 12 then
    Slime:MoveDir( 1 );
  elseif count < 16 then
    Slime:MoveDir( 3 );
  else
    count = 0;
  end
  count = count + 1;
end
+1  A: 

The canonical reference for this is link text. Explaining this briefly we'll work off the following code from the site:

a = 1
local newgt = {}        -- create new environment
setmetatable(newgt, {__index = _G})
setfenv(1, newgt)    -- set it

The first line sets up the (global) variable "a". You can view this as setting default values for your code. (Keep in mind that in Lua all variables are global unless you declare them with "local".)

The next line creates a table that will be your new environment. It is local to the function/chunk you're executing in so it won't be trashed by anything else that runs now or later.

The third line is the beginnings of the magic. To understand it you're going to have to understand metamethods In essence, however, you're using Lua's metamagic to ensure that any global names that aren't defined in your soon-to-be function environment get resolved in the context of your old global environment. Basically it means if you use a name that's not in your function environment, Lua will automagically hunt in the global environment you used to have to find the name. (In a word: inheritance.)

The fourth line is where you get what you're looking for. Setfenv(1,...) means that this changes the environment for your current function. (You could use 2 for the calling function, 3 for the calling function's caller, etc. on up the line.) The second parameter is the table you just set up, complete with inheritance of the old behaviour. Your function is now executing in a new global environment. It has all the names and values of the old environment handy (including functions and that global variable "a" you put in). If, however, you WRITE to a name it will not overwrite the global state. It will overwrite your local copy of it.

Consider the following subsequent code:

a = 10
b = 20

What you have done now is made your function environment table look like this:

{a = 10, b=20}

Your "global" environment, in short, contains two variables only: a (value 10) and b (value 20). When you access "a" later you'll get your local copy with 10 -- the old global value stored in your metatable is shadowed now and is still set to 1 -- and if you access "b" you'll get 20, despite the original global state likely not even having a variable "b" to access. And you'll still be able to access all the functions, etc. you've defined before this point as well.


Edited to add test code to debug OP's problem.

I put the following code into "junk.lua":

a = 1
local newgt = {}
setmetatable(newgt, {__index = _G})
setfenv(1, newgt)
print(a)
a = 10
print(a)
print(newgt)

The output of it is as follows:

$ lua junk.lua
1
10
table: 0x976d040

This is using Lua 5.1.4. What is the output you're seeing?

JUST MY correct OPINION
That's the exact link I went to when researching the problem. You've explained it better though. I'll see if it solves the issue :)
Person
Hm. My program crashes when I try to set up something like that. Must be doing something wrong.
Person
Please ignore this as it turns out comments are not code-friendly. Edited answer instead.
JUST MY correct OPINION
No the code you specified works, it's getting it to work in context with what I posted in the OP.
Person
Actually, when I put that code into the command line for Lua I get a message saying bad argument #1 to setmetatable
Person
Ah, that's the problem. You *CAN'T* do any of this stuff in the Lua shell. (The article mentions this at the beginning.) This has to be done all in a single chunk, you see, and each line you enter in the Lua shell is its own chunk, if memory serves.Put it into a file and test it.
JUST MY correct OPINION
+1  A: 

The lua interpreter runs each line as its own chunk which means that locals have line scope, so the example code can't be run as-is. It either needs to be run all at once (no line breaks), without locals, or run in a do ... end block.

As to the question in the OP. If you want to share the exact same function (that is the same function at runtime) then the function needs to take the data as arguments. If, however, you are ok with using the same code but different (runtime) functions than you can use closures to hold the local/individual data.

local function make_counter()
    local count = 0

    return function ()
        local c = count
        count = count + 1
        return c
    end
end

c1 = make_counter()
c2 = make_counter()
c3 = make_counter()

print(c1())
print(c1())
print(c1())
print(c1())
print(c2())
print(c3())
print(c2())
print(c3())
print(c2())
print(c3())

Alternatively, you could play with the environment of the function each time it is called, but that will only work correctly for some cases (depends on what the internals of the function are).

Etan Reisner
Thanks. I actually went with a different approach and had each object have it's own state. But if I need to do something like this in the future I'll know where to look.
Person