local gfx = playdate.graphics local AnnouncementFont = playdate.graphics.font.new("fonts/Roobert-20-Medium.pft") local AnnouncementTransitionMs = 300 local AnnouncerMarginX = 26 local AnnouncerAnimatorInY = playdate.graphics.animator.new(AnnouncementTransitionMs, -70, 0, playdate.easingFunctions.outBounce) local AnnouncerAnimatorOutY = playdate.graphics.animator.new(AnnouncementTransitionMs, 0, -70, playdate.easingFunctions.outQuint) ---@class Announcer ---@field textQueue string[] ---@field animatorY pd_animator Announcer = {} function Announcer.new() return setmetatable({ textQueue = {}, animatorY = AnnouncerAnimatorInY, }, { __index = Announcer }) end local DurationMs = 2000 function Announcer:popIn() self.animatorY = AnnouncerAnimatorInY self.animatorY:reset() playdate.timer.new(DurationMs, function() self.animatorY = AnnouncerAnimatorOutY self.animatorY:reset() -- If this popIn() call was inside a timer, successive messages would be -- allowed to transition out. However, the Out animation, shortly followed by -- a new message popping in, is actually *more* jarring than the interrupt. if #self.textQueue ~= 1 then self:popIn() table.remove(self.textQueue, 1) else playdate.timer.new(AnnouncementTransitionMs, function() table.remove(self.textQueue, 1) end) end end) end ---@param text string function Announcer:say(text) self.textQueue[#self.textQueue + 1] = text if #self.textQueue == 1 then self:popIn() end end function Announcer:draw(x, y) if #self.textQueue == 0 then return end x = x - 5 -- Infield center is slightly offset from screen center local originalDrawMode = gfx.getImageDrawMode() local width = math.max(150, (AnnouncerMarginX * 2) + AnnouncementFont:getTextWidth(self.textQueue[1])) local animY = self.animatorY:currentValue() gfx.setColor(gfx.kColorBlack) gfx.fillRect(x - (width / 2), y + animY, width, 50) gfx.setImageDrawMode(gfx.kDrawModeInverted) AnnouncementFont:drawTextAligned(self.textQueue[1], x, y + 10 + animY, kTextAlignment.center) gfx.setImageDrawMode(originalDrawMode) end