Creating a Rotation-based Indicator

After making an arena shooter for the Playdate titled Plight of the Wizard, there were a few areas that I knew needed some polish. One of those areas was how I handle my character's rotation. Here was my previous implementation:

An older build of Plight of the Wizard that has awkward rotations when playing the game.

There were two issues with my approach:

1. Rotating a sprite in code is not very performant

According the the Playdate SDK:

This function should be used with discretion, as it’s likely to be slow on the hardware. Consider pre-rendering rotated images for your sprites instead.

2. It looked plain goofy

Having the player's sprite as the only sprite on the screen that is upside down looks odd. Further, rotating the sprite applies some bizarre distortion at certain angles. Nothing about this approach screams polished or professional.

I decided fix both problems with one solution: an indicator that displays where the player's spells are aimed. In order to make this work, I had to create a new sprite that maps to the player's x and y coordinates with a slight offset so that the indicator doesn't appear on top of the player. I chose to make the offset equal to the distance the spell will travel. I implemented this in the Player class in the following manner:

-- player.lua
function Player:init(x, y)
    ...
    self.spellIndicator = SpellIndicator(self.x, self.y, self.equippedSpellDistance)
    self.spellIndicator:setPlayerInstance(self)
end

In the new SpellIndicator class, I initialize its coordinates with the offset like so:

-- spellIndicator.lua
function SpellIndicator:init(x, y, distance)
    self.indicatorDistance = distance
    self.angle = 0
    ...
    self:moveTo(x + self.indicatorDistance, y)
end

After initializing the indicator, I initially tried to make it follow the player by spawning a new instance, which resulted in a fun bug.

A bug with the indicator that makes a fun drawing on the screen.

That looks awesome but doesn't lend itself very well to gameplay.

Instead, I updated the indicator's coordinates in its update loop to both follow the player as they move around as well as map it to the rotational value of the crank, which can be rotated 360°.

-- spellIndicator.lua
function SpellIndicator:update()
    if playerInstance then
        -- Rotate the indicator around the player when the crank is rotated
        self.angle = self.angle + pd.getCrankChange()
        local newX = playerInstance.x + self.indicatorDistance * math.cos(math.rad(self.angle))
        local newY = playerInstance.y + self.indicatorDistance * math.sin(math.rad(self.angle))
        self:moveTo(newX, newY)
    end
end

A bug where the spell fired does not follow the spell indicator.

Now I have an indicator that rotates smoothly around the player that maps to the clockwise and counterclockwise rotations of the Playdate's crank.

But wait! The fireball spell is shooting to the default position to the right rather where the spell indicator is aimed. Well that's because it's still set up to follow the rotation of the player's sprite, which is now no longer being rotated. The fix is simple. When the player casts a fireball, I just need to pass the SpellIndicator's angle rather than the player's.

-- player.lua
castFireballSpell(self, self.spellIndicator.angle)
-- spells.lua
spellSpawnOffset = 48

function castFireballSpell(player, angle)
    local spawnOffsetX = spellSpawnOffset * math.cos(math.rad(angle))
    local spawnOffsetY = spellSpawnOffset * math.sin(math.rad(angle))
    FireballSpell(player.x + spawnOffsetX, player.y + spawnOffsetY, angle)
end

So, what does this new fancy indicator look like?

The new spell indicator, which rotates around the player and spells are fired towards.

It's not much, but it gets the job done.

The gameplay is starting to feel like a computer game that uses a mouse to rotate, and I'm very happy with that.