Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411274 Posts in 69323 Topics- by 58380 Members - Latest Member: bob1029

March 28, 2024, 03:40:44 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)Lua scripting inside ActionScript3
Pages: [1]
Print
Author Topic: Lua scripting inside ActionScript3  (Read 2199 times)
deWiTTERS
Level 1
*


deWiTTERS


View Profile WWW
« on: August 22, 2014, 06:18:20 AM »

I hope to find some LUA experts here Beer!.

So I want to extend my ActionScript3 game engine with some LUA based scripting, using lua-alchemy. No real issues there.

The thing is, I want my scripts to have blocking function calls, which in my opinion is the most intuitive for gameplay scripters. For example:

Code:
character.walk.go_to(3, 4)
character.talk.say("Take a look in this book, the prophecy is clear")
if hero.inventory.has('prophecy book') then
    hero.talk.say("I already have that book")
    character.walk.go_to(0, -6)
    door1_house.latch.open()
    character.walk.go_to(0, -1)
else
    hero.talk.say("Let me see!")
end

So each of those statements happens in sequence.

Let's take the example of the character.walk.go_to() function. Calling this function from LUA starts a function implemented in AS3. But this last AS3 function shouldn't be blocking, because the rest of my game engine needs to keep running. So it basically has to be suspended until the character reaches his destination. At that point the LUA script is resumed.

I looked into coroutines and yield, and if my entire game engine was written in Lua, creating a lua scheduler would be the solution. But I can't see how I can make the bridge to AS3 (and its runtime). Maybe this is a limitation in the AS3-lua interface (if so, please let me know how you would do it using C++).

Anyway, I hope my problem is clear. Any suggestions?
Logged

Geti
Level 10
*****



View Profile WWW
« Reply #1 on: August 22, 2014, 06:35:39 AM »

It sounds like you want to implement a state machine for actors in your script rather than actually call functions. I'm too tired to explain how you might accomplish this from a lua script - coroutines would be one way if you can actually use them (yield out of walk.go_to into the engine, resume at completion) but it'd possibly be simpler to go from a script/config representation (not necessarily lua) to some internal representation and run the state machine from there rather than jumping language barriers and having to make sure each lua function is implemented and set up correctly.
Logged

deWiTTERS
Level 1
*


deWiTTERS


View Profile WWW
« Reply #2 on: August 22, 2014, 09:24:08 AM »

Yes, yielding out of walk_to() would be great, but there are 2 problems: In AS3, I can't seem to lua_yield within AS3, and I can't see whether a script was yieled or just ended. And if yielded, I'm certainly not able to resume it in AS3.

Your answer made it clear to me that I should first take a look at how to use LUA within C/C++, and then go to AS3. As you can see here, the LUA API for AS3 is very, *very* limited Cry.

I'll figure things out with C first, and then see which things I need to extend in AS3's lua-alchemy. Oh joy!

Thanks for the reply!
Logged

BorisTheBrave
Level 10
*****


View Profile WWW
« Reply #3 on: August 24, 2014, 03:08:24 PM »

You don't need lua_yield unless you have lua code calling "native" code, and you wish to suspend halfway through that code (which you may not need). If you are suspending in Lua code, you can just call coroutine.yield().

IOW I think you can code something like this, which avoids having to *yield* from AS3:
Code:
talk.say = (function(text)
-- Tell A3 engine to display text, and give it this coroutine for resuming
engine.say_text(text, coroutine.running())
-- Suspend
coroutine.yield()
end)

Code:
class Engine
{
  void say_text(text:String, coro:*)
  {
    // Actually do some text display stuff
    // Then store the coroutine for resumption when the user clicks next
    this.next_coro = coro;
  }

  void onNextClick()
  {
    lua.callGlobal("coroutine.resume", this.next_coro);
  }
}
Logged
Ashaman73
Level 0
***



View Profile WWW
« Reply #4 on: August 26, 2014, 10:16:42 PM »

Blocking calls are kind of ugly when it comes down to AI (IMHO). To archive such a behavior, look into behavior trees. I've implemented my AI in Lua with behavior tree, a lot of other games uses BHTs. Here's a good overview of BHTs of Spore from Chris Hecker: http://chrishecker.com/My_liner_notes_for_spore/Spore_Behavior_Tree_Docs

It allows you asynchronous 'scripting', eg by a simple 'walk' sequence:
Code:
sequence (walk)
- send path finding request
- wait until path as been calculated
- start move action along path
- wait until target has been reached
The BHT code will execute the tree like this
Code:
1 frame: sequence->send path.. (=>done)
2 frame: sequence->wait until.. (=>still processing)

..
12 frame: sequence->wait until.. (=>done)
13 frame: sequence->start move action.. (=>done)
14 frame: sequence->wait until target .. (=>still processing)
..
20 frame: sequence->wait until target .. (=>done)
This way you dont need any threads, yield whatever.
Logged

nikki
Level 10
*****


View Profile
« Reply #5 on: August 27, 2014, 03:21:49 AM »

@ Ashaman73

that's a very interesting read cool!,
could I see your implementation of that behavior tree, it would help immensely with understanding it.
Logged
Boreal
Level 6
*


Reinventing the wheel


View Profile
« Reply #6 on: August 27, 2014, 10:47:43 AM »

I agree with Geti.  I think instead of scripting this directly (which is very verbose and overengineered) I would instead make it configurable through a more declarative language/style like JSON (never XML!) or even through a simple GUI tool.
Logged

"In software, the only numbers of significance are 0, 1, and N." - Josh Barczak

magma - Reconstructed Mantle API
BorisTheBrave
Level 10
*****


View Profile WWW
« Reply #7 on: August 27, 2014, 02:00:05 PM »

I think the scripting is the perfect solution to the original problem. JSON is not pretty at making things like scripts (don't believe me? try to redo dewitter's example in JSON style). And you'd have to reimplement all sorts of useful things for yourself like control flow, variables, etc.

For AIs, the answer might be different, but it doesn't sound like that is what OP is going for.
Logged
Ashaman73
Level 0
***



View Profile WWW
« Reply #8 on: August 27, 2014, 10:35:55 PM »

@ Ashaman73

that's a very interesting read cool!,
could I see your implementation of that behavior tree, it would help immensely with understanding it.
Sry, but I like to keep my source closed. Nevertheless, here is a compact overview/example:

The tree is build up of nodes like this one:
Code:
node =
{
   -- returns true, if this node is ready to be executed
   accept = function(...),
   -- only called once
   enter = function(...),
   -- return true, if finished and succeed, false on failure and nil if it is still processing
   update = function(...),
   -- only called once
   exit = function(...),
   child_nodes = {...}
}

You always hold one valid current path, but you always evaluate the tree depending on the path. A simple example (pseudo-code quality   Wink )
Code:
tree =
{
  flee =
  {
     -- accept this node ?
     accept = function(_node , _obj)
        return _obj.health<50
     end,
     -- enter this node, add to path
     enter = function(_node, _obj, _path, _depth)
       -- add this node to the path
       _path.nodes[_depth] = _node
       -- do some other stuff
       _obj.startSound("scream")
     end,
     -- update this node
     update = function(_node, _obj, _path,depth)
        local result = nil

        -- priority behavior
        for i,child_node in pairs(_node.child_nodes) do
          -- same node, just execute, dont call accept again
          if child_node==_path.nodes[depth+1] then
             result = child_node.update(child_node, _obj, _path,depth+1)
             -- done
             break
          else
             -- node on path has not been visited yet, therefor this node seems to have a higher priority
             -- check if a node with a higher priority needs to be executed
             if child_node.accept(child_node,_obj)==true then
                -- ok, exit all nodes
                _path.nodes[depth+1].exit(__path.nodes[depth+1], _obj, _path, _depth+1)

                -- enter new node
                child_node.enter(child_node, _obj, _path, _depth+1)

                -- done, node will be updated next frame
                break
             end  
          end
        end

        -- handle result, nil=>still running, true/false=succeed/failed
        if result~=nil then
          -- call exit
          _node.exit(_node, _obj, _path, _depth)
        end
        return result
      end
      -- exit this node, remove from path
      exit = function(_node, _obj, _path, _depth)
        -- ensure to clear path behind depth !
        if _path.nodes[_depth+1]~=nil then
          _path.nodes[_depth+1].exit(_path.nodes[_depth+1], _obj, _path, _depth+1)
        end
        -- remove this node from the path
        _path[_depth] = nil

        -- stop screaming !!
        _obj.stopSound()
      end,
      -- child nodes, top nodes have higher priority
      child_nodes =
      {
         run_to_ally = { accept= .. ally near => run to ally.. },
         run_to_shelter = {accept= .. shelter near => run to shelter..},
         run_in_random_direction = { accept = ..always ..},
      }
   }
}
This way you only need some simple template nodes like
- priority (see above)
- sequence (execute all nodes in a sequence)
- choice (choose a child node depending on some stimuli/properties)
- loop (loop through all nodes)

Some special nodes
- scan (scan the environment for other objects, eg enemies)
- move (calculate path and move to target)
- action (interact with environment/objects)

Important is, that you only updates a single node once per frame, thought you can update more than one node on the path.

BHT helps a lot to manage more complex behavior, my code alone has several thousands lines of code only for BHT behavior and is purely data driven to support multi-core AI (which is really hard with Lua).

I hope it gives you some insight, happy coding  Coffee
Logged

nikki
Level 10
*****


View Profile
« Reply #9 on: August 28, 2014, 12:34:29 PM »

hey cool thanks!
Logged
deWiTTERS
Level 1
*


deWiTTERS


View Profile WWW
« Reply #10 on: August 29, 2014, 02:04:57 AM »

@BorisTheBrave: Thanks! This was exactly what I was looking for!  Toast Left Smiley Toast Right

This way you dont need any threads, yield whatever.

Your wait function still needs to be able to suspend and resume once the condition has met, so you just shifted the problem to the wait() function.

Blocking or async calls both have advantages and disadvantages. Doing async with blocking calls can be achieved by starting a new thread/coroutine. The same as blocking calls can be done with async functions by calling those wait() methods.

For my scripts, async isn't isolated and probably comes with multiple calls, so I decided to go for blocking calls. It also looks more like a movie script when you read it.
Logged

deWiTTERS
Level 1
*


deWiTTERS


View Profile WWW
« Reply #11 on: August 29, 2014, 02:09:15 AM »

I agree with Geti.  I think instead of scripting this directly (which is very verbose and overengineered) I would instead make it configurable through a more declarative language/style like JSON (never XML!) or even through a simple GUI tool.

Well, my initial plan was to create a user friendly GUI for this. But I want to roll out as much functionality as fast as possible, and therefore I'm going for LUA. Once it's in place, LUA itself will provide plenty of functionality for game designers to create whatever they want. Exposing new functions to my engine also doesn't take that much effort in this case.
Logged

BorisTheBrave
Level 10
*****


View Profile WWW
« Reply #12 on: August 30, 2014, 02:09:21 AM »

@BorisTheBrave: Thanks! This was exactly what I was looking for!  Toast Left Smiley Toast Right

Great, I'm glad you got what I'm saying. One extra twist I'll add is that that example lets you suspend, but it still only has a single thread of execution. Once you want multiple threads, you'll need a primitive Lua scheduler:

Code:
pending_coros = {}
def run()
  while table.getn(pending_coros) > 0 do
    next_coro = table.remove(pending_coros, 1)
    coroutine.resume(next_coro)
  end
end
-- Use this instead of coroutine.resume
def resume(coro)
  table.insert(pending_coros, coro)
  run()
end
-- Launch a new "thread"
def async(f)
  table.insert(coroutine.create(f))
end

This is the simplest possible scheduler, which assumes you are doing all the work of scheduling a resume prior to calling coroutine.yield, just as in the previous example. You can fancy it up by adding the ability to wait on previously launched threads, error handling, whatever.
Logged
Ashaman73
Level 0
***



View Profile WWW
« Reply #13 on: August 31, 2014, 08:42:43 PM »


Your wait function still needs to be able to suspend and resume once the condition has met, so you just shifted the problem to the wait() function.
Nope, the wait detects if the action has been finished and returend immediatly (return nil) when the action is still in progress. The effect will be, that the wait function will be called next frame again.

Yes, this sounds like active checking, but this is the real benefit of BHTs. It can react to changes in your environment and/or entity. Eg. you have a sleep-action and want to block the entity AI for the duration of the sleep, now a hord of orcs enters your sleeping chamber, what happens ? In your blocking-AI you need an additional secondary system to reactive the entity, whereas the BHT would detect the new situtaion itself and will react by waking the entity (from the noise).
Logged

Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic