More Scene Help
Hit boxes. The bane of my existence (at the moment at least).
I'm referring to hit boxes as in a button. If you press the button, something happens. If you press anywhere other else on the screen however, nothing happens.
The way I figured out how to do this is to create a rectangle with many if statements.
(If this didn't make sense I'll provide an example).
This method, however, requires a lot of code. And I'm going to have five buttons at least. There has to be an easier way!
That is where you (yes, you on the other side of the screen) come in. If you know how, then I'd like to know.
Thanks in advance,
omz last edited by
There is an easier way. The scene module has <code>Rect</code> and <code>Point</code> classes that can be used with the <code>in</code> operator. You can see an example of this in the included "Cascade" example. Have a look at the <code>hit_test</code> method in the <code>Tile</code> class.
If you're working with layers, a typical usage would be to check whether a touch is inside a layer's frame. This can be done very easily like this:
<pre>def touch_began(self, touch):
if touch.location in self.some_layer.frame:
# do something...</pre>
Thanks for your helpful (and speedy) reply! That being said, I'm still having some "slight" trouble... As I'm not using layers, I'm trying to use the "Cascade" method.
I think the root of my trouble stems from the inputs into the "Tile" class and then the "hit_test" function.
I'll walk through the following code so you can (hopefully) understand what is going on in my brain:
<pre> def hit_test(self, touch):
frame = Rect(self.x * tile_size + self.offset.x,
self.y * tile_size + self.offset.y,
return touch.location in frame</pre>
So let me see...
Line 1) Basic enough. creates the function and has the inputs self and touch. Touch consists of touch.x and touch.y.
Line 2) creates the variable frame. QUESTION: is frame just the name you used, or does it have to be frame?
Frame represents a rectangle. the x value is (ignoring tile_size and self.offset.x/self.offset.y) self.x (which was defined in the initial setup. I'll go through that later), the y value is self.y, and then the width and height is tile_size (defined earlier).
Line 3) return (as a value) "touch.location in frame". So, in the variable that we created above is somewhere touch.location. But isn't it one of the inputs?
Now to the initial setup (which I probably should have done first):
<pre> def init(self, image, x, y):
self.offset = Point() # used for falling animation
self.selected = False
self.image = image
self.x, self.y = x, y</pre>
So now we have four inputs. self, image, x, and y. Only problem is, where do they come from? Also, it appears to me that "self.selected = False" and "self. image = image" aren't used in the "Tile" class (are they, perhaps, carried out of the class into the "Game" class?). Nonetheless, where does x and y come from?
Well, it seems to me that the input from the "main" code is as follows:
<pre>for i in xrange(cols * rows):
tile = Tile(images[randint(0, len(images)-1)], i % cols, i / cols)</pre>
I have no clue what that means... I take that back, I think I understand it a little. The "images[randint(0, len(images)-1)]" is the image input, "i%cols" is x, and "i/cols" is the y. I don't fully get the meaning of each though.
If I didn't make sense (or left something out) let me know and I'll clarify/modify the post. Thanks for dealing with rookies like me.
Sebastian last edited by
From the documentation:
<i><b>Points</b> support the <b>in</b> operator to test whether they lie within a given <b>Rect</b>.</i>
Because touch.location is a <b>Point</b>, and some_layer.frame is a <b>Rect</b>, you can use it like omz described. i.e:
<PRE>if touch.location in self.some_layer.frame:
# do something...</PRE>
That means you can also do this:
<PRE>if some_point in some_rect:
# do something...</PRE>
Here's how I would check if a button was touched:
from scene import *
self.button = Rect(self.size.w/2-100, self.size.h/2-100, 200, 200)
self.button_colour = Color(1,0,0) def draw(self): background(0,0,0) fill(*self.button_colour) rect(*self.button) def touch_began(self, touch): # if touch.location is inside button if touch.location in self.button: self.button_colour = Color(0,1,0) # if touch.location is anywhere else else: self.button_colour = Color(1,0,0)
omz last edited by
"Line 2) creates the variable frame. QUESTION: is frame just the name you used, or does it have to be frame?"
Doesn't matter, it's just a variable name.
"Line 3) return (as a value) "touch.location in frame". So, in the variable that we created above is somewhere touch.location. But isn't it one of the inputs?"
It returns <code>True</code> if the location of the touch is within the bounds of the rectangle (<code>frame</code>) created earlier, and <code>False</code> otherwise. This works because the <code>Rect</code> class overloads the <code>in</code> operator. Semantically, you could think of it as <code>frame.contains(touch.location)</code> (which wouldn't work, this is just to illustrate what it means).
"So now we have four inputs. self, image, x, and y. Only problem is, where do they come from?"
They are passed as arguments when creating a <code>Tile</code> object. You can see these arguments in the <code>new_game</code> method.
"Also, it appears to me that "self.selected = False" and "self. image = image" aren't used in the "Tile" class (are they, perhaps, carried out of the class into the "Game" class?)."
You're right that the <code>selected</code> and <code>image</code> attributes aren't used directly in the <code>Tile</code> class. They're used in the <code>draw</code> method of the <code>Game</code> class.
"I have no clue what that means... I take that back, I think I understand it a little. The "images[randint(0, len(images)-1)]" is the image input, "i%cols" is x, and "i/cols" is the y. I don't fully get the meaning of each though."
Well, you understood most of it. ;) The rest is just simple math. <code>%</code> is the <em>modulo</em> operator, in case you didn't know that, it returns the <em>rest</em> of a division. The for-loop basically just sets up the x and y positions of the tiles based on the counter variable <code>i</code>. You can think of it as going through a grid with numbered tiles, like this:
<pre>0 1 2 3
4 5 6 7
8 9 10 11</pre>
The tile labelled as <code>6</code> would have an <code>x</code> value of <code>2</code> (counted from zero) and a <code>y</code> value of <code>1</code>, which leads us to <code>y = 6 / 4 = 1</code> (rounded down because this is an integer division) and <code>x = 6 % 4 = 2</code> (the rest of the division).
To be honest, this would probably be easier to understand when written as two nested loops which would give the same result:
<pre>for x in xrange(cols):
for y in xrange(rows):
tile = Tile(image, x, y)
(don't get confused by the name of the <code>xrange</code> function, the x here has nothing to do with the coordinates, it's just a slightly more efficient variant of the <code>range</code> function that is often used for loops.)
jose3f23 last edited by ccc
My two cents:
from scene import * class Label(): def __init__(self): self.text = '' def draw(self): text(self.text, font_size = 16, x = 200, y = 200, alignment = 9) # ------------------- end class label --------------- class Buttons(Layer): def __init__(self): self.bstr=['BUT1','BUT2','BUT3','BUT4'] def draw(self): tint(0.1,0.1,0.1) for i in range(4): rect(10+80*i,10,70,50) text(self.bstr[i], font_size = 16, x=40+80*i, y = 35, alignment = 5) def pressed(self,x,y): n='NONE' if y>80: return 'NONE' for i in range(4): if ((x > 80*i) and (x < 80+80*i)): n = i return self.bstr[n] # ------------------- end class buttons --------------- class MyScene (Scene): def setup(self): self.but='NONE' self.buttons=Buttons() self.label = Label() def draw(self): background(0.9, 0.9, 0.9) self.buttons.draw() self.label.draw() def touch_began(self, touch): pass def touch_moved(self, touch): pass def touch_ended(self, touch): self.but=self.buttons.pressed(touch.location.x,touch.location.y) self.label.text=self.but run(MyScene())
Thanks for the simple code! That (combined with omz) really explained the hit box.
Thanks for the very detailed explanations! Cascade makes more sense now, as does hit boxes. I learned quite a lot today :)
I haven't looked at yours yet (only just finished supper), but I'll look at it right away! Then I'll comment again.
eliskan175 last edited by
Nice, I learned something new too! Didn't know xrange was better than range for iterating through loops. Apparently, the only time we should use range is when we actually need the list.
Gotta love lurking other peoples threads haha
Now for my contribution...
@Cubbarooney - Hit tests are easier than they appear, depending on what you need them to do. If you look at some of my projects, you will see they all contain a hit test function (the best one imo is the one I'm using in my Space Shooter and RTS demo, which checks a single point to see if it's hitting a box). I use these functions because often times the object I'm checking to be hit isn't technically a Rect object, they are images. Either way it's identical to testing a rectangle.
Hit testing circles is a bit trickier especially if you're like me and didn't pay much attention in geometry. Here's a few working examples of hit tests that I did: http://omz-software.com/pythonista/forums/discussion/138/crude-hittest#Item_2