omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    Welcome!

    This is the community forum for my apps Pythonista and Editorial.

    For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.


    More Scene Help

    Pythonista
    5
    8
    4859
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • Cubbarooney104
      Cubbarooney104 last edited by

      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,
      Cubbarooney

      1 Reply Last reply Reply Quote 0
      • omz
        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>

        1 Reply Last reply Reply Quote 0
        • Cubbarooney104
          Cubbarooney104 last edited by

          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,
          tile_size, tile_size)
          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.

          Cubbarooney

          1 Reply Last reply Reply Quote 0
          • Sebastian
            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:
            <PRE>
            from scene import *

            class Test(Scene):
            def setup(self):
            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)
            

            run(Test())</PRE>

            1 Reply Last reply Reply Quote 0
            • omz
              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)
              </pre>
              (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.)

              1 Reply Last reply Reply Quote 0
              • jose3f23
                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())
                
                1 Reply Last reply Reply Quote 0
                • Cubbarooney104
                  Cubbarooney104 last edited by

                  @Sebastian
                  Thanks for the simple code! That (combined with omz) really explained the hit box.
                  @omz
                  Thanks for the very detailed explanations! Cascade makes more sense now, as does hit boxes. I learned quite a lot today :)
                  @jose3f
                  I haven't looked at yours yet (only just finished supper), but I'll look at it right away! Then I'll comment again.

                  Cubbarooney

                  1 Reply Last reply Reply Quote 0
                  • eliskan175
                    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

                    1 Reply Last reply Reply Quote 0
                    • First post
                      Last post
                    Powered by NodeBB Forums | Contributors