pythonでゲーム開発してみよう!その2〜機能追加してもっとゲーム感アップしてみた!

はじめに

前回の記事にて、pythonでゲーム開発してみましたが、お試しで作ったものでしたので、実行と共にいきなりゲームスタートしていたり汗、プレイヤーも左右に移動するのみで単調な動きでした。

そのため今回は、よりゲーム感をでるように機能を追加して改修してみました!

実施環境

macOS Monterey 12.2.1(M1チップ)

python 3.10

pygame 2.1.2(pygameドキュメントはこちら

追加対応

今回は、下記仕様を追加して改修しました。

  • スタート画面追加
    STARTボタン:ゲーム開始
    QUITボタン:ゲーム終了
  • スペースキー+左右キーでジャンプの動作を追加
  • 唐辛子ヒットした時のアクションを追加
  • ゲームオーバー画面にリトライボタンと終了ボタンを追加
    RETRYボタン:ゲーム再開始
    QUITボタン:ゲーム終了

スタート画面を用意して、よりゲーム感を出してみます。

game_init()関数で、スタート画面の設定を行なっています。
ボタン設定のところで
STARTボタン:ボタン押下でゲームスタート(main呼び出し)
QUITボタン:ボタン押下でゲーム終了(game_quit呼び出し)
のように設定しています。

def game_init():
    global game_start
    while game_start:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                game_quit()
        surface.blit(start_img, (0, 0))
        display_text(GAME_TITLE, 100, white_color, (SURFACE_WIDTH/2), (SURFACE_HEIGHT / 8)*4.5)

        display_button("START", 15, black_color, (SURFACE_WIDTH / 4), (SURFACE_HEIGHT/4)*3, 100, 50, button_white, button_blue, main)
        display_button("QUIT", 15, black_color, (SURFACE_WIDTH / 4)*3-80, (SURFACE_HEIGHT / 4)*3, 100, 50, button_white, button_blue, game_quit)

        pygame.display.update()
        clock.tick(30)

ジャンプの動作は、左右キーのアクションのmove_player()関数にスペースキーが押されていたらプレイヤー位置の高さを足す制御を入れました。
プレイヤー描画後元に戻して、着地な動作に見せています。重力計算とかはまだ。。。;;
ジャンプ用のドット絵も追加して、ジャンプ感出しました。

# プレイヤーの移動
def move_player(key):
    global px, is_jump, last_key, flg_turn, p_width, p_height, jump

    # 「⇦」キー押下の動き
    if key[pygame.K_LEFT] == 1:
        px -= 10
        if px < 50+p_width/2:
            px = 50+p_width/2
        if last_key == pygame.K_RIGHT:
            flg_turn = True
            last_key = pygame.K_LEFT
    # 「→」キー押下した時の動き
    elif key[pygame.K_RIGHT] == 1:
        px += 10
        if px > SURFACE_WIDTH-50-p_width/2:
            px = SURFACE_WIDTH-50-p_width/2
        if last_key == pygame.K_LEFT:
            flg_turn = True
            last_key = pygame.K_RIGHT

    # NEW スペースキーはジャンプ
    if key[pygame.K_SPACE] ==1:
        # spaceはジャンプ
        p_height = PLAYER_HEIGHT + 15o
        jump = 1

画面はこのようなイメージになりました。

プレイヤーの描画は、各アクションの情報を保持し、アクションに沿ったイメージ画像に切り替えています。

        if dmg_effect > 0:
            # ダメージ受けた場合は画面揺らす
            bx = random.randint(-60, 30)
            by = random.randint(-10, 20)

            # 背景
            surface.blit(img_dmg_bg, [bx, by])

            dmg_effect = 0

        elif jump > 0:
            # 背景
            surface.blit(img_bg, [bx, by])
            surface.blit(jump_player, [px-p_width/2, PLAYER_Y-p_height/2])

            # jump後元に戻す
            p_height = PLAYER_HEIGHT
            jump = 0
        else:
            # 背景
            surface.blit(img_bg, [bx, by])
            surface.blit(img_player, [px-p_width/2, PLAYER_Y-p_height/2])

実際に遊んでみよう!

それではコードを実行してゲームで遊んでみましょう!

今回は動画も撮ってみましたー

え待って!プレイヤーのジャンプアクション追加したらすごい可愛くなりますね!!!動いてる感が出ました!

各画面キャプチャは下記になります。

スタート画面あるとゲーム感出ますね!

STARTボタンを押して、ゲーム開始します!

ジャンプ!

唐辛子ヒット!ヒィィィィィ!

激辛唐辛子なので火を吹いちゃってます><

ゲームオーバー!

更新後のソースはこちらになります。

import logging
import pygame
import random
import sys

# 画面サイズ
SURFACE_WIDTH = 800
SURFACE_HEIGHT = 550

# STEP
STEP_READY = 0
STEP_PLAY = 1
STEP_GAMEOVER = 2

# アイテム設定
ITEM_TYPE_NUM = 2
ITEM_WIDTH = 50
ITEM_HEIGHT = 72
ITEM_MAX = 60

# ビーバーちゃん設定
PLAYER_WIDTH = 50
PLAYER_HEIGHT = 72
PLAYER_Y = 520

# 満腹メータMAX
STUFFED_MAX = 200

GAME_TITLE = "まじうまドーナツ"

# 各種初期値を設定
step = STEP_READY
timer = 0
is_jump = 0
p_width = PLAYER_WIDTH
p_height = PLAYER_HEIGHT
stuffed = STUFFED_MAX
item_hit = [False] * ITEM_MAX
item_x = [0] * ITEM_MAX
item_y = [0] * ITEM_MAX
item_type = [''] * ITEM_MAX
item_num = 10
flg_turn = False
last_key = pygame.K_RIGHT
dmg_effect = 0
jump = 0

# 画像読み込み
# アイコン
icon = pygame.image.load('img/icon_beaver.png')
# 背景
img_bg = pygame.image.load('img/img_bg.png')
img_dmg_bg = pygame.image.load('img/img_dmg_bg.png')
# プレイヤー
img_player = pygame.image.load('img/beaver.png')
jump_player = pygame.image.load('img/jump.png')
dmg_player = pygame.image.load('img/dmg_player.png')
# アイテム(ドーナツ)
img_donuts = pygame.image.load('img/donuts.png')
# アイテム(唐辛子)
img_red_hot = pygame.image.load('img/redhot.png')
# ゲームオーバー
txt_gameover = pygame.image.load('img/txt_gameover.png')
# スタートアップ
start_img = pygame.image.load('img/start.png')
# ボタン
button_white = pygame.image.load('img/white_button.png')
button_blue = pygame.image.load('img/blue_button.png')

game_start = True

# ウィンドウを作成
pygame.init()
surface = pygame.display.set_mode((SURFACE_WIDTH, SURFACE_HEIGHT))

black_color = (0, 0, 0)
white_color = (255, 255, 255)
green_color = (60, 186, 84)

clock = pygame.time.Clock()

def game_init():
    global game_start
    while game_start:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                game_quit()
        surface.blit(start_img, (0, 0))
        display_text(GAME_TITLE, 100, white_color, (SURFACE_WIDTH/2), (SURFACE_HEIGHT / 8)*4.5)

        display_button("START", 15, black_color, (SURFACE_WIDTH / 4), (SURFACE_HEIGHT/4)*3, 100, 50, button_white, button_blue, main)
        display_button("QUIT", 15, black_color, (SURFACE_WIDTH / 4)*3-80, (SURFACE_HEIGHT / 4)*3, 100, 50, button_white, button_blue, game_quit)

        pygame.display.update()
        clock.tick(30)

def display_button(msg, size, msg_color, x, y, w, h, ic, ac, action = None):
    mouse = pygame.mouse.get_pos()
    click = pygame.mouse.get_pressed()

    if x+w > mouse[0] > x and y+h > mouse[1] > y:
        surface.blit(ac, (x, y))
        if click[0] == 1 and action != None:
            action()
        else:
            surface.blit(ic, (x, y))

        display_text(msg, size, msg_color, (x+(w/2)), (y+(h/2)))

def display_text(text, size, color, center_x, center_y):
    textFont = pygame.font.SysFont("arialunicode", size)
    TextSurf = textFont.render(text, True, color)
    TextRect = TextSurf.get_rect()
    TextRect.center = (center_x, center_y)
    surface.blit(TextSurf, TextRect)

def game_quit():
    pygame.quit()
    sys.exit()

# プレイヤーの移動
def move_player(key):
    global px, is_jump, last_key, flg_turn, p_width, p_height, jump

    # 「⇦」キー押下の動き
    if key[pygame.K_LEFT] == 1:
        px -= 10
        if px < 50+p_width/2:
            px = 50+p_width/2
        if last_key == pygame.K_RIGHT:
            flg_turn = True
            last_key = pygame.K_LEFT
    # 「→」キー押下した時の動き
    elif key[pygame.K_RIGHT] == 1:
        px += 10
        if px > SURFACE_WIDTH-50-p_width/2:
            px = SURFACE_WIDTH-50-p_width/2
        if last_key == pygame.K_LEFT:
            flg_turn = True
            last_key = pygame.K_RIGHT
    if key[pygame.K_SPACE] ==1:
        # spaceはジャンプ
        p_height = PLAYER_HEIGHT + 15o
        jump = 1
# アイテムの作成
def locate_item():
    # アイテム数MAX値まで繰り返し
    for i in range(ITEM_MAX):
        # ランダムでドーナツか唐辛子どっちかを設定する
        item_x[i] = random.randint(50, SURFACE_WIDTH-50-ITEM_WIDTH/2)
        item_y[i] = random.randint(-500, 0)

        if i % ITEM_TYPE_NUM == 0:
            # ドーナツ
            item_type[i] = 'd'
        else:
            # 激辛唐辛子
            item_type[i] = 'r'

# アイテムの落下と当たり判定
def move_item(surface):
    for i in range(item_num):
        item_y[i] += 6 + i / 5
        if item_y[i] > SURFACE_HEIGHT:
            item_hit[i] = False
            item_x[i] = random.randint(50, SURFACE_WIDTH-50-ITEM_WIDTH/2)
            item_y[i] = random.randint(-500, 0)

        if item_hit[i] == False:
            # プレイヤーとアイテムの座標を見て当たったか判定
            if is_item_hit(px, PLAYER_Y, item_x[i], item_y[i]) == True:
                item_hit[i] = True
                hit_item(item_type[i], surface)

# アイテムを描画
def draw_item(surface):
    for i in range(item_num):
        if item_hit[i] == False and item_type[i] == 'd':
            surface.blit(img_donuts, [item_x[i]-ITEM_WIDTH/2, item_y[i]-ITEM_HEIGHT/2])
elif item_hit[i] == False and item_type[i] == 'r':
            surface.blit(img_red_hot, [item_x[i]-ITEM_WIDTH/2, item_y[i]-ITEM_HEIGHT/2])


# アイテムに当たったときの処理
def hit_item(category, surface):
    global stuffed, dmg_effect

    # ドーナツゲットの時は満腹メータプラス
    if category == 'd':
        stuffed += 10
        if stuffed > STUFFED_MAX:
            stuffed = STUFFED_MAX
        # 激辛唐辛子の場合は満腹メータ激減り
        elif category == 'r':
            stuffed -= 20
            if stuffed < 0:
                stuffed = 0
            dmg_effect = 1

# 当たり判定
def is_item_hit(x1, y1, x2, y2):
    if (abs(x1-x2) <= +p_width/2+ITEM_WIDTH/2 and abs(y1-y2) <= p_height/2+ITEM_HEIGHT/2):
        return True
    return False

# ゲームオーバーからのリトライ
def retry():
    global step, flg_turn, stuffed
    step = STEP_READY
    flg_turn = False
    last_key = pygame.K_RIGHT

# main関数
def main():
    global step, timer, stuffed, px, is_jump
    global item_num, img_player, jump_player, dmg_player, flg_turn, dmg_effect
    global p_width, p_height, jump

    pygame.display.set_caption(GAME_TITLE)
    pygame.display.set_icon(icon)

    #surface = pygame.display.set_mode((SURFACE_WIDTH, SURFACE_HEIGHT))

    # ループ
    while True:
    timer += 1

    # イベントごとの処理
    for event in pygame.event.get():
        # 閉じるボタン押下された時はゲーム終了
        if event.type == pygame.QUIT:
            game_quit()

        # プレイ開始時
        if step == STEP_READY:
            # プレイモードへ
            step = STEP_PLAY

            # 満腹メータMAX
            stuffed = STUFFED_MAX

            flg_turn = False

            # ビーバーちゃんスタンバイ
            px = SURFACE_WIDTH / 2
            p_width = PLAYER_WIDTH
            p_height = PLAYER_HEIGHT
            img_player = pygame.image.load('img/beaver.png')
            jump_player = pygame.image.load('img/jump.png')
            img_player = pygame.image.load('img/dmg_player.png')

            # 最初は10個からスタートし、だんだん増えてくる
            item_num = 10

            # アイテム落下
            locate_item()

        # プレイモード
        elif step == STEP_PLAY:
            if stuffed <= 0:
                step = STEP_GAMEOVER
                timer = 0
            if item_num != ITEM_MAX and timer % 15 == 0:
                item_num += 10

            # 時間経過でも徐々に満腹メータ減る
            stuffed -= 0.5

            # キャラクター、アイテム移動
            move_player(pygame.key.get_pressed())
            move_item(surface)

        # 各種描画
        bx = 0
        by = 0

        if dmg_effect > 0:
            # ダメージ受けた場合は画面揺らす
            bx = random.randint(-60, 30)
            by = random.randint(-10, 20)

            # 背景
            surface.blit(img_dmg_bg, [bx, by])

            dmg_effect = 0

        elif jump > 0:
            # 背景
            surface.blit(img_bg, [bx, by])
            surface.blit(jump_player, [px-p_width/2, PLAYER_Y-p_height/2])

            # jump後元に戻す
            p_height = PLAYER_HEIGHT
            jump = 0
        else:
            # 背景
            surface.blit(img_bg, [bx, by])
            surface.blit(img_player, [px-p_width/2, PLAYER_Y-p_height/2])

        if flg_turn == True:
            img_player = pygame.transform.flip(img_player, True, False)
            jump_player = pygame.transform.flip(jump_player, True, False)
            flg_turn = False

        # アイテム描画
        draw_item(surface)

        # ゲームオーバー
        if step == STEP_GAMEOVER:
            logging.info(stuffed)

            # ゲームオーバーの文言のサブ画面を作ってメーン画面へかぶせる
            sub_surface = pygame.Surface((SURFACE_WIDTH, SURFACE_HEIGHT), pygame.SRCALPHA)
            sub_surface.fill((0, 0, 0, 100))
            surface.blit(sub_surface, [0, 0])
            surface.blit(txt_gameover, [100, 220])
            display_button("RETRY", 15, black_color, (SURFACE_WIDTH / 4), (SURFACE_HEIGHT/4)*3, 100, 50, button_white, button_blue, retry)
            display_button("QUIT", 15, black_color, (SURFACE_WIDTH / 4)*3-80, (SURFACE_HEIGHT / 4)*3, 100, 50, button_white, button_blue, game_quit)
            stuffed = 0

        # 満腹メータ
        surface.fill((250, 237, 240), (50, 30, STUFFED_MAX, 40))
        stuffed_r = 60
        stuffed_g = 186
        stuffed_b = 84
        if stuffed < STUFFED_MAX/5:
            # メーターが残り1/3になったら赤くする 
            stuffed_r = 255
            stuffed_g = 100
            stuffed_b = 128
        surface.fill((stuffed_r, stuffed_g, stuffed_b), (50, 30, stuffed, 40))

        # ゲーム画面更新
        pygame.display.update()
        clock.tick(10)

game_init()
game_quit()

おわりに

今回スタート画面やプレイヤーの動きを追加することでよりゲーム感が増して実装していて楽しかったですね。今度は別のゲームも作ってみたくなりました。


--------------------------
システム開発のご要望・ご相談はこちらから

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です