Jump to content

Coroutine enabled state machine


Rick
 Share

Recommended Posts

[EDIT]

Added better error checking. If an error happens in a coroutine Leadwerks doesn't report on it and the game doesn't stop, but I checked for an error and get the message and write it to console/log and fail the app saying to check out the console so it acts more like a normal error instead of it just not working.

 

 

I'm using this for the competition so thought others might find it handy.

Coroutines can be handy to allow you to do tasks over time but do them sequentially in 1 function. For example I could code something like the below where each function is done sequentially and we'd see/hear the results on the screen. If you did the below inside UpdateWorld() you'd only see the end result after that 1 iteration.

function MyFunction()
	MoveToPoint()
	PlaySound()
	TypeText()
end

A state machine is a way to control flow between different states (or ideas). When you combine the 2 (coroutines and state machine) you can get some pretty powerful functionality. With this, each state has 3 functions. Enter/Leave/Update. Enter and Leave are coroutine enabled and Update() is like UpdateWorld() where it runs each frame.

If you want more ideas on how to use feel free to ask.

 

if StateManager ~= nil then return end

StateManager = {}

function StateManager:Create(container)
	local obj = {}

	obj.container = container
	obj.currentState = ""
	obj.nextState = ""
	obj.coLeave = nil
	obj.coEnter = nil

	for k,v in pairs(StateManager) do
		obj[k] = v
	end

	return obj
end

function StateManager:changeState(state)
	self.nextState = state
	
	-- if this is the first state then don't call a leave state
	if self.currentState ~= "" then
		local onLeaveMethod = self.container[self.currentState.."_Leave"]
		if onLeaveMethod ~= nil then
			self.coLeave = coroutine.create(onLeaveMethod)
		end
	end
	
	local onEnterMethod = self.container[self.nextState.."_Enter"]
	if onEnterMethod ~= nil then
		self.coEnter = coroutine.create(onEnterMethod)
	end
	
	self.currentState = self.nextState
end

function StateManager:update()
	if self.coLeave ~= nil then
		if coroutine.status(self.coLeave) ~= "dead" then
			local ret, err = coroutine.resume(self.coLeave, self.container)
      		if ret == false then
        		System:Print("Error: "..err)
        		error("Check console/log for error")
        	end
			return
		else
			self.coLeave = nil
		end
	end
	
	if self.coEnter ~= nil then
		if coroutine.status(self.coEnter) ~= "dead" then
			local ret, err = coroutine.resume(self.coEnter, self.container)
      		if ret == false then
        		System:Print("Error: "..err)
        		error("Check console/log for error")
        	end
			return
		else
			self.coEnter = nil
		end
	end
	
	-- call the update if we have one defined and aren't doing an enter/leave
	local onUpdateMethod = self.container[self.currentState.."_Update"]
	if onUpdateMethod ~= nil then
		onUpdateMethod(self.container)
	end
end

 

  • Like 1
Link to comment
Share on other sites

I suppose I should give usage :). Often times before a level you may want to do a small something, then give control to the player, then at the end of the level do a little something else. This would work perfect for that situation. You'd have 3 states, PreGame, PlayGame, & PostGame. Each one has an Enter/Update/Leave for you to do things in and because Enter/Leave are coroutine enabled you can show progress while staying inside that function. You can wait 1 second, increase a number over time that's tied to the UI so it shows it going up over time, then wait then do whatever.



function Script:Start()
	self.stateMgr = StateManager:Create(self)

	-- when you change states the state manager looks for functions with that state name and _Enter() _Leave() _Update() and if found will call them.
	self.stateMgr:changeState("PreGame")
	
	self.text = ""
end

function Script:PreGame_Enter()
	WaitForSeconds(2)	-- these functions have yield in them
	
	self.text = "Ready"
	self:PlayBeepSound()
	
	WaitForSeconds(1)
	
	self.text = "Set"
	self:PlayBeepSound()
	
	WaitForSeconds(1)
	
	self.text = "Go!!"
	self:PlayBeepSound()
	
	self.stateMgr:changeState("PlayGame")
end

function Script:PreGame_Leave()
	-- not doing anything here but showing you can have it
end

function Script:PreGame_Update()
	-- don't need this but showing it anyway
end

function Script:PlayGame_Enter()
end

function Script:PlayGame_Update()
	-- do your game code here and when finsihed call self.stateMgr:changeState("PostGame")
end

function Script:PlayGame_Leave()
	-- maybe remove control?
end

function Script:PostGame_Enter()
	-- tween in your end game UI here and do some counting that updates the UI
	-- changing states could happen from a button click event function perhaps to continue
end

-- don't need to define Update()/Leave() for a function if you don't want to

function Script:PostRender(ctx)
	ctx:DrawText(self.text, 0, 0)
end

function Script:UpdateWorld()
	self.stateMgr:update()
end

 

function WaitForSeconds(interval)
	local tm = Time:GetCurrent()
	
	while Time:GetCurrent() < tm + (interval * 1000) do
		coroutine.yield()
	end
end

 

Link to comment
Share on other sites

  • 1 month later...

I added a script to the workshop which you can get from the LE editor Workshop->Browse Workshop. I also did 3 video tutorials on usage from beginner to more advanced coroutine usage within the states.

Don't ask me why the 2nd video link didn't show up in this post. No clue. :)

http://steamcommunity.com/sharedfiles/filedetails/?id=1338038813

 

https://www.youtube.com/watch?v=AXiJ0DhDV7w

 

 

  • Like 3
Link to comment
Share on other sites

12 minutes ago, gamecreator said:

Middle one (the one that's just a link) says it's unavailable.  Never clicked it before.  I guess that explains why it can't embed it.  The other two are ok.

Hmm it plays for me. I'll have to dig into some youtube menus where I can't find anything.

 

OK, it was private. Changed to public. Try now please.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...