2
\$\begingroup\$

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 Up

flappy_down.png:

Flappy Down


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()
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.