MSBOOKS

プログラミングとか深層学習とか日常の出来事とか

【Pygame】リンゴキャッチゲーム的なものをつくってみた

はじめに

ゲーム制作は昔Dxライブラリというものを使ってやっていたことがあるんですが,研究でpythonを使っているため,pythonのコードでゲーム作れないかなーって思ってpygameというものに出会いました.よくあるリンゴキャッチゲームみたいなものを作ってみました.

f:id:msteacher:20200627173620p:plain

いろんな人にゲームプログラミングを楽しんでもらいたいのでソースコードは全部公開するんですが,画像は公開しないのでもしソースコードから自分で作ってみる場合はみなさん好きな画像をもってきてください.ちなみに↓こんなゲームです.README.txtに操作方法や素材元などが書いてあるので,併せてご覧ください.

www.dropbox.com

環境

環境は以下のようになっています.

プログラム

今回pythonを用いて,見やすさ重視で基本はオブジェクト指向で記述しました.でもあんまり綺麗に書けていないかも….enum型クラス2つ(Difficulty.py, Mode.py)と普通のクラス9つ(game.py(main), Media.py, Play.py, Menu.py, Setting.py, Bye.py, Ranking.py, Star.py, Control.py)から構成されています.それぞれ説明していきます.
まずenum型クラスです.

・Mode.py

from enum import IntEnum
class Mode(IntEnum):
    MENU = 0
    PLAY = 1
    SETTING = 2
    RANKING = 3
    BYE = 4

このゲームのモード(ゲーム画面)はこの5つから構成されています.メニューとゲームメイン(プレイ),設定画面,ランキング画面,終了画面です.

・Difficulty.py

from enum import IntEnum
class Difficulty(IntEnum):
    SIZE = 0
    STAR = 1
    SPEED = 2

設定画面では難易度を設定できます.キャラクターのサイズ,落ちてくる星の量,キャラクターの動く速さです.今更ですが,それぞれの難易度に応じたランキングを作らないとサイズ大きくして,星の落ちる量増やして,スピード早くすれば有利な気がしてきました….

続いてメインクラスの部分です.

・game.py

import pygame
import math
from Mode import Mode
from Difficulty import Difficulty
from Menu import Menu
from Play import Play
from Setting import Setting
from Bye import Bye
from Ranking import Ranking
from Media import Media

'''
ゲームメインクラス
'''
class STAR_GAME():
    def __init__(self):
        pygame.init() # pygame初期化
        pygame.display.set_mode((1200, 675), 0, 32) # 画面設定
        self.screen = pygame.display.get_surface()
        pygame.display.set_caption('catch a star game') # ウィンドウタイトル
        # 各ゲームモードのインスタンスの生成
        self.media = Media()
        self.menu = Menu(self.media)
        self.play = Play(self.media)
        self.setting = Setting(self.media)
        self.bye = Bye(self.media)
        self.ranking = Ranking(self.media)
        self.media.play_bgm(1)
        # 各モードへ分岐するための辞書型
        self.game_mode_list = {Mode.MENU:self.menu, Mode.PLAY:self.play, Mode.SETTING:self.setting, Mode.RANKING:self.ranking, Mode.BYE:self.bye}

    '''
    キャラクター表示に関する設定
    '''
    def chara_set(self, screen, difficulty, rect_player, anim_counter):
        # キャラクターがアニメーションするための操作
        anim_counter += 1
        if anim_counter == 360:
            anim_counter = 0
        player_num = round((math.sin(math.radians(anim_counter))+1)*1.5) # 正弦を使ってアニメーションを変化させる
        # 正弦に応じた画像を難易度設定に応じたサイズに変更して表示
        player = pygame.transform.rotozoom(self.media.player_anim[player_num], 0, 0.5*difficulty[Difficulty.SIZE])

        # 難易度によるキャラクター描画位置ズレ補正
        x, y = rect_player.topleft
        size = (25 * -(difficulty[Difficulty.SIZE] - 2))
        return player, x + size, y + size, anim_counter

    '''
    メインループ
    '''
    def main(self):
        rect_player = self.media.player_anim[0].get_rect()
        rect_player.center = (450, 220) # プレイヤー画像の初期位置
        difficulty = [2, 2, 2] # 難易度の初期化
        game_mode = Mode.MENU
        anim_counter = 0
        while True:
            self.screen.blit(self.media.bg[game_mode], self.media.bg[game_mode].get_rect()) # 背景の描画
            
            # 各ゲームモードへの分岐
            last_game_mode = game_mode
            mode = self.game_mode_list[game_mode]
            game_mode = mode.main(self.screen, difficulty, rect_player)

            # 次ゲームモードが変わるときに初期化を行う
            if last_game_mode == Mode.MENU and game_mode != Mode.MENU:
                self.game_mode_list[game_mode].__init__(self.media)

            # キャラクターの描画
            player, x, y, anim_counter = self.chara_set(self.screen, difficulty, rect_player, anim_counter)
            self.screen.blit(player, (x, y))

            pygame.display.update()


if __name__ == "__main__":
    game = STAR_GAME()
    game.main()

メイン部分になります.キャラクターのアニメーションとか,背景の描画とか,どのクラスでもやるようなことはここで記述しました.各モードは記述方法を均一にして,辞書型で全部1文で呼び出せるようにしました.ここでこだわった点はキャラクターの動きを正弦(サイン)で表現したことです.よりなめらかにキャラクターが動いているのを表現できたと思います.

次にゲームを作る上で必要な画像等のメディアファイル読み込みクラスと,キーボード入力を受け付けるクラスを紹介します.

・Media.py

import pygame
import os
import glob
import sys

'''
Mediaクラス
    画像や音楽などを読み込むクラス
'''
class Media():
    def __init__(self):
        # 背景画像
        back_list = glob.glob(self.resource_path("*.jpg")) # 末尾が.jpgのものをすべて取得
        back_list.sort()
        self.bg = [pygame.image.load(self.resource_path(img)).convert_alpha() for img in back_list]
        # プレイヤー画像
        chara_list = glob.glob(self.resource_path("chara*.png")) # chara〇.pngのものをすべて取得
        chara_list.sort()
        self.player_anim = [pygame.image.load(self.resource_path(chara)).convert_alpha() for chara in chara_list]
        # 星の画像
        self.star = pygame.image.load(self.resource_path("star.png")).convert_alpha() 
        # 効果音
        self.button1 = pygame.mixer.Sound(self.resource_path("button1.wav"))
        self.button2 = pygame.mixer.Sound(self.resource_path("button2.wav"))
        self.kirakira = pygame.mixer.Sound(self.resource_path("kirakira.wav"))
        self.hit = pygame.mixer.Sound(self.resource_path("hit.wav"))
        # ランキング保存テキスト(個人のフォルダに書き残すためにファイルパスはそのまま)
        self.ranking_txt = 'Save\\ranking.txt'
        try:
            os.mkdir("Save/")
        except FileExistsError:
            pass

    def play_bgm(self, num):        
        pygame.mixer.init(frequency = 44100)
        pygame.mixer.music.load(self.resource_path("music{0}.mp3").format(num))
        pygame.mixer.music.play(-1)


    '''
    exe化したとき画像等が_MEIPASSに展開されるためそれを参照する
    '''
    def resource_path(self, relative_path):
        if hasattr(sys, '_MEIPASS'):
            return os.path.join(sys._MEIPASS, relative_path)
        return os.path.join(os.path.abspath("."), relative_path)

前の記事で言ったのですが,exe化するときに_MEIPASSのようなところで画像ファイルなどが展開されるため,resource_path()という関数を付けています.尚,セーブデータは書き込めないので,Saveというフォルダを作って書き込む形にしています.

・Control.py

import pygame
from pygame.locals import *
import sys
''''
Controlクラス
    キーボード入力に応じた処理を返すクラス
'''
class Control():
    def __init__(self):
        pass
    ''''
    キーボード入力を受け付け、命令を返すメソッド
    キーが押されたタイミングで判断する
    @return : 操作
    '''
    def control(self):
        for event in pygame.event.get():
            # 閉じるボタンが押されたとき
            if event.type == QUIT:          
                pygame.quit()
                sys.exit()
            if event.type == KEYDOWN:  # キーを押したとき
                # ESCキーならスクリプトを終了
                if event.key == K_ESCAPE:
                    sys.exit()
                elif event.key == K_LEFT:
                    return 'left'
                elif event.key == K_RIGHT:
                    return 'right'
                elif event.key == K_UP:
                    return 'up'
                elif event.key == K_DOWN:
                    return 'down'
                elif event.key == K_RETURN:
                    return 'return'

        return None

    '''
    キーボード入力を受け付け、命令を返すメソッド
    キーが押されている間ずっと認識する
    @return : 操作
    '''
    def control2(self):
        for event in pygame.event.get():
            if event.type == QUIT:          
                pygame.quit()
                sys.exit()
            if event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    sys.exit()

        pressed_key = pygame.key.get_pressed()
        if pressed_key[K_LEFT]:
            return 'left'
        elif pressed_key[K_RIGHT]:
            return 'right'
        elif pressed_key[K_UP]:
            return 'up'
        elif pressed_key[K_DOWN]:
            return 'down'
        elif pressed_key[K_RETURN]:
            return 'return'

        return None

なるべく汎用的になるように書きました.キーが押されている間ずっと認識する場合とキーが押されたタイミングを認識する2種類を用意しました.キャラクターを横移動させたりするときはキーが押されている間ずっと動いてほしい場合と,メニュー画面などで項目を選択するときは一回押しただけでバーっと進んでしまうのは使いにくいので,一回押したら一個しか進まないといった場合を別々に表現をするためにこの2通り作りました.returnはその押されたキーを文字列で返します.使いやすいかなって思ったので….

次からはそれぞれのゲームモードについてのプログラムになります.

・Menu.py

import pygame
from Control import Control
from Mode import Mode

'''
Menuクラス
    メニューを表示するクラス
'''
class Menu():
    def __init__(self, media):
        self.titlefont = pygame.font.SysFont('inkfree', 80)
        self.title = self.titlefont.render("Catch a star game!", True, (255,255,255))
        self.comment = pygame.font.SysFont('inkfree', 40)
        self.title_menu = [self.comment.render(st, True, (255,255,255)) for st in ["PLAY","SETTING","RANKING","BYE"]]
        self.choose = 0 # 現在の選択されている位置
        self.control = Control()
        self.media = media

    def main(self, screen, difficulty, rect_player):
        # タイトルと設定情報の描画
        screen.blit(self.title, (20,50))
        for i, me in enumerate(self.title_menu):
            screen.blit(me, (500,50*i+200))

        # コントロール操作
        con = self.control.control()
        if con == 'up':
            if self.choose > 0:
                self.media.button1.play()
                self.choose -= 1
        elif con == 'down':
            if self.choose < len(self.title_menu)-1:
                self.media.button1.play()
                self.choose += 1
        if con == 'return':
            if self.choose + 1 == Mode.PLAY:
                self.media.kirakira.play()
                self.media.play_bgm(2)
                rect_player.center = (100, 550)
                return Mode.PLAY
            elif self.choose + 1 == Mode.SETTING:
                self.media.kirakira.play()
                return Mode.SETTING
            elif self.choose + 1 == Mode.RANKING:
                self.media.kirakira.play()
                return Mode.RANKING
            elif self.choose + 1 == Mode.BYE:
                self.media.kirakira.play()
                return Mode.BYE
            else:
                rect_player.center = (450, 220 + self.choose*50)
                return Mode.MENU
        
        rect_player.center = (450, 220 + self.choose*50)
        return Mode.MENU

まずはメニュークラスです.まず起動したら表示される画面です.選択(エンター)したらそれぞれの画面に移るっていうやつです.こういう書き方がいいのかはよくわかりません.私が昔読んだゲームプログラミングの本が,こういうEnum型とif文を使って書いていたので真似ています.今回フォントはinkfreeを使いました.理由はなんか手書き感あってすごく味があったからです笑
次は設定画面です.

・Setting.py

import pygame
from Control import Control
from Mode import Mode

# 規定値を定義
X = 0
Y = 1

'''
Settingクラス
    ゲームの難易度を設定するためのクラス
'''
class Setting():
    def __init__(self, media):
        self.titlefont = pygame.font.SysFont('inkfree', 80)
        self.title = self.titlefont.render("Setting", True, (255,255,255))
        self.comment = pygame.font.SysFont('inkfree', 40)
        self.choose = [1, 0] # 現在の選択されている位置
        self.control = Control()
        self.media = media
        self.menu_str = [["SIZE"," SMALL"," NORMAL"," BIG",""],
                         ["STARS"," FEW"," NORMAL"," MANY","EXIT"],
                         ["SPEED"," SLOW"," NORMAL"," FAST",""]]
    
    def main(self, screen, difficulty, rect_player):
        # タイトルと設定情報の描画
        screen.blit(self.title, (20,50))
        for j, strs in enumerate(self.menu_str):
            for i, st in enumerate(strs):
                if i == difficulty[j]:
                    screen.blit(self.comment.render(st+' <-', True, (255,255,255)), (200+300*j,150+50*i))
                else:
                    screen.blit(self.comment.render(st, True, (255,255,255)), (200+300*j,150+50*i))

        # コントロール操作
        con = self.control.control()
        if con == 'up':
            if self.choose[Y] > 0:
                self.media.button1.play()
                self.choose[Y] -= 1
        elif con == 'down':
            if self.choose[Y] < len(strs) - 2:
                self.media.button1.play()
                self.choose[Y] += 1
        elif con == 'left':
            if self.choose[X] > 0:
                self.media.button1.play()
                self.choose[X] -= 1
        elif con == 'right':
            if self.choose[X] < len(self.menu_str) - 1:
                self.media.button1.play()
                self.choose[X] += 1
        elif con == 'return':
            if self.choose[Y] == 3: #Exit
                self.media.kirakira.play()
                return Mode.MENU
            else:
                self.media.button2.play()
                difficulty[self.choose[X]] = self.choose[Y] + 1

        # 最下部を選択していたら中央になるようにする
        if self.choose[Y] == 3:
            self.choose[X] = 1

        # 実際の画面のサイズに応じた位置にカーソルを移動する
        rect_player.center = (150 + self.choose[X] * 300, 220 + self.choose[Y] * 50)

        return Mode.SETTING

今回キャラクターの大きさと落ちてくる星の量,キャラクターの動くスピードを変えられるといいましたが,ここで変えることができます.ちょっとややこしいんですがdifficultyは3個の要素が入っているリストで,1つ目がsize,2つ目がstarの数,3つ目がspeedです.なるべく短く書きたかったので,difficulty[self.choose[X]] = self.choose[Y] + 1とかパっと見意味不明な文がありますが,これは,difficulty[なんの難易度?] = どれくらいの難しさ?っていうイメージです.個人的にはシンプルに書けて満足なんですが,可読性は低い気がします….
次はランキング画面です.

・Ranking.py

import pygame
from Mode import Mode
from Control import Control

'''
Rankingクラス
    ランキングを管理するクラス
'''
class Ranking():
    def __init__(self, media):
        self.titlefont = pygame.font.SysFont('inkfree', 80)
        self.title = self.titlefont.render("Ranking", True, (255,255,255))
        self.comment = pygame.font.SysFont('inkfree', 40)
        self.control = Control()
        self.media = media
        self.ranking = []
        self.write_point = 0
        
    def main(self, screen, defficulty, rect_player):
        # タイトルと情報の描画
        screen.blit(self.title, (20,50))
        screen.blit(self.comment.render("EXIT", True, (255,255,255)), (580, 580))
        screen.blit(self.comment.render("Rank   Score    Name", True, (255,0,0)), (450, 0))
        try:
            with open(self.media.ranking_txt) as f:
                ranking_str = [s.strip() for s in f.readlines()]
                for i, row in enumerate(ranking_str):
                    score, name = row.split(",")
                    screen.blit(self.comment.render(str(i+1), True, (255,255,255)), (480, 50+i*50))
                    screen.blit(self.comment.render(score, True, (255,255,255)), (580, 50+i*50))
                    screen.blit(self.comment.render(name, True, (255,255,255)), (715, 50+i*50))
        except FileNotFoundError:
            pass # まだファイルが作成されていなければ何も表示しない

        # exitの位置に常に配置
        rect_player.center = (530, 600)

        # コントロール操作(exitのみ)
        con = self.control.control()
        if con == 'return':
            self.media.kirakira.play()
            return Mode.MENU

        return Mode.RANKING

    def read_ranking(self, get_star):
        try:
            with open(self.media.ranking_txt) as f:
                # テキストからランキング情報を読み込む
                # ランキングは"socre,name"というカンマ区切りで記述されている
                ranking_str = [s.strip() for s in f.readlines()]
                renew_ranking = False
                if len(ranking_str) != 0:
                    for i, row in enumerate(ranking_str):
                        score, name = row.split(",")
                        if int(score) <= get_star and renew_ranking == False:
                            self.ranking.append((str(get_star)+","))
                            renew_ranking = True
                            self.write_point = i
                        self.ranking.append(score + ',' + name)
                    if renew_ranking == False and i != 9: # ランキング最下位にまだ余裕があったら
                        self.ranking.append(str(get_star) + ',')
                        self.write_point = i + 1
                        renew_ranking = True
                    elif renew_ranking == True and i == 9: # ランキング満員で更新されたら
                        self.ranking.pop(10) # 一番下の人を取り除く
                else:
                    raise FileNotFoundError # テキストファイルが何も書かれていなかったら

        except FileNotFoundError: # テキストファイルが存在しないor何も書いていない場合
            self.ranking.append(str(get_star) + ',')
            self.write_point = 0
            renew_ranking = True
        
        return renew_ranking

    def write_ranking(self, your_name):
        self.ranking[self.write_point] += your_name
        with open(self.media.ranking_txt, 'w') as f:
            f.write('\n'.join(self.ranking))

これに関してはあんまり上手く書けなかったと思っています.mainの部分では現在のランキングをSave//ranking.txtから読みだして表示するものです.このテキストファイルを修正するのってこのクラスだよなーって思って,書き出したり,読み出したりする処理をまとめています.read_rankingメソッドはどっちかというと,今回取った星の数がランキング圏内かっていうのを判定するメソッドです.それでは入りそうであればTrue,入らないのであればFalseを返します.最後のwriter_rankingは名前を入力してもらってそれを,さっき読みだしたときに入る予定だったスコアのところに名前を追記するって感じです.もっとうまい書き方がある気がしますが,書いているうちに何が綺麗な書き方なのかわからなくなってきたのでこの辺で止めました笑
次は終了画面です.

・Bye.py

import pygame
import sys

'''
Byeクラス
    終了用画像を表示し3秒後に画面を閉じる
'''
class Bye():
    def __init__(self, media):
        self.title = pygame.font.SysFont('inkfree', 80).render("Thank you for playing!", True, (255,255,255))

    def main(self, screen, defficulty, rect_player):
        screen.blit(self.title, (20,50))
        pygame.display.update()
        pygame.time.wait(3000)
        pygame.quit()
        sys.exit()

特に難しいことはなく,呼ばれたらプレイしてくれてありがと!ってコメントを表示して終了するだけです.
最後にゲームプレイ画面について書く前に,starクラスを書いておきます.

・Star.py

import pygame

'''
Starクラス
    星本体のクラス
'''
class Star():
    def __init__(self, x, y):
        self.x = x
        self.y = y

    '''
    当たり判定メソッド
    '''
    def hit(self, rect_player, hit_range):
        # プレイヤーが星の中心から縦横hit_range以内であればTrueを返す
        if rect_player.centery - hit_range < self.y + 25 < rect_player.centery + hit_range and \
            rect_player.centerx - hit_range < self.x + 25 < rect_player.centerx + hit_range:
            return True
        else:
            return False

ゲーム画面では星が落ちてくるのですが,その星はそれぞれこのクラスのインスタンスが生成されます.このクラスはオブジェクト指向の良さがいかせてるかなって思います.自身にxとy座標をもって,操作するキャラクターが星と近づいたら当たり判定をTrueで返すところが特にオブジェクト指向感がある気がします.

・Play.py

import pygame
from pygame.locals import *
import time
import random
from Control import Control
from Mode import Mode
from Difficulty import Difficulty
from Star import Star
from Ranking import Ranking

# 規定値を定義
LIMIT_TIME = 20
X = 0
Y = 1

'''
Playクラス
    ゲーム画面を表示するクラス
'''
class Play():
    def __init__(self, media):
        self.start_time = time.time()
        self.star_list = [] # 降ってくる星用のリスト
        self.get_star = 0 # 取得した星の数
        self.titlefont = pygame.font.SysFont('inkfree', 80)
        self.comment = pygame.font.SysFont('inkfree', 40)
        self.title = self.titlefont.render("Play", True, (255,255,255))
        self.result = self.titlefont.render("Result", True, (255,255,255))
        self.control = Control()
        self.media = media
        self.ranking = Ranking(media)
        self.read_ranking = False # ランキングを読み込んだかどうか
        self.write_point = False # ランキングに書き込めるかどうか
        self.eng_chara = [['a','b','c','d','e','f','g','h','i','j','k','l','m',' '],
                          ['n','o','p','q','r','s','t','u','v','w','x','y','z','Back']]
        self.choose = [0, 0]
        self.your_name = ''


    def main(self, screen, difficulty, rect_player):
        # タイトルと設定情報の描画
        star_num = self.titlefont.render("Star:{0}".format(self.get_star), True, (255,255,255))
        lim_time = LIMIT_TIME - int((time.time() - self.start_time))
        time_num = self.titlefont.render("Time:{0}".format(lim_time), True, (255,255,255))
        screen.blit(self.title, (20,50))

        # 終了条件を満たしていたら
        if lim_time <= 0:
            screen.blit(self.result, (500,50))
            screen.blit(star_num, (500,150))
            # ランキング情報を1度だけ読み出し順位を確認する
            if self.read_ranking == False:
                self.write_point = self.ranking.read_ranking(self.get_star)
                self.read_ranking = True

            # ランキングで更新があったら名前を入力させる
            if self.write_point:
                screen.blit(self.titlefont.render("New Record!", True, (255,255,255)), (430, 250))
                # スクリーンキーボードを表示する
                for i, eng in enumerate(self.eng_chara):
                    for j, chara in enumerate(eng):
                        screen.blit(self.comment.render(chara, True, (255,255,255)), (350+j*40,400+i*40))
                screen.blit(self.comment.render("END", True, (255,255,255)), (350+220,400+2*40))
                screen.blit(self.comment.render(self.your_name, True, (255,255,255)), (350, 350)) # 名前を表示
                # 今自分が選択している位置を四角形で表示
                pygame.draw.rect(screen, (255,255,0), Rect(350+40*self.choose[X],410+40*self.choose[Y],30,30),2)
                self.input_name()
            else:
                screen.blit(self.titlefont.render("press enter!", True, (255,255,255)), (430, 250))
                # エンターを押したら終了するための処理
                con = self.control.control()
                if con == 'return':
                    rect_player.center = (450, 220)
                    self.media.kirakira.play()
                    self.media.play_bgm(1)
                    return Mode.MENU
        # まだ終了していなかったら(制限時間内なら)
        else:
            screen.blit(time_num, (900, 10))
            screen.blit(star_num, (900, 100))
            ran = random.randint(0, 100)
            # 生成した乱数が難易度の一定数以内なら星を生成する
            if ran <= (difficulty[Difficulty.STAR] - 1):
                self.star_list.append(Star(random.randint(100, 1100), 50))
            # 生成された星を描画する
            for i, star in enumerate(self.star_list):
                screen.blit(self.media.star, (star.x, star.y))
                star.y += 2 # 星を落とす
                # もしキャラクターと星が重なっていたら星を取得
                if star.hit(rect_player, 15*difficulty[Difficulty.SIZE]):
                    self.star_list.pop(i)
                    self.get_star += 1
                    self.media.hit.play()
                # 地面以下なら星を消滅
                elif self.star_list[i].y > 600:
                    self.star_list.pop(i) 

            # 横移動用コントロール
            con = self.control.control2()
            if con == 'left':
                if rect_player.centerx > 100:
                    rect_player.center = (rect_player.centerx - difficulty[Difficulty.SPEED], 550)
            elif con == 'right':
                if rect_player.centerx < 1100:
                    rect_player.center = (rect_player.centerx + difficulty[Difficulty.SPEED], 550)

        return Mode.PLAY

    def input_name(self):
        # コントロール操作
        con = self.control.control()
        if con == 'up':
            if self.choose[Y] > 0:
                self.choose[Y] -= 1
        elif con == 'down':
            if self.choose[Y] < 2:
                self.choose[Y] += 1
        elif con == 'left':
            if self.choose[X] > 0:
                self.choose[X] -= 1
        elif con == 'right':
            if self.choose[X] < 13:
                self.choose[X] += 1
        elif con == 'return':
            if self.choose[Y] == 2 and self.your_name != '': # Exitで名前有
                self.media.kirakira.play()
                # 新しいランキングを書き出す
                self.ranking.write_ranking(self.your_name)
                self.write_point = False
            elif self.choose[Y] == 2 and self.your_name == '': # Exitで名前無
                self.media.kirakira.play()
                self.write_point = False
            else:
                # enterした場所にある文字を名前として保存する
                for i in range(14):
                    for j in range(2):
                        if self.choose[Y] == j and self.choose[X] == i:
                            if self.choose[X] == 13 and self.choose[Y] == 1:
                                self.your_name = self.your_name[0:-1]
                            else:
                                self.your_name += self.eng_chara[j][i]
        # 一番下ならENDにする
        if self.choose[Y] == 2:
            self.choose[X] = 6

これがゲームの一番のメインになるところです.ゲームが開始したら,制限時間内であれば,ランダムで星を生成して,それを落としていきます.落ちてきた星と操作キャラクターが近づいたら星をgetします.ここの部分がリスト全部を読みだして処理するのですごく重い部分だと思います.制限時間になると,ランキングを参照して,新記録だったら名前を入力する画面に移ります.キーボード入力にしてもよかったんですが,なんかcontrolクラスを変更するのが面倒だったので,スクリーンキーボード的なものを表示して,一文字一文字ぽちぽち押していく形にしました.ランキング更新をしたくない場合は無記入でENDで飛ばせます.このクラスを出たら次呼ばれるときには一度__init__(初期化)メソッドが呼ばれるため,rankingでスコアだけ記述されちゃいますが,テキストファイルに書き出さないので,初期化の際に消えます.

おわりに

以上で全部のプログラムの説明が終わりです.
プログラミングを勉強するとき,やっぱりCUIでよくわかんないデータ処理するよりもゲームみたいに見た目ですぐわかるようなもののほうがやっぱり楽しいですね.ゲーム制作って難しそうって感じている人も多いかと思いますが,pygameを使うと気軽にゲーム制作ができますよ.私も改めてpythonの勉強になりました.pythonが使えるとDeep Learningとかでも遊べるようになるので,学校の講義とかではあまりやらないとは思いますが,ぜひpythonを学んでみてはどうでしょうか?