ネコからドーナツを守れ!

ネコからドーナツを守れ!(中3女子/作)
講師から一言
砲弾の処理がリアルに再現されていますね。砲弾を打つたびに砲塔が後ろに下がる処理が特にいいと思います。

戦車本体と砲塔の画像を別で用意している点に工夫の跡がみられます。

学校で三角関数を学んだらsinとcosを使って砲塔を回転するアクションにもチャレンジしてみてください。


Pythonで記述したコード
import pygame
import sys,random

#############変数定義#######################
key=0 #キー操作の判定用
count=0 #タイムラグ発生用
移動音フラグ=False #移動時の音を管理
スモーク処理_白=0 #着弾したときに生じる煙
スモーク処理_黒=0 #着弾したときに生じる煙
g=60 #砲弾ゲージの数
tx=100#戦車のx座標※tはtank(戦車)のt
ty=250#戦車のy座標※tはtank(戦車)のt
弾補給フラグ=False #弾の補給に使うフラグ※これがないと弾_補給に触れている間、ずっとgが増え続ける
c=0 #ネコのコスチューム切り替え用変数
c1=0#ダメージのコスチューム切り替え用変数
index=0#ゲームの進行を管理する変数

#pygameの初期化
pygame.init()

#ディスプレイの作成
screen = pygame.display.set_mode((840, 650))#背景画像は840x610※下のゲージを描くスペースを確保

#タイトルの作成
pygame.display.set_caption("ネコからドーナツを守れ!")

#############画像データのアップロード###################
戦車 = pygame.image.load("image\戦車ボディ.png")
砲塔 = pygame.image.load("image\戦車砲塔.png")
背景=pygame.image.load("image\背景.png")
弾=pygame.image.load("image\弾.png")
スモーク白=pygame.image.load("image\スモーク_白.png")
スモーク黒=pygame.image.load("image\スモーク_黒.png")
弾_補給=pygame.image.load("image\弾_補給.png")
壁=pygame.image.load("image\壁.png")
ドーナツ=pygame.image.load("image\ドーナツ.png")
ネコ1=pygame.image.load(r"image\ネコ1.png")
ネコ2=pygame.image.load(r"image\ネコ2.png")
破壊=pygame.image.load("image\破壊.png")
フラグ=pygame.image.load("image\フラグ.png")
ダメージ1=pygame.image.load("image\ダメージ1.png")
ダメージ2=pygame.image.load("image\ダメージ2.png")
ダメージ3=pygame.image.load("image\ダメージ3.png")

#########フォントデータのアップロード####################
font1=pygame.font.Font("font\日本語フォント.ttf",120)
font2=pygame.font.Font("font\日本語フォント.ttf",60)

##########音声データのアップロード#####################
shot=pygame.mixer.Sound(r"sound\shot.wav")
移動音=pygame.mixer.Sound("sound\移動音.wav")
弾切音=pygame.mixer.Sound("sound\弾切音.mp3")
薬莢排出音=pygame.mixer.Sound("sound\薬莢排出音.mp3")
弾補給音=pygame.mixer.Sound("sound\弾補給音.mp3")
被弾音=pygame.mixer.Sound("sound\被弾音.wav")
BGM=pygame.mixer.Sound("sound\BGM.wav")
衝突=pygame.mixer.Sound("sound\衝突.wav")
ミス=pygame.mixer.Sound("sound\ミス.wav")
ゲームオーバー音=pygame.mixer.Sound("sound\ゲームオーバー.mp3")
ゲームクリア音=pygame.mixer.Sound("sound\ゲームクリア.mp3")

########rectの定義#####################
戦車_rect = pygame.Rect(tx, ty, 60, 60)
砲塔_rect = pygame.Rect(tx+15, ty+15, 60, 60) #砲塔は戦車の座標+15で計算するといつでも戦車の上にくる
壁_rect="" #壁リストに追加するように変数を空で定義。※関数の中で定義するとglobal宣言が複数回必要になるためここで定義。
弾_補給_rect="" #弾_補給リストに追加するように変数を空で定義。※関数の中で定義するとglobal宣言が複数回必要になるためここで定義。
ドーナツ_rect="" #二次元リストで3の所をドーナツにするため、最初はブランクにする。
ネコ_rect="" #二次元リストで4の所をネコにするため、最初はブランクにする。
フラグ_rect =""#二次元リストで5の所をフラグにするため、最初はブランクにする。

########リスト定義#####################
#二次元リストを格納するリスト
stage=[]
#弾を格納するリスト
弾倉 = []
#弾数ゲージを表示するためのリスト
弾倉ゲージ=[]
#壁を格納するリスト
壁リスト=[]
#補給用の弾を格納するリスト
弾_補給リスト=[]
#ネコ_rectを格納するリスト
ネコリスト=[]
#ネコ画像を格納するリスト※コスチューム切り替え用
ネコ画像リスト=[ネコ1,ネコ2]
#戦車とネコが衝突したときの画像リスト
ダメージリスト=[ダメージ1,ダメージ2,ダメージ3]

def ステージ作成():
    global stage
    #二次元リスト※1:壁 2:砲弾  3:ドーナツ 4:ネコ 5:フラグ
    stage=[
        [1,1,1,1,1,1,1,1,1,1,1,1,1,1],
        [1,0,0,0,2,0,0,0,0,0,0,1,0,4],
        [1,0,0,0,0,0,0,1,0,0,0,0,1,4],
        [1,0,0,0,0,0,0,0,0,0,0,1,0,4],
        [1,0,0,3,0,0,0,0,0,0,0,0,0,5],
        [1,0,0,0,0,0,0,0,0,0,0,0,0,4],
        [1,0,0,2,0,0,0,0,0,0,0,1,0,4],
        [1,0,0,0,0,0,0,0,2,0,0,0,0,4],
        [1,0,0,0,0,0,0,0,0,0,0,0,0,4],
        [1,1,1,1,1,1,1,1,1,1,1,1,1,1]
        ]
  
    #二次元リストをもとにステージを作成
    for y in range(10):
        for x in range(14):
            if stage[y][x]==1:#二次元リストが1の所に壁を配置する
                壁_rect = pygame.Rect(x*60, y*61, 60, 61)
                壁リスト.append(壁_rect)#collidelist()を使うために壁リストに「壁_rect」を追加していく
            if stage[y][x]==2: #二次元リストstageの中に2を見つけたら弾_補給_rectを作成し、弾_補給リストに追加する
                弾_補給_rect=pygame.Rect(x*60, y*61, 30, 30)#弾と壁が衝突できるようにrectに(30,30)を追記する
                弾_補給リスト.append(弾_補給_rect)
            if stage[y][x]==4: #二次元リストstageの中に4を見つけたらネコ_rectを作成し、ネコリストに追加する
                ネコ_rect=pygame.Rect(x*60, y*61, 30, 30)#弾とネコが衝突できるようにrectに(30,30)を追記する
                ネコリスト.append(ネコ_rect)

#ステージとアイテム、ネコの描画
def ステージ描画():
    global 壁_rect,弾_補給_rect,弾_補給リスト,弾補給フラグ,g,壁リスト,ドーナツ_rect,ネコ_rect,c,フラグ_rect
    c+=1#ネコのコスチューム切り替えに使用する
    for y in range(10):
        for x in range(14):
            if stage[y][x]==1:#二次元リストが1の所に壁を配置する
                壁_rect = pygame.Rect(x*60, y*61, 60, 61)#この処理がないとscreen.blit(背景,(0,0))に上書きされる
                screen.blit(壁,壁_rect)#for文の中で転送をかける。※この処理がないとscreen.blit(背景,(0,0))に上書きされる
            if stage[y][x]==2:#二次元リストが2の所に弾_補給を配置する。繰り返しの中にあるので二次元リストが更新されたタイミングで弾_補給が消える
                弾_補給_rect=pygame.Rect(x*60, y*61, 60, 60)
                screen.blit(弾_補給,弾_補給_rect)
            if stage[y][x]==3:#二次元リストが3の所にドーナツを配置する
                ドーナツ_rect=pygame.Rect(x*60, y*61, 60, 60)
                screen.blit(ドーナツ,ドーナツ_rect)
            if stage[y][x]==4:#二次元リストが4の所にネコを配置する
                ネコ_rect=pygame.Rect(x*60, y*61, 60, 60)
                if c%150==0:#一定時間ごとにネコリストにネコ_rectを追加する
                    ネコリスト.append(ネコ_rect)
            if stage[y][x]==5:#二次元リストが5の所に勝利フラグを配置する
                フラグ_rect=pygame.Rect(x*60, y*61, 60, 60)
                screen.blit(フラグ,フラグ_rect)

    #ネコの移動
    for i in ネコリスト:#ネコリストから順番に取り出す
        if ドーナツ_rect.x<=i.x:#ネコがドーナツより右側にいるとき
            i.x -=random.randint(-2,4) #ネコの移動量を-2から4の間の数をランダムでいれる
        if 20=i.y:
                i.y +=random.randint(-4,5)
            if  i.x==ドーナツ_rect.x:#ネコがドーナツのx座標まできたら上下にいてもドーナツのところに強制的に移動させる
                i.y =ドーナツ_rect.y
        screen.blit(ネコ画像リスト[c%2],i)#スクリーンに転送

    #戦車と弾_補給が衝突したときの処理            
    if 砲塔_rect.collidelist(弾_補給リスト) !=-1:#collidelistはどのrectとも衝突していないときは-1を返す。どれかとの衝突を調べるには!=-1を使う
        if 砲塔_rect.collidelist(弾_補給リスト)==0:#collidelistはrectと衝突した際、リストの何番目のrectと衝突したかを番号で返す。
            stage[1][4]=0 #2が配置してある二次元リストの場所に0を配置する。
            弾_補給リスト[0]=pygame.Rect(0, 0, 0, 0)#弾_補給リストの0番目にあるrectの座標を変更する。
            if 弾補給フラグ== False:#このフラグがないと、弾_補給とふれている間はずっと、gが増え続ける。
                g+=50#ゲージを増やす
                薬莢排出音.play()#薬莢が排出される音
                弾補給音.play()#砲弾が補給される音
                弾補給フラグ=True#フラグを変更して何度も実行できなくする
        if 砲塔_rect.collidelist(弾_補給リスト)==1:#砲塔_rect.collidelist(弾_補給リスト)==0:と同じ処理
            stage[6][3]=0#上と同じ
            弾_補給リスト[1]=pygame.Rect(0, 0, 0, 0)#上と同じ
            if 弾補給フラグ== False:#上と同じ
                g+=50#上と同じ
                薬莢排出音.play()#上と同じ
                弾補給音.play()#上と同じ
                弾補給フラグ=True#上と同じ
        if 砲塔_rect.collidelist(弾_補給リスト)==2:#砲塔_rect.collidelist(弾_補給リスト)==0:と同じ処理
            stage[7][8]=0#上と同じ
            弾_補給リスト[2]=pygame.Rect(0, 0, 0, 0)#上と同じ
            if 弾補給フラグ== False:#上と同じ
                g+=50#上と同じ
                薬莢排出音.play()#上と同じ
                弾補給音.play()#上と同じ
                弾補給フラグ=True#上と同じ
    else:#弾_補給に触れたときにTrueにしたフラグをFalseに戻す。
        弾補給フラグ=False
    

#移動音を管理する関数
def move_sound():
    global 移動音フラグ
    if 移動音フラグ==False:
            移動音.play()
            移動音フラグ=True
    else:
            移動音フラグ=False

#ネコが被弾したときの処理
def ネコ被弾():
    global index
    for i in 弾倉:#弾倉リストから順に取り出した弾_rectがネコリストのネコ_rectと衝突したときの処理
        if i.collidelist(ネコリスト) !=-1:#ネコリストのどれかと衝突したとき
            被弾ネコ=i.collidelist(ネコリスト)#被弾したネコがリストの何番目かを調べる。print(i.collidelist(ネコリスト))で出力可能
            screen.blit(破壊,ネコリスト[被弾ネコ])#被弾したネコを破壊のコスチュームに変更する
            ネコリスト.remove(ネコリスト[被弾ネコ])#被弾したネコ_rectをネコリストから除外する
            被弾音.play()#被弾音を鳴らす。
        if ネコリスト==[]:#ネコリストが空のとき⇒ネコが全滅したときの処理
            index = 3#GAME CLEARを送る

def ネコ衝突():#戦車とネコが衝突したときの処理
    global index,c1
    c1+=1 #戦車がネコと衝突したときのダメージのコスチューム変更に使用
    for i in ネコリスト:
        if i. colliderect(戦車_rect) :#ネコと戦車の衝突時の処理
            戦車_rect.x-=40 #戦車が後退する
            砲塔_rect.x-=40 #砲塔が後退する
            screen.blit(ダメージリスト[c1%3],戦車_rect)
            衝突.play()#衝突音を鳴らす
        #ネコとドーナツが衝突したときの処理    
        if i.colliderect(ドーナツ_rect):
            BGM.stop()#BGMを先に止める
            ミス.play()
            ゲームオーバー音.play()
            index=2#GAME OVERを送る

def フラグゲット():#勝利フラグをゲットしたときの処理
    global index
    if 戦車_rect.colliderect(フラグ_rect):#戦車と勝利フラグが衝突したときの処理
        BGM.stop()#BGMを先に止める
        ゲームクリア音.play()
        index=3#GAME CLEARを送る
                           
#砲弾の処理1(スペースが押された時の処理)
def 砲弾発射1():
    弾_rect = pygame.Rect(砲塔_rect.x+50, 砲塔_rect.y, 30, 30) #戦車の座標をもとに算出
    弾倉.append(弾_rect) #弾倉リストに弾_rectを追加していく
    for i in 弾倉:
        if  i.collidelist(壁リスト) ==-1:#壁に当たるまでx座標を30ずつ動かす
            if i.colliderect(ドーナツ_rect) :#ドーナツに被弾したときの処理
                弾倉.remove(i)#弾倉リストからiを削除する処理 ※これをしないと重くなる
                screen.blit(スモーク白,(i.x+スモーク処理_白,i.y+スモーク処理_白))
                screen.blit(スモーク黒,(i.x+スモーク処理_黒,i.y+スモーク処理_黒))
            elif i.collidelist(ネコリスト) !=-1:
                ネコ被弾()#ネコが被弾した時の処理
                弾倉.remove(i)#弾倉リストからiを削除する処理 
                screen.blit(スモーク白,(i.x+スモーク処理_白,i.y+スモーク処理_白))
                screen.blit(スモーク黒,(i.x+スモーク処理_黒,i.y+スモーク処理_黒))
            else:
                i.x+=30#弾_rectのx座標を30ずつ増やす
                screen.blit(弾,i)
        else:#弾が画面の端まで行ったときの処理
            弾倉.remove(i)#弾倉リストから弾_rectを削除する処理 
            screen.blit(スモーク白,(i.x+スモーク処理_白,i.y+スモーク処理_白))
            screen.blit(スモーク黒,(i.x+スモーク処理_黒,i.y+スモーク処理_黒))

#砲弾の処理2(スペースが離れたときの処理※砲弾発射1だけだとスペースが押したときしか弾が動かない。一度発射された弾は自動で端まで行く必要がある)
def 砲弾発射2():
    for i in 弾倉:
        if i.collidelist(壁リスト)==-1:#壁に当たるまでx座標を30ずつ動かす ※どのrectとも衝突しないときは-1が返される
            if i.colliderect(ドーナツ_rect):
                弾倉.remove(i)#弾倉リストからiを削除する処理 ※これをしないと重くなる
                screen.blit(スモーク白,(i.x+スモーク処理_白,i.y+スモーク処理_白))
                screen.blit(スモーク黒,(i.x+スモーク処理_黒,i.y+スモーク処理_黒))
            else:
                ネコ被弾()#ネコが衝突した時の処理
                i.x+=30#弾_rectのx座標を30ずつ増やす
                screen.blit(弾,i)
        else:#弾が画面の端まで行ったときの処理
            弾倉.remove(i)#弾倉リストからiを削除する処理 ※これをしないと重くなる
            screen.blit(スモーク白,(i.x+スモーク処理_白,i.y+スモーク処理_白))
            screen.blit(スモーク黒,(i.x+スモーク処理_黒,i.y+スモーク処理_黒))

#弾倉ゲージの表示
def 弾倉ゲージ表示():
    global 弾倉ゲージ,g
    # ゲージを表示するためのリスト
    for i in range(g):
        弾倉ゲージ.append(pygame.Rect(10+i*15,613,10,33))
        pygame.draw.rect(screen,"#e360e6",弾倉ゲージ[i]) #矩形を描く処理
    弾倉ゲージ=[] #弾倉ゲージリストを毎回、空にして重くなるのを防ぐ。appendでリストへ無限にrectを追加すると重くなる。

#リプレイ時の処理
def リプレイ():
    global key,g,tx,ty,index,戦車_rect,砲塔_rect   
    for i in ネコリスト:#ネコリストから順番に取り出す
        ネコリスト.remove(i)
    for i in 弾_補給リスト:#弾_補給リストから順番に取り出す
        弾_補給リスト.remove(i)

    key=pygame.key.get_pressed()
    if key[pygame.K_RETURN]:
        #変数初期化
        g=60 #ゲージの数
        tx=100#戦車のx座標※tはtank(戦車)のt
        ty=250#戦車のy座標※tはtank(戦車)のt
        戦車_rect = pygame.Rect(tx, ty, 60, 60)#戦車を元の位置に戻す
        砲塔_rect = pygame.Rect(tx+15, ty+15, 60, 60) #砲塔を元の位置に戻す
        index=0 #ゲーム開始時の状態にする       

#×ボタンで終了処理
def finish():
    for i in pygame.event.get():
        if i.type==pygame.QUIT:
            pygame.quit()
            sys.exit()
    
#メインの処理        
def game_main():
    global count,発射フラグ,c,スモーク処理_白,スモーク処理_黒,g,壁リスト,弾倉,index,key
    if index ==0:#ゲーム開始するための準備をする ※0⇒準備、1⇒進行、2⇒ゲームオーバー、3⇒ゲームクリア
        ステージ作成()
        ステージ描画()
        BGM.stop()
        ゲームオーバー音.stop()
        ゲームクリア音.stop()
        BGM.set_volume(0.3)#BGMのボリューム調整
        BGM.play(-1)
        index=1#ゲームの準備が完了したらindexを1にする。game_main()はループしているので準備完了後はこの操作をさせないようにする。
   
    if index==1:#ゲームが進行する処理
        count+=1#砲塔のタイムラグを作る変数
        スモーク処理_白=random.randint(-25,25)#白のスモークが描画される位置
        スモーク処理_黒=random.randint(-25,25)#黒のスモークが描画される位置
        #画像の描画※基本構成は黒の背景⇒背景画像⇒戦車⇒砲塔の順に描画する
        screen.fill("black")#スクリーンそのものを転送※これがないとゲージが減らない。古いゲージ画像が残り、減っていることが分からなくなる。
        screen.blit(背景,(0,0))
        screen.blit(戦車, 戦車_rect)
        screen.blit(砲塔, 砲塔_rect)

        #キー入力を取得する
        key=pygame.key.get_pressed()
        
        #戦車の移動(右)
        if key[pygame.K_RIGHT] :
            if 砲塔_rect.collidelist(壁リスト)!=-1 or 砲塔_rect.x>770:#collidelistはどこにも衝突していないときは-1を返す。-1以外のときとはどこかに触れていることを意味する。
                戦車_rect.x -= 10 #3の速度で戦車は進んでいるが、壁に触れたときは強めに跳ね返す必要がある。-3にすると動かなくなる。
                砲塔_rect.x -= 10
            else:#壁にふれていないときは5ずつ進む。
                戦車_rect.x +=5
                砲塔_rect.x +=5
                move_sound()#戦車の移動音
            
       #戦車の移動(左)
        if key[pygame.K_LEFT]:
            if 戦車_rect.collidelist(壁リスト)!=-1 or 戦車_rect.x<65:#壁に触れるか、戦車のx座標が65より小さくなったとき
                戦車_rect.x +=10
                砲塔_rect.x +=10
            else:#壁にふれていないときは5ずつ進む。
                戦車_rect.x -=5
                砲塔_rect.x -=5
                move_sound()
                
        #戦車の移動(上)
        if key[pygame.K_UP]:
            if 戦車_rect.collidelist(壁リスト)!=-1 or 戦車_rect.y<65:
                戦車_rect.y +=10
                砲塔_rect.y +=10
            else:#壁にふれていないときは3ずつ進む。
                戦車_rect.y -=3
                砲塔_rect.y -=3
                move_sound()
            
        #戦車の移動(下)   
        if key[pygame.K_DOWN]:
            if 戦車_rect.collidelist(壁リスト)!=-1 or 戦車_rect.y>470:
                戦車_rect.y -=10
                砲塔_rect.y -=10
            else:#壁にふれていないときは3ずつ進む。
                戦車_rect.y +=3
                砲塔_rect.y +=3
                move_sound()
                
        #砲弾発射
        if key[pygame.K_SPACE]:#スペースキーがおされたの処理
            #砲塔の動き
            砲塔_rect.x -=4 #砲塔を後ろに下げる※砲弾が発射されたときのアクション
            if count%2==0:#タイムラグを作っている
                砲塔_rect.x +=8
                shot.play()
                g -=1 #弾数を減らす処理
            #砲弾の動き
            if g>0:
                砲弾発射1()#スペースキーが押されたときの弾の発射処理
            else:
                弾倉 = []#弾が無くなったときに弾倉リストを空にする。※この処理がないと弾_補給に触れた際、弾の残像が表示される
                if count%4==0:#タイムラグを作っている
                    弾切音.play()
        else:#押下されたスペースキーが離れたときの処理
            #無理にelseを使う必要はないが、スペースキーが離れたことを明示的に表現するためにあえて記載
            #スペースキーが離れた後も砲弾を端まで移動させる処理
            #この処理がないとスペースキーが押されたときしか砲弾は進まない
            if g>0:#弾がある間の処理
                砲弾発射2()#スペースキーが離れたときの弾の発射処理
        
        #弾倉ゲージの表示
        弾倉ゲージ表示()
        if g<0: #弾がなくなったときにgがマイナスになるのを防ぐため、gに0を代入する
            g=0
        #ステージの描画
        ステージ描画()
        #ステージ描画()#2次元リストをもとに壁とアイテムを作る。
        ネコ衝突()#戦車とネコの衝突を監視
        フラグゲット()#戦車と勝利フラグの衝突を監視
    #ゲームオーバ時の処理    
    if index==2:
        ゲームオーバーメッセージ=font1.render("GAME OVER",True,"blue")
        リプレイメッセージ=font2.render("リプレイ:Enterキーを押す",True,"green")
        screen.blit(ゲームオーバーメッセージ,(90,250))
        screen.blit(リプレイメッセージ,(90,450))
        リプレイ()#やり直しの処理
   #ゲームクリア時の処理
    if index==3:
        ゲームクリアメッセージ=font1.render("GAME CLEAR",True,"orange")
        リプレイメッセージ=font2.render("リプレイ:Enterキーを押す",True,"green")
        screen.blit(ゲームクリアメッセージ,(80,250))
        screen.blit(リプレイメッセージ,(90,450))
        リプレイ()#やり直しの処理      
    
#繰り返し
while True:
    finish()#×ボタンでいつでも終了できるように繰り返しの中にfinish()関数を配備する
    game_main()#メインの処理
    pygame.display.update()#ディスプレイのアップデート
    pygame.time.Clock().tick(60)#フレームレートを60に設定