Pixel-Perfect Sprite Clicks
JoeStrout
Posted on October 13, 2023
A common need in any graphical program is to detect clicks on a button or other sprite. The Mini Micro wiki contains an article on "How to detect a click on a sprite," but (at least at the time of this writing), it assumes a simple rectangular click area. What if your sprite is an odd shape, and you really need to detect clicks only on the visible part of the sprite?
In this case, we need pixel-perfect click detection. The basic idea is: first do your simple rectangular-bounds check; and then if that passes, check the pixel color of the sprite image at the location of the mouse. Consider it a valid click only if the pixel there is not transparent. (Or, if your sprite has some solid background color, then check for that color instead.)
Let's start by reviewing the simple rectangular check first. Launch Mini Micro, edit
a fresh program, and paste in this short demo code:
clear
sp = new Sprite
sp.image = file.loadImage("/sys/pics/Wumpus.png")
sp.localBounds = new Bounds
sp.localBounds.width = sp.image.width
sp.localBounds.height = sp.image.height
sp.x = 480; sp.y = 320; sp.scale = 2
display(4).sprites.push sp
while true
if sp.contains(mouse) then
sp.tint = color.green
else
sp.tint = color.white
end if
end while
This sets up a sprite, loading our beloved Wumpus from the system pictures. It assigns a localBounds
based on the image size, so that we can use the contains
method to check whether the mouse is over those bounds. (This accounts for the position, scale, and rotation of the sprite automatically.) In our main loop, we tint the sprite green when the mouse is within the sprite bounds, and white when it is not.
Now let's extend this to pixel perfection! Press control-C to break out of the previous demo, and edit
again. Modify the code as follows, with a new overSolidPixel
function to check whether a given x,y position (such as the mouse) is over a non-transparent pixel in the sprite image.
clear
sp = new Sprite
sp.image = file.loadImage("/sys/pics/Wumpus.png")
sp.localBounds = new Bounds
sp.localBounds.width = sp.image.width
sp.localBounds.height = sp.image.height
sp.x = 480; sp.y = 320; sp.scale = 2
display(4).sprites.push sp
Sprite.overSolidPixel = function(pos)
x = (pos.x - self.x) / self.scale + self.image.width/2
y = (pos.y - self.y) / self.scale + self.image.height/2
c = self.image.pixel(x,y)
text.row = 25; print c // (for debugging)
return c[-2:] > "88"
end function
while true
if sp.contains(mouse) and sp.overSolidPixel(mouse) then
sp.tint = color.green
else
sp.tint = color.white
end if
end while
This overSolidPixel
function starts by converting the global x/y position given into coordinates local to the sprite image. (The code as shown assumes a non-rotated sprite with uniform scale. Extending it to support-non uniform scale and rotation is left as an exercise for the reader.) Then we look up the pixel color at that point in the sprite, and return whether the last two digits (the alpha channel) are greater than "88", i.e., are closer to fully solid (FF) than transparent (00).
Now when you run it, you'll find you can mouse over the corners or gaps in the sprite without it turning green. Only when the mouse is over a solid part of the sprite image, do we consider it really "over" the sprite.
So there you have it! The next time you find a rectangular bounds isn't good enough for click detection on your sprite, remember this technique, and you'll have pixel perfection in no time.
Posted on October 13, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.