Python シューティングプロトタイプ

        #!/usr/bin/env python
        # -*- coding: utf-8 -*-
        """
            シューティングプロトタイプ v0.00.01
                    作者 :めがねん(aminoパレットネーム:HK)
                    URI  :http://megane0303.html.xdomain.jp/meganeGames/
                    amino project
                        http://web1.kcn.jp/as-studio/amino/
        """
        """
            各ファイルのインポートの説明
                pygame.locals   全てのライブラリをインポート
                                必須ではないが重要なものが含まれるらしいので全て入れておく
                sys             必須
                math            数学関数に必要。今回は三角関数を利用
                csv             csv処理に必要
        
        """
        import pygame
        from pygame.locals import *
        import sys
        import math
        import csv
        
        # 画面サイズ
        # Rectは短形を保持するオブジェクト
        # 上下の幅を簡単に記憶できる
        SCR_RECT = Rect(0,0,640,480)
        
        # ゲーム画面サイズ
        SCR_GAME = Rect(0, 0, 410, 460)
        
        # 敵クラス
        # 各タイプは未実装なものも含むが、今後の拡張のため値は保存している
        class Enemy(pygame.sprite.Sprite):
            # 種類、x,y,移動パターン、発射パターン、出現、停止、発射、帰還(それぞれフレーム数)
            # 弾速度、移動速度、ライフ、落とすアイテム
            """
                発射パターン
                    今回のプロトタイプでは直線発射、プレイヤーを狙う弾、3way弾、全方向弾、ランダムway弾がある
                時間について
                    各時間はメインフレーム数(frame)を基準に設定している
                    たとえば出現時間が120であれば、ゲーム開始2秒後に行動を開始する
                    同じく発射時間が180なら3秒後に弾を発射し、帰還時間360なら6秒後に帰還する
        
                    moveframeは移動ルーチンにのみ利用する個別フレーム
                    最大で2秒(120frame)まで記憶
            """
            # 『=』はデフォルト引数。
            def __init__(self, enemytype=0, x=0, y=0, movep=1, shotp=1, showt=120, stopt=240, shott=360, byet=420, shotspd=2, movespd=1, life=1, item=1, score=10):
        
                pygame.sprite.Sprite.__init__(self, self.containers)
        
                self.frame = 0
                self.moveframe=0
                self.shotflag=1
        
                # 種類に応じて画像変更
                if enemytype == 1:
                    self.tmpimg = "enemy1.png"
                elif enemytype == 2:
                    self.tmpimg = "enemy2.png"
                elif enemytype == 3:
                    self.tmpimg = "enemy3.png"
                elif enemytype == 4:
                    self.tmpimg = "enemy4.png"
                elif enemytype == 5:
                    self.tmpimg = "enemy5.png"
                elif enemytype == 100:
                    self.tmpimg = "enemy100.png"
                else:
                    self.tmpimg = "enemy1.png"
        
                self.image = pygame.image.load(self.tmpimg).convert_alpha()
                width = self.image.get_width()
                height= self.image.get_height()
                self.rect = Rect(x, y, width, height)
        #        self.rect.center = pos
        
                self.type = enemytype
        
                self.move = movep
                self.shottype = shotp
                self.stoptime = stopt
                self.showtime = showt
                self.shottime = shott
                self.byetime  = byet
                self.shotspd  = shotspd
                self.movespd  = movespd
                self.life     = life
                self.item     = item
                self.score = score
        
                self.vx = 0
                self.vy = 0
        
            # 移動パターンから移動値を設定する
            def moveset(self):
                if (frame < self.showtime):
                    return
        
                # パターン1:出現して停止後、帰って行く
                if (self.move == 1):
                    if(frame < self.stoptime):
                        self.vy=self.movespd
                    elif(frame > self.byetime):
                        self.vy=-self.movespd
        
                # パターン2:そのまま下へ下がっていくだけ
                elif(self.move == 2):
                    self.vy=self.movespd
        
                # パターン3:左右にジグザグ移動
                elif(self.move == 3):
                    if ((self.moveframe < 60) == 0):
                        self.vx=-self.movespd
                    elif ((self.frame < 120) == 0):
                        self.vx=self.movespd
                    self.vy=self.movespd
        
                    if(frame > self.byetime):
                        self.vy=-self.movespd
        
                # パターン4:右下へ移動
                elif(self.move == 4):
                    self.vy=self.movespd
                    self.vx=self.movespd
        
                # パターン5:左下へ移動
                elif(self.move == 5):
                    self.vy=self.movespd
                    self.vx=-self.movespd
        
                # パターン100:中央固定
                elif(self.move == 6):
                    self.vy=self.movespd
                    self.vx=-self.movespd
        
                else:
                    self.vx=0
                    self.vy=0
        
                if (self.moveframe > 120):
                    self.moveframe = 0
        
            def update(self):
                self.frame += 1
                self.moveframe += 1
                self.moveset()
                self.rect.move_ip(self.vx, self.vy)
                self.shot()
        
                # ボムによる消滅
                if (self.my.bomflag):
                    if (self.showtime <= frame):
                        global score
                        score += self.score
                        self.kill()
        
                if not self.rect.colliderect(SCR_GAME):
                    # 停止時刻に達していなければ消滅しない
                    if(frame > self.stoptime):
                        self.kill()
        
                self.vx = 0
                self.vy = 0
        
            def shot(self):
                # 弾発射
                if (self.shottime == frame):
        
                    self.count = 0
                    self.rad = math.atan2(self.my.rect.centery - self.rect.centery, self.my.rect.centerx - self.rect.centerx)
        
                    # 真下に発射
                    if (self.shottype == 1):
                        emyShot(self.rect.center, math.pi/2, self.shotspd, "emyshot1.png")
                    # プレイヤーを狙う発射
                    elif (self.shottype == 2):
                        emyShot(self.rect.center, self.rad, self.shotspd, "emyshot2.png")
        
                    self.count = self.count + 1
        
            def draw(self, screen):
                screen.blit(self.image, self.rect)
        
        """ 自機クラス
            スプライトクラスを継承する
        
        """
        # このように、pythonではクラス名に括弧をつけてスーパークラスを指定する
        class Player(pygame.sprite.Sprite):
        
            # コンストラクタ
            # 第一引数は自分自身を指すselfを指定
            # @@param fname 画像ファイル名
            # @@param x,y   座標(X,Y)
            # @@param spd   移動速度
            def __init__(self, fname, x, y, spd):
                # スーパークラスのコンストラクタを呼ぶ
                pygame.sprite.Sprite.__init__(self)
        
                # 透明度を維持して画像を読み込む
                # このほか、透明にしたい色座標を指定する方法もある
                self.image = pygame.image.load(fname).convert_alpha()
                width = self.image.get_width()
                height= self.image.get_height()
        
                # このメソッドを呼べば、自身の短形を簡単に保存可能
                self.rect = Rect(x, y, width, height)
                self.spd = spd;
        
                # 一時速度
                # 速度を変更すると速度が変わるので、本来の速度を記憶させておく
                self.spd2 = spd
        
                # vx,vyは実際に移動する移動量
                # 値を指定すると、update時に実際に移動するため、通常は0にする
                # 逆に言えば、vx,vyに数値を入れるだけで、毎フレームその数値分自動移動する
                self.vx = 0
                self.vy = 0
        
                # このオブジェクトが作られてからのフレームカウント
                # 60fpsでは1秒間に60がカウントされる。つまりframe=1/60である
                self.frame = 0
        
                """
                    以下、基本能力 ライフ、ボム、火力
                """
                self.life = 3
                self.bomcount = 3
        
                # ボム設定
                self.bomflag = 0
                self.bomframe = 0
        
            # 周期処理
            # メインフレームから呼ばれる周期処理
            # 敵弾との当たり判定、ショットなどを記入
            def update(self):
                self.frame += 1
        
                # ボムの効果時間
                if (self.bomflag):
                    self.bomframe += 1
                    if (self.bomframe > 120):
                        self.bomflag = 0
                        self.bomframe= 0
        
                # キー検出
                # キーを押し続ける状態を検出するなら、このメソッドで。
                press_key = pygame.key.get_pressed()
        
                # ショット Zキーでショットする
                if press_key[K_z]:
        
                    # 経過フレームが12の倍数ならショット可能
                    # 60fpsの場合1秒間に5発の弾が撃てることになる
                    if ((self.frame % 12) == 0):
        
                        # これは左シフトをおされた時。
                        # 右シフトはK_RSHIFT を指定するが、ゲームでは左シフト使用が一般的
                        if press_key[K_LSHIFT]:
                            # 各数値は角度(ラジアン)を指定。-パイ÷2が真上なので、それを基準に3way
                            # 値はかなりアバウト
                            myShot(self.rect.center, -math.pi/2.5, "myshot_normal2.png")
                            myShot(self.rect.center, -math.pi/2)
                            myShot(self.rect.center, -math.pi/1.65, "myshot_normal2.png")
                        else:
                            # シフトが押されてなければノーマルショット
                            myShot(self.rect.center, -math.pi/2)
        
                # Cキーでボム
                elif press_key[K_c]:
                    if (self.bomflag == 0):
                        if (self.bomcount > 0):
                            self.bomcount -= 1
                            self.bomflag = 1
        
                # シフトが押されていれば速度を半減、つまり低速移動
                # 弾幕シューティングにおいて、低速移動は必須といえる動作である
                if press_key[K_LSHIFT]:
                    self.spd = self.spd/2
                else:
                    # 押されてなければ通常速度に戻す
                    # これを忘れると、どんどん速度が低下する
                    self.spd = self.spd2
        
                """
                    移動関連の解説
                        xは左右、yは上下を指す
                        左上を0,0とするので、xが負なら左へ、正なら右へ進む
                        同じ考え方でyも負なら上へ、正なら下へ進む
                        self.vx -= 1
                        であれば、毎フレーム左へ1ずつ進む
        
                        ※移動量は『毎フレーム』であって毎秒ではない
                        上の指定では一秒に60進むことになる
        
                """
        
                # 斜め移動を考慮
                if press_key[K_LEFT]:
                    if press_key[K_UP] or press_key[K_DOWN]:
                        self.vx = -self.spd *0.71
                    else:
                        self.vx = -self.spd
        
                if press_key[K_RIGHT]:
                    if press_key[K_UP] or press_key[K_DOWN]:
                        self.vx = self.spd *0.71
                    else:
                        self.vx = self.spd
        
                if press_key[K_UP]:
                    if press_key[K_LEFT] or press_key[K_RIGHT]:
                        self.vy = -self.spd *0.71
                    else:
                        self.vy = -self.spd
        
                if press_key[K_DOWN]:
                    if press_key[K_LEFT] or press_key[K_RIGHT]:
                        self.vy = self.spd *0.71
                    else:
                        self.vy = self.spd
        
                # pygameでは、このメソッドを使うだけで移動可能(便利!)
                self.rect.move_ip(self.vx, self.vy)
        
                # これは、ゲーム画面のRectからはみ出た場合、戻すメソッド
                # SCR_RECTを指定すると右側のステータスまではみ出るので注意
                self.rect = self.rect.clamp(SCR_GAME)
        
                # 速度を戻す
                # 念のため、メソッドを抜ける前に速度を強制的に戻す
                # これだけでも、くだらないバグ防止になる
                self.spd = self.spd2
        
                # 移動量を戻す
                # これも同じく。忘れると何も押してないのに移動を続けたりする
                self.vx = 0
                self.vy = 0
        
            # 画面描画メソッド
            # これが呼ばれることで初めて画面上に描画される
            def draw(self, screen):
                screen.blit(self.image, self.rect)
        
        # 背景スクロール用クラス
        class backGraund(pygame.sprite.Sprite):
            def __init__(self, second=0):
                pygame.sprite.Sprite.__init__(self, self.containers)
                self.image = pygame.image.load("game_frame.jpg").convert_alpha()
                self.vy=1
                width = self.image.get_width()
                height= self.image.get_height()
                if (second == 1):
                    self.top = 0
                else:
                    self.top = -460
        
                self.rect = Rect(0, self.top, width, height)
        
            def update(self):
                self.rect.move_ip(0, self.vy)
                if (self.rect.top > 460):
                    self.rect.top = -460
        
        # 敵ショットクラス
        class emyShot(pygame.sprite.Sprite):
        
            def __init__(self, pos, rad, spd, img="emyshot1.png"):
                pygame.sprite.Sprite.__init__(self, self.containers)
                self.image = pygame.image.load(img).convert_alpha()
                self.rect = self.image.get_rect()
                self.rect.center = pos
                self.rad = rad
                self.spd = spd
        
            def update(self):
                self.rect.move_ip( math.cos(self.rad) * self.spd , math.sin(self.rad) * self.spd )
                if not self.rect.colliderect(SCR_GAME):
                    self.kill()
        
            def draw(self, screen):
                rotateimg = pygame.transform.rotate(screen, self.rad)
                w2, h2 = rotateimg.get_size() #回転後のサイズ
        
                topleft = (self.rect.centerx - w2 / 2, self.rect.centery - h2 / 2)
                screen.blit(rotateimg, topleft)
        
        # 自機ショットクラス。スプライトを継承
        # このクラスは弾の一発分のオブジェクト用
        class myShot(pygame.sprite.Sprite):
        
            spd = 10 # 弾速度
        
            def __init__(self, pos, rad, img="myshot_normal1.png"):
                pygame.sprite.Sprite.__init__(self, self.containers)
                self.image = pygame.image.load(img).convert_alpha()
                # スプライトクラスのget_rectメソッドを呼ぶだけで短形取得
                self.rect = self.image.get_rect()
                # 自身の短形の中央をposとする
                self.rect.center = pos
                # 自身の角度(方向)
                self.rad = rad;
        
            def update(self):
                #self.rect.move_ip(0, -self.spd)
                # 角度に応じて弾の発射方向を変える。
                # 角度はラジアンで指定する。例えば真上の角度は『-パイ÷2』およそ-1.57になる
                # 詳しい説明は数学の分野になるため、三角関数を勉強してください
                # ここでは、角度のコサインに速度を掛けた値をx,サインをyとしている
                self.rect.move_ip(math.cos(self.rad)*self.spd, math.sin(self.rad)*self.spd)
        
                # 本来は角度が変われば画像を角度に応じて回転させるべきだが
                # 今回は標準動作が目的なので、画像描画に関しては実装しない
                # ちなみにpygameで回転縮小機能は標準で実装されているため、実現は容易である
        
                # 自身の消滅。
                # ゲーム画面(SCR_GAME)からはみ出たら消滅させる
                # containsはrectのメソッド
                # ゲーム画面上に弾があれば真なので、その否定を条件としている
                # ※killはオブジェクトを消滅させるスプライトのメソッド
        #        if self.rect.top < 0 or self.rect.right > SCR_GAME.right or self.rect.left < SCR_GAME.left:
                if not SCR_GAME.contains(self.rect):
                    self.kill()
        
        # メイン関数
        # クラスにしても良いが、今回はメイン関数をゲーム本体とした
        def main():
            # Pygameを初期化
            pygame.init()
            # SCREEN_SIZEの画面を作成
            screen = pygame.display.set_mode(SCR_RECT.size)
            # タイトルバーの文字列をセット
            pygame.display.set_caption(u"シューティングプロトタイプ")
        
            # 背景セット
        #    backImage = pygame.image.load("game_frame.jpg").convert()
        
            # スプライトグループ作成
            all = pygame.sprite.RenderUpdates()
            enemy = pygame.sprite.Group()
            mshots = pygame.sprite.Group()
            eshots = pygame.sprite.Group()
            backImages = pygame.sprite.Group()
        
            backGraund.containers = backImages
            myShot.containers = mshots,all
            emyShot.containers = eshots,all
            Enemy.containers = enemy,all
        
            backImage = backGraund()
            backImage2 = backGraund(1)
        
            # 自機作成
            # 画像ファイル名、XY座標、速度 の順番
            my = Player("myChara0.png", 205, 420, 4)
            emyShot.my = my
            Enemy.my = my
        
            # 敵の読み込みとオブジェクト作成
            ef = open("enemy.csv", "r")
            enemyData = csv.reader(ef)
        
            # csvの一行目は項目の説明なので飛ばす
            i = 0
            for line in enemyData:
                if (i ==0):
                    i+=1
                else:
                    # 要素は文字列で作られているようなので、intメソッドで整数に変換した
                    # Enemyはスプライトグループ登録なので、あえて変数に代入する必要はない
                    Enemy(int(line[0]), int(line[1]), int(line[2]), int(line[3]), int(line[4]),
                          int(line[5]),int(line[6]), int(line[7]), int(line[8]), int(line[9]),
                          int(line[10]), int(line[11]), int(line[12]), int(line[13]))
        
            # クロックオブジェクト。フレームカウントを利用するお約束として覚える
            clock = pygame.time.Clock()
        
            # ゲーム起動からのフレーム数
            global frame
            frame = 0
        
            # ゲームステータス
            global score
            score = 0
            fonts = pygame.font.SysFont(None, 24)
        
            # ゲームループ
            # ここからは毎フレーム処理になる
            # 最低限更新が必要な処理だけを記述し、初期化は外に出す
            while True:
        
                # FPSを60に設定
                clock.tick(60)
                # ↓ この記述によって、全ての表示をリセットしている
                screen.fill((0,0,0))            # 画面を黒色で塗りつぶす
        #        screen.blit(backImage , (0,0))  # 背景画像を上乗せ描画
                # ↑ 全画面リセットは楽だが、処理に負荷がかかるため、
                # 本来は部分的な更新が望ましい。が、今回はプロトタイプなので…(略)
        
                frame += 1
        
                # ゲームステータス更新
                state_score_str = fonts.render("SCORE", True, (128, 128, 0))
                state_life_str = fonts.render("LIFE", True, (128, 128, 0))
                state_bom_str = fonts.render("BOM", True, (128, 128, 0))
                state_graze_str = fonts.render("GRAZE", True, (128, 128, 0))
        
                state_score = fonts.render(str(score), True, (0, 128, 128))
                state_life = fonts.render(str(my.life), True, (0, 128, 128))
                state_bom = fonts.render(str(my.bomcount), True, (0, 128, 128))
                state_frame = fonts.render(str(frame/60), True, (0, 128, 128))
        
                # 各ゲームステータスの描画
                screen.blit(state_score_str, (420, 20))
                screen.blit(state_score, (440, 40))
                screen.blit(state_life_str, (420, 60))
                screen.blit(state_life, (480, 60))
                screen.blit(state_bom_str, (420, 80))
                screen.blit(state_bom, (480, 80))
                screen.blit(state_graze_str, (420, 120))
                screen.blit(state_frame, (600, 450))
        
                backImages.update()
                backImages.draw(screen)
        
                # スプライト更新と描画
                my.update()
                my.draw(screen)
        
                # スプライトグループの更新
                # グループを使っているため、本来はループ処理が必要なところ、たったこれだけ。
                all.update()
                all.draw(screen)
        
                # 敵と弾の当たり判定
                for e in pygame.sprite.groupcollide(enemy, mshots, 1, 1).keys():
                    if (e.showtime >= frame):
                        continue
                    e.life -= 1
                    if (e.life == 0):
                        score += e.score
                        e.kill()
        
                # 最終的な画面更新。これで画面が書き換わる
                # ここまでの処理を、今回は1秒に60回行う
                # ↓ 画面更新
                pygame.display.update()
                # ↑ 画面更新終了
        
                # イベント処理。イベントをキャッチする
                # イベントは各オブジェクトが請け負うので
                # ここでは終了メッセージが来たら終了させるにとどめる
                # ※event.get()はメッセージが来た瞬間をキャッチできるため
                # 毎フレーム、確実に処理を行える
                # ※key.get_pressed()では更新のタイミングで押されていなければ処理されない
                for event in pygame.event.get():
                    if event.type == QUIT:
                        sys.exit()
        
                    # ESCが押されたらゲーム終了
                    if event.type == KEYDOWN:
                        if event.key == K_ESCAPE:
                            sys.exit()
        
        # おまじないみたいなものです。プログラムの起動でmain関数を呼ぶ
        if __name__ == "__main__":
            main()