見出し画像

実施報告01:Pygame Platformer

このnoteではChatGPTを使った報告を書こうと思っていたのですが、今回の話題は次のチュートリアルの実施報告が主体でChatGTPの関与が少ないのでタイトルからChatGPTを外しました。これからもいろいろと脱線しますが、よろしくご了承ください。

いろんな概念

今回のプログラムに含まれるのは次のような概念です。
1. Collision Detection
2. Player movement(realistic sideways movement)
3.  Jump mechanics
4. Gravity and Friction
5. Random Level Generation
6. Warpable screen movement
7. Scrolling the screen(creating an infinite height)
8. Creating a Score counter
9. "Game Over" Mechanic
10. Random Platform movement

Part1-Setting the Foundation

まず、最初に今回のゲームの基礎を述べます。

初期化と定数

import pygame
from pygame.locals import *
 
pygame.init()
vec = pygame.math.Vector2  # 2 for two dimensional
 
HEIGHT = 450
WIDTH = 400
ACC = 0.5
FRIC = -0.12
FPS = 60
 
FramePerSec = pygame.time.Clock()
 
displaysurface = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Game")

とりあえずは、この初期化と必要な定数を設定します。
この結果は下記になります。

なぜか、Mu-editorでは表示されてVS Codeでは表示されませんでした。

Player and Platform Classes

ここではPlayerとPlatformClassとして表示します。

class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__() 
        self.surf = pygame.Surface((30, 30))
        self.surf.fill((128,255,40))
        self.rect = self.surf.get_rect(center = (10, 420))
 
class platform(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.surf = pygame.Surface((WIDTH, 20))
        self.surf.fill((255,0,0))
        self.rect = self.surf.get_rect(center = (WIDTH/2, HEIGHT - 10))
 
PT1 = platform()
P1 = Player()

各々のClassは固定の大きさ、各々の色、最後に各々の四角形を決めます。最終的に二つのオブジェクトPT1,P1を得ます。

Sprites Groups + Game Loop

前期で作ったPT1とP1をそのまま使うのでなく、Groupの一員としてあつかい、いろんなスプライトを容易に扱えるようにします。

all_sprites = pygame.sprite.Group()
all_sprites.add(PT1)
all_sprites.add(P1)
 
while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
     
    displaysurface.fill((0,0,0))
 
    for entity in all_sprites:
        displaysurface.blit(entity.surf, entity.rect)
 
    pygame.display.update()
    FramePerSec.tick(FPS)

具体的には一つ一つ、PT1、P1でなく、下記の形でグループ化の効果が示される。

    for entity in all_sprites:
        displaysurface.blit(entity.surf, entity.rect)

この時点の結果は次に示されます。

下記のコードでPT1を描画し、P1を描画しているようです
all_sprites = pygame.sprite.Group()
all_sprites.add(PT1)
all_sprites.add(P1)


Implementing Movement

Playerを動かすためにClassに位置、速度、加速度の(x、y)方向の成分を追加します。ちなみにvecはベクトルの意味ですね。

class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__() 
        self.surf = pygame.Surface((30, 30))
        self.surf.fill((128,255,40))
        self.rect = self.surf.get_rect()
   
        self.pos = vec((10, 385))
        self.vel = vec(0,0)
        self.acc = vec(0,0)

次にmoveの関数を付け加えます。

def move(self):
    self.acc = vec(0,0)
 
    pressed_keys = pygame.key.get_pressed()
            
    if pressed_keys[K_LEFT]:
        self.acc.x = -ACC
    if pressed_keys[K_RIGHT]:
        self.acc.x = ACC  

一般的に動かすときのプログラムは次になります。

self.acc.x += self.vel.x * FRIC
self.vel += self.acc
self.pos += self.vel + 0.5 * self.acc

また、X方向の両端まで行けば反対側から動かすようにます。

if self.pos.x > WIDTH:
    self.pos.x = 0
if self.pos.x < 0:
    self.pos.x = WIDTH
     
self.rect.midbottom = self.pos

これでPlayerを動かすためには、つぎを記述します。

P1.move()

ちょっと細切れのCodeになりましたが、無事、前のCodeに挿入できたでしょうか? P1.move()の位置が難しかったかもしれません。
次に今までのコードを示します。

import pygame
from pygame.locals import *
import sys
 
pygame.init()
 
vec = pygame.math.Vector2 
HEIGHT = 450
WIDTH = 400
ACC = 0.5
FRIC = -0.12
FPS = 60
FramePerSec = pygame.time.Clock()
 
displaysurface = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Game")
 
class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__() 
        self.surf = pygame.Surface((30, 30))
        self.surf.fill((128,255,40))
        self.rect = self.surf.get_rect()
   
        self.pos = vec((10, 385))
        self.vel = vec(0,0)
        self.acc = vec(0,0)
 
    def move(self):
        self.acc = vec(0,0)
    
        pressed_keys = pygame.key.get_pressed()            
        if pressed_keys[K_LEFT]:
            self.acc.x = -ACC
        if pressed_keys[K_RIGHT]:
            self.acc.x = ACC
             
        self.acc.x += self.vel.x * FRIC
        self.vel += self.acc
        self.pos += self.vel + 0.5 * self.acc
         
        if self.pos.x > WIDTH:
            self.pos.x = 0
        if self.pos.x < 0:
            self.pos.x = WIDTH
            
        self.rect.midbottom = self.pos    
 
class platform(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.surf = pygame.Surface((WIDTH, 20))
        self.surf.fill((255,0,0))
        self.rect = self.surf.get_rect(center = (WIDTH/2, HEIGHT - 10))
 
PT1 = platform()
P1 = Player()
 
all_sprites = pygame.sprite.Group()
all_sprites.add(PT1)
all_sprites.add(P1)
 
while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
     
    displaysurface.fill((0,0,0))
 
    P1.move()
    for entity in all_sprites:
        displaysurface.blit(entity.surf, entity.rect)
    
    pygame.display.update()
    FramePerSec.tick(FPS)

結果はつぎのようになります。

自分で動かしてみてくださいね。

Part2 – Gravity and Jumping

Implementing Gravity

今まで、X軸方向に加速度を考慮したので、Y軸方向に重力加速度を考慮するのはただつぎのようにmove関数のvec(0,0)をvec(0,0.5)とするだけでいい。

def move(self):
      self.acc = vec(0,0.5)
  
      pressed_keys = pygame.key.get_pressed()
      ...
      ...

The effects of Gravity

一寸プログラムをいじって下記に動画を示します。

次に衝突の問題があります。

   ... 
   def update(self):
        hits = pygame.sprite.spritecollide(P1 , platforms, False)
        if hits:
            self.pos.y = hits[0].rect.top + 1
            self.vel.y = 0
...
...
platforms = pygame.sprite.Group()
platforms.add(PT1)

ここでは新しいcodeが二つあります。一つは衝突の判定、もう一つは新しくはPlatformのGroupの作成です。なお、spritecollideの第3のパラメーターは通常Falseでいいかと思います。
あと、二つのことが重要です。一つは着地の際にPlayerの速度をゼロにすること、一つはPlayerの位置をplatformの上に接地させないといけません。
この結果を下に示します。

Player Jumping

重力の影響を入れることができたので次はジャンプの機能を追加します。これはすごく簡単です。上へ行くには負の速度を与えます。

def jump(self):
    self.vel.y = -15

次にスペースキーを押したときにジャンプさせるために次のcodeを追加します。

while True:    
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN:    
            if event.key == pygame.K_SPACE:
                P1.jump()
        ...
        ...

これだけだと、スペースキーを押すとすぐに飛び上がり、重力で落ちてくるという感じが出ません。スペースキーを押すと一度だけ効いてあとは重力で落ちてきたいのです。

	def update(self):
    hits = pygame.sprite.spritecollide(P1 ,platforms, False)
    if P1.vel.y > 0:        
        if hits:
            self.vel.y = 0
            self.pos.y = hits[0].rect.top + 1

上記の if codeを追加することでPlayerが落ちてくるときに衝突判定をし、その時に速度をゼロ、地上に接地としている。
ダブルジャンプの問題はつぎのcodeを挿入します。

def jump(self):
    hits = pygame.sprite.spritecollide(self, platforms, False)
    if hits:
       self.vel.y = -15

Part2 を終えるにあたり、全体のCodeを整理しておきます。

import pygame
from pygame.locals import *
import sys
import random
 
pygame.init()
vec = pygame.math.Vector2 #2 for two dimensional
 
HEIGHT = 450
WIDTH = 400
ACC = 0.5
FRIC = -0.12
FPS = 60
 
FramePerSec = pygame.time.Clock()
 
displaysurface = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Game")
 
class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__() 
        #self.image = pygame.image.load("character.png")
        self.surf = pygame.Surface((30, 30))
        self.surf.fill((128,255,40))
        self.rect = self.surf.get_rect()
   
        self.pos = vec((10, 360))
        self.vel = vec(0,0)
        self.acc = vec(0,0)
 
    def move(self):
        self.acc = vec(0,0.5)
    
        pressed_keys = pygame.key.get_pressed()
                
        if pressed_keys[K_LEFT]:
            self.acc.x = -ACC
        if pressed_keys[K_RIGHT]:
            self.acc.x = ACC
                 
        self.acc.x += self.vel.x * FRIC
        self.vel += self.acc
        self.pos += self.vel + 0.5 * self.acc
         
        if self.pos.x > WIDTH:
            self.pos.x = 0
        if self.pos.x < 0:
            self.pos.x = WIDTH
             
        self.rect.midbottom = self.pos
 
    def jump(self):
        hits = pygame.sprite.spritecollide(self, platforms, False)
        if hits:
           self.vel.y = -15
 
 
    def update(self):
        hits = pygame.sprite.spritecollide(P1 ,platforms, False)
        if P1.vel.y > 0:        
            if hits:
                self.vel.y = 0
                self.pos.y = hits[0].rect.top + 1
 
 
class platform(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.surf = pygame.Surface((WIDTH, 20))
        self.surf.fill((255,0,0))
        self.rect = self.surf.get_rect(center = (WIDTH/2, HEIGHT - 10))
 
    def move(self):
        pass
 
PT1 = platform()
P1 = Player()
 
all_sprites = pygame.sprite.Group()
all_sprites.add(PT1)
all_sprites.add(P1)
 
platforms = pygame.sprite.Group()
platforms.add(PT1)
 
 
while True: 
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN:    
            if event.key == pygame.K_SPACE:
                P1.jump()
         
    displaysurface.fill((0,0,0))
    P1.update()
 
    for entity in all_sprites:
        displaysurface.blit(entity.surf, entity.rect)
        entity.move()
 
    pygame.display.update()
    FramePerSec.tick(FPS)

結果をここに示します。

Part3-Level Generation

Initial Level Generation

レベル生成コードを二つの部分に分けます。
最初のコードはゲーム開始時に一回だけ実行されます。

for x in range(random.randint(5, 6)):
    pl = platform()
    platforms.add(pl)
    all_sprites.add(pl)

上記のコードは5~6のPlatformを生成します

Updating the Platform Class

ランダムなレベル生成のためにplatformの拡張をします。

class platform(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.surf = pygame.Surface((random.randint(50,100), 12))
        self.surf.fill((0,255,0))
        self.rect = self.surf.get_rect(center = (random.randint(0,WIDTH-10),
                                                 random.randint(0, HEIGHT-30)))

当然、今までのplatformは別扱いしなければなりません。

PT1.surf = pygame.Surface((WIDTH, 20))
PT1.surf.fill((255,0,0))
PT1.rect = PT1.surf.get_rect(center = (WIDTH/2, HEIGHT - 10))

Infinite Scrolling Screen

キャラクターが無限に上に移動できる機能を作成する必要があります。次のコードをゲームループに挿入してください。

if P1.rect.top <= HEIGHT / 3:
    P1.pos.y += abs(P1.vel.y)
    for plat in platforms:
        plat.rect.y += abs(P1.vel.y)
        if plat.rect.top >= HEIGHT:
            plat.kill()

・ if P1.rect.top <= HEIGHT / 3:
Playerの位置がHEIGHT/3でこの中のコード if statement が実行されます。この位置は実験的に決められました
・ P1.pos.y += abs(P1.vel.y)
p1をその速度で動かします。絶対値をつけて速度値から府の符号を削除します
・ for plat in platforms:
・   plat.rect.y += abs(P1.vel.y)
playerだけでなく、すべてのplatformについても行います。
・ if plat.rect.top >= HEIGHT:
・   plat.kill()
画面からはみ出したplatformはすべて破壊されます。

Random Level Generation

platformは画面の上の方まで作っておき、playerが上に移動するとこれらのplatformが表示されます。

def plat_gen():
    while len(platforms) < 7 :
        width = random.randrange(50,100)
        p  = platform()             
        p.rect.center = (random.randrange(0, WIDTH - width),
                             random.randrange(-50, 0))
        platforms.add(p)
        all_sprites.add(p)

このCodeの簡単な説明です
・ while len(platforms) < 7 :
実験結果と深い考察で決められたようです(^^♪
・ width = random.randrange(50,100)
多様性のためランダムな幅を追加
・ p.rect.center = (random.randrange(0, WIDTH - width),
         random.randrange(-50, 0))
  platforms.add(p)
  all_sprites.add(p)
画面の表示部分の真上にplatformを作成して配置します。platforms Groupとall_sprites Groupに追加されます。

ちょっと難しくなりましたが、次にCodeを示します。

import pygame
from pygame.locals import *
import sys
import random
 
pygame.init()
vec = pygame.math.Vector2 
 
HEIGHT = 450
WIDTH = 400
ACC = 0.5
FRIC = -0.12
FPS = 60
 
FramePerSec = pygame.time.Clock()
 
displaysurface = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Game")
 
class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__() 
        self.surf = pygame.Surface((30, 30))
        self.surf.fill((255,255,0))
        self.rect = self.surf.get_rect()
   
        self.pos = vec((10, 360))
        self.vel = vec(0,0)
        self.acc = vec(0,0)
 
    def move(self):
        self.acc = vec(0,0.5)
        pressed_keys = pygame.key.get_pressed()
                
        if pressed_keys[K_LEFT]:
            self.acc.x = -ACC
        if pressed_keys[K_RIGHT]:
            self.acc.x = ACC
                 
        self.acc.x += self.vel.x * FRIC
        self.vel += self.acc
        self.pos += self.vel + 0.5 * self.acc
         
        if self.pos.x > WIDTH:
            self.pos.x = 0
        if self.pos.x < 0:
            self.pos.x = WIDTH         
        self.rect.midbottom = self.pos
 
    def jump(self):
        hits = pygame.sprite.spritecollide(self, platforms, False)
        if hits:
           self.vel.y = -15
 
    def update(self):
        hits = pygame.sprite.spritecollide(P1 ,platforms, False)
        if P1.vel.y > 0:        
            if hits:
                self.vel.y = 0
                self.pos.y = hits[0].rect.top + 1
 
class platform(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.surf = pygame.Surface((random.randint(50,100), 12))
        self.surf.fill((0,255,0))
        self.rect = self.surf.get_rect(center = (random.randint(0,WIDTH-10),
                                                 random.randint(0, HEIGHT-30)))
 
    def move(self):
        pass
 
 
def plat_gen():
    while len(platforms) < 7 :
        width = random.randrange(50,100)
        p  = platform()             
        p.rect.center = (random.randrange(0, WIDTH - width),
                             random.randrange(-50, 0))
        platforms.add(p)
        all_sprites.add(p)
 
PT1 = platform()
P1 = Player()
 
PT1.surf = pygame.Surface((WIDTH, 20))
PT1.surf.fill((255,0,0))
PT1.rect = PT1.surf.get_rect(center = (WIDTH/2, HEIGHT - 10))
 
all_sprites = pygame.sprite.Group()
all_sprites.add(PT1)
all_sprites.add(P1)
 
platforms = pygame.sprite.Group()
platforms.add(PT1)
 
for x in range(random.randint(5, 6)):
    pl = platform()
    platforms.add(pl)
    all_sprites.add(pl)
 
 
while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN:    
            if event.key == pygame.K_SPACE:
                P1.jump()
 
    if P1.rect.top <= HEIGHT / 3:
        P1.pos.y += abs(P1.vel.y)
        for plat in platforms:
            plat.rect.y += abs(P1.vel.y)
            if plat.rect.top >= HEIGHT:
                plat.kill()
         
    displaysurface.fill((0,0,0))
    P1.update()
    plat_gen()
 
    for entity in all_sprites:
        displaysurface.blit(entity.surf, entity.rect)
        entity.move()
 
    pygame.display.update()
    FramePerSec.tick(FPS

結果を下記に示します。

最初にランダムに数枚のページを作ったのが理解できます(^^♪ ちょっとできないなというのも出てきました。

Part4-Improving the game

このセクションでは次の小さな修正を追加します。
 ・ プレイヤーがプラットフォームに着地する方法を修正する
 ・ ジャンプ機構の改善
 ・ プラットフォーム生成の改善 (プラットフォーム間の衝突)

pygameでゲームを作成するのは簡単ですが、それを改善して完成させることは一つの挑戦です。以下には幾つかの小さい改善についてすべて説明します。

The Jump Mechanic

ここではジャンプの種類を大きなジャンプと小さなジャンプに分けます。

class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__() 
         ...
         ...
         self.jumping = False
 
    def jump(self): 
        hits = pygame.sprite.spritecollide(self, platforms, False)
        if hits and not self.jumping:
           self.jumping = True
           self.vel.y = -15
 
    def cancel_jump(self):
        if self.jumping:
            if self.vel.y < -3:
                self.vel.y = -3

最初の変更は、self.jumping という新しい変数の追加です。これを使用して、プレイヤーがジャンプ位置にいるかどうかを判断します。

if event.type == pygame.KEYDOWN:    
    if event.key == pygame.K_SPACE:
        P1.jump()
if event.type == pygame.KEYUP:    
    if event.key == pygame.K_SPACE:
        P1.cancel_jump()  

スペースバーをさまざまな時間押すことで、ジャンプの持続時間と大きさを変えることができ、短いジャンプと長いジャンプの両方を行うことができました。

Platform Generation

近すぎたplatformを整理するために次の関数を作ります。

def check(platform, groupies):
    if pygame.sprite.spritecollideany(platform,groupies):
        return True
    else:
        for entity in groupies:
            if entity == platform:
                continue
            if (abs(platform.rect.top - entity.rect.bottom) < 50) and (abs(platform.rect.bottom - entity.rect.top) < 50):
                return True
        C = False

次にplat_gen()関数を修正します。

def plat_gen():
    while len(platforms) < HARD :
        width = random.randrange(50,100)
        p  = platform()      
        C = True
         
        while C:           
             p = platform()
             p.rect.center = (random.randrange(0, WIDTH - width),
                             random.randrange(-50, 0))
             C = check(p, platforms)
 
        platforms.add(p)
        all_sprites.add(p)

Player Landing

	def update(self):
    hits = pygame.sprite.spritecollide(self ,platforms, False)
    if self.vel.y > 0:        
        if hits:
            if self.pos.y < hits[0].rect.bottom:               
                self.pos.y = hits[0].rect.top +1
                self.vel.y = 0
                self.jumping = False

if self.pos.y < lowest.rect.bottom:
プレイヤーの y 位置がプラットフォームの下部を超えなくなるまで、「着陸」が登録されないようにします。

いままでの議論をまとめると、つぎのようになります。

import pygame
from pygame.locals import *
import sys
import random
 
pygame.init()
vec = pygame.math.Vector2 #2 for two dimensional
 
HEIGHT = 450
WIDTH = 400
ACC = 0.5
FRIC = -0.12
FPS = 60
 
FramePerSec = pygame.time.Clock()
 
displaysurface = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Game")
 
class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__() 
        #self.image = pygame.image.load("character.png")
        self.surf = pygame.Surface((30, 30))
        self.surf.fill((255,255,0))
        self.rect = self.surf.get_rect()
   
        self.pos = vec((10, 360))
        self.vel = vec(0,0)
        self.acc = vec(0,0)
        self.jumping = False
 
    def move(self):
        self.acc = vec(0,0.5)
    
        pressed_keys = pygame.key.get_pressed()
                
        if pressed_keys[K_LEFT]:
            self.acc.x = -ACC
        if pressed_keys[K_RIGHT]:
            self.acc.x = ACC
                 
        self.acc.x += self.vel.x * FRIC
        self.vel += self.acc
        self.pos += self.vel + 0.5 * self.acc
         
        if self.pos.x > WIDTH:
            self.pos.x = 0
        if self.pos.x < 0:
            self.pos.x = WIDTH
             
        self.rect.midbottom = self.pos
 
    def jump(self): 
        hits = pygame.sprite.spritecollide(self, platforms, False)
        if hits and not self.jumping:
           self.jumping = True
           self.vel.y = -15
 
    def cancel_jump(self):
        if self.jumping:
            if self.vel.y < -3:
                self.vel.y = -3
 
    def update(self):
        hits = pygame.sprite.spritecollide(self ,platforms, False)
        if self.vel.y > 0:        
            if hits:
                if self.pos.y < hits[0].rect.bottom:               
                    self.pos.y = hits[0].rect.top +1
                    self.vel.y = 0
                    self.jumping = False
 
 
class platform(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.surf = pygame.Surface((random.randint(50,100), 12))
        self.surf.fill((0,255,0))
        self.rect = self.surf.get_rect(center = (random.randint(0,WIDTH-10),
                                                 random.randint(0, HEIGHT-30)))
 
    def move(self):
        pass
 
 
def check(platform, groupies):
    if pygame.sprite.spritecollideany(platform,groupies):
        return True
    else:
        for entity in groupies:
            if entity == platform:
                continue
            if (abs(platform.rect.top - entity.rect.bottom) < 40) and (abs(platform.rect.bottom - entity.rect.top) < 40):
                return True
        C = False
 
def plat_gen():
    while len(platforms) < 6:
        width = random.randrange(50,100)
        p  = platform()      
        C = True
         
        while C:
             p = platform()
             p.rect.center = (random.randrange(0, WIDTH - width),
                              random.randrange(-50, 0))
             C = check(p, platforms)
        platforms.add(p)
        all_sprites.add(p)
 
 
        
PT1 = platform()
P1 = Player()
 
PT1.surf = pygame.Surface((WIDTH, 20))
PT1.surf.fill((255,0,0))
PT1.rect = PT1.surf.get_rect(center = (WIDTH/2, HEIGHT - 10))
 
all_sprites = pygame.sprite.Group()
all_sprites.add(PT1)
all_sprites.add(P1)
 
platforms = pygame.sprite.Group()
platforms.add(PT1)
 
for x in range(random.randint(4,5)):
    C = True
    pl = platform()
    while C:
        pl = platform()
        C = check(pl, platforms)
    platforms.add(pl)
    all_sprites.add(pl)
 
 
while True:
    P1.update()
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN:    
            if event.key == pygame.K_SPACE:
                P1.jump()
        if event.type == pygame.KEYUP:    
            if event.key == pygame.K_SPACE:
                P1.cancel_jump()  
 
    if P1.rect.top <= HEIGHT / 3:
        P1.pos.y += abs(P1.vel.y)
        for plat in platforms:
            plat.rect.y += abs(P1.vel.y)
            if plat.rect.top >= HEIGHT:
                plat.kill()
 
    plat_gen()
    displaysurface.fill((0,0,0))
     
    for entity in all_sprites:
        displaysurface.blit(entity.surf, entity.rect)
        entity.move()
 
    pygame.display.update()
    FramePerSec.tick(FPS)

結果の初期状態を示します。purattoformが程よい間隔で並べられているようですね・

Pygame Platformer – Completing the Game

前のセクションまででほとんどのことは終了した。あと少し、次のようなことを行います。
  ・ ゲームオーバー画面
  ・ スコアシステム
  ・ ムービングプラットフォーム

Implementing a Game Over Screen

import time
...
...
 
while True:
   ...
   ...
   ... 
 
   if P1.rect.top > HEIGHT:
        for entity in all_sprites:
            entity.kill()
            time.sleep(1)
            displaysurface.fill((255,0,0))
            pygame.display.update()
            time.sleep(1)
            pygame.quit()
            sys.exit()

if P1.rect.top > HEIGHT:

最後、失敗すると画面下にplayerは画面外へ落ちていくが、その時点ですべてのスプライトは強制終了となり、画面が赤になり、ゲームオーバーとなります。

High Score

class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__() 
        ...
        ...
        self.jumping = False
        self.score = 0       ##
    ...
    ...
 
    def update(self):
        hits = pygame.sprite.spritecollide(self ,platforms, False)
        if self.vel.y > 0:        
            if hits:
                if self.pos.y < hits[0].rect.bottom:
                    if hits[0].point == True:   ##
                        hits[0].point = False   ##
                        self.score += 1         ## 
                    self.pos.y = hits[0].rect.top +1
                    self.vel.y = 0
                    self.jumping = False
 
class platform(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        ...
        ...
        self.moving = True
        self.point = True   ##
...
...
PT1.moving = False
PT1.point = False   ##
...
...
 
while True:
    ...
    ...
    ...
    displaysurface.fill((0,0,0))
    f = pygame.font.SysFont("Verdana", 20)     ##
    g  = f.render(str(P1.score), True, (123,255,0))   ##
    displaysurface.blit(g, (WIDTH/2, 10))   ##
 
    for entity in all_sprites:
    ...

Moving Platforms in Pygame

class platform(pygame.sprite.Sprite):
    def __init__(self):
        ...
        ...
        self.speed = random.randint(-1, 1)
        self.moving = True
 
    def move(self):
        if self.moving == True:  
            self.rect.move_ip(self.speed,0)
            if self.speed > 0 and self.rect.left > WIDTH:
                self.rect.right = 0
            if self.speed < 0 and self.rect.right < 0:
                self.rect.left = WIDTH
 
...
...
...
PT1.moving = False

最終的なCodeはつぎのようになります。

import pygame
from pygame.locals import *
import sys
import random
import time
 
pygame.init()
vec = pygame.math.Vector2 #2 for two dimensional
 
HEIGHT = 450
WIDTH = 400
ACC = 0.5
FRIC = -0.12
FPS = 60
 
FramePerSec = pygame.time.Clock()
 
displaysurface = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Game")
 
class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__() 
        #self.image = pygame.image.load("character.png")
        self.surf = pygame.Surface((30, 30))
        self.surf.fill((255,255,0))
        self.rect = self.surf.get_rect()
   
        self.pos = vec((10, 360))
        self.vel = vec(0,0)
        self.acc = vec(0,0)
        self.jumping = False
        self.score = 0 
 
    def move(self):
        self.acc = vec(0,0.5)
    
        pressed_keys = pygame.key.get_pressed()
                
        if pressed_keys[K_LEFT]:
            self.acc.x = -ACC
        if pressed_keys[K_RIGHT]:
            self.acc.x = ACC
                 
        self.acc.x += self.vel.x * FRIC
        self.vel += self.acc
        self.pos += self.vel + 0.5 * self.acc
         
        if self.pos.x > WIDTH:
            self.pos.x = 0
        if self.pos.x < 0:
            self.pos.x = WIDTH
             
        self.rect.midbottom = self.pos
 
    def jump(self): 
        hits = pygame.sprite.spritecollide(self, platforms, False)
        if hits and not self.jumping:
           self.jumping = True
           self.vel.y = -15
 
    def cancel_jump(self):
        if self.jumping:
            if self.vel.y < -3:
                self.vel.y = -3
 
    def update(self):
        hits = pygame.sprite.spritecollide(self ,platforms, False)
        if self.vel.y > 0:        
            if hits:
                if self.pos.y < hits[0].rect.bottom:
                    if hits[0].point == True:   
                        hits[0].point = False   
                        self.score += 1          
                    self.pos.y = hits[0].rect.top +1
                    self.vel.y = 0
                    self.jumping = False
 
 
class platform(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.surf = pygame.Surface((random.randint(50,100), 12))
        self.surf.fill((0,255,0))
        self.rect = self.surf.get_rect(center = (random.randint(0,WIDTH-10),
                                                 random.randint(0, HEIGHT-30)))
        self.speed = random.randint(-1, 1)
        
        self.point = True   
        self.moving = True
        
    
    def move(self):
        if self.moving == True:  
            self.rect.move_ip(self.speed,0)
            if self.speed > 0 and self.rect.left > WIDTH:
                self.rect.right = 0
            if self.speed < 0 and self.rect.right < 0:
                self.rect.left = WIDTH
 
 
def check(platform, groupies):
    if pygame.sprite.spritecollideany(platform,groupies):
        return True
    else:
        for entity in groupies:
            if entity == platform:
                continue
            if (abs(platform.rect.top - entity.rect.bottom) < 40) and (abs(platform.rect.bottom - entity.rect.top) < 40):
                return True
        C = False
 
def plat_gen():
    while len(platforms) < 6:
        width = random.randrange(50,100)
        p  = platform()      
        C = True
         
        while C:
             p = platform()
             p.rect.center = (random.randrange(0, WIDTH - width),
                              random.randrange(-50, 0))
             C = check(p, platforms)
        platforms.add(p)
        all_sprites.add(p)
 
 
        
PT1 = platform()
P1 = Player()
 
PT1.surf = pygame.Surface((WIDTH, 20))
PT1.surf.fill((255,0,0))
PT1.rect = PT1.surf.get_rect(center = (WIDTH/2, HEIGHT - 10))
 
all_sprites = pygame.sprite.Group()
all_sprites.add(PT1)
all_sprites.add(P1)
 
platforms = pygame.sprite.Group()
platforms.add(PT1)

PT1.moving = False
PT1.point = False   ##
 
for x in range(random.randint(4,5)):
    C = True
    pl = platform()
    while C:
        pl = platform()
        C = check(pl, platforms)
    platforms.add(pl)
    all_sprites.add(pl)
 
 
while True:
    P1.update()
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN:    
            if event.key == pygame.K_SPACE:
                P1.jump()
        if event.type == pygame.KEYUP:    
            if event.key == pygame.K_SPACE:
                P1.cancel_jump()

    if P1.rect.top > HEIGHT:
        for entity in all_sprites:
            entity.kill()
            time.sleep(1)
            displaysurface.fill((255,0,0))
            pygame.display.update()
            time.sleep(1)
            pygame.quit()
            sys.exit()
 
    if P1.rect.top <= HEIGHT / 3:
        P1.pos.y += abs(P1.vel.y)
        for plat in platforms:
            plat.rect.y += abs(P1.vel.y)
            if plat.rect.top >= HEIGHT:
                plat.kill()
 
    plat_gen()
    displaysurface.fill((0,0,0))
    f = pygame.font.SysFont("Verdana", 20)     
    g  = f.render(str(P1.score), True, (123,255,0))   
    displaysurface.blit(g, (WIDTH/2, 10))   
     
    for entity in all_sprites:
        displaysurface.blit(entity.surf, entity.rect)
        entity.move()
 
    pygame.display.update()
    FramePerSec.tick(FPS)

結果はつぎのようになります。ちょっと短いですが、よかったら、うえのCodeをコピーして実行してみてください。

実施後の感想

ChatGPTでプログラミングをしたときに、ジャンプの扱いができなくて、ChatGPTからも重力加速度の考慮が必要だといわれ、そういうモジュールがあるのではと思い、うまくコミュニケーションが取れなかった。今回、そこは自分で行うことと分かり、一つの理解が進んだかと思う。
後半の理解が不十分だけれど、また、自分での作品つくりで、使うときに考えたいと思う。

2023年4月30日 記録


この記事が気に入ったらサポートをしてみませんか?