Friday, June 20, 2014

31. Controlling Movements

Now, the ball motion, will be partly controlled, by on_touch_down, which is the mouse click, on desktop.




The on_touch_down can be received by any widget as well as the root. If the Ball and Wall classes are modified as such, we can see what are the printouts.




After clicking, the coordinates are passed to each widget. The widget must check the coordinates so it may detect if that particular widget was clicked.




This is shown here. Usually the function collide_point is used to check if a point actually belongs to the widget. Now, if you run the program, and you click on one of the four walls, you will receive a printout, and not if you click anywhere else.




There was a * symbol for touch.pos. This will force Python to make the arguments a tuple as can be seen by this example. This allows passing of many values into a function. The * symbol is used throughout Kivy and Python in general.




This figure shows what is the direction of the ball after a touch or click. The vector goes from ball center to the touch position. If you are at midpoint between the balls, you will be guaranteed a collision.


# figure.py

from kivy.app import App
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.graphics import *
from math import pi,atan

class Figure(RelativeLayout):
    def __init__(self, **kwargs):
        touchPos=100,350.5
        ballPos=200,370.8
        size=50,50
        def lineAng():
            dy = ballPos[1]-touchPos[1]+size[1]/2
            dx = ballPos[0]-touchPos[0]+size[0]/2
            ang=atan(dy/dx)
            ang=ang*180/pi
            return ang
        super(Figure, self).__init__(**kwargs)
        with self.canvas:
            Color(1,1,1)
            Rectangle(pos=(0,0), size=(800,600))
            Color(0,0,1)
            Point(points=(touchPos),pointsize=3)
            Point(points=(ballPos),pointsize=3)
            Point(points=(ballPos[0]+size[0]/2,ballPos[1]+size[1]/2),pointsize=3)
            Line(rectangle=(ballPos+size),width=2)
            Line(points=((ballPos[0]+size[0]/2,
                         ballPos[1]+size[1]/2)+touchPos),width=2)
            ang = lineAng()
            Line(points=(touchPos+(touchPos[0]+25,touchPos[1])),width=2)
            PushMatrix()
            Rotate(angle=2*ang,origin=touchPos)
            Line(points=(touchPos+(touchPos[0]+25,touchPos[1])),width=2)
            PopMatrix()
        b1 = Button(text='touchPos',pos=(touchPos[0]-20,touchPos[1]-40),
                                   size_hint=(.1,.05))
        self.add_widget(b1)
        b2 = Button(text='ballPos',pos=(ballPos[0]-20,ballPos[1]-40),
                                   size_hint=(.1,.05))
        self.add_widget(b2)
        

      
class FigureApp(App):
    def build(self):
        return Figure()

if __name__=='__main__':
    FigureApp().run()



The on_touch_down function is in the root class. First the touch_vector is found, as the difference between two points. Now, we explicitly calculate the unity vector, which has a magnitude of 1. It is multiplied by 5.0, which remains constant for all collisions. This means there is always constant speed.




The first part of the update function is changed, where ball-wall collisions are detected. This is because sometimes we have oscillatory behavior when the ball is almost parallel to one of the edges and makes two contacts in 2 consecutive frames. To avoid this, we always force the ball to go away from the wall. This change is necessary due to the many new angles, for the velocity, now possible.


# ex31.py

from kivy.uix.widget import Widget
from kivy.app import App
from kivy.properties import ListProperty
from kivy.vector import Vector as Vec
from kivy.clock import Clock
import kivy
kivy.require('1.8.0')

class Ball(Widget):
    vel = ListProperty()
    
class Wall(Widget):
    pass

class Ex31(Widget):
      
    def __init__(self, **kwargs):
        super(Ex31, self).__init__(**kwargs)
        self.ball1.pos = self.width/2, self.height/2
        self.ball2.pos = self.width/3, self.height/3

    def on_touch_down(self,touch):
        for ball in [self.ball1,self.ball2]:
            touch_vector = Vec(touch.pos) -  Vec(ball.center)
            touch_vector_mag = touch_vector.length()
            unit_touch_vector = touch_vector/touch_vector_mag
            ball.vel = 5.0*unit_touch_vector

    def update(self,dt):
        for ball in [self.ball1,self.ball2]:
            if ball.collide_widget(self.wall_left):
                ball.vel[0] = abs(ball.vel[0])
            elif ball.collide_widget(self.wall_right):
                ball.vel[0] = -abs(ball.vel[0])
            if ball.collide_widget(self.wall_top):
                ball.vel[1] = -abs(ball.vel[1])
            elif ball.collide_widget(self.wall_down):
                ball.vel[1] = abs(ball.vel[1])
            
        if self.ball1.collide_widget(self.ball2):
            col_vector=Vec(self.ball1.pos)-Vec(self.ball2.pos)
            col_vector_mag=col_vector.length()
            self.ball1.vel = 5.0/col_vector_mag*col_vector
            self.ball2.vel = -5.0/col_vector_mag*col_vector
            
        self.ball1.pos = Vec(self.ball1.vel) + Vec(self.ball1.pos)
        self.ball2.pos = Vec(self.ball2.vel) + Vec(self.ball2.pos)
       
class Ex31App(App):
    def build(self):
        self.title = "Two Bouncing Balls with Widget Collisions"
        ex31 = Ex31()
        Clock.schedule_interval(ex31.update, 1.0/60)
        return ex31

if __name__=='__main__':
    Ex31App().run()



3 comments:

  1. Hello, I think you forgot to include ex31.kv
    It is the same file as ex30.kv except for 31 instead for 30 wherever this number occurs within file.
    4 years later this blog is still useful.
    Thank you :)

    ReplyDelete
  2. Python:

    Good Post! Thank you so much for sharing this pretty post, it was so good to read and useful to improve my knowledge as updated one, keep blogging.

    https://www.emexotechnologies.com/online-courses/python-training-in-electronic-city/

    ReplyDelete