Использование Lua для определения поведения NPC в игровом движке C ++ - PullRequest
4 голосов
/ 30 мая 2011

Я работаю над игровым движком на C ++, используя Lua для поведения NPC. Я столкнулся с некоторыми проблемами во время проектирования.

Для всего, что требует более одного фрейма для выполнения, я хотел использовать связанный список процессов (которые являются классами C ++). Итак, это:

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

создаст объект GotoProcess, у которого будет указатель на объект SayProcess, у которого будет указатель на объект MowLawnProcess. Эти объекты будут созданы мгновенно, когда появится NPC, дальнейшие сценарии не требуются. Первый из этих объектов будет обновляться каждый кадр. Когда он будет завершен, он будет удален, а следующий будет использован для обновления. Я расширил эту модель с помощью ParallelProcess, который будет содержать несколько процессов, которые обновляются одновременно.

Я обнаружил некоторые серьезные проблемы. Посмотрите на этот пример: я хочу, чтобы персонаж прошел к point_a, а затем впал в бешенство и просто атаковал любого, кто приближается. Сценарий будет выглядеть так:

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

Это не сработает с моим дизайном. Прежде всего, символьная переменная будет установлена ​​в начале, когда персонаж даже не начал переходить к точке point_a. Затем скрипт продолжит добавлять AttackProcesses навсегда из-за цикла while.

Я мог бы реализовать WhileProcess для цикла и оценивать скрипт построчно. Я сомневаюсь, что это увеличит читабельность кода.

Есть ли другой общий подход, который я не придумал для решения этой проблемы?

Ответы [ 3 ]

5 голосов
/ 31 мая 2011

Я думаю, что ваш подход теряет многие преимущества использования языка сценариев. Он будет порваться как с условиями, так и с циклами.

С сопрограммами все, что вам действительно нужно сделать, это:

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

goto, say и mowLawn возвращаются немедленно, но инициируют действие в C ++. Когда C ++ завершает эти действия, он вызывает coroutine.resume (npc_behaviour)

Чтобы избежать всех выходов, вы можете скрыть их в функциях goto и т. Д. Или сделать то, что я делаю, с функцией waitFor, например:

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

activeEvents - это просто таблица Lua, которая отслеживает все, что в данный момент выполняется, поэтому goto добавит идентификатор в таблицу при запуске и удалит ее по завершении, а затем при каждом завершении действия все сопрограммы активируются, чтобы проверить, завершено ли ожидаемое действие.

1 голос
/ 30 мая 2011

Вы смотрели на Конечные автоматы ? На вашем месте я бы не использовал связанный список, а только стек. Я думаю, что конечный результат тот же.

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))

Последовательное выполнение действий даст что-то вроде:

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 и другие функции будут выглядеть так:

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

Таким образом, каждый раз, когда вы stack:push(action:new(berserk, character)) будете каждый раз атаковать другую цель.

Я также сделал вам реализацию стека и действия в объекте lua здесь . Не пробовал это. Может быть жужжит, как ад. Удачи в игре!

0 голосов
/ 30 мая 2011

Я не знаю причин, по которым вы разрабатываете дизайн, и, возможно, существуют более простые / идиоматические способы.

Однако, может ли написать собственный процесс "цикл", который каким-то образом получит функцию в качестве аргумента?

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

Поскольку у Lua есть замыкания (см. здесь, ввручную ), функция может быть присоединена к вашему LoopProcess, и вы вызываете эту же функцию в каждом кадре.Вам, вероятно, придется реализовать свой LoopProcess, чтобы он никогда не удалялся из списка процессов ...

Если вы хотите, чтобы ваш цикл мог останавливаться, он немного сложнее;вам нужно будет передать другую функцию, содержащую тестовую логику (и снова вам, LoopProcess, придется вызывать это каждый кадр или что-то в этом роде).

Надеюсь, я понял вашу проблему ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...