@ThomasL: Ein paar Anmerkungen zu dem Quelltext:
In der Hauptfunktion ist das ``for i in range(len(sequence)):``-Anti-Pattern.  Man kann da direkt, ohne einen Umweg über einen Index über die `Pipe`-Objekte iterieren.
Vom Datentyp her wäre eine `collections.deque` passender für `pipes`.
Klassen die nur aus einer `__init__()` bestehen sind fast nie wirklich Klassen.  Denn selbst Klassen die aus einer `__init__()` und einer zusätzlichen Methode bestehen, sind sehr oft einfach nur umständlich geschriebene Funktionen.
Das betrifft `Canvas`.  Da werden zudem alle Argumente der `__init__()` an das Objekt gebunden, obwohl später nur zwei davon tatsächlich benötigt werden.
Man kann die ”Klasse” in einem ersten Schritt durch eine Funktion ersetzen, die ein von `collections.namedtuple` abgeleitetes Objekt zurück gibt das die Attribute `screen`, `width`, `height`, und `clock` besitzt.  Wobei ich `clock` da auch gleich heraus nehmen würde, denn das gehört eigentlich nicht zu einer Leinwand und könnte problemlos ein lokaler Name in der Hauptfunktion sein.
Im nächsten Schritt kann man dann auch dieses `namedtuple` loswerden, denn das enthält redundante Daten.  `screen` ist ein `Surface` und von diesen Objekten kann man die Höhe und Breite abfragen.  Es macht wenig Sinn die gleichen Werte extern noch einmal zu speichern.
`Pipe.hits()` hat inkonsistente Rückgabewerte.  Statt `True` und `None` sollte die Methode `True` und `False` liefern.
Die verschachtelten ``if``\s kann man alle zusammenfassen.
``bird.y <= self.top or bird.y >= self.bottom`` hätte ich wahrscheinlich eher als ``not self.top <= bird.y < self.bottom`` ausgedrückt.
Das `fullhd`-Argument ist ein bisschen komisch, denn man kann da `True` angeben, aber trotzdem eine Auflösung die nicht Full-HD ist.
Code: Alles auswählen
#!/usr/bin/env python3
import os
import random
from collections import deque
import pygame as pg
from pygame.locals import *
BLACK = [0, 0, 0]
WHITE = [255, 255, 255]
RED = [255, 0, 0]
def create_canvas(fullhd=False, width=800, height=800, mode=0):
    os.environ['SDL_VIDEO_CENTERED'] = '1'
    pg.init()
    if not fullhd:
        pg.display.set_caption('Flappy Bird')
    screen = pg.display.set_mode([width, height], mode)
    screen.fill(WHITE)
    pg.display.update()
    return screen
class Bird:
    
    GRAVITY = 0.5
    LIFT = -10
    def __init__(self, canvas):
        self.canvas = canvas
        self.y = self.canvas.get_height() // 2
        self.x = 64
        self.velocity = 0
    def show(self):
        pg.draw.circle(self.canvas, BLACK, [self.x, self.y], 10, 0)
    def up(self):
        self.velocity += self.LIFT
    def update(self):
        self.velocity = (self.velocity + self.GRAVITY) * 0.9
        self.y = int(self.y + self.velocity)
        if self.y > self.canvas.get_height():
            self.y = self.canvas.get_height()
            self.velocity = 0
        if self.y < 0:
            self.y = 0
            self.velocity = 0
class Pipe:
    
    WIDTH = 20
    SPEED = 1
    
    def __init__(self, canvas):
        self.canvas = canvas
        self.top = random.randint(100, self.canvas.get_height() - 200)
        self.bottom = self.top + random.randint(100, 150)
        self.x = self.canvas.get_width() + self.WIDTH // 2
        self.highlight = False
    def hits(self, bird):
        if (
            not self.highlight
            and not self.top <= bird.y < self.bottom
            and self.x <= bird.x <= self.x + 1
        ):
            self.highlight = True
            return True
        return False
    def show(self):
        color = RED if self.highlight else BLACK
        pg.draw.rect(self.canvas, color, [self.x, 0, self.WIDTH, self.top], 0)
        pg.draw.rect(
            self.canvas,
            color,
            [self.x, self.bottom, self.WIDTH, self.canvas.get_height()],
            0
        )
    def update(self):
        self.x -= self.SPEED
    def is_offscreen(self):
        return self.x < -self.WIDTH
def main():
    canvas = create_canvas()  # windowed mode
    # canvas = create_canvas(
    #     fullhd=True, width=1920, height=1080, mode=FULLSCREEN
    # )  # fullscreen mode
    bird = Bird(canvas)
    pipes = deque([Pipe(canvas)])
    clock = pg.time.Clock()
    frame_count = 0
    pause = main_exit = False
    while not main_exit:
        clock.tick(60)  # set fps rate
        for event in pg.event.get():
            if event.type == QUIT:
                main_exit = pause = True
                break
            elif event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    main_exit = pause = True
                if event.key == K_UP or event.key == K_w:
                    bird.up()
                if event.key == K_p:
                    pause = not pause   # pause the action
        if not pause:
            frame_count += 1
            canvas.fill(WHITE)  # clear surface
            for pipe in pipes:
                pipe.update()
                pipe.show()
                if pipe.hits(bird):
                    print(' HIT')
            if pipes[0].is_offscreen():
                pipes.popleft()
            bird.update()
            bird.show()
            if frame_count % 300 == 0:
                pipes.append(Pipe(canvas))
            pg.display.update()
    pg.quit()
if __name__ == '__main__':
    main()