LoginSignup
0
0

More than 5 years have passed since last update.

Pygame - Platform ゲーム - [13] Animationを作ろう

Posted at

Pygame Platform ゲームを作成 [13]

Animationを作ろう

Tasks

  • Playerクラスにload_images()メソッドを準備
    • spritesheet_jumper.pngにある歩いている画像をロード
  • 切り取られた画像をstading_frames walk_frames_r walk_frames_lに格納
  • Playerクラスにanimate()メソッドを準備

プロジェクトストラクチャー

  • project/ -- 全てを入れるフォルダ(ディレクトリ)
    • main.py -- ゲームをスタートするファイル
    • settings.py -- constantを入れておくファイル
    • sprites.py -- PlayerなどのSpriteのコードを書くファイル
    • highscore.txt -- Highscoreを保存するためのテキストファイル
    • img -- 画像ファイル(.pngなど)を入れておくフォルダ
      • spritesheet_jumper.png -- 全ての画像一枚にある
      • spritesheet_jumper.xml -- 書くアニメーションの座標(x, y)と幅(width)と高さ(height)が書いてある
sprites.py

# Sprite classes
import pygame as pg
from settings import *

vec = pg.math.Vector2


class Player(pg.sprite.Sprite):
    def __init__(self, game):
        super().__init__()
        self.game = game
        self.walking = False
        self.jumping = False
        self.standing_frames = []
        self.walk_frames_r = []
        self.walk_frames_l = []
        self.jump_frame = []
        self.load_images()
        self.current_frame = 0  # to keep track of animation frame
        self.last_update = 0  # to keep time of animation
        self.image = self.standing_frames[0]
        self.rect = self.image.get_rect()
        self.rect.center = (WIDTH / 2, HEIGHT / 2)
        self.pos = vec(WIDTH / 2, HEIGHT / 2)
        self.vel = vec(0, 0)
        self.acc = vec(0, 0)

    def load_images(self):
        """アニメーションのフレーム画像をロード"""
        # NEW!!!
        # 立っているときのフレーム
        self.standing_frames = [
            self.game.spritesheet.get_image(614, 1063, 120, 191),
            self.game.spritesheet.get_image(690, 406, 120, 201),
        ]
        # 個々のフレーム画像の背景を消す
        for frame in self.standing_frames:
            frame.set_colorkey((0, 0, 0))
        # NEW!!!
        # 右を向いて歩いているときのフレーム
        self.walk_frames_r = [
            self.game.spritesheet.get_image(678, 860, 120, 201),
            self.game.spritesheet.get_image(692, 1458, 120, 207),
        ]
        # 個々のフレーム画像の背景を消す
        for frame in self.walk_frames_r:
            frame.set_colorkey((0, 0, 0))
        # NEW!!!
        # 左を向いて歩いているときのフレーム
        self.walk_frames_l = []

        # 個々のフレーム画像の背景を消す
        for frame in self.walk_frames_r:
            frame.set_colorkey((0, 0, 0))
            self.walk_frames_l.append(pg.transform.flip(frame, True, False))

        # NEW!!!
        # jumpしているときのフレーム
        self.jump_frame = self.game.spritesheet.get_image(382, 763, 150, 181)
        self.jump_frame.set_colorkey((0, 0, 0))

    def jump(self):
        # jump only if on a platform
        self.rect.x += 1
        hits = pg.sprite.spritecollide(self, self.game.platforms, False)
        self.rect.x -= 1
        if hits:
            self.vel.y = -PLAYER_JUMP

    def update(self):
        self.animate()
        # 重力の設定
        self.acc = vec(0, PLAYER_GRAV)
        keys = pg.key.get_pressed()
        if keys[pg.K_LEFT]:
            self.acc.x = -PLAYER_ACC
        if keys[pg.K_RIGHT]:
            self.acc.x = PLAYER_ACC

        # 摩擦を計算
        self.acc.x += self.vel.x * PLAYER_FRICTION
        # Velocity に Accelerationを足す
        self.vel += self.acc

        # 微かな動きを止める
        if abs(self.vel.x) < 0.1:
            self.vel.x = 0

        # Position に Velocity を足す
        self.pos += self.vel + 0.5 * self.acc

        # Check Edges
        if self.pos.x > WIDTH + self.rect.width / 2:
            self.pos.x = 0 - self.rect.width / 2
        if self.pos.x < 0 - self.rect.width / 2:
            self.pos.x = WIDTH + self.rect.width / 2

        # 現在の位置に Positionを設定
        self.rect.midbottom = self.pos

    # NEW!!!
    def animate(self):
        """アニメーション"""
        now = pg.time.get_ticks()  # 現在のtick(時間)を取得
        if self.vel.x != 0:
            self.walking = True
        else:
            self.walking = False

        # 歩くアニメーション
        if self.walking:
            if now - self.last_update > 200:
                self.last_update = now
                self.current_frame = (self.current_frame + 1) % len(
                    self.walk_frames_l)  # フレーム画像の配列番号を計算
                bottom = self.rect.bottom
                if self.vel.x > 0:
                    self.image = self.walk_frames_r[self.current_frame]
                else:
                    self.image = self.walk_frames_l[self.current_frame]
                self.rect = self.image.get_rect()
                self.rect.bottom = bottom

        # NEW!!!
        # アイドルアニメーション
        if not self.jumping and not self.walking:
            if now - self.last_update > 350:  # 現在と最後にupdateした時間を比較
                self.last_update = now  # もしそうだったらlast_updateをnow(現在)に設定
                self.current_frame = (self.current_frame + 1) % len(
                    self.standing_frames)  # フレーム画像の配列番号を計算
                bottom = self.rect.bottom  # フレームごとにimageのサイズが変更になるかもしれないから
                # 地面に必ず足がついているように画像が変更になる前のbottom を取得
                self.image = self.standing_frames[
                    self.current_frame]  # imageを計算したフレームに画像に変更
                self.rect = self.image.get_rect()  # rectを新たに取得
                self.rect.bottom = bottom  # rectのbottomを更新


class Platform(pg.sprite.Sprite):
    def __init__(self, x, y, w, h):
        super().__init__()
        self.image = pg.Surface((w, h))
        self.image.fill(GREEN)
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
main.py
import pygame as pg
import random
from settings import *
from sprites import *
from os import path


class SpriteSheet:
    def __init__(self, filename):
        """ SpriteSheet専用クラス"""
        self.spritesheet = pg.image.load(filename).convert()

    def get_image(self, x, y, width, height):
        """ spritesheetの中の特定の画像を切り取る """
        image = pg.Surface((width, height))
        image.blit(self.spritesheet, (0, 0), (x, y, width, height))
        image = pg.transform.scale(image, (width // 2, height // 2))
        return image


class Game:
    def __init__(self):
        """ ゲームを初期化 """
        self.running = True
        pg.init()
        pg.mixer.init()
        self.screen = pg.display.set_mode((WIDTH, HEIGHT))
        pg.display.set_caption(TITLE)
        self.clock = pg.time.Clock()
        self.all_sprites = None
        self.platforms = None
        self.playing = False
        self.player = None
        self.score = 0
        self.highscore = 0
        self.dir = None
        self.spritesheet = None

        self.font_name = pg.font.match_font(FONT_NAME)  # FONTを探す
        self.load_data()

    def load_data(self):
        """ HighScoreデータをロード """
        self.dir = path.dirname(__file__)
        img_dir = path.join(self.dir, 'img')
        with open(path.join(self.dir, HS_FILE), 'r') as f:
            try:
                self.highscore = int(f.read())
            except:
                self.highscore = 0
        # spritesheetをロード
        self.spritesheet = SpriteSheet(path.join(img_dir, SPRITESHEET))

    def new(self):
        # ゲームオーバー後のニューゲーム
        self.score = 0
        self.all_sprites = pg.sprite.Group()
        self.platforms = pg.sprite.Group()

        self.player = Player(self)
        self.all_sprites.add(self.player)

        for plat in PLATFORM_LIST:
            p = Platform(*plat)
            self.all_sprites.add(p)
            self.platforms.add(p)
        self.run()

    def run(self):
        # ゲームループ
        self.playing = True
        while self.playing:
            self.clock.tick(FPS)
            self.events()
            self.update()
            self.draw()

    def update(self):
        # アップデート
        self.all_sprites.update()
        # check if player hits a platform - only if falling
        if self.player.vel.y > 0:
            hits = pg.sprite.spritecollide(self.player, self.platforms, False)
            if hits:
                self.player.pos.y = hits[0].rect.top + 1
                self.player.vel.y = 0

        # もしplayerが画面上部1/4に達したら
        if self.player.rect.top <= HEIGHT / 4:
            self.player.pos.y += abs(self.player.vel.y)  # abs = 絶対値を取得
            for plat in self.platforms:
                plat.rect.y += abs(self.player.vel.y)
                # 画面外に行ったplatformを消す
                if plat.rect.top >= HEIGHT:
                    plat.kill()
                    self.score += 10

        # ゲームオーバー
        # 落下を表現
        if self.player.rect.bottom > HEIGHT:
            # 全てのsprite
            for sprite in self.all_sprites:
                sprite.rect.y -= max(self.player.vel.y, 10)  # max値を取得
                if sprite.rect.bottom < 0:  # spriteが画面上部に消えたら
                    sprite.kill()
        if len(self.platforms) == 0:
            self.playing = False

        # 新しいplatform を作成 / 画面には平均的に同じ数のplatform
        while len(self.platforms) < 6:
            width = random.randrange(50, 100)

            p = Platform(random.randrange(0, WIDTH - width),
                         random.randrange(-75, -30),
                         width, 20)
            self.platforms.add(p)
            self.all_sprites.add(p)

    def events(self):
        # イベント
        for event in pg.event.get():
            if event.type == pg.QUIT:
                if self.playing:
                    self.playing = False
                self.running = False
            if event.type == pg.KEYDOWN:
                if event.key == pg.K_SPACE:
                    self.player.jump()

    def draw(self):
        # 描画
        self.screen.fill(BGCOLOR)
        self.all_sprites.draw(self.screen)
        self.draw_text(str(self.score), 22, WHITE, WIDTH / 2, 15)
        pg.display.flip()

    def show_start_screen(self):
        # ゲームスタート画面
        self.screen.fill(BGCOLOR)
        self.draw_text(TITLE, 48, WHITE, WIDTH / 2, HEIGHT / 4)
        self.draw_text("Arrows to move, Space to jump", 22, WHITE, WIDTH / 2,
                       HEIGHT / 2)
        self.draw_text("Press a key to play", 22, WHITE, WIDTH / 2,
                       HEIGHT * 3 / 4)
        self.draw_text("HIGH SCORE: {}".format(str(self.highscore)), 22, WHITE,
                       WIDTH / 2, 15)
        pg.display.flip()
        self.wait_for_key()

    def show_go_screen(self):
        # ゲームオーバー画面
        if not self.running:
            return
        self.screen.fill(BGCOLOR)
        self.draw_text("GAME OVER", 48, WHITE, WIDTH / 2, HEIGHT / 4)
        self.draw_text("Score: {}".format(str(self.score)), 22, WHITE,
                       WIDTH / 2,
                       HEIGHT / 2)
        self.draw_text("Press a key to play again", 22, WHITE, WIDTH / 2,
                       HEIGHT * 3 / 4)
        if self.score > self.highscore:
            self.highscore = self.score
            self.draw_text("NEW HIGH SCORE!", 22, WHITE, WIDTH / 2,
                           HEIGHT / 2 + 40)
            with open(path.join(self.dir, HS_FILE), 'w') as f:
                f.write(str(self.score))
                print("finished writing")
        else:
            self.draw_text("HIGH SCORE: {}".format(str(self.highscore)), 22,
                           WHITE,
                           WIDTH / 2, HEIGHT / 2 + 40)

        pg.display.flip()
        self.wait_for_key()

    def wait_for_key(self):
        waiting = True
        while waiting:
            self.clock.tick(FPS)
            for event in pg.event.get():
                if event.type == pg.QUIT:
                    waiting = False
                    self.running = False
                if event.type == pg.KEYUP:
                    waiting = False

    def draw_text(self, text, size, color, x, y):
        font = pg.font.Font(self.font_name, size)
        text_surface = font.render(text, True, color)
        text_rect = text_surface.get_rect()
        text_rect.midtop = (x, y)
        self.screen.blit(text_surface, text_rect)


g = Game()
g.show_start_screen()
while g.running:
    g.new()
    g.show_go_screen()

pg.quit()


settings.py

# game options/settings
TITLE = "Jumpy!"
WIDTH = 480
HEIGHT = 600
FPS = 60
FONT_NAME = 'arial'
HS_FILE = "highscore.txt"
SPRITESHEET = "spritesheet_jumper.png"

# Player properties
PLAYER_ACC = 0.5
PLAYER_FRICTION = -0.12
PLAYER_GRAV = 0.8
PLAYER_JUMP = 20

# Starting platforms
PLATFORM_LIST = [(0, HEIGHT - 40, WIDTH, 40),
                 (WIDTH / 2 - 50, HEIGHT * 3 / 4, 100, 20),
                 (125, HEIGHT - 350, 100, 20),
                 (350, 200, 100, 20),
                 (175, 100, 50, 20)]

# define colors
WHITE = (255, 255, 255)
BLACK = (47, 53, 66)
DARKGREY = (27, 140, 141)
LIGHTGREY = (189, 195, 199)
GREEN = (60, 186, 84)
RED = (219, 50, 54)
YELLOW = (244, 194, 13)
BLUE = (72, 133, 237)
LIGHTBLUE = (41, 128, 185)
BGCOLOR = LIGHTBLUE

:link: Links :link:

Github platformer

Pygame - Platform ゲーム - [12]

Pygame - Platform ゲーム - [11]

Pygame - Platform ゲーム - [10]

Pygame - Platform ゲーム - [9]

Pygame - Platform ゲーム - [8]

Pygame - Platform ゲーム - [7]

Pygame - Platform ゲーム - [6]

Pygame - Platform ゲーム - [5]

Pygame - Platform ゲーム - [4]

Pygame - Platform ゲーム - [3]

Pygame - Platform ゲーム - [2]

Pygame - Platform ゲーム - [1]

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0