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