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()
Hello, I think you forgot to include ex31.kv
ReplyDeleteIt 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 :)
Python:
ReplyDeleteGood 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/
nice information for beginners.thank you.
ReplyDeletejavacodegeeks
welookups python