On click event with animated sprite 2D

Godot Version

Godot 4.4

Question

Hi everyone !

I’m developing a turn-based strategy game and need to implement pixel-perfect unit hovering. The units use animated 2D sprites with spritesheets for frames. This seems like a common issue for classic sprite 2D , but I haven’t definitive solutions for animated sprite.

I’ve founds two main options:

  1. Area2D with Collision Polygons
    Classic method for mouse-sprite interaction. Variables to consider: runtime vs. preprocessed, specific animations vs. all animations, single frames vs. all frames.
  • As I will have hundreds of unit types, i’d like to not preprocess collision polygons but generate them at runtime
  • Generating polygons for every frame of current animation at runtime would be ideal but may be too resource-intensive for medium-spec computers.
  • An more optimized way is to generate polygons only for idle animations (hovering during unit movement is not usefull for the player).
  • An even more optimized way is to generation the collision polygon and rotate it based on unit orientation.
  1. Pixel Alpha Check with Image.get_pixel()

- Detect hover by checking if the sprite pixel under the cursor is opaque.

- I am Less familiar with this method, so I have not much to say yet.

Do you have any suggestions ?
Thanks by advance !

Here are several points against it needing to be “pixel perfect”

  1. Sprites being animated means pixels inside sprites won’t be persistently opaque or transparent. What should happen if the player aims at an opaque pixel but it turns transparent when they click? The other way around?
  2. Player really won’t be hunting individual pixels to click on, and in fact you expecting them to may make the interaction frustrating.
  3. A formally opaque area will not in all cases coincide with what visually is perceived as a clickable area.

I think you should define meaningful areas for your units by hand. A little extra work on top of creating the units themselves. As a compromise, you could generate smaller transparency masks from the sprites, and test the value in those on the fly. Or a combination of the two, that would allow some manual adjustment for the tricky cases.

2 Likes

Thank you for your answer !

I try to mimic an old flash game where pixel perfect was used. I agree that this method could be frustrating, I’ll see how it goes during playtest

For anyone who need, here is the script I use !


func _is_pixel_opaque(global_mouse: Vector2) → bool:
# Create or get cached image MAYBE TO REMOVE
if animation != _cached_animation or frame != _cached_frame:
	_cached_animation = animation
	_cached_frame = frame
	var tex = sprite_frames.get_frame_texture(animation, frame)
	_cached_image = tex.get_image() if tex else null
var image = _cached_image
if not image:
	return false

var tex_size = Vector2(image.get_width(), image.get_height())
var local = to_local(global_mouse)
var pixel_pos = local - offset

# Flip
if flip_h: # PAS SUR QUE CE SOIT UTILE
	pixel_pos.x = tex_size.x - pixel_pos.x
if flip_v:
	pixel_pos.y = tex_size.y - pixel_pos.y

# Bounds check
if pixel_pos.x < 0 or pixel_pos.y < 0:
	return false
if pixel_pos.x >= tex_size.x or pixel_pos.y >= tex_size.y:
	return false

var a = image.get_pixel(int(pixel_pos.x), int(pixel_pos.y)).a
return a > 0.1

Related PR.

Add 'mouse_entered', 'mouse_exited' and 'mouse_pressed' to Sprite2D and Polygon2D by Ezequias-Rodrigues · Pull Request #114776 · godotengine/godot · GitHub

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.