lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


On Mon, 19 Mar 2007 05:26:38 -0600, Jose Marin <jose_marin2@yahoo.com.br> wrote:
 
> Hi.
 
 
> I'm studying Lua corotines, and looks very interesting.
 
> Is there any sample of use of Lua corotines in games?
 
Hi.
 
I use something like the following in my own projects. It was originally just supposed to be for cut scenes, but
has since spared me from lots of really messy state machines when scripting stuff like game mode logic and AI.
 
The update parameter in each operation (Wait, WaitForSignals, WaitUntil) is optional, but lets you do stuff while
the operation is in progress. You can return true from it for an early out, in which case the operation returns
nothing; otherwise the operation returns true when it finishes. The data parameter is just meant for passing
input to a predefined update function, where you can't trap needed upvalues.
 
Also, in the Wait operation, the "Pump" and "Timeline" instances are a message pump and Flash-ish timelines
that I use to set up and run optional events during the wait. They're a secondary thing here, though, so I won't
clutter this up with their implementations. Everything else should be fairly straightforward and at least make
sense with the examples.
 
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
local error = error
local GetLapse = GetLapse -- Returns the time lapse since the last frame
local IsCallable = IsCallable -- Argument is a function or has a __call metamethod
local ipairs = ipairs
local new = class.new         -- Instantiates a class of the type in argument #1; constructor takes arguments #2-N
local yield = coroutine.yield
 
local function Call (func, ...)
     if func then
        return func(...)
    end
end
 
local function Body (update, body, done, finish)
    while not done() do
        -- Update any user-defined logic. On a true result, do an early out.
        if Call(update) then
            Call(finish)
            return
        end
 
        -- Update any body logic.
        Call(body)
 
        -- Pause the operation until the next turn.
        yield()
    end
 
    -- Do any cleanup and report success.
    Call(finish, params)
    return true
end
 
function Wait (duration, builders, update, data)
    local pump, time, timelines = new("Pump"), 0, {}
 
    -- Build and install each timeline, providing the pump as a helper.
    for _, func in ipairs(builders or {}) do
        timelines[#timelines + 1] = new("Timeline")
        func(timelines[#timelines], pump, data)
    end
 
    -- Wait for the duration to pass.
    return Body(update and function()
        return update(time, duration, pump, data)
    end or nil, function()
        for _, timeline in ipairs(timelines) do
            timeline:Update(time - timeline:GetTime())
        end
        time = time + GetLapse()
    end, function()
        return time > duration
    end)
end
 
function WaitForSignals (signals, count, how, update, data)
    local func, test
 
    -- If the signals are not callable, build an indexing function. Build a table if
    -- nothing is provided.
    func, signals = IsCallable(signals) and signals or function(index)
        return signals[index]
    end, signals or {}
 
    -- Build the test function.
    if how == "any" then
        test = function()
            for index = 1, count do
                if func(index) then
                    return true
                end
            end
        end
    elseif how == "every" then
        test = function()
            for index = 1, count do
                if not func(index) then
                    return
                end
            end
            return true
        end
    else
        error("Unsupported test behavior")
    end
 
    -- Wait for the operation to succeed.
    return Body(update and function()
        return update(signals, count, data)
    end or nil, nil, test)
end
 
function WaitUntil (test, update, data)
    return Body(update and function()
        return update(data)
    end or nil, nil, function()
        return test(data)
    end)
end
 
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
A bunch of toy examples to show how it works. The caps/underscores were just used in the documentation to
distinguish from functions that actually exist. And of course, these must all be called within the body of a function
passed to coroutine.create/wrap.
 
-- Wait two seconds.
Wait(2)
 
-- Run the game for five minutes, but quit if everybody leaves the game.
if Wait(300, nil, function(time)
    if EVERYBODY_LEFT() then
        return true
    end
    UPDATE_GAME(time)
end) then
    SHOW_WINNERS()
end
GO_BACK_TO_LOBBY()
 
-- Wait five seconds; update objects during the second half.
Wait(5, {
    function(timeline, pump)
        timeline:Add(2.5, function()
            pump:Add(function()
                UPDATE_OBJECTS()
                return true
            end)
        end)
    end
}, function(_, _, pump)
    pump:Run()
end)
 
-- Wait for any opponent to show and then attack.
WaitForSignals(function(index)
    return me ~= players[index] and IS_IN_THE_OPEN(players[index])
end, #players, "any")
CHOOSE_AND_ATTACK(me, players)
 
-- Wait until every lever is turned, then open the door.
WaitForSignals(nil, #levers, "every", function(signals)
    for index, lever in ipairs(levers) do
        signals[index] = signals[index] or IS_BEING_TURNED(lever)
    end
end)
OPEN_DOOR()
 
-- Run until all 50 tiles are flipped. The creatures will be flipping tiles the whole time. Since
-- they also do this before the WaitForSignals call, the tiles set is passed as input, instead of
-- letting WaitForSignals create a temporary table.
local tiles = {}
local function UpdateCreatures ()
    for creature in GET_CREATURES() do
        local index = GET_TILE(creature)
        tiles[index] = not tiles[index]
    end
end
Wait(10, nil, UpdateCreatures ) -- Mill around for 10 seconds
WaitForSignals(tiles, 50, "every", function(signals, count)
    local index = math.random(count)
    signals[index] = not signals[index]
 
    -- Creatures wander over nearby tiles, flipping them.
    UpdateCreatures()
end)
 
-- Wait for the light, then go.
WaitUntil(SEMAPHORE_IS_GREEN)
GO()
 
-- Wait for the hunter to reach its prey, then attack. Look for another target if it gets away.
WaitUntil(PREY_IS_IN_RANGE, function()
    if PREY_GOT_AWAY() then
        return true
    end
    MOVE_HUNTER_CLOSER()
end) and ATTACK() or GO_HUNTING()
 
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
Similar:

http://lua-users.org/lists/lua-l/2005-03/msg00109.html

The last few Game Programming Gems books are probably worth a look, too.


 
____________________________________________________________________________________
Food fight? Enjoy some healthy debate 
in the Yahoo! Answers Food & Drink Q&A.
http://answers.yahoo.com/dir/?link=list&sid=396545367