【Pygame】放置ゲームのプロトタイプを作ってみた

## 【Pygame】放置ゲームのプロトタイプを作ってみた
　Pygameで放置ゲームのプロトタイプ的なプログラムを作ってみました。

<img width=450mm src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3850890/77fda9b6-3543-5da2-43fc-31378176d5a4.png>

### 超絶シンプルな仕様
- 円をクリックするとリソース+1。
- オートマトンの部分をクリックすると、リソースが足りていた時のみレベルが上がってプロデュースが増加。
- プログラムを終了後再起動すると経過時間に応じてリソースがもらえる。

　各部分の英単語は適当です。

## 　ソースコード

~~~python:game.py
#インポート
import pygame
import time
from pygame.locals import *
import sys

#クラス
class App:
    #コンストラクタ
    def __init__(self):
        #初期化
        self.running=True
        self.fps=15
        self.resouce=0
        self.produce=0
        self.level=0
        self.clock=pygame.time.Clock()
        self.res=None
        self.t=None

        #ウインドウの設定
        self.screen=pygame.display.set_mode((600,600))
        pygame.display.set_caption("Producing & Developing")

        #セーブデータ読み込み
        try:
            with open("data.txt") as f:
                self.t=int(time.time())
                t_b=None
                l=[s.rstrip() for s in f.readlines()]
                if len(l)==3:
                    for i in range(len(l)):
                        l[i]=int(l[i])
                    t_b=l[0]
                    self.resouce=l[1]
                    self.level=l[2]
                    self.t-=t_b
                    self.produce=self.level
                    for i in range(self.level//10):
                        self.produce*=2
                    self.res=self.produce*self.t
                    self.resouce+=self.res
        except:pass

        #フォントの設定
        self.font=pygame.font.SysFont("Menlo",30)
        self.f_b=pygame.font.SysFont("Menlo",15)

        #一秒間隔でタイマーを設定
        pygame.time.set_timer(1,1000)

        #更新処理
        self.run()

    #更新関数
    def run(self):
        #ループ
        while self.running:
            #fpsを設定
            self.clock.tick(self.fps)
            #イベント取得
            for event in pygame.event.get():
                #終了処理
                if event.type==QUIT:
                    self.running=False
                    s=int(time.time())
                    #セーブ
                    with open("data.txt",mode="w") as f:
                        f.write("\n".join([str(s),str(self.resouce),str(self.level)]))
                    #プログラムを終了
                    pygame.mixer.stop()
                    pygame.quit()
                    sys.exit()

                #マウスクリック
                elif event.type==MOUSEBUTTONDOWN:
                    btn=event.button
                    x,y=event.pos
                    if btn==1 and x>=150 and x<=300 and y>=225 and y<=375:
                        self.resouce+=1
                    elif x>=150 and y<=495 and y>=450 and x<=385:
                        if self.resouce>=(self.level+1)*10+200*(self.level//10)**2:
                            self.resouce-=(self.level+1)*10+200*(self.level//10)**2
                            self.level+=1

                #タイマーに対してリソースの所持数を更新
                elif event.type==1:
                    self.resouce+=self.produce
                
                #エスケープキーで画面遷移
                elif event.type==KEYDOWN:
                    if event.key==K_ESCAPE:
                        self.res=None
                        self.screen.fill((0,0,0))
                    
            #毎秒生産量を計算
            self.produce=self.level
            for i in range(self.level//10):
                self.produce*=1.5
            
            #描画処理
            #描画の順番:円->通常テキスト->放置テキスト
            self.txt=[self.font.render("Resouce:%5g"%(self.resouce),True,(255,255,255),(0,0,0)),
                self.font.render("Produce:%3g/s"%(self.produce),True,(255,255,255),(0,0,0)),
                self.font.render("AutomatonLv:%d"%(self.level),True,(0,0,0),(255,255,255)),
                self.f_b.render("Necessary:%3d"%((self.level+1)*10+200*(self.level//10)**2),True,(255,255,255),(0,0,0))]
            
            #放置情報
            if self.res!=None:
                self.spec=self.f_b.render("Game was frozen for %d seconds"%(self.t),True,(255,0,0),(0,0,0))
                self.spec_b=self.f_b.render("Resouce + %d"%(self.res),True,(255,0,0),(0,0,0))
                self.end=self.f_b.render("Plese press to ESC",True,(255,255,255),(0,0,0))
                self.screen.blit(self.spec,(50,300))
                self.screen.blit(self.spec_b,(50,340))
                self.screen.blit(self.end,(50,380))

            self.screen.blit(self.txt[1],(150,55))
            self.screen.blit(self.txt[0],(150,15))
            self.screen.blit(self.txt[2],(150,450))
            self.screen.blit(self.txt[3],(150,485))
            #アップデート
            pygame.display.update()
            
            #サークルの描画
            pygame.draw.circle(self.screen,(255,255,255),(225,300),75)

#メイン
if __name__=="__main__":
    #pygame初期化
    pygame.init()
    pygame.mixer.init()
    #インスタンス生成
    main=App()

~~~
## 解説
　といっても、そこまで解説することはありません。
　インターフェース部分は「クリックに反応してリソースを加算する」ということと、「規定のリソースを消費してプロデュースを加算する」というシンプルな構成です。更新処理も一秒ごとにプロデュースの値分リソースを増加させるということをやるだけ。放置ゲームは意外と単純なロジックで実装できます。
　肝は開始時のロード処理と終了時のセーブ処理です。終了時、このプログラムはセーブファイルに`time.time()`の値とリソースの値、オートマトンのレベルを書き込みます。次回起動時は初めにその三つを読み込むわけです。リソースの値はそのままリソースに、オートマトンのレベルはオートマトンのレベルに反映したのち、プロデュースの値の計算に使います。`time.time()`の値は前回の終了時刻を示すため、それとは別に今回の開始時刻を`time.time()`を計算し差を取ることで経過時間を計算します。その経過時間とプロデュースをかけた値が「放置報酬」として通知、加算される仕組みです。

## pygameを触っていて思ったこと
　普段はドット絵ばかり扱っているため、描画関係を考えるのが大変でした。もう少し更新だとか描画の上下関係とか画面遷移とか、その辺を楽に管理できるといいんですが。
　描画の内容を簡潔に表現できるライブラリがあればいいなあ。