初級編：pygameでゲームを作成


日付：2021/07/21
Pythonのpygameで簡単なゲームを作成していきます。
![名称未設定.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411052/9e778838-4a9c-18ba-2a6e-38ef4464e2e1.gif)

# チートシートそのままコピペ
まずはチートシートを開き、最初のサンプルコードをそのまま持ってきます。
https://qiita.com/bkh4149/items/25169ffbd375dcc19740

```py
import pygame
from pygame.locals import *
import sys
def main():
    pygame.init()                                 # Pygameの初期化
    screen = pygame.display.set_mode((800, 600))  # 800*600の画面
    px=120
    py=100
    while True:
        screen.fill((255,255,255))                                    # 背景を白
        pygame.draw.circle(screen,(10,10,10),(px,py),50)              # ●
        pygame.draw.rect(screen, (255,0,0), Rect(10,100,50,50), 1)    # ■
        pygame.draw.line(screen, (0,255,0), (0,200), (100,300), 2)    # 線
        pygame.display.update()                                       # 画面更新

        # イベント処理
        for event in pygame.event.get():  # イベントを取得
            if event.type == QUIT:        # 閉じるボタンが押されたら
                pygame.quit()             # 全てのpygameモジュールの初期化を解除
                sys.exit()                # 終了（ないとエラーで終了することになる）
if __name__ == "__main__":
    main()
```


チートシートのテンプレートはざっくりいうと
　①初期化
　②ループ
をやっています。

初期化ではPygameの初期化や各種import、変数の初期化をおこない、ループは描画一回分をwhile文でぐるぐるまわしています。本来なら30fpsとか60fpsになるように調整するのですが、いまは適当です。　

ループの中では
　①画面を消す
　②いろんなキャラを描く
　③イベント入力

ということをやっています。


# まずは、矢印キーの←と→で赤い枠が左右に動くようにする

イベント処理の部分を書き換え、変数名pvxはpxにします。

```diff_python
for event in pygame.event.get(): 
    if event. type == QUIT: 
        pygame.quit() 
        sys.exit()
+   elif event.type == KEYDOWN: 
+       if event.key==K_LEFT:
+           px -= 5  # 横方向、変数名pvxはpxにします。
+       elif event.key==K_RIGHT:
+           px += 5  # 横方向、変数名pvxはpxにします。
```


# 変数名の変更

登場人物は以下の通り
　UFO			上の方に出てくる緑のやつ	
　プレイヤー		赤いやつ（UFOに向けてタマを打てる）
　タマ			プレイヤーの売ったタマ

座標を表す変数名は以下のように決めました。
　UFO			ux,uy
　プレイヤー		px,py
　タマ			tx,ty


インデントに気をつけて注意深く書き換えます。

```py
import pygame
from pygame.locals import *
import sys
def main():
    pygame.init()                                 # Pygameの初期化
    screen = pygame.display.set_mode((800, 600))  # 800*600の画面
    
    #プレイヤ
    px=120
    py=500
    
    #タマ
    tx=px
    ty=py
    
    while True:
        screen.fill((255,255,255))                                    # 背景を白
        pygame.draw.circle(screen,(10,10,10),(tx,ty),5)              # ●
        pygame.draw.rect(screen, (255,0,0), Rect(px,py,50,50), 1)    # ■
        #pygame.draw.line(screen, (0,255,0), (0,200), (100,300), 2)    # 線
        pygame.display.update()                                       # 画面更新

        # イベント処理
        for event in pygame.event.get(): 
            if event. type == QUIT: 
                pygame.quit() 
                sys. exit()
            elif event.type == KEYDOWN: 
                if event.key==K_LEFT:
                    px -=5  # 横方向
                    tx=px
                elif event.key==K_RIGHT:
                    px +=5  # 横方向
                    tx=px
if __name__ == "__main__":
    main()
```

# スペースキーでタマが上に飛んでいくようにする

タマの位置に速度を足しこむようにします。また、スペースキーが押されたら球の速度を－１にします。

```py
import pygame
from pygame.locals import *
import sys
def main():
    #pygameの初期設定
    pygame.init()                                 # Pygameの初期化
    screen = pygame.display.set_mode((800, 600))  # 800*600の画面
    
    #プレイヤの初期設定
    px = 120
    py = 500
    
    #タマの初期設定
    tx = px
    ty = py
    tvy = 0
    
    while True:
        #画面を消去
        screen.fill((255,255,255))                                    # 背景を白

        #タマの処理と描画
        ty = ty + tvy
        pygame.draw.circle(screen,(10,10,10),(tx,ty),5)              # ●

        #プレイヤの処理と描画
        pygame.draw.rect(screen, (255,0,0), Rect(px,py,50,50), 1)    # ■
        # イベント処理
        for event in pygame.event.get(): 
            if event. type == QUIT: 
                pygame.quit() 
                sys. exit()
            elif event.type == KEYDOWN: 
                if event.key == K_LEFT:
                    px -= 5  # 横方向
                    tx = px
                elif event.key == K_RIGHT:
                    px += 5  # 横方向
                    tx = px
                elif event.key == K_SPACE:
                    ty = py
                    tvy = -1
                    
        pygame.display.update()                                       # 画面更新
            
if __name__ == "__main__":
    main()
```




# 発射位置の修正　
プレイヤーの左端から飛んでいくのでまんなから発射されるように変更します。

```py
import pygame
from pygame.locals import *
import sys
def main():
    #pygameの初期設定
    pygame.init()                                 # Pygameの初期化
    screen = pygame.display.set_mode((800, 600))  # 800*600の画面
    
    #プレイヤの初期設定
    px = 120
    py = 500
    
    #タマの初期設定
    tx = px+25
    ty = py
    tvy = 0
    
    while True:
        #画面を消去
        screen.fill((255,255,255))                                    # 背景を白

        #タマの処理と描画
        ty = ty + tvy
        pygame.draw.circle(screen,(10,10,10),(tx,ty),5)              # ●

        #プレイヤの処理と描画
        pygame.draw.rect(screen, (255,0,0), Rect(px,py,50,50), 1)    # ■
        # イベント処理
        for event in pygame.event.get(): 
            if event. type == QUIT: 
                pygame.quit() 
                sys. exit()
            elif event.type == KEYDOWN: 
                if event.key == K_LEFT:
                    px -= 5  # 横方向
                    tx = px+25
                elif event.key == K_RIGHT:
                    px += 5  # 横方向
                    tx = px+25
                elif event.key == K_SPACE:
                    ty = py
                    tx = px+25
                    tvy = -1
                    
        pygame.display.update()                                       # 画面更新
            
if __name__ == "__main__":
    main()
```

# UFOの描画
画面上部にUFOを描画します。とりあえず位置は固定で動きません。

```py
import pygame
from pygame.locals import *
import sys
def main():
    #pygameの初期設定
    pygame.init()                                 # Pygameの初期化
    screen = pygame.display.set_mode((800, 600))  # 800*600の画面
    
    #UFOの初期設定
    ux = 200
    uy = 100

    #プレイヤの初期設定
    px = 120
    py = 500
    
    #タマの初期設定
    tx = px+25
    ty = py
    tvy = 0
    
    while True:
        #画面を消去
        screen.fill((255,255,255))                                    # 背景を白

        #UFOの処理と描画
        pygame.draw.rect(screen, (0,255,0), Rect(ux,uy,150,50), 1)    # ■
        
        #タマの処理と描画
        ty = ty + tvy
        pygame.draw.circle(screen,(10,10,10),(tx,ty),5)              # ●

        #プレイヤの処理と描画
        pygame.draw.rect(screen, (255,0,0), Rect(px,py,50,50), 1)    # ■
        # イベント処理
        for event in pygame.event.get(): 
            if event. type == QUIT: 
                pygame.quit() 
                sys. exit()
            elif event.type == KEYDOWN: 
                if event.key == K_LEFT:
                    px -= 5  # 横方向
                    tx = px+25
                elif event.key == K_RIGHT:
                    px += 5  # 横方向
                    tx = px+25
                elif event.key == K_SPACE:
                    ty = py
                    tx = px+25
                    tvy = -1
                    
        pygame.display.update()                                       # 画面更新
            
if __name__ == "__main__":
    main()

```



# UFOを左右に動かす
とまっていたUFOを左右に動かします。

```py
import pygame
from pygame.locals import *
import sys
def main():
    #pygameの初期設定
    pygame.init()                                 # Pygameの初期化
    screen = pygame.display.set_mode((800, 600))  # 800*600の画面
    
    #UFOの初期設定
    ux = 200
    uy = 100
    uvx = 1

    #プレイヤの初期設定
    px = 120
    py = 500
    
    #タマの初期設定
    tx = px+25
    ty = py
    tvy = 0
    
    while True:
        #画面を消去
        screen.fill((255,255,255))                                    # 背景を白

        #UFOの処理と描画
        ux = ux + uvx
        if ux>700 or ux <0:
            uvx *= -1
        pygame.draw.rect(screen, (0,255,0), Rect(ux,uy,150,50), 1)    # ■
        
        #タマの処理と描画
        ty = ty + tvy
        pygame.draw.circle(screen,(10,10,10),(tx,ty),5)              # ●

        #プレイヤの処理と描画
        pygame.draw.rect(screen, (255,0,0), Rect(px,py,50,50), 1)    # ■
        # イベント処理
        for event in pygame.event.get(): 
            if event. type == QUIT: 
                pygame.quit() 
                sys. exit()
            elif event.type == KEYDOWN: 
                if event.key == K_LEFT:
                    px -= 5  # 横方向
                    tx = px+25
                elif event.key == K_RIGHT:
                    px += 5  # 横方向
                    tx = px+25
                elif event.key == K_SPACE:
                    ty = py
                    tx = px+25
                    tvy = -1
                    
        pygame.display.update()                                       # 画面更新
            
if __name__ == "__main__":
    main()
```

# 当たり判定
タマがUFOにあたったら、UFOを消します。

```py
import pygame
from pygame.locals import *
import sys
def main():
    #pygameの初期設定
    pygame.init()                                 # Pygameの初期化
    screen = pygame.display.set_mode((800, 600))  # 800*600の画面
    
    #UFOの初期設定
    ux = 200
    uy = 100
    uw = 150
    uh = 50
    uvx = 1
    uhp = 100

    #プレイヤの初期設定
    px = 120
    py = 500
    
    #タマの初期設定
    tx = px+25
    ty = py
    tvy = 0
    
    while True:
        #画面を消去
        screen.fill((255,255,255))                                    # 背景を白

        #UFOの処理と描画
        ux = ux + uvx
        if ux>700 or ux <0:
            uvx *= -1
        #当たり判定    
        if (ux <= tx <= ux + uw) and (uy <= ty <= uy + uh):
            uhp=0
        if uhp > 0:    
            pygame.draw.rect(screen, (0,255,0), Rect(ux,uy,uw,uh), 1)    # ■
        
        #タマの処理と描画
        ty = ty + tvy
        pygame.draw.circle(screen,(10,10,10),(tx,ty),5)              # ●

        #プレイヤの処理と描画
        pygame.draw.rect(screen, (255,0,0), Rect(px,py,50,50), 1)    # ■
        # イベント処理
        for event in pygame.event.get(): 
            if event. type == QUIT: 
                pygame.quit() 
                sys. exit()
            elif event.type == KEYDOWN: 
                if event.key == K_LEFT:
                    px -= 5  # 横方向
                    tx = px+25
                elif event.key == K_RIGHT:
                    px += 5  # 横方向
                    tx = px+25
                elif event.key == K_SPACE:
                    ty = py
                    tx = px+25
                    tvy = -1
                    
        pygame.display.update()                                       # 画面更新
            
if __name__ == "__main__":
    main()
```

とりあえず最初のステップはこれで完成です！

# クラスを使って書き換えてみる
動きは同じですが、クラスを使うとプログラムがスッキリします。

```py
import pygame
from pygame.locals import *
import sys

class Player():
    def __init__(self):
        self.px = 120
        self.py = 500
    def update(self,screen,T):
        pygame.draw.rect(screen, (255,0,0), Rect(self.px,self.py,50,50), 1)    # ■
        # イベント処理
        for event in pygame.event.get(): 
            if event. type == QUIT: 
                pygame.quit() 
                sys. exit()
            elif event.type == KEYDOWN: 
                if event.key == K_LEFT:
                    self.px -= 5  # 横方向
                    T.x = self.px+25
                elif event.key == K_RIGHT:
                    self.px += 5  # 横方向
                    T.x = self.px+25
                elif event.key == K_SPACE:
                    T.y = self.py
                    T.x = self.px+25
                    T.vy = -1

class UFO():
    def __init__(self):
        self.ux = 200
        self.uy = 100
        self.uw = 150
        self.uh = 50
        self.uvx = 1
        self.uhp = 100

    def update(self,screen,T):
        self.ux = self.ux + self.uvx
        if self.ux>700 or self.ux <0:
            self.uvx *= -1
        #当たり判定    
        if (self.ux <= T.x <= self.ux + self.uw) and (self.uy <= T.y <= self.uy + self.uh):
            self.uhp=0
        if self.uhp > 0:    
            pygame.draw.rect(screen, (0,255,0), Rect(self.ux,self.uy,self.uw,self.uh), 1)    # ■

class Tama():
    def __init__(self):
        #プレイヤの初期設定
        self.x = 120
        self.y = 500
        self.vy = 0
    def update(self,screen):
        #タマの処理と描画
        self.y = self.y + self.vy
        pygame.draw.circle(screen,(10,10,10),(self.x,self.y),5)              # ●

def main():
    pygame.init()                                 # Pygameの初期化
    screen = pygame.display.set_mode((800, 600))  # 800*600の画面
    T1=Tama()
    P1=Player()
    U1=UFO()
    while True:
        screen.fill((255,255,255))                                    # 背景を白
        T1.update(screen)
        P1.update(screen,T1)
        U1.update(screen,T1)
        pygame.display.update()                                       # 画面更新
            
if __name__ == "__main__":
    main()
```

### なんとrepl.itで動作確認できました！　
https://replit.com/@bkh4149/HappyPerfumedListeners#main.py
ただし、初期状態で画面が小さいです。
下が見えない人は下に引き伸ばして画面を大きくしてみてください。
また、キーボードは動作している画面上でクリックしないと有効になりません。


# 宇宙船を増やし、動きを換えてみる
せっかくクラスを使ったので、UFOを増やし動きを変えてみました

```py
import pygame
from pygame.locals import *
import sys

class Player():
    def __init__(self):
        self.px = 120
        self.py = 500
    def update(self,screen,T):
        pygame.draw.rect(screen, (255,0,0), Rect(self.px,self.py,50,50), 1)    # ■
        # イベント処理
        for event in pygame.event.get(): 
            if event. type == QUIT: 
                pygame.quit() 
                sys. exit()
            elif event.type == KEYDOWN: 
                if event.key == K_LEFT:
                    self.px -= 5  # 横方向
                    T.x = self.px+25
                elif event.key == K_RIGHT:
                    self.px += 5  # 横方向
                    T.x = self.px+25
                elif event.key == K_SPACE:
                    T.y = self.py
                    T.x = self.px+25
                    T.vy = -1

class UFO():
    def __init__(self,x):
        self.ux = x
        self.uy = x
        self.uw = 20
        self.uh = 20
        self.uvx = 1
        self.uvy = 1
        self.uhp = 100

    def update(self,screen,T):
        self.ux = self.ux + self.uvx
        self.uy = self.uy + self.uvy
        if self.ux>700 or self.ux <0:
            self.uvx *= -1
        if self.uy>600 or self.uy <0:
            self.uvy *= -1
        #当たり判定    
        if (self.ux <= T.x <= self.ux + self.uw) and (self.uy <= T.y <= self.uy + self.uh):
            self.uhp=0
        if self.uhp > 0:    
            pygame.draw.rect(screen, (0,255,0), Rect(self.ux,self.uy,self.uw,self.uh), )    # ■

class Tama():
    def __init__(self):
        #プレイヤの初期設定
        self.x = 120
        self.y = 500
        self.vy = 0
    def update(self,screen):
        #タマの処理と描画
        self.y = self.y + self.vy
        pygame.draw.circle(screen,(10,10,10),(self.x,self.y),5)              # ●

def main():
    pygame.init()                                 # Pygameの初期化
    screen = pygame.display.set_mode((800, 600))  # 800*600の画面
    T1=Tama()
    P1=Player()
    Us=[]
    for i in range(10):
      Us.append(UFO(i*50))
    while True:
        screen.fill((255,255,255))                                    # 背景を白
        T1.update(screen)
        P1.update(screen,T1)
        for U in Us:
            U.update(screen,T1)
        pygame.display.update()                                       # 画面更新
            
if __name__ == "__main__":
    main()
```
# その後の改善　2023-07-19

上記プログラムにいろいろ改善点があり、修正しました

##### 症状：PCを変えると以上に早くなったり遅くなったりする
対策：フレームレート調整を入れる
結果：ok

##### 症状：弾を撃って次弾を打つと、前の弾が消えてしまう
#対策：フラグにする
#結果：ok

##### 症状：弾を撃って、砲台を左右に動かすと弾も左右に動いてしまう
#原因：L,Rキーのところで弾の位置を変えていた
#対策：上を削除でok

##### 症状：弾が当たったとき、ufoは消えるが弾が消えない
#対策：hit時に弾をリセットする
#結果：ok

```py
import pygame
from pygame.locals import *
import sys

class Player():
    def __init__(self):
        self.px = 120
        self.py = 500
    def update(self,screen,T):
        pygame.draw.rect(screen, (255,0,0), Rect(self.px,self.py,50,50), 1)    # ■
        # イベント処理
        for event in pygame.event.get(): 
            if event. type == QUIT: 
                pygame.quit() 
                sys. exit()
            elif event.type == KEYDOWN: 
                if event.key == K_LEFT:
                    self.px -= 5  # 横方向
                elif event.key == K_RIGHT:
                    self.px += 5  # 横方向
                elif event.key == K_SPACE:
                    if T.isFire==False:
                      T.isFire=True
                      T.y = self.py
                      T.x = self.px+25

class UFO():
    def __init__(self,x):
        self.ux = x
        self.uy = x
        self.uw = 20
        self.uh = 20
        self.uvx = 3
        self.uvy = 2
        self.uhp = 100

    def update(self,screen,T):
        self.ux = self.ux + self.uvx
        self.uy = self.uy + self.uvy
        if self.ux>700 or self.ux <0:
            self.uvx *= -1
        if self.uy>600 or self.uy <0:
            self.uvy *= -1
        #当たり判定    
        if (self.ux <= T.x <= self.ux + self.uw) and (self.uy <= T.y <= self.uy + self.uh):
            self.uhp=0
            T.isFire=False
        if self.uhp > 0:    
            pygame.draw.rect(screen, (0,255,0), Rect(self.ux,self.uy,self.uw,self.uh), )    # ■

class Tama():
    def __init__(self):
        #プレイヤの初期設定
        self.x = 120
        self.y = 500
        self.vy = -5
        self.isFire = False
    def update(self,screen):
        print("isFile",self.isFire,"self.x=",self.x,"self.y=",self.y)
        #タマの処理と描画
        if self.isFire:
          self.y += self.vy
          if self.y<-10:
              self.isFire=False
          pygame.draw.circle(screen,(10,10,10),(self.x,self.y),5)              # ●

def main():
    pygame.init()                                 # Pygameの初期化
    screen = pygame.display.set_mode((800, 600))  # 800*600の画面
    T1=Tama()
    P1=Player()
    Us=[]
    ck = pygame.time.Clock()
    for i in range(10):
      Us.append(UFO(i*50))
    while True:
        ck.tick(60) #1秒間で60フレーム
        screen.fill((255,255,255))                                    # 背景を白
        T1.update(screen)
        P1.update(screen,T1)
        for U in Us:
            U.update(screen,T1)
        pygame.display.update()                                       # 画面更新
            
if __name__ == "__main__":
    main()

```

お疲れさまでした！