Collision detection

Started by linky, July 28, 2014, 01:32:12 AM

Previous topic - Next topic
Hello to all,

I am a beginer in Solarus scripting and I wish to ask you if it is possible to do the following.

I'm trying to use custom_entity to define my own entity behavior, but i don't know how to avoid the collision between the hero and the custom entity. The sprite of my entity (a stone) is big (64px width) and has irregular shape (definitely not square) and I need to avoid the hero can traverse it as it is supposed to be a plain object.

If I have well understood the collision detection of solarus engine works only on square shape. For example in the first tutorial you show how to put a black protection square under trees or houses on the low layer because it has a "ground" type as wall, so the solarus engine tells the hero not to go on the square area.

But with a sprite that has an irregular shape this is not very satisfactory as if the protection square is too small the hero can go a little inside the edge of the sprite and if the square is larger then at some edge the hero is stuck away from the entity as if it is stopped by an invisible wall.

I then thought that I could put many little protection square to try to cover all the surface of the entity approximately along the edge instead of using just one big, but as my sprite is animated, the protection is ok only on one frame and then completely wrong for the next frame.

Its why i then try to use the lua method "add_collision_test" that works well to detect a collision between the sprite of the hero and my custom entity, and this time at the pixel precision as you say on the doc. But then what ?

In the callback function, after having detected that the hero is coming inside a sprite it should not, what can i do to say to the hero not to go further more inside the sprite of the entity ? Is it possible to take back the control of the movement of the hero instead of let the solarus engine doing it ?

To resume my point, is it possible to script a more precise collision detection system to stop the hero when the shape of the sprite is really not square and animated ?

Thank you very much for your help.

July 28, 2014, 09:31:47 AM #1 Last Edit: July 28, 2014, 10:23:02 AM by Christopho
Hi,

You are mixing two separate notions:
1) Collision detection: detecting when two entities overlap each other.
2) Obstacles: allowing or not an entity to traverse another one.

In your case, you are interested in 2).
(Indeed, you could also use 1) to detect the overlapping *after* it happened and force the hero to move back. This is complicated and seems like a dirty hack).

For custom entities, 1) is add_collision_test() and 2) is set_can_traverse() and set_traversable_by().

You can do
your_custom_entity:set_traversable_by("hero", false)
but here, the rectangle (the bounding box) of the custom entity will entirely be an obstacle.
It is possible to do something more elaborate by passing a function instead of a boolean:
your_custom_entity:set_traversable_by("hero", function(custom_entity, hero)
  local hero_x, hero_y = hero:get_position()
  local traversable = ......... -- some kind of elaborate test here depends on the exact position of the hero on the custom entity
  return traversable
end)

I use this kind of code in the engine to implement complex traversable properties of diagonal jumpers. But this is quite advanced. If you really need that, there is usually something wrong.

I'm not sure it is a good idea to make a sprite that blocks the hero depending on the exact pixels that overlap him. It is very strange that the obstacle part of the sprite changes at each frame. What if the hero is outside the sprite during one frame, and at the next frame blocked by it? This is weird. The hero should never be inside an obstacle. Plus, there is no easy way to implement that with the API. Also, it would be slow.

For example, if your entity a big sprite of 64x64, most of the time you can give the entity a smaller (fixed) bounding box, like 48x32 or something else and you  will be okay with obstacles. You don't need another entity to act as a protection square.

If your entity is really too irregular for that, can you tell me more? What kind of entity are you making?

Hi Christopho,

Thank you very much for taking the time to answer me so precisely, especially during those summer holydays.

I have put 2 images to show in what case this kind of problem can occured :

One with a standard tile of zelda which represent the entrance to a cavern :



and the other one my ugly stone :



In its animation it is just rotating on itself around its center, so depending of the frame the hero can be closer or not from the edge of the stone. If an edge of the stone is touching the hero when it is rotating then the hero should be pushed away for example.

For the first image of the cavern, what is the most efficient way to avoid the hero being blocked by an invisble wall away from the wall of the cavern like this ?

The binding box are not of much help here. If i put it too small the hero will go inside the middle wall when coming by the middle, and with a big binding box, the hero will stay blocked away from the small wall (where there is the little horn) if he is coming from left or right as on the picture. I think the only solution is to use differents size "square blockers" under the sprite like with the trees ? Or maybe on the tileset it is more easy to create new tiles by copying this big one and cut it in 3 parts with adapted binding boxes ?


Altough, I have tried to continue to use add_collision_test combined with set_traversable_by as you proposed, but don't find what to do with.

In particular if i put "set_traversable_by() = true" to let the hero goes closer to the entity (ie without taking care of bounding box which is sometimes too far from the entity) then i detect the collision with pixel precision when the hero touch it. Then i put back "set_traversable_by = false" to avoid link continue to go further inside the entity.

But then the hero is completely stucked in the entity and can't get of it because i think the solarus engine blocked the movement of link as it is not supposed to be inside an entity (but in this situation, he is :) ).

So with standard api you propose I don't know if there is something else that can be done, especially for a very beginer like me. But don't worry about that, it's not of real use for the moment. I just wished to explore the possibilities offered with the new custom_entity to be able to create my own behavior instead of using those proposed by the engine to have furthermore flexibility in the game creation (but it has already an enormous one).

Also, i'm not sure, but i didn't find in the doc if there is an api "custom_entity:is_transversable" although it exists for the enemy:  enemy:is_traversable(). It could exists for both as a custom entity can be another type of enemy.

Thanks again.

July 29, 2014, 06:26:26 PM #3 Last Edit: July 29, 2014, 06:28:14 PM by Christopho
Hi,
Thanks for the detailed answer and the screenshot, now I better understand what you are trying to do :)

- For the entrance of cavern: you can easily fix this by changing the tileset. Make proper obstacle tiles for the north part of the entrance, including diagonal walls. I guess you re-used the tileset I made for ZSDX. In ZSDX, the player cannot go to the north of the entrance, so I did not bother making these tiles.

- And for your « ugly » stone: yes, exactly, ugly is the appropriate word hehe :) There are no such irregular tiles in old-school games. Or their obstacles are implemented with regular tiles aligned on an 8x8 grid. Diagonal walls can help you here too. But this is hard if the stone rotates on itself.
When the hero gets stucked into a wall, or is close to get stuck, there is no mechanism to move him back.

If you really want a pixel-precise wall, you should let the stone always traversable, but use add_collision_test() to move the hero back, sending him a few pixels just to be sure.
Here is the idead (untested code):

stone:add_collision_test("sprite", function(entity)
  if not entity:get_type() == "hero" then
    return
  end

  local hero = entity
  local angle = stone:get_angle(hero)  -- angle from the stone to the hero
  local movement = sol.movement.create("straight")
  movement:set_speed(128)
  movement:set_max_distance(16)
  hero:freeze()
  movement:start(hero, function()
    hero:unfreeze()
  end)
end)


- custom_entity:is_traversable() does not exist because I was not sure how to design it. It is either true, false or a function. What should I return if the traversable property is a function and not a boolean? (Now that I think about it, a solution could be to always return a function.)

July 29, 2014, 10:20:14 PM #4 Last Edit: July 30, 2014, 05:44:29 PM by linky
Hi again,

Your solution works well, i have just added a little more to make it works also with the enemy walking around that could altough go through the stone. I have to find something to replace freeze that don't seem to exist for enemy because otherwise the enemies got definitely stucked inside the stone and use enemy:set_enabled(false). Normaly it makes disappear the enemy but it is so short that you don't see the difference.

The only very little annoyance is that the hero is shaking when come in touch with the stone because the code make him go back again and again until you stop making him forward, but it is very acceptable. You can't see it on the picture, but it works also when the stone is rotating on itself, pushing the hero backward if a larger edge of the stone comes to him. Here is the little modification that works like a charm, if it can be useful for someone :



-- This script stop the collision between two sprites with pixel precision
-- if the collision detection with binding box is not sufficient for your needs.
-- recquire to set: stone:set_traversable_by(true) otherwise binding box are used.

stone:add_collision_test("sprite",
    function(sprite, entity)
        stone:collision_do(sprite, entity)
    end)

function stone:collision_do (sprite, entity)
   
    local angle = sprite:get_angle(entity) 
    local movement = sol.movement.create("straight")
 
    movement:set_speed(300) -- higher value or sometimes hero is stucked in the sprite
    movement:set_max_distance(1) -- take the entity backward from 1px
    movement:set_angle(angle)    -- is sufficient and more natural
       
    if entity:get_type() == "hero" then
       entity:freeze()
    else
       entity:set_enabled(false) 
    end
   
    movement:start(entity, function()
        if entity:get_type() == "hero" then
           entity:unfreeze()
        else
           entity:set_enabled(true)
end
    end)
end


I don't know why the two images i have uploaded was removed so fast, so i have uploaded one new in an attachment to be sure it last more. You can see that even with a lot of simple green soldiers, none goes through the stone and pass around it.

Thanks Christopho.