Pythonでゲーム開発しよう!Pygameを利用しオリジナルゲーム作って遊ぼう!

はじめに

pythonでデータ分析など学習しているのですが、ちょっと息抜きに違うことをやってみたくなりました><

そんな時に、pygameというライブラリを使用してオリジナルゲームが作成できることを知り、これだ!と思った次第です。
pythonでもゲームって作れるんですね〜! (^ワ^)
それでは、サンプルコードを交えながら、オリジナルゲームを作成してみます!

実施環境

macOS Monterey 12.2.1(M1チップ)

python 3.10

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

ゲーム仕様内容

まずどんなゲーム作成するか決めないと始まらないですね><

ということで、下記のようなゲームを作ってみます。

  • 右、左キーでプレイヤーを動かして、上から降ってくるアイテムをゲットする
  • アイテムは、まじうまドーナツと激辛唐辛子の2種類
  • メータは時間経過とともに減っていく
  • まじうまドーナツをゲットするとメータが復活
  • 激辛唐辛子をゲットとメータが減ると同時にプレイヤー巨大化
  • メータが「0」になったらゲームオーバー
  • 一定時間経ったら降ってくるアイテムが増えていき難易度がアップ
  • 閉じるボタン押されたらゲーム終了

ゲーム用にプレイヤー、アイテムのビット画像を自作で作ってみました(^o^)

環境構築

まずは、環境作ってpygameをインストールします。

  • python仮想環境構築
python -m venv beaver_game
  • 仮想環境アクティブ化
source bin/activate
  • 仮想環境にて、pygameをインストール
% pip install pygame

% pip list
Package    Version
---------- -------
pip        21.2.4
pygame     2.1.2
setuptools 58.1.0

これで環境ができました。それでは実際にpygameを使ってプログラミングしてみます。

pygame設定

<pygame基本設定>

pygameの基本設定処理は下記のようになっています。

  • paygame初期化
    paygame.init()
  • 画像ファイルを読み込み
    pygame.image.load(‘画像パス’);
  • pygame終了
    pygame.quit()
  • ゲームタイトル設定
    pygame.display.set_caption(ゲームタイトル)
  • ゲームアイコン設定
    pygame.display.set_icon(読み込んだアイコン画像オブジェクト)
  • タイマー管理用オブジェクト生成
    pygame.time.Clock()
  • ゲーム画面初期化しディスプレイオブジェクト生成
    pygame.display.set_mode((長さ, 高さ))

set_modeで作成したディスプレイオブジェクトが基盤となります。このオブジェクトにプレイヤーやアイテムを描画していきます。

<Game Main処理>
  • ゲーム状態として
    ゲーム開始状態(STEP_READY)
    ゲームプレイ中(STEP_PLAY)
    ゲームオーバ(STEP_GAMEOVER)
    としています。
    ゲームオーバーすると最初のSTEP_READYの状態になるようにしています。
  • ゲーム終了判定
    main関数で無限ループとして、閉じるボタン押下のイベントを取得すると終了するようにしています。閉じるボタンが押下された時、paygameを終了させます。同時に、pythonプログラムも修正させます。
# 閉じるボタン押下された時はゲーム終了
if event.type == pygame.QUIT:
    pygame.quit()
    sys.exit()
  • アイテムが当たったかの判定
    プレイヤー、アイテム自体の中心位置の距離と、プレイヤーとアイテムが現在いる場所のX座標、Y座標の絶対値を比較し、プレイヤーとアイテムが当たっているかを判定しています。文章だとわかりづらいですが式になるとこのようになります。
    abs(プレイヤーX座標 – アイテムX座標) <= プレイヤー長さ/2 + アイテム長さ/2
    ※高さとY座標も上記と同じように計算します。
    どちらも条件満たしていたら”アイテムがヒットした!”状態となります。
    if (abs(x1-x2) <= +p_width/2+ITEM_WIDTH/2 and abs(y1-y2) <= p_height/2+ITEM_HEIGHT/2):
        return True
    return False
<Sample Code>

上記をベースにしたサンプルコードはこちらになります。

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

# 各種初期値を設定
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

# 画像読み込み
# アイコン
icon = pygame.image.load('img/icon_beaver.png')
# 背景
img_bg = pygame.image.load('img/img_bg.png')
# プレイヤー
img_player = pygame.image.load('img/beaver.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')

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

    # 「⇦」キー押下の動き
    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

# アイテムの作成
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

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

    # ウィンドウを作成
    pygame.init()
    pygame.display.set_caption('くいしんぼうビーバーちゃんのドーナツいっぱい食べたい!')
    pygame.display.set_icon(icon)
    clock = pygame.time.Clock()
    surface = pygame.display.set_mode((SURFACE_WIDTH, SURFACE_HEIGHT))

    # ループ
    while True:
        timer += 1

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

        # プレイ開始時
        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')

            # 最初は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)

        # ゲームオーバー
        elif step == STEP_GAMEOVER:
            if timer == 50:
                step = STEP_READY
                timer = 0

        # 各種描画
        bx = 0
        by = 0

        if dmg_effect > 0:
            # ダメージ受けた場合は画面揺らす
            bx = random.randint(-60, 20)
            by = random.randint(-40, 10)
            dmg_effect = 0
            # ダメージ受けたらビーバーちゃん巨大化
            p_width = p_width*1.2
            p_height = p_height*1.2
            img_player = pygame.transform.scale(img_player, (p_width,p_height))

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

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

        # ビーバーちゃん描画
        surface.blit(img_player, [px-p_width/2, PLAYER_Y-p_height/2])

        # アイテム描画
        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])
            stuffed = 0

        # 満腹メータ
        surface.fill((250, 237, 240), (50, 30, STUFFED_MAX, 40))
        stuffed_r = 100
        stuffed_g = 100
        stuffed_b = 255
        if stuffed < STUFFED_MAX/5:
            # メーターが残り1/5になったら赤くする 
            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)

# main実行
main()

 

実際に遊んでみよう!

それでは上記で作成したコードを実行してみますー(^ワ^)

実行するとこのようにゲーム画面が立ち上がりゲーム開始します!

おわりに

いかがでしたでしょうか?今回はpygameを使ってオリジナルゲームを作成してみました。今回作ったコードから色々派生させて面白いゲームに育てていくのも通かもしれませんね。


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

2件のコメント

    1. コメントありがとうございます!

      まじうまドーナツや唐辛子は、自作したドット絵になります。
      “ドット絵 作成” で検索したブラウザ上でドット絵作成できるツールを使って自作しました。

      この作成した画像を、ソースがある直下にimgフォルダを作成して格納し、
      pygame.image.load(‘img/beaver.png’)
      のように読み込ませています。

コメントを残す

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