Python(Pygame)でフラッピーバード(Flappy Bird)を作る方法

スポンサーリンク

Flappy birdはもともと、画面をタップして鳥を飛ばすモバイルゲームとして発売されました。

鳥がパイプや画面の端にぶつかるとゲームが終了し、プレイヤーは再スタートする必要があります。

この記事では、鳥をアップキーやスペースバーで操作するコンピュータ版のゲームを作りました。

コードを書くのにPython言語を使用します。

また、ビデオゲームを書くために設計されたクロスプラットフォームのPythonモジュールであるPygameを使用する予定です。

Pygameには、Pythonプログラミング言語で使用するために設計されたコンピュータグラフィックスとサウンドのライブラリが含まれています。

Pygameは、クライアントサイドのアプリケーションを作成するのに適しており、スタンドアロンの実行ファイルにラップすることができる可能性があります。

そのため、このプロジェクトでは、pythonとPygameの予備知識が必要です。

スポンサーリンク

PythonでFlappy Birdゲームを作る

1. モジュールのインポート

このプロジェクトでは、必要なモジュールをインポートしています。

ゲームに使用する乱数の生成には random を使用します。

プログラムの終了には、sysモジュールの sys.exit を使用します。

3行目と4行目では、それぞれPygameとPygameの基本的なインポートをインポートしています。

1
2
3
4
import random
import sys
import pygame
from pygame.locals import *

2. グローバル変数の宣言

このステップでは、ゲームに必要なさまざまなグローバル変数を宣言します。

まず、fps`(frames per second)、screen_width、screen_heightに値を設定します。

スクリーンは pygame.display.set_mode() 関数の引数として screen_widthscreen_height を指定して作成します。

次に、ベース画像のy座標を与えるground-y変数と、ゲームに使用する様々な画像やサウンドを格納するgame_imagesとgame_soundsの2つのディクショナリを作成します。

そして、これらの変数に、プレイヤー(鳥)、背景、パイプ、タイトルの画像をパスを指定して格納します。

1
2
3
4
5
6
7
8
9
10
11
fps = 32
screen_width = 289
screen_height = 511
screen = pygame.display.set_mode((screen_width,screen_height))
ground_y = screen_height*0.8
game_images = {}
game_sounds = {}
player = 'gallery/images/bird.png'
background = 'gallery/images/background.png'
pipe = 'gallery/images/pipe.png'
title = 'gallery/images/title.png'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
if __name__ == "__main__":
    pygame.init()
    fps_clock = pygame.time.Clock()
    pygame.display.set_caption('Flappy Bird')
    game_images['numbers'] = (
        pygame.image.load('gallery/images/0.png').convert_alpha(),
        pygame.image.load('gallery/images/1.png').convert_alpha(),
        pygame.image.load('gallery/images/2.png').convert_alpha(),
        pygame.image.load('gallery/images/3.png').convert_alpha(),
        pygame.image.load('gallery/images/4.png').convert_alpha(),
        pygame.image.load('gallery/images/5.png').convert_alpha(),
        pygame.image.load('gallery/images/6.png').convert_alpha(),
        pygame.image.load('gallery/images/7.png').convert_alpha(),
        pygame.image.load('gallery/images/8.png').convert_alpha(),
        pygame.image.load('gallery/images/9.png').convert_alpha()
        )
    game_images['message'] = pygame.image.load('gallery/images/message.png').convert_alpha()
    game_images['base'] = pygame.image.load('gallery/images/base.png').convert_alpha()
    game_images['pipe'] = (
        pygame.transform.rotate(pygame.image.load(pipe).convert_alpha(), 180),
        pygame.image.load(pipe).convert_alpha()
        )
    game_images['background'] = pygame.image.load(background).convert_alpha()
    game_images['player'] = pygame.image.load(player).convert_alpha()
    game_images['title'] = pygame.image.load(title).convert_alpha()
 
    #Game Sounds
    game_sounds['die'] = pygame.mixer.Sound('gallery/audio/die.wav')
    game_sounds['hit'] = pygame.mixer.Sound('gallery/audio/hit.wav')
    game_sounds['point'] = pygame.mixer.Sound('gallery/audio/point.wav')
    game_sounds['swoosh'] = pygame.mixer.Sound('gallery/audio/swoosh.wav')
    game_sounds['wing'] = pygame.mixer.Sound('gallery/audio/wing.wav')
 
    while True:
        welcomeScreen()
        mainGame()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def welcomeScreen():
    player_x = int(screen_width/8)
    player_y = int((screen_height - game_images['player'].get_height())/2)
    message_x = int((screen_width - game_images['message'].get_width())/2)
    message_y = int(screen_height*0.2)
    title_x = int((screen_width - game_images['message'].get_width())/2)
    title_y = int(screen_height*0.04)
    base_x = 0
    while True:
        for event in pygame.event.get():
            if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
                pygame.quit()
                sys.exit()
            elif event.type == KEYDOWN and (event.key == K_SPACE or event.key == K_UP):
                return
            else:
                screen.blit(game_images['background'],(0,0))   
                screen.blit(game_images['message'],(message_x,message_y))
                screen.blit(game_images['player'],(player_x,player_y))
                screen.blit(game_images['base'],(base_x,ground_y))
                screen.blit(game_images['title'],(title_x,title_y))
                pygame.display.update()
                fps_clock.tick(fps)

3. 3. 関数 “⌈Main⌋ の作成

次に、ゲームを開始するmain関数を作成し、pygame.init()を使用してすべてのpygameモジュールを初期化します。

また、pygame.tick.Clock()関数を使用して、瞬間の時間を追跡するための変数 fps_clock を作成します。

次に、メインゲームウィンドウにタイトルを付け、すべての画像を最初のタプルに格納し、それを game_images 辞書の ‘numbers’ キーに代入します。

画像のパスを引数として pygame.image.load() を使用し、 convert_alpha() で画像のピクセルフォーマット(ピクセル単位のアルファベットも含む)を変更します。

同様に、メッセージ、ベース、パイプ、背景、プレーヤー、タイトルの画像を、様々なキーを使って辞書に追加します。

パイプについては、pygame.transform.rotate()関数を使って画像を180度回転させ、反転させたパイプ画像も追加しています。

次に、さまざまなキーを使用して、サウンドを game_sounds 辞書に追加します。

これは画像の場合と似ていますが、ここでは pygame.mixer.Sound() 関数を使用して、さまざまなサウンドのパスを引数としてサウンドを格納します。

そして、後のセクションで定義する welcomeScreen()mainGame() の関数を呼び出すループを開始します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def mainGame():
    score = 0
    player_x = int(screen_width/8)
    player_y = int(screen_height/2)
    base_x = 0
 
    newPipe1 = getRandomPipe()
    newPipe2 = getRandomPipe()
 
    upperPipes = [
        {'x': screen_width+200, 'y': newPipe1[0]['y']},
        {'x': screen_width+200+(screen_width/2), 'y': newPipe2[0]['y']}
    ]
 
    lowerPipes = [
        {'x': screen_width+200, 'y': newPipe1[1]['y']},
        {'x': screen_width+200+(screen_width/2), 'y': newPipe2[1]['y']}
    ]
 
    pipeVelX = -4
 
    playerVelY = -9
    playerMaxVelY = 10
    playerMinVelY = -8
    playerAccY = 1
 
    playerFlapVel = -8
    playerFlapped = False
 
 
    while True:
        for event in pygame.event.get():
            if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
                pygame.quit()
                sys.exit()
            if event.type == KEYDOWN and (event.key == K_SPACE or event.key == K_UP):
                if player_y > 0:
                    playerVelY = playerFlapVel
                    playerFlapped = True
                    game_sounds['wing'].play()
 
        crashTest = isCollide(player_x, player_y, upperPipes, lowerPipes)
        if crashTest:
            return
 
        playerMidPos = player_x + game_images['player'].get_width()/2  
        for pipe in upperPipes:
            pipeMidPos = pipe['x'] + game_images['pipe'][0].get_width()/2
            if pipeMidPos<= playerMidPos < pipeMidPos + 4:
                score +=1
                print(f"Your Score is {score}")
                game_sounds['point'].play()
 
        if playerVelY <playerMaxVelY and not playerFlapped:
            playerVelY += playerAccY
 
        if playerFlapped:
            playerFlapped = False
        playerHeight = game_images['player'].get_height()
        player_y = player_y + min(playerVelY, ground_y - player_y - playerHeight)  
 
        for upperPipe, lowerPipe in zip(upperPipes, lowerPipes):
            upperPipe['x'] += pipeVelX
            lowerPipe['x'+= pipeVelX
 
        if 0<upperPipes[0]['x']<5:
            newPipe = getRandomPipe()
            upperPipes.append(newPipe[0])
            lowerPipes.append(newPipe[1])  
 
        if upperPipes[0]['x'] < -game_images['pipe'][0].get_width():
            upperPipes.pop(0)
            lowerPipes.pop(0)  
 
        screen.blit(game_images['background'], (0, 0))
        for upperPipe, lowerPipe in zip(upperPipes, lowerPipes):
            screen.blit(game_images['pipe'][0], (upperPipe['x'], upperPipe['y']))
            screen.blit(game_images['pipe'][1], (lowerPipe['x'], lowerPipe['y']))
        screen.blit(game_images['base'], (base_x, ground_y))   
        screen.blit(game_images['player'], (player_x, player_y))
 
        myDigits = [int(x) for x in list(str(score))]
        width = 0
        for digit in myDigits:
            width += game_images['numbers'][digit].get_width()
        Xoffset = (screen_width - width)/2 
 
        for digit in myDigits:
            screen.blit(game_images['numbers'][digit], (Xoffset, screen_height*0.12))
            Xoffset += game_images['numbers'][digit].get_width()
        pygame.display.update()
        fps_clock.tick(fps)

4. welcomeScreen “関数の作成

では、ゲーム開始時にウェルカムスクリーンを表示する welcomeScreen() 関数を定義します。

まず、プレイヤー、メッセージ、タイトルの画像のx座標とy座標を指定します。

引数は試行錯誤で選んでいますので、お好みの値に変更してください。

また、ここではbaseのx座標を与えています。

そして、whileループを開始すると、常にTrueになり、コントロールがquitと言わない限り止まらないループを開始します。

ここでは、pygame.event.get()関数を使ってゲーム中に発生するすべてのイベントを分析するためにforループを利用しています。

そして、escapeキーを押してquitタイプのイベントが発生するたびに、ゲームウィンドウを閉じることをチェックします。

次に、アップキーやスペースボタンを押したかどうかをチェックします。

もしそうなら、この関数から戻り、ゲームを開始します。

そして、キーもボタンも押されていない場合は、ウェルカムスクリーンを表示します。

そのために、screen.blit()関数を使って背景、メッセージ、プレイヤー、ベース、タイトルの画像を配置します。

最後に、pygame.display.update()を使ってウィンドウを更新し、fpsの値を引数としてクロック変数を更新し、ちょうど1秒間に32フレームを表示するようにします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def isCollide(player_x, player_y, upperPipes, lowerPipes):
    if player_y>ground_y-25 or player_y<0:
        game_sounds['hit'].play()
        return True
 
    for pipe in upperPipes:
        pipeHeight = game_images['pipe'][0].get_height()
        if (player_y < pipeHeight + pipe['y']) and (abs(player_x - pipe['x']) < game_images['pipe'][0].get_width() - 15):
            game_sounds['hit'].play()
            return True
 
    for pipe in lowerPipes:
        if (player_y + game_images['player'].get_height() > pipe['y']) and (abs(player_x - pipe['x']) < game_images['pipe'][0].get_width() - 15):
            game_sounds['hit'].play()
            return True
 
    return False
 
 
def getRandomPipe():
    pipeHeight = game_images['pipe'][0].get_height()   
    offset = screen_height/3
    y2 = offset + random.randrange(0, int(screen_height - game_images['base'].get_height() - 1.2*offset))
    pipeX = screen_width + 10
    y1 = pipeHeight - y2 + offset
    pipe = [
        {'x': pipeX, 'y': -y1},
        {'x': pipeX, 'y': y2}
    ]
    return pipe

5. mainGame()” 関数の作成

mainGame()関数を定義します。

まず、変数scoreを0に初期化し、プレイヤー画像とベースの座標を再び指定します。

次に、後で定義する getRandomPipe() を使って、画面にブリットするためのパイプを2つ作成します。

次に、上側のパイプ(反転したもの)と下側のパイプのリストを作成し、そのxとyの座標を指定します。

ここでも試行錯誤で値を決めている。

次に、鳥の各方向の速度を表す変数を宣言します。

また、加速度の変数も用意しました。

playerFlapVel は羽ばたき時の速度で、playerFlapped は false に設定されている(鳥が羽ばたくときだけ true になる)。

そしてまた、イベントをチェックします。

  1. まずゲームから抜けるかどうか、trueならゲームを終了します。
  2. 次に、アップキーまたはスペースキーが押されたかどうかをチェックします。もしそうなら、プレーヤーが画面上部の下にいるかどうかをチェックし、もしそうなら、いくつかの更新を行い、.play()を使って翼の音を再生します。
    1. この後、まもなく定義するisCollide()関数を使って、クラッシュしたかどうかをチェックします。もしtrueなら、この関数からリターンします。

次に、スコアのチェックと更新を行います。

プレイヤーの位置、中間位置、パイプの位置から、パイプを横切ったらスコアを増やし、それをコンソールに表示します。

また、各パイプを横切ったときの点数音を鳴らします。

次に、プレイヤーのY方向の速度がまだ最大になっていない場合は、加速度を与えます。

その後、playerFlppedの値を更新し、鳥の位置を更新します。

パイプを左に移動させ、最初のパイプが画面の左端を横切るところで新しいパイプを追加します。

また、パイプが画面外に出ているかどうかを確認し、出ていれば削除し、パイプとスコアを画面に配置し、後で、表示画面を更新します。

スコアについては、まずスコアの全桁にアクセスし(スコアが1桁以上ある場合)、必要な画像を配置します。

また時計を更新します。

Images Used Flappy Bird
Images Used Flappy Bird

6. isCollide()関数とgetRandomPipe()関数

isCollide()関数では、まず、ベースのインラインの上部にぶつかったかどうかをチェックし、鳥の位置とパイプの位置を比較して、上部のパイプとの衝突を調べます。

下側のパイプについても同じことを繰り返す。

いずれかの衝突条件が真であれば、ヒット音を再生して True を返します。

getRandomPipe()関数では、パイプの高さをpipeHeight変数に格納し、screen_widthの3分の1をoffset変数に格納しています。

次に、ランダム関数を用いてパイプのx、y座標の値を等間隔に、ただし上下のパイプの大きさを変えて代入しています。

そして、その座標をpipeというリストに格納し、それを返す。

Audio Used Flappy Bird
Audio Used Flappy Bird

最終的な出力

以下のビデオは、最終的なflappy birdゲームの出力を示している

まとめ

今日、私たちはゼロから自分たち自身のフラッピーバードゲームを作りました。

気に入ってもらえるといいのですが。

読んでくれてありがとうございました。

タイトルとURLをコピーしました