Quick Links: Download Gideros Studio | Gideros Documentation | Gideros Development Center | Gideros community chat | DONATE
Button clickable issue while rotated — Gideros Forum

Button clickable issue while rotated

qxmatqxmat Member
edited November 2017 in General questions
Hi there.

I'm new to Gideros, so I decided I wanted to play around a little bit, to develop a better feeling for it. For this I'm coding a little cardgame. For each card I use this button class giderosmobile[dot]com/tools/button (this is a link, forum said I cannot post links, so I edited it)

To add a little eye candy, I rotate the player cards, so they look like you hold them in your hands. Here comes the issue: When I click on the marked green dot, not the "real" card gets active, instead the right card (it is colored thatswhy in a blue tone).

The Image is here: www2[DOT]pic-upload[DOT]de/img/34314920/issue.png (Again, sorry, forum doesn't let me post links)

I guess this would be correct, if the image wasn't rotated. But of course I want that the "diamonds 8" card gets clicked, when I tap on that marked position.

I have a card class, where each card looks like
local button_pushed = Bitmap.new(Texture.new("images/start_down.png"))
 
local startButton = Button.new(button_default, button_pushed)
 
-- ! --
self.mycard = startButton
-- ! --
Then I have a PlayTable class, it arranges the cards like following:

foreach card = actualcard.mycard:
card:setAnchorPosition(....)
card:setPosition(....)
card:setRotation(....)
card:setScale(....)
 
card:addEventListener("click", onPlayerCardClicked, card)
This visually rotates the cards the right way, only the clickable area don't seem to rotate, like described.

Comments

  • OP here, I found out I simply can attach a file, so here is it.
    issue.png
    200 x 200 - 28K
  • hgy29hgy29 Maintainer
    Hi @qxmat,
    You are right, this is a long standing gideros issue (and my fault). See here for the record: https://github.com/gideros/gideros/issues/170
  • now i'm not sure if checking hittespoint for the bitmap itself would work (instead of hittespointing a containing sprite, which i think your button class is - and that's what you are doing currently i guess). try it.

    in any case luckily it is a rectangular shape, so you can do globalToLocal of the event x,y values to the local of the bitmap itself and test if it is in the bounds.
  • hgy29hgy29 Maintainer
    using globalToLocal should work I think, @keszegh is right
  • Thank you both for the quick answers. I had a look at my code and the docs but cannot really imagine what I should do with it? I've read that I can get x and y coords from globalToLocal but don't understand from what. I guess I need them from the rotated card, but where do I have to apply them, and don't I need all 4 coords for each edge of the card?
  • hgy29hgy29 Maintainer
    The idea is to convert click coordinates given in the event (screen coordinates=global) to sprite coordinate, then check if those sprite-local coordinates are within the bounds of your (axis-aligned/non rotated) rectangle
  • @qxmat, you could use some code that determines if a point (where the screen was touched) lies inside a rotated polygon (the card).

    Because your cards overlap you would need more code to determine which card is the correct card that was clicked, based on which card is topmost in the spread of cards.
  • Hm well, I've no idea howto accomplish that. Not even remotely. I guess I'll just omit the rotation idea. Of course this isn't ideal. I've read in the linked bugtracker ticket that the issue is already 2 years old, so I guess it won't be fixed in the near future.

    Beside that, during my journey the last days exploring gideros I googled a lot. The most hits were more than 5 years old. Because of this I want to ask, is gideros still active and thus save to learn it in favor of, say corona or something else?

    I get that gideros is free and opensource and really appreciate all the devs work, but of course I want to learn a future-safe framework.
  • yes, gideros is quite actively developed nowadays.

    also, don't give up on globaltolocal, it is really not hard, if you do not manage, then somebody might spend the time to write you a small example code.
  • @qmax okay I might be able to throw something together. So let me ask, How many cards will there be displayed at once?

    As far as Gideros vs Corona goes, in my opinion Corona has better infrastructure (website, documentation, tutorials) but, that's it. The only reason these things are better is that they have huge amounts of money and people to throw at these things. Gideros has a very small team who are trying their best. You can of course help wherever you feel you can too :)

    By far the worst aspect of Corona is having to have an internet connection to actually compile your project. No internet.. no can compile! You can go Corona Native to get offline builds but I think there is a hefty price tag attached to that path. This isn't the case with Gideros.
  • corona offline build might be free since some time, i'm not sure though.
  • Regarding the card count: the game starts with 5 cards, but due to the nature of the game, it can possibly have "unlimited" cards (haven't thought about that aspect enough yet). But in a common game it should occure sometimes that one has 10 cards, which looks like the appended image. Like you can see, I tried to solve the problem by changing the rotation angle a little bit. I also tried to not use rotation at all, which leads to the problem that the cards become more and more stuffed and thus unreadable.
    cards.png
    413 x 308 - 75K
  • antixantix Member
    edited November 2017
    @qxmat yep I see your dilemma now. I'll think about it :)

    But the game has only one deck of cards? If so "unlimited" becomes 52 right? ;)
  • in the button class file (assuming it is the standard one for gideros) add the following function:
    function Button:hitTestPoint(x,y)
        local lx,ly=self:globalToLocal(x,y)
        if (lx>0) and (ly>0) and (lx<self:getWidth(true)) and (ly<self:getHeight(true)) then return true else return false end
    end
    this should redefine the hitTestPoint calls inside the class from the built-in one to this new one. and hopefully this is good. try it and let us know if it worked.
  • I personally would not be confident in releasing a corona built ipa (or apk to a lesser extent) file... ;p

    https://blog.hmil.fr/2015/02/hacking-funrun2-how-to-reverse-engineer-a-corona-app/
    Coder, video game industry veteran (since the '80s, ❤'s assembler), arrested - never convicted hacker (in the '90s), dad of five, he/him (if that even matters!).
    https://deluxepixel.com
  • john26john26 Maintainer
    Gideros can certainly do hitTestPoint correctly in the case of rotated/scaled Sprites (including when they children of other Sprites). So I think this is not a Gideros bug. @qxmat can you post a complete program which illustrates the bug?
  • @john26, interesting, I did not realize this :)
    However I just tested and using hitTestPoint() on a rotated sprite only determines that the touch happened within the sprites bounds, not the actual shape (ie the rotated rectangle).
  • antixantix Member
    edited November 2017
    Here is a Card class that I made.
    - It is subclassed from Bitmap so you need to supply a Texture or TextureRegion when creating a new card.
    - When creating a new card it get's the dimensions for its collision polygon from the dimensions of the Texture (or TextureRegion) supplied.
    - Call setCardPosition(x, y) to position the card on screen.
    - Call setCardrotation(angle) to set the cards rotation.
    - Call containsPoint(x, y) to determine if x, y are inside the cards rotated rectangle.
    Here is the class..
    Card = Core.class(Bitmap)
     
    -- create new card
    function Card:init(texture)
      self:setAnchorPoint(0.5, 0.5)
     
      local rX = self:getWidth() * 0.5 -- half width and half height
      local rY = self:getHeight() * 0.5
     
      self.original = { -- original vertices
        {x = - rX, y = - rY},
        {x = - rX, y =  rY},
        {x = rX, y =  rY,},
        {x = rX, y =  - rY},
      }
     
      self.transformed = { -- transformed vertices
        {x = 0, y = 0},
        {x = 0, y = 0},
        {x = 0, y = 0},
        {x = 0, y = 0},
      }
    end
     
    -- set card screen position
    function Card:setCardPosition(x, y)
      local ox, oy = self:getPosition()
      local verts = self.transformed
      for i = 1, #verts do
        local v = verts[i]
    	v.x = (v.x - ox) + x -- recalculate x position
    	v.y = (v.y - oy) + y
      end
      self:setPosition(x, y) -- set image position
    end
     
    -- set card rotation
    function Card:setCardRotation(angle)
      local function add(x1, y1, x2, y2) -- add two vectors
        return x1 + x2, y1 + y2
      end
     
      local function rotate(x, y, angle) -- rotate a point about 0, 0
        local sin, cos = math.sin, math.cos
        local c, s = cos(^<angle), sin(^<angle)
        return c * x - s * y, s * x + c * y
      end
     
      local px, py = self:getPosition()
     
      local o = self.original
      local t = self.transformed
      for i = 1, #o do
        local ov, tv = o[i], t[i] -- next vertices to rotate
        tv.x, tv.y = add(px, py, rotate(ov.x, ov.y, angle)) -- rotate and position vertice
      end
     
      self:setRotation(angle) -- set image rotation
    end
     
    -- return true if the coordinate resides inside the polygon
    -- This function from Hardon Collider (<a href="https://github.com/vrld/HC" rel="nofollow">https://github.com/vrld/HC</a>), Copyright (c) 2011 Matthias Richter
    function Card:containsPoint(x, y)
      -- test if an edge cuts the ray
      local function cut_ray(p,q)
        return ((p.y > y and q.y < y) or (p.y < y and q.y > y)) -- possible cut
        and (x - p.x < (y - p.y) * (q.x - p.x) / (q.y - p.y)) -- x < cut.x
      end
     
      -- test if the ray crosses boundary from interior to exterior.
      -- this is needed due to edge cases, when the ray passes through
      -- polygon corners
      local function cross_boundary(p,q)
        return (p.y == y and p.x > x and q.y < y)
        or (q.y == y and q.x > x and p.y < y)
      end
     
      local v = self.transformed
      local in_polygon = false
      local p, q = v[#v], v[#v]
      for i = 1, #v do
        p, q = q, v[i]
        if cut_ray(p, q) or cross_boundary(p, q) then
          in_polygon = not in_polygon
        end
      end
      return in_polygon
    end
    And here is an example of it in action..
    application:setFps(60)
    application:setBackgroundColor(0x508050)
    stage:setOrientation(Stage.LANDSCAPE_LEFT)
     
    cards = {}
     
    local card = Card.new(Texture.new("images/card.png", true))
    card:setCardRotation(120)
    card:setCardPosition(100, 200)
     
    cards[#cards + 1] = card -- add to list of cards on screen
     
    local cards = cards -- add visible cards to screen
    for i = 1, #cards do
      stage:addChild(cards[i])
    end
     
    -- some text to show when we click a card
    local info = TextField.new(nil, "touch the screen")
    info:setPosition(12, 24)
    info:setScale(2)
    stage:addChild(info)
     
    -- process touch events
    local function onTouched(e)
      local tx, ty = e.touch.x, e.touch.y -- where the screen was touched
     
      local count = 0
      local cards = cards
      for i = 1, #cards do
        if card:containsPoint(tx, ty) then -- check if a card was touched
          count += 1
        end
      end
     
      if count > 0 then
        info:setText("a card was touched")
      else
        info:setText("no cards were touched")
      end
    end
     
    local function onEnterFrame(e)
      local dt = e.deltaTime
    end
     
    card:addEventListener(Event.TOUCHES_END, onTouched) -- add listeners
    stage:addEventListener(Event.ENTER_FRAME, onEnterFrame)
    It all seems to work pretty well but of course there still remains the issue of how to determine which card has been clicked when there are overlapping cards involved.
    zip
    zip
    Card Select.zip
    3K
  • @john26:

    Here is the complete program:
    print("starting")
     
    angles = {330, 360, 30}
     
    for i=1, 3, 1 do 
     
    	card_default = Bitmap.new(Texture.new("Ace-Spades.jpg"))
    	card_active = Bitmap.new(Texture.new("Ace-Spades.jpg"))
     
    	card = Button.new(card_default, card_active)
     
    	card:setPosition(100, 100)
    	card:setRotation(angles[i])
     
    	card:addEventListener("click", function(lcard) 
    		lcard:setColorTransform(0.5, 0.5, 0.5, 1) 
    		timer = Timer.new(1000, 1)
    		function onTimer(tcard)
    			tcard:setColorTransform(1, 1, 1, 1)
    		end
    		timer:addEventListener(Event.TIMER, onTimer, lcard)
    		timer:start()
    	end, card)
    	stage:addChild(card)
     
    end
    This renders 3 cards. The card Ace-Spades.jpg I attached to this post. Furthermore I attached a picture which illustrates the issue. If I click on the red marked dots, not the middle card gets selected, rather the topmost card gets selected, which seems the right behavior if the cards weren't rotated.

    @keszegh:

    I tried your code, and added it to the generic button class (to the end of it). After adding it, the buttons don't seem to be clickable anymore at all. Although I've to admit, I don't understand your code or how it works, so I cannot fiddle around with it.

    @antix:

    Wow, thank you for your efforts, I need to test and understand it, I'll go to try it out now. But it seems that you "reprogrammed" the whole actual problem class.
    showissue.png
    289 x 336 - 11K
    Ace-Spades.jpg
    175 x 246 - 6K
  • @antix:

    So I tried your code, all works well if I just have one card, but if I have two card I have the issue that on each click on one of the cards, both get selected. I don't know why. I used your card class as it was and modified your Main.lua as follows:
    application:setFps(60)
    application:setBackgroundColor(0x508050)
    stage:setOrientation(Stage.LANDSCAPE_LEFT)
     
    cards = {}
     
    cards[1] = Card.new(Texture.new("Ace-Spades.jpg", true))
    cards[1]:setCardRotation(120)
    cards[1]:setCardPosition(10, 20)
     
    cards[2] = Card.new(Texture.new("Ace-Spades.jpg", true))
    cards[2]:setCardRotation(160)
    cards[2]:setCardPosition(100, 200)
     
    local cards = cards -- add visible cards to screen
    for i = 1, #cards do
      stage:addChild(cards[i])
    end
     
     
    -- process touch events
    local function onTouched(mycard)
    	one, two, three, four = mycard:getColorTransform()
    	if one == 1 then
    		print("touched")
    		mycard:setColorTransform(0.5, 0.5, 0.5, 1)
    	else
    		print("touched again")
    		mycard:setColorTransform(1, 1, 1, 1)
    	end
    end
     
    local function onEnterFrame(e)
      local dt = e.deltaTime
    end
     
    cards[1]:addEventListener(Event.MOUSE_DOWN, onTouched, cards[1]) -- add listeners
    cards[2]:addEventListener(Event.MOUSE_DOWN, onTouched, cards[2]) -- add listeners
    stage:addEventListener(Event.ENTER_FRAME, onEnterFrame)
    You mentioned "It all seems to work pretty well but of course there still remains the issue of how to determine which card has been clicked when there are overlapping cards involved." Is this what you have meant?
  • @qxmat, yes that is exactly what I mean :D

    So I am just playing with this a little more and am making some code to choose random cards from a deck and then once you have a bunch of cards, it can spread them out in an arc across the screen.

    I have been pondering the overlapping cards and I think that the way to solve it would be to do something like this...
    1. Spread the cards in the hand in an arc.
    2. When the screen is touched, go through the list of cards (from left to right) and find the first one that the touch is inside (if any).
    3. get a list of all other cards (to the right) that overlap (intersect) the first card.
    4. check each card in that list and the last one that was touched should be the correct card.

    Well this is my theory and I'll test it in the morning when I get up :)
  • with my suggestion (even if it would work), the issue of deciding which card is on the top at the clicked place is also an issue, so probably it's better to take the code provided by @antix.
  • What about providing an id property to each card upon creation and get it back on click event? I can't write this well on mobile, but maybe I will find some time later :)
    Another option would be to getChildIndex on the touched card.
    http://docs.giderosmobile.com/reference/gideros/Sprite/getChildIndex#Sprite:getChildIndex
    But this one would require that cards are placed inside another Sprite (on stage there may be something else in addition to the cards)
  • @pie, unfortunately that won't work with overlapping cards and also normal hit testing only works with a sprites bounding box (the area which it's graphic occupies, not the actual rotated rectangle).
  • antixantix Member
    edited November 2017
    Find attached a new example..
    image
    Press the reset button to reset the deck.
    Press deal to deal a new random card.
    Click individual cards (overlapping is handled) to select them.
    Click discard to enter discard mode where you can select and discard multiple cards.

    The Card class is essentially the same and there is a new class called Deck, which creates cards and manages them.
    When created, Deck will load the default card TexturePack. You can load new packs as you like and update any currently visible cards with the new imagery.

    EDIT1:
    I updated the example and now you can drag and drop cards to re-order them.

    EDIT2:
    I have updated this for the final time and now it includes functionality to select and discard multiple cards.

    Any questions just holler :bz
    zip
    zip
    Card Select.zip
    625K
    card_select_screen.png
    480 x 320 - 135K

    Likes: pie

    +1 -1 (+1 / -0 )Share on Facebook
  • Wow, thanks a lot. Sorry for my late answer, I forgot my forum account password. I'll have to take a closer look at the source and how you have done things. Thanks again.

    Likes: antix

    +1 -1 (+1 / -0 )Share on Facebook
  • @qxmat2, your'e welcome. This example should be enough to keep you moving forward :bz
Sign In or Register to comment.