2018年6月9日 星期六

Kivy PongGame adaption

markdown Lately I try to do some side projects and I found an app might be great. So I start to look for some Python libraries as a GUI solution. I compared Tkinter, PyQt, WxPython,  Kivy. I want it free, modern, and have good API reference. Tkinter looks old, PyQt is not free and API of PySide is somewhat difficult for me to read. WxPython is good, but I like the UI of Kivy more. Therefore I have gone through the tutorials and Development guide, and also the PongGame one. Following the steps is really easy and it did not take much time to complete. As the author said, the app was the bare minimum to understand the application development in Kivy, so I made some improvements, mostly to make it more naturally, and I think share it would be interesting. Here are the improvements I did: 1. Instead of Touch event( hard to play by PC users), I try to read the API and use Window.KeyBoard class. 2. Moving the paddle was not smooth and it can stuck when you change direction promptly. Here it is: #pong.kv ``` #:kivy 1.0.9 #step2 : size_x:self.size_x size_y:self.size_y size: self.size_x, self.size_y canvas: Ellipse: pos: self.pos size: self.size : size: 25, 200 canvas: Rectangle: pos:self.pos size:self.size : #ball id here ball: pong_ball p1: player_left p2: player_right canvas: Rectangle: pos: self.center_x - 5, 0 size: 5, self.height Label: font_size: 70 center_x: root.width / 4 top: root.top - 5 text: str(root.p1.score) Label: font_size: 70 center_x: root.width * 3 / 4 top: root.top - 5 text: str(root.p2.score) PongBall: id: pong_ball center: self.parent.center PongPaddle: id: player_left x: root.x center_y: root.center_y PongPaddle: id: player_right x: root.width - self.width center_y: root.center_y ``` #PongGame.py ``` #Original by the author of Kivy- inclement, Adapted by iamlockon on 2018-06-09 #More PongGame information from the author: #https://kivy.org/docs/tutorials/pong.html from kivy.app import App from kivy.uix.widget import Widget from kivy.properties import NumericProperty, ReferenceListProperty, ObjectProperty from kivy.vector import Vector from kivy.clock import Clock from kivy.core.window import Window class PongPaddle(Widget): score = NumericProperty(0) #setting paddle speed vel_paddle_y = NumericProperty(0) #track the key states with mask key_mask = NumericProperty(0) def bounce_ball(self, ball): if self.collide_widget(ball): vx, vy = ball.velocity offset = (ball.center_y - self.center_y) / (self.height / 2) bounced = Vector(-1 * vx, vy) vel = bounced *1.1 ball.velocity = vel.x, vel.y + offset def move(self): #12,3: both pressed ; 0: both unpressed --> stop if self.key_mask in (12, 3, 0): self.vel_paddle_y = 0 #8,2: pressing up if self.key_mask in (8, 2): self.vel_paddle_y = 8 #4,1: pressing down if self.key_mask in (4, 1): self.vel_paddle_y = -8 self.center_y = self.vel_paddle_y + self.center_y #step2 class PongBall(Widget): size_x = NumericProperty(50) size_y = NumericProperty(50) velocity_x = NumericProperty(0) velocity_y = NumericProperty(0) velocity = ReferenceListProperty(velocity_x, velocity_y) def move(self): self.pos = Vector(*self.velocity) + self.pos class PongGame(Widget): ball = ObjectProperty(None) p1 = ObjectProperty(None) p2 = ObjectProperty(None) def __init__(self, **kwargs): super(PongGame, self).__init__(**kwargs) #Get a keyboard instance self._keyboard = Window.request_keyboard( self._keyboard_closed, self, 'text') if self._keyboard.widget: pass #Binding key events to keyboard self._keyboard.bind(on_key_down=self._on_keyboard_down) self._keyboard.bind(on_key_up=self._on_keyboard_up) def _keyboard_closed(self): print('My kb has been closed!') def _on_keyboard_down(self, keyboard, keycode, *args): ''' Setting the key_mask of paddles(players) respectively. You might need some bitwise operation knowledge to understand this. 'c' for player 1 up, 'v' for player 1 down. 'n' for player 2 up, 'm' for player 2 down. bit: 4 3 2 1 key: c v n m ex: 0 1 0 0 -> 4, player 1 goes down ex: 1 1 0 1 -> 13, player 1 stops, player 2 moves down ''' if keycode[1] == 'c': self.p1.key_mask = self.p1.key_mask | 8 return True if keycode[1] == 'v': self.p1.key_mask = self.p1.key_mask | 4 return True if keycode[1] == 'n': self.p2.key_mask = self.p2.key_mask | 2 return True if keycode[1] == 'm': self.p2.key_mask = self.p2.key_mask | 1 return True def _on_keyboard_up(self, keyboard, keycode): if keycode[1] == 'c': self.p1.key_mask = self.p1.key_mask & 7 return True if keycode[1] == 'v': self.p1.key_mask = self.p1.key_mask & 11 return True if keycode[1] == 'n': self.p2.key_mask = self.p2.key_mask & 13 return True if keycode[1] == 'm': self.p2.key_mask = self.p2.key_mask & 14 return True def serve_ball(self, vel=(4, 0)): self.ball.center = self.center self.ball.velocity = vel def update(self, dt): self.ball.move() #go outside the fringes, pull it back 2 pixels to make sure it can still move. if self.p1.top > self.top: self.p1.top = self.top-2 if self.p1.pos[1] < self.x: self.p1.pos[1] = self.x+2 self.p1.move() if self.p2.top > self.top: self.p2.top = self.top-2 if self.p2.pos[1] < self.x: self.p2.pos[1] = self.x+2 self.p2.move() self.p1.bounce_ball(self.ball) self.p2.bounce_ball(self.ball) if (self.ball.y < self.y) or (self.ball.top > self.top): self.ball.velocity_y *= -1 if self.ball.x < self.x: self.p2.score += 1 self.serve_ball(vel=(4,0)) if self.ball.x > self.width: self.p1.score += 1 self.serve_ball(vel=(-4,0)) class PongApp(App): def build(self): game = PongGame() game.serve_ball() Clock.schedule_interval(game.update, 1.0 / 60.0) return game if __name__ == "__main__": PongApp().run() ```