local function cutscene(player, npc) player:goTo(npc) if player:hasCompleted(quest) then npc:say("You did it!") delay(0.5) npc:say("Thank you") else npc:say("Please help me") end end 

while game:isRunning() do processInput() dt = clock.delta() update(dt) render() end player:goTo(npc) npc:say("You did it!") delay(0.5) npc:say("Thank you") delay function, you cannot call the same sleep - it will look as if the game is frozen. function update(dt) if cutsceneState == 'playerGoingToNpc' then player:continueGoingTo(npc) if player:closeTo(npc) then cutsceneState = 'npcSayingYouDidIt' dialogueWindow:show("You did it!") end elseif cutsceneState == 'npcSayingYouDidIt' then if dialogueWindow:wasClosed() then cutsceneState = 'delay' end elseif ... ... -- ... end end update function is called for the current action, which allows us to process the input and render the game, even if the action has been performed for a long time. After the action is completed, we proceed to the next.DelayAction implemented: -- function DelayAction:initialize(params) self.delay = params.delay self.currentTime = 0 self.isFinished = false end function DelayAction:update(dt) self.currentTime = self.currentTime + dt if self.currentTime > self.delay then self.isFinished = true end end ActionList:update function looks like this: function ActionList:update(dt) if not self.isFinished then self.currentAction:update(dt) if self.currentAction.isFinished then self:goToNextAction() if not self.currentAction then self.isFinished = true end end end end function makeCutsceneActionList(player, npc) return ActionList:new { GoToAction:new { entity = player, target = npc }, SayAction:new { entity = npc, text = "You did it!" }, DelayAction:new { delay = 0.5 }, SayAction:new { entity = npc, text = "Thank you" } } end -- ... - actionList:update(dt) someFunction({ ... }) can be made like this: someFunction{...} . This allows you to write DelayAction:new{ delay = 0.5 } instead of DelayAction:new({delay = 0.5}) .DelayAction to make writing cutscents easier. local function cutscene(player, npc) player:goTo(npc) if player:hasCompleted(quest) then npc:say("You did it!") delay(0.5) npc:say("Thank you") else npc:say("Please help me") end end coroutine.yield to resume - coroutine.resume . A simple example: local function f() print("hello") coroutine.yield() print("world!") end local c = coroutine.create(f) coroutine.resume(c) print("uhh...") coroutine.resume(c) hello uhh ... world
coroutine.create . After this call, the korutin does not start executing. For this to happen, we need to run it with coroutine.resume . Then the function f is called, which writes “hello” and pauses itself using coroutine.yield . This is similar to return , but we can resume executing f with coroutine.resume .coroutine.yield , then they will be the return values of the corresponding call to coroutine.resume in the “main thread”. local function f() ... coroutine.yield(42, "some text") ... end ok, num, text = coroutine.resume(c) print(num, text) -- will print '42 "some text"' ok is a variable that allows us to find out the status of cortina. If ok is true , then with korutina everything is fine, no errors occurred inside. The return values that follow it ( num , text ) are the very arguments that we passed to yield .ok is false , then something went wrong with Corutina, for example, the error function was called inside it. In this case, the second return value will be an error message. An example of a korutina in which an error occurs: local function f() print(1 + notDefined) end c = coroutine.create(f) ok, msg = coroutine.resume(c) if not ok then print("Coroutine failed!", msg) end Coroutine failed! input: 4: attempt to perform arithmetic on a nil value (global 'notDefined')
coroutine.status . Korutina can be in the following states:coroutine.status was called from the corutina itselfAction class will look like in the new system: function Action:launch() self:init() while not self.finished do local dt = coroutine.yield() self:update(dt) end self:exit() end update function of the action is called until the action has completed. But here we use cortuins and do a yield in each iteration of the game cycle ( Action:launch is called from some cortina). Somewhere in the update game cycle, we resume the execution of the current cutscete like this: coroutine.resume(c, dt) function cutscene(player, npc) player:goTo(npc) npc:say("You did it!") delay(0.5) npc:say("Thank you") end -- - ... local c = coroutine.create(cutscene, player, npc) coroutine.resume(c, dt) delay function is implemented: function delay(time) action = DelayAction:new { delay = time } action:launch() end DelayAction implemented like this: -- Action - DelayAction local DelayAction = class("DelayAction", Action) function DelayAction:initialize(params) self.delay = params.delay self.currentTime = 0 self.isFinished = false end function DelayAction:update(dt) self.currentTime = self.currentTime + dt if self.currentTime >= self.delayTime then self.finished = true end end Action:launch function: function Action:launch() self:init() while not self.finished do local dt = coroutine.yield() -- the most important part self:update(dt) end self:exit() end while , which runs until the action completes. It looks like this:
goTo function: function Entity:goTo(target) local action = GoToAction:new { entity = self, target = target } action:launch() end function GoToAction:initialize(params) ... end function GoToAction:update(dt) if not self.entity:closeTo(self.target) then ... -- , AI else self.finished = true end end WaitForEventAction class: function WaitForEventAction:initialize(params) self.finished = false eventManager:subscribe { listener = self, eventType = params.eventType, callback = WaitForEventAction.onEvent } end function WaitForEventAction:onEvent(event) self.finished = true end update method. It will be executed (although it will not do anything ...) until it receives an event with the required type. Here is the practical application of this class - the implementation of the say function: function Entity:say(text) DialogueWindow:show(text) local action = WaitForEventAction:new { eventType = 'DialogueWindowClosed' } action:launch() end local answer = girl:say('do_you_love_lua', { 'YES', 'NO' }) if answer == 'YES' then girl:setMood('happy') girl:say('happy_response') else girl:setMood('angry') girl:say('angry_response') end 
say function is slightly more complex than the one I showed earlier. It returns the player's choice in the dialogue, but it is not difficult to implement For example, WaitForEventAction can be used WaitForEventAction , which will catch the PlayerChoiceEvent event and then return the player's choice, information about which will be contained in the event object. girl:say("Kill that monster!") waitForEvent('EnemyKilled') girl:setMood('happy') girl:say("You did it! Thank you!") 
function followPath(monster, path) local numberOfPoints = path:getNumberOfPoints() local i = 0 -- while true do monster:goTo(path:getPoint(i)) if i < numberOfPoints - 1 then i = i + 1 -- else -- i = 0 end end end 
while true ) inside followPath is not really infinite. function cutscene(cat, girl, meetingPoint) local c1 = coroutine.create( function() cat:goTo(meetingPoint) end) local c2 = coroutine.create( function() girl:goTo(meetingPoint) end) c1.resume() c2.resume() -- waitForFinish(c1, c2) -- cat:say("meow") ... end waitForFinish function, which is a wrapper around the WaitForFinishAction class, which can be implemented as follows: function WaitForFinishAction:update(dt) if coroutine.status(self.c1) == 'dead' and coroutine.status(self.c2) == 'dead' then self.finished = true else if coroutine.status(self.c1) ~= 'dead' then coroutine.resume(self.c1, dt) end if coroutine.status(self.c2) ~= 'dead' then coroutine.resume(self.c2, dt) end end Source: https://habr.com/ru/post/427135/
All Articles