Using Lua to determine NPC behavior in a C ++ game engine

I am working on a C ++ game engine using Lua for NPC behavior. I encountered some problems during development.

For anything that requires more than one frame to execute, I wanted to use a linked list of processes (which are C ++ classes). So:

goto(point_a) say("Oh dear, this lawn looks really scruffy!") mowLawn() 

will create a GotoProcess object that will have a pointer to a SayProcess object that will have a pointer to a MowLawnProcess object. These objects will be instantly created, when the NPC is spawned, no further scripts are required. The first of these objects will be updated every frame. At the end, it will be deleted, and the next will be used for updating. I have expanded this model with ParallelProcess, which will contain several processes that are updated at the same time.

I found some serious problems. Look at this example: I want the character to go to point_a, and then go into a rage and just attack anyone who is approaching. The script will look like this:

 goto(point_a) while true do character = getNearestCharacterId() attack(character) end 

That would not work with my design. First of all, the symbol variable will be set at the beginning, when the character has not even started going to point_a. Then the script will continue to add AttackProcesses forever due to the while loop.

I could implement WhileProcess for the loop and evaluate the script line by line. I doubt this will improve code readability.

Is there any other general approach that I did not think about to solve this problem?

+4
source share
3 answers

I think the approach you give is losing many of the benefits of using a scripting language. It will be divided into conditional as well as into cycles.

With coroutines, all you really need to do is:

 npc_behaviour = coroutine.create( function() goto(point_a) coroutine.yield() say("Oh dear, this lawn looks really scruffy!") coroutine.yield() mowLawn() coroutine.yield() end ) 

go, say, and mowLawn come back immediately, but initiate an action in C ++. When C ++ completes these steps, it calls coroutine.resume (npc_behaviour)

To avoid all the lessons, you can hide them inside goto functions, etc. or do what I do, that there is a waitFor function, for example:

 function waitFor(id) while activeEvents[id] ~= nil do coroutine.yield() end end 

activeEvents is just a Lua table that keeps track of everything that is happening at the moment, so goto will add an identifier to the table when it starts and delete it when it finishes, and then every time the action completes, all coroutines are activated to check Is the action they are waiting for completed.

+5
source

Have you looked at state machines ? If I were you, I would not use a linked list except the stack. I think the end result is the same.

 stack:push(action:new(goto, character, point_a)) stack:push(action:new(say, character, "Oh dear, this lawn was stomped by a mammoth!")) stack:push(action:new(mowLawn, character)) 

Performing the actions sequentially will give something like:

 while stack.count > 0 do -- do all actions in the stack action = stack:peek() -- gets the action on top of the stack while action.over ~= true do -- continue action until it is done action:execute() -- execute is what the action actually does end stack:pop() -- action over, remove it and proceed to next one end 

goto and other functions will look like this:

 function goto(action, character, point) -- INSTANT MOVE YEAH character.x = point.x character.y = point.y action.over = true -- set the overlying action to be over end function attack(action, character, target) -- INSTANT DEATH WOOHOO target.hp = 0 action.over = true -- attack is a punctual action end function berserk(action, character) attack(action, character, getNearestCharacterId()) -- Call the underlying attack action.over = false -- but don't set action as done ! end 

Therefore, whenever you stack:push(action:new(berserk, character)) , it will attack another target each time.

I also made you a stack and implementation of the action in the lua object here . I have not tried it. Maybe damned like hell. Good luck with the game!

+1
source

I donโ€™t know the reasons why you are building a design, and there may be simpler / more idiomatic ways.

However, would you write a custom "loop" that would somehow perform the function, since this argument does the trick?

  goto(point_a) your_loop(function () character = getNearestCharacterId() attack(character) end) 

Since Lua has closures (see here in the manual ), a function can be attached to your โ€œLoopProcessโ€, and you call it functions on every frame. You may have to implement LoopProcess so that it is never removed from the process list ...

If you want your cycle to stop, it's a little more complicated; you will need to pass another function containing the test logic (and, again, LoopProcess will have to call this frame or something else).

I hope I understand your problem ...

0
source

All Articles