I’ve noticed a performance problem with nine slices on actual hardware. It happens any time the nine slice is drawn at a particular size for the first time. The results in the sprite’s animation being jerky. After the first hiccup, it’s fine… unless I need to draw the nine slice at a different size. In my game that happens pretty often.
Steps to reproduce:
- Run the sample project on actual hardware.
- Press the A button to make a text box appear. Note that the animation is a bit jerky.
- Press the B button to dismiss the text box.
- Press the A button again. Note that the animation is smooth now.
The animation will continue to be smooth unless the size of the box is changed.
So far the best workaround I’ve found is to draw the nine slice into an image, then draw that image in the draw()
method instead of the slice. You can set the ENABLE_WORKAROUND
flag to true to test this workaround. With this fix in place, the animation is always smooth.
One other thing worth noting is that this only seems to be a problem at larger sizes. At 100x100 or even 200x100 I can't notice an issue.
This bug took me a while to track down and I’m a bit surprised by it! I figured drawing a nine slice would perform pretty well. Is there anything else I can do to get this to perform better, or should I stick to my current workaround?
NineSliceDelay.zip (10.3 KB)
Here's the textbox.lua
file, where all the relevant code is:
local gfx <const> = playdate.graphics
local ENABLE_WORKAROUND = false
class('TextBox').extends(gfx.sprite)
function TextBox:init()
TextBox.super.init(self)
self.backgroundSlice = gfx.nineSlice.new("box.png", 10, 10, 1, 1)
self.backgroundImage = nil
self.isPreparing = false
self.isShowing = false
self:setVisible(false)
self:setCenter(0, 0)
self:setSize(340, 200)
self:moveTo(30, 240)
end
function TextBox:draw()
if ENABLE_WORKAROUND then
if self.backgroundImage then
self.backgroundImage:draw(0, 0)
end
else
gfx.setImageDrawMode(gfx.kDrawModeCopy)
self.backgroundSlice:drawInRect(0, 0, self.width, self.height)
end
end
function TextBox:update()
local animator = self.animator
if animator then
if animator:ended() then
self.animator = nil
if self.isShowing == false then
self:setVisible(false)
end
else
local y = animator:currentValue()
self:moveTo(self.x, y)
end
elseif self.isPreparing then
self.isPreparing = false
self.isShowing = true
self.animator = gfx.animator.new(500, 240, 20,
playdate.easingFunctions.outBack)
end
end
function TextBox:processInput()
if playdate.buttonJustPressed(playdate.kButtonA) or
playdate.buttonJustPressed(playdate.kButtonB) then
self:hide(true)
end
end
function TextBox:show()
if ENABLE_WORKAROUND then
local width, height = self:getSize()
local image = gfx.image.new(width, height)
gfx.lockFocus(image)
self.backgroundSlice:drawInRect(0, 0, width, height)
gfx.unlockFocus(image)
self.backgroundImage = image
end
self.isPreparing = true
self:moveTo(30, 240)
self:setVisible(true)
end
function TextBox:hide(animated)
self.isShowing = false
if animated then
self.animator = gfx.animator.new(300, self.y, 360,
playdate.easingFunctions.inBack)
else
self:setVisible(false)
end
end