as per XedurR: you probably won’t find anything ready-to-go out-of-the-box.
you’ve already started listing the various “UI support” routines you’d want available, so just begin implementing them, one at a time. you may find you need a couple more as you go along, just implement them if/as the need occurs.
the trickier part will be the control logic. essentially you need to guide the user through a number of “steps” (and you should persist this progress in case player exits the game only part-way through tutorial, so that you can resume), where you write code to accomplish: “is user at this step? if so, ready to see next hint? if so, display hint. wait until user has properly interacted. advance.”
that could all be accomplished with a big state machine. (or might be a good place to use coroutines) super-rough example:
local TSM = {} -- the tutorial state machine function TSM:setState(fn) self.state = fn end function TSM:performState() if (self.state) then self:state() end end function TSM:prepStep1() -- check if user should even be here, or if already beyond this step if (persistedTutorialStep \> 1) then -- and step 2 will auto-advance to step 3 if needed, and so on self:setState(self.prepStep2) end -- we DO belong on step 1, but are we still waiting for preconditions? -- i can't give you exact code here, you must figure out how/when to start if (StatsStuff:getTotalInteractionCount() \> 0) then self:setState(self.showStep1) end end function TSM:showStep1() UIStuff:showTutorialOverlayMessage("Keep clicking the cookie!") -- in this simple case we can immediately advance state self:setState(self.waitStep1) end function TSM:waitStep1() -- has user done the right thing yet? if (StatsStuff:getCookieClickCount() \> 10) then UIStuff:hideTutorialOverlayMessage() -- remember user's progress: persistedTutorialStep = 2 -- begin step 2 self:setState(self.prepStep2) end end -- step 2's (et al) prep/show/wait will resemble step 1's -- start it up: TSM:setState(TSM.prepStep1) -- from game loop, call it: -- (maybe just once per second is enough, depending on how "responsive" it needs to be) TSM:performState()
hth