Not logged inGosu Forums
Forum back to Help Search Register Login
Up Topic Gosu / Gosu Exchange / How get the id of clicked sprite
- - By JosepM Date 2016-02-08 11:55

I'm new to Gosu, excuse me if I ask something basic but I don't found an answer to this after read many post and tutorials.

How can I get the id of a clicked sprite? I want to create a map with clickable zones and show some data about the zone.
I have the whole map and the XY of each zone (.jpg) to put over the main map. Then the user click over any zone and perform and action.

It's possible do that?

Thanks in advance,
Josep M
Parent - - By lol_o2 Date 2016-02-08 13:15
You can't get information which image you clicked (unless there's some OpenGL stuff that does it, but I don't know it)
But if you store the x/y position of sprites, you can check the clicked sprite by using the x/y values from your data.

your zone look like this: zone = [x, y, image]
you draw them like this: zone[2].draw(zone[0], zone[1], 0)
you can check if your cursor is over zone by: mouse_x.between?(zone[0], zone[0] + zone[2].width) and mouse_y.between?(zone[1], zone[1] + zone[2].height)

If you have few zones, you just iterate them to perform these actions.
Parent - - By JosepM Date 2016-02-08 13:45
Thanks for answer.

Would be possible add the "mouse_over" into the sprite class? I mean each sprite "know" if the cursor is over or not.
If I must iterate over many zone each update I guess will be slow, isn't?

I have over 50-100 zones, and are irregular forms, each zone is a country with transparent area.

Thoughts? :)

Josep M
Parent - - By lol_o2 Date 2016-02-08 14:01
If by "sprite" you mean Gosu::Image, then it's impossible. Gosu doesn't provide any access to already drawn images. You can view/edit this data with OpenGL operations, but I don't know if you are able to extract single images from it.

However, even if your zones are irregular, you still can check them like rectangles. First check if mouse is over rectangular area, and if it is, then you can check if it's over non-transparent pixel (you need some additional library for this). If you have only 50-100 zones, Ruby is fast enough to handle this. If it wasn't effective enough for you, you could also make some optimizations, like putting your zones into spatial hash.
Parent - - By JosepM Date 2016-02-08 14:14
Yes, sprite is a .png. I like the idea of check the pixel if is inside the rect but keep in mind that the transparent area of one zone is over the non-transparent area of other zone, so the pixel don't be transparent. Is posible define a shape and check if the pixel is inside?
What you mean as "spatial hash", an array of positions-zone?

Josep M
Parent - - By lol_o2 Date 2016-02-08 14:35
Spatial hash is a technique used for optimizations, especially collisions. I never used it, so I don't know exactly how it works :P, but it has something to do with putting objects into hash, which represents "areas". Since its hash, you can quickly determine which area is object in, so you know that objects in different areas won't collide.
But as I said, it's possible that you won't need this, because you don't use that many areas.

As for the rectangles, you first check check rectangular area, so you don't need to check for non-transparent pixels for every sprite. I don't know if there are any algorithms that let you define exact shape (you'd need to use vertices to make a polygon etc.), but there are libraries like RMagic, Ahston or Texplay that are compatible with Gosu and allow to check single pixels of image for transparency.

It looks like this:
for each zone:
check if mouse is inside rectangular area of zone
if yes: check if mouse is over non-transparent pixel
Parent - By JosepM Date 2016-02-08 19:34

I will try this but if the transparent os over a non-transparent, then ever the mouse will over non-transparent...
When I initialize a new zone exist any way to associate a ID for this instance, creating an array of ID-instance?
The objetive is like a Risk game, I have zones and the players pawns on zones. Selecting one player pawn I should show the posibles destinations.
Thinking about maybe I can do the same from the pawn position that will be a square like the wargames chips, and from the know position xy show the zones in other color. Each zone will have a sprite sheet with the status of the zone. Then changing the frame I can show the posibles destinations.
But how select them,... :(
Parent - - By JosepM Date 2016-02-08 22:04

Some ideas about what I want.

A map with zones and pawns.
Parent - - By ericathegreat Date 2016-02-09 04:08 Edited 2016-02-09 04:15
I built something very much like this a while back. I defined actual polys for the clickable areas. You can take a look here:

Defining a screen with one clickable region:

The image map mixin which makes that work:

I can't speak to how performant it would be with thousands of polys, but if it's only on click then it shouldn't be a problem. If you're doing dragging logic and need to know continuously though, that might be a bit heavier, code wise.

In your case, where I'm storing interactions, you'd just need to store the ID of the region.

(This does rely on the ruby-geometry gem, which makes the calculations super simple)
Parent - - By JosepM Date 2016-02-09 10:30 Edited 2016-02-09 11:07

Thanks for answer.

OK. I figure how work but I don't understand at the end.
I understand that I need all the xy vertex that form each zone.

Inside your TownScreen Class with the method associate add each zone.

But this I don't understand how work
module ImageMap
    def define_zone polygon, interaction
      @zones ||=
      @zones <<, interaction)

    def interactions_under point{ |z| z.region.contains?(point) }.map(&:interaction).flatten

    def click(x,y)
      puts interactions_under(Point(x,y))

I'm very glad if you could help me.

I download your GitHub project but I don't know exactly how execute it. :(

Josep M
Parent - By ericathegreat Date 2016-02-09 23:36
The 'define_zone' method is creating/updating an array called '@zones', which stores the polygons. So if I added three polygons then dumped that to screen, it might look like this:

Polygon( (x1,y1), (x2,y2), (x3,y3) ),
Polygon( (x4,y4), (x5,y5), (x6,y6) ),
Polygon( (x7,y7), (x8,y8), (x9,y9) )

Now, the interactions_under method takes an x,y point for where the mouse clicked. You can get that from the Gosu mouse up/down methods. It then searches all of the polygons in the @zones array for one which contains that point. The calculation for whether a region contains a point is provided by the library 'ruby-geometry'.

z.region.contains? means, for each of these zones, get the polygon (region) and check whether it contains that point. will keep all the zones which return 'true' from the above. It then runs .map(&:interaction), which in my case collects the interactions for all of these zones. That's a concept that's specific to my game. You'd want to adjust that to collect your IDs instead. The 'flatten' method will do handy things like remove any nested arrays. That's also really only relevant for my game, not for yours. The end result of this is an array of all the matching interactions (or in your case, IDs).

You're welcome to run my code. It won't help you very much at this point because this code is not currently active. (It's for the HUD, which is still a work in progress), but assuming you're all set up for Gosu development then you should be able to just run:
bundle exec ruby lib/gui.rb
Parent - - By RunnerPack Date 2016-02-10 02:42
If you're going to define clickable polygons, there's also lol_o2's wrapper around Chipmunk. I don't know how the speed, memory footprint, or ease-of-use compares to your ruby-geometry-based solution, but it's another option I figured I'd mention, just in case.
Parent - - By ericathegreat Date 2016-02-14 09:34
If I were having objects which moved, or were governed by physics, I'd almost certainly switch to Chipmunk. As it is though, my zones are static for the entire game (HUD), so adding a physics engine and then turning off all the actual physics just so that it can identify polygons is probably overkill for my particular project. It depends a lot on the situation though, certainly.
Parent - By RunnerPack Date 2016-02-14 17:56
Yes, I suppose it can be considered overkill, but it's a pretty light and fast library, and it would basically give you infinite room to expand, while never worrying about bogging down the collision engine. It's your project, though. Like I said before, it's just another option.
Here's another one (although it doesn't do arbitrary polygons):
It's kind of a WIP, and I have no idea what the performance is like.
Parent - By nietzschette Date 2016-02-09 18:43
I'm also working on a gui right now.  I think people here have already given some great advice about how to detect the sprite you are mousing over, so i don't think my methods would contribute anything new.  I like to use hit boxes and hit circles for both physics and cursor interaction (the cursor being, after, just another sprite.).  For complex shapes use a combination of these hitbox primitives.  The basic idea is to restrict click-able sprites so they are simply not allowed to overlap.  On some Ruby/SDL projects, I had pixel-precise-alpha-channel-ignoring precision in my detection methods; to do this in Gosu would require mapping your img's #blob data into a 'collision map' capable of taking an x,y from your cursor, applying whatever scale and transformation your of your view angle, and then searching the relevant collision map for a transparent/false value or a clickable/true value.  Doing all this, however, seems to go against the entire philosophy behind the simplicity coding with Gosu.  You might want to consider forgoing some level of 'desired' precision for an 'acceptable' level of precision, although I suppose I shouldn't discourage you from the utter badassery of pursuing the the former.

However you approach, assume we have some hitbox object with a #detect(x,y) method, and let's consider optimization:

Your mouse can only ever be in ONE location at any given frame, so given a list of geometries:

@hitboxes = [ hitbox0, hitbox1, ... ]

Remember a value between 0 and hitboxes.size, the last hitbox your mouse was detect in, and only check this one element until it is nolonger detected:


if this method returns false, we might need some enumeration over the hitboxes array to re-set value.  This reduces the time spent enumerating through the list.   if #detect(x,y) returns a bool, you could do something like this:

x = YourGosuWindow.mouse_x
y = YourGosuWindow.mouse_y
if @value
  unless @hitboxes[@value].detect(x,y)
   @value = nil
  @value = @hitboxes.collect{|h| h.detect(x,y)}.rindex(true)

@value can be nil, or a number.  Whenever a click event is evoked, you already have @value stored so all you need is some method to associate the index references of your hitbox array with your sprite array.  This is just an example to illustrate the idea so it'll no doubt look much different when incorporated into your own update method, and that second-to-last line with the

  @value = @hitboxes.collect{|h| h.detect(x,y)}.rindex(true)

requires further optimization especially.
Up Topic Gosu / Gosu Exchange / How get the id of clicked sprite

Powered by mwForum 2.29.7 © 1999-2015 Markus Wichitill