Gosu Forums
Topic Gosu / Gosu Exchange / Collision detection in a roguelike
By max_n_ruby Date 2016-01-02 22:36
I'm writing a basic roguelike and I can't figure out collision detection from all four sides. Looking for suggestions.

I've been trying this method from the Captain Ruby code:

`def solid?(x, y)      @x.between?(x, x + 20) and @y.between?(y, y + 20)    end`

This method is in my Map class. It detects collisions on two of four sides.

If I reverse the instance variable and the method arguments like so:

`def solid?(x, y)          x.between?(@x, @x + 20) and y.between(@y, @y + 20)        end`

It detects collisions on the other 2 of 4 sides. :P

Any suggestions on how to put this into one method?

side note: I really like Gosu. With my modest ruby knowledge I've been able to make some real strides on making games. :D
By jlnr (dev) Date 2016-01-03 00:02
Great to hear that you're enjoying Gosu! :)

I can't tell what `x/y` and `@x/@y` are from just looking at these two lines, but:
`x.between?(@x, @x + 20)`
is not the same as:
`@x.between?(x, x + 20)`

In the first case, x ≥ @x. In the second case, x ≤ @x. Or in other words,

`x.between?(@x, @x + 20)`
is the same as:
`@x.between?(x - 20, x)`

You should be able to put together a combination that works by reversing some parts of the equation :)

But...I wonder if some of these variables should have a different name? If this is in the `Map` class, then there usually shouldn't be a @x/@y, just maybe a @width/@height?
By max_n_ruby Date 2016-01-03 00:14
Ha ha!
`@x ` and `@y` are the x and y coordinates of the tile in the Map class. Sorry.
Thanks. I didn't think of this. I'll give it a try.
By ericathegreat Date 2016-01-04 01:38
I suspect your problem is that you're getting confused by the difference between points and areas.

In your first example:
@x.between?(x, x + 20) and @y.between?(y, y + 20)

@x is a point. More precisely, it's probably the point a the far left edge of your sprite. When you check whether it is between those two ranges, you are checking whether that point is inside that range.

However, your sprite is much bigger than a point. It's probably about 20 px wide, if I were to guess from your code.

Let's imagine that your solid object is at coordinate 20,20. It's covering the area:
`         20 ------ 40          |          |          |         40`

When your moving object's x is at 0, it does not overlap at all. Its area is:
` 0 ------ 20 | | |20`

When your moving object moves 5 pixels to the right, it looks like it should overlap, because the last five pixels overlap with the first five pixels of the solid object.

However, you're only comparing a point! And the @x value is the far left edge of your moving object, which is still only at pixel 5. That's not overlapping yet. In fact, it won't actually overlap until it is completely covering the solid object.

You have two options for this. You can either pick a more meaningful point, or you can compare area to area.

If you're lucky, you might be able to get away with a more meaningful point. Figure out the distance between 0,0 in your image and, say, the base of the characters feet (if you're checking for collision with terrain). Or the tip of the sword (if you're checking for collision with stabby things). Whichever point actually matters in the collision. Add these offsets to your @x and @y values in the calculation and it will trigger when that point enters the collision zone.

More likely though, you need to compare area against area. In that case, you want to do something like this:
( @x.between?(x, x + 20) and @y.between?(y, y + 20) ) || ( (@x+sprite_width).between?(x, x + 20) and (@y+sprite_height).between?(y, y + 20) )
By max_n_ruby Date 2016-01-04 14:24
Yes, I am comparing points. I think I'm on the right track because my `solid?` method returns true/false when it's next to a map tile.
But I'll try out comparing area to area. See what happens.
Here's a link to my code:
https://github.com/ceejaay/ruby_rogue/blob/master/map.rb#L19-L20

My `solid?` method is only checking for a map tile in one direction right now.

When I run the program, I get this:

I'm not sure if it matters, but I'm using text instead of sprites. If I'm correct the X/Y coordinates I'm using start from the top-left corner of the text.

What I'm having trouble with now is checking for which button is being pressed and if the space next to the player is occupied by a map tile.
Plus, I guess I have to check for map tiles in all four directions at the same time.
I think I'm missing a fundamental programming concept. I'm still working through it.
You can see from my code where I've tried some things.
https://github.com/ceejaay/ruby_rogue/blob/master/map.rb#L21-L36

I'll show some of my other attempts at solutions later. I have like 4 branches in my code trying to figure this out. :)
Thanks for your response!
By ericathegreat Date 2016-01-05 02:37
Do you intend to have all movement happen in terms of whole cells? (If you go left, you go left by one whole cell all at once, not gradually moving between tiles?) And the cells are all 20px apart?

If so, the maths for this is actually a lot easier. Just divide @x, @y, x and y by the size of the tiles, and then when you compare the numbers they'll either be exactly the same (collision) or different (not collision)

if ( @x/20 == x/20 &&
@y/20 == y/20 )
# collision, because they're in the same cell.
else
# not a collision, they are in different cells.
end
By max_n_ruby Date 2016-01-05 02:51
Yes. In this roguelike the player will move one 'square' at a time. In this case, the squares are 20 px high. I'm actually not sure how wide the Gosu::Font class makes things. I'll look that up.

I'll try this out. Thanks!
Topic Gosu / Gosu Exchange / Collision detection in a roguelike