This is a very simple clone of Flappy Bird using Kivy, to be used for programming class for teens. The game consists of 3 classes: Bird
, Game
, and ResetBtn
.
Q: How to make it more efficient, clean, and clear for the students? (without using kv
file)
Code Explanation:
The Bird
class inherits from Image
class, a Clock.schedule_interval
will call the update
method of the bird class (method for the continuous fall). It also has on_touch_down
that sets the height of the bird higher than before (discontinuously). There is also a short time image changing effect (Bird facing up after a mouse press, then facing down again after a short time, by the self.fall
increment). I was careful to not put equation of physics equation, so I only use increments for the motion of the bird.
flappy_down = "flappy_down.png"
flappy_up = "flappy_up.png"
class Bird(Image):
def __init__(self):
super().__init__(source = flappy_down)
self.height_frac = 0.5
self.fall = 0
def on_touch_down(self, touch):
self.height_frac += 0.15
self.source = flappy_up
self.fall = 0
def update(self, dt):
self.fall += 1
self.height_frac += -1/100
print(self.height_frac)
self.pos_hint = {'x': 0.5, 'y': self.height_frac}
if self.source != flappy_down and self.fall == 15:
self.source = flappy_down
The Game
class is a FloatLayout
that has 2 children: Bird()
and ResetBtn()
. The walls/obstacles are made without widgets, they are made using Game.canvas
and Rectangle
. Collision is counted whenever the center of the bird's image is inside a rectangle. A new wall will be generated every time the previous wall has passed the position at 40% of the window's width, with random height and bottom position. When collision happens, the game will unschedule
all scheduled intervals.
class Game(FloatLayout):
def __init__(self):
super().__init__()
self.kiwi = Bird()
self.resetbtn = ResetBtn()
self.label = Label(text = "", halign = 'center', valign = 'center', font_size = 100)
self.add_widget(self.kiwi)
self.add_widget(self.resetbtn)
self.add_widget(self.label)
self.kiwi.size_hint = (50/Window.size[0], 50/Window.size[1])
self.kiwi.pos_hint = {'x': 0.5, 'y': 0.5}
self.resetbtn.size_hint = (0.25, 0.1)
self.resetbtn.pos_hint = {'x': 0.05, 'y': 0.85}
self.label.size_hint = (0.5, 0.2)
self.label.pos_hint = {'x': 0.25, 'y': 0.75}
self.walls = []
self.current_wall = None
self.add_random_wall()
def add_wall(self, pos, size, color):
self.rect = Rectangle(pos=pos, size=size)
self.color = Color(*color)
self.canvas.add(self.color)
self.canvas.add(self.rect)
self.walls.append(self.rect)
self.current_wall = self.rect
def add_random_wall(self):
height = random.uniform(100, 400)
pos_y = random.uniform(0, Window.size[1]-400)
self.add_wall((745, pos_y), (50, height), (1, 0.3, 0, 1))
def update(self, dt):
for i in self.walls:
i.pos = (i.pos[0]-100*dt, i.pos[1])
self.walls = [i for i in self.walls if i.pos[0] >= -50]
if self.walls[-1].pos[0] <= 0.5*Window.size[0] - 70:
self.add_random_wall()
print(len(self.walls))
kiwi_windowpos = [self.kiwi.pos_hint['x']*Window.size[0], \
self.kiwi.pos_hint['y']*Window.size[1]]
print(kiwi_windowpos)
if ((self.current_wall.pos[0] <=(kiwi_windowpos[0] + 25) <= self.current_wall.pos[0] + 50) \
and (self.current_wall.pos[1] <=(kiwi_windowpos[1] + 25) <= self.current_wall.pos[1] + self.current_wall.size[1])) \
or (kiwi_windowpos[1] + 25 <= 0) or (kiwi_windowpos[1] + 25 >= Window.size[1]):
self.label.text = "Game Over"
Clock.unschedule(self.kiwi_update)
Clock.unschedule(self.wall_update)
def reset(self):
self.clear_widgets()
for i in self.walls:
del i
del self.current_wall
self.canvas.clear()
Clock.unschedule(self.kiwi_update)
Clock.unschedule(self.wall_update)
self.kiwi = Bird()
self.resetbtn = ResetBtn()
self.label = Label(text = "", halign = 'center', valign = 'center', font_size = 100)
self.add_widget(self.kiwi)
self.add_widget(self.resetbtn)
self.add_widget(self.label)
self.kiwi.size_hint = (50/Window.size[0], 50/Window.size[1])
self.kiwi.pos_hint = {'x': 0.5, 'y': 0.5}
self.resetbtn.size_hint = (0.25, 0.1)
self.resetbtn.pos_hint = {'x': 0.05, 'y': 0.85}
self.label.size_hint = (0.5, 0.2)
self.label.pos_hint = {'x': 0.25, 'y': 0.75}
self.walls = []
self.current_wall = None
self.add_random_wall()
self.kiwi_update = Clock.schedule_interval(self.kiwi.update, 0.01)
self.wall_update = Clock.schedule_interval(self.update, 0.01)
Here is the reset button:
class ResetBtn(Button):
def __init__(self):
super().__init__(text = "Reset", halign = "center", valign = "center")
def on_release(self):
super().on_release()
self.parent.reset()
The imports:
import random
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.clock import Clock
from kivy.graphics import Rectangle, Color
Image files:
flappy_up.png
:
flappy_down.png
:
Implementation:
import random
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.clock import Clock
from kivy.graphics import Rectangle, Color
flappy_down = "/home/asus/Arief_tempo/Tutor_JKT/Python_Tutor/FlappyKivy-master/images/flappy.png"
flappy_up = "/home/asus/Arief_tempo/Tutor_JKT/Python_Tutor/FlappyKivy-master/images/flappyup.png"
class ResetBtn(Button):
def __init__(self):
super().__init__(text = "Reset", halign = "center", valign = "center")
def on_release(self):
super().on_release()
self.parent.reset()
class Bird(Image):
def __init__(self):
super().__init__(source = flappy_down)
self.height_frac = 0.5
self.fall = 0
def on_touch_down(self, touch):
self.height_frac += 0.15
self.source = flappy_up
self.fall = 0
def update(self, dt):
self.fall += 1
self.height_frac += -1/100
print(self.height_frac)
self.pos_hint = {'x': 0.5, 'y': self.height_frac}
if self.source != flappy_down and self.fall == 15:
self.source = flappy_down
class Game(FloatLayout):
def __init__(self):
super().__init__()
self.kiwi = Bird()
self.resetbtn = ResetBtn()
self.label = Label(text = "", halign = 'center', valign = 'center', font_size = 100)
self.add_widget(self.kiwi)
self.add_widget(self.resetbtn)
self.add_widget(self.label)
self.kiwi.size_hint = (50/Window.size[0], 50/Window.size[1])
self.kiwi.pos_hint = {'x': 0.5, 'y': 0.5}
self.resetbtn.size_hint = (0.25, 0.1)
self.resetbtn.pos_hint = {'x': 0.05, 'y': 0.85}
self.label.size_hint = (0.5, 0.2)
self.label.pos_hint = {'x': 0.25, 'y': 0.75}
self.walls = []
self.current_wall = None
self.add_random_wall()
def add_wall(self, pos, size, color):
self.rect = Rectangle(pos=pos, size=size)
self.color = Color(*color)
self.canvas.add(self.color)
self.canvas.add(self.rect)
self.walls.append(self.rect)
self.current_wall = self.rect
def add_random_wall(self):
height = random.uniform(100, 400)
pos_y = random.uniform(0, Window.size[1]-400)
self.add_wall((745, pos_y), (50, height), (1, 0.3, 0, 1))
def update(self, dt):
for i in self.walls:
i.pos = (i.pos[0]-100*dt, i.pos[1])
self.walls = [i for i in self.walls if i.pos[0] >= -50]
if self.walls[-1].pos[0] <= 0.5*Window.size[0] - 70:
self.add_random_wall()
print(len(self.walls))
kiwi_windowpos = [self.kiwi.pos_hint['x']*Window.size[0], \
self.kiwi.pos_hint['y']*Window.size[1]]
print(kiwi_windowpos)
if ((self.current_wall.pos[0] <=(kiwi_windowpos[0] + 25) <= self.current_wall.pos[0] + 50) \
and (self.current_wall.pos[1] <=(kiwi_windowpos[1] + 25) <= self.current_wall.pos[1] + self.current_wall.size[1])) \
or (kiwi_windowpos[1] + 25 <= 0) or (kiwi_windowpos[1] + 25 >= Window.size[1]):
self.label.text = "Game Over"
Clock.unschedule(self.kiwi_update)
Clock.unschedule(self.wall_update)
def reset(self):
self.clear_widgets()
for i in self.walls:
del i
del self.current_wall
self.canvas.clear()
Clock.unschedule(self.kiwi_update)
Clock.unschedule(self.wall_update)
self.kiwi = Bird()
self.resetbtn = ResetBtn()
self.label = Label(text = "", halign = 'center', valign = 'center', font_size = 100)
self.add_widget(self.kiwi)
self.add_widget(self.resetbtn)
self.add_widget(self.label)
self.kiwi.size_hint = (50/Window.size[0], 50/Window.size[1])
self.kiwi.pos_hint = {'x': 0.5, 'y': 0.5}
self.resetbtn.size_hint = (0.25, 0.1)
self.resetbtn.pos_hint = {'x': 0.05, 'y': 0.85}
self.label.size_hint = (0.5, 0.2)
self.label.pos_hint = {'x': 0.25, 'y': 0.75}
self.walls = []
self.current_wall = None
self.add_random_wall()
self.kiwi_update = Clock.schedule_interval(self.kiwi.update, 0.01)
self.wall_update = Clock.schedule_interval(self.update, 0.01)
class FlApp(App):
def build(self):
g= Game()
g.kiwi_update = Clock.schedule_interval(g.kiwi.update, 0.01)
g.wall_update = Clock.schedule_interval(g.update, 0.01)
return g
if __name__ == "__main__":
FlApp().run()