タイトル画像
琴葉葵

おねーちゃん!ホッケーゲームは良い感じになってきてるけど、プログラムを起動すると突然ゲームが始まるのは良くないと思うんだよね

琴葉葵

ほとんどのゲームはちゃんとタイトル画面があって、そこから自分の好きなタイミングでゲームを始められるって感じだし

琴葉茜

たしかにそうだね!
そしたら今回は「タイトル画面」を作って、画面を切り替える処理を作っていこうか!

琴葉葵

よろしくお願いします!

琴葉茜

まずは、「タイトル画面」「ゲーム画面」の切り替えを管理するための変数「self.mode」を用意しよう

琴葉茜

ついでに関数「__init__」内で処理していたホッケーやバーの初期化をまとめて関数「reset_game」にしておくと管理が楽になるからオススメだよ!

class App:
    def __init__(self):
        pyxel.init(160, 120, title="ホッケーゲーム")
        self.mode = "TITLE" # "PLAY" と切り替える

        self.reset_game()

        pyxel.run(self.update, self.draw)

    # ゲームの初期化
    def reset_game(self):
        # ホッケーの初期化
        self.reset_hockey()

        # プレイヤー(左側)のバー
        self.bar_height = 20 # バーの高さ
        self.bar_width = 5 # バーの幅
        self.bar_x = 10 # プレイヤーのx座標
        self.bar_y = (pyxel.height - self.bar_height) // 2 # プレイヤーのy座標

        # CPU(右側)のバー
        self.cpu_x = pyxel.width - 10 - self.bar_width # CPUのx座標
        self.cpu_y = (pyxel.height - self.bar_height) // 2 # CPUのy座標

        # スコア用
        self.score_player = 0
        self.score_cpu = 0
琴葉葵

関数「__init__」の中がスッキリしたね!

琴葉茜

変数「self.mode」の中身については、タイトル画面「"TITLE"」とプレイ画面「"PLAY"」で管理するって覚えておいてね

琴葉茜

次に、関数「update」と「draw」内で作ってきたプログラムを、「self.mode」が「"PLAY"」の場合だけ動くようにしよう

def update(self):
    if self.mode == "PLAY": # if文の追加
        self.x += self.vx
        self.y += self.vy
        
        # 以下略

def draw(self):
    pyxel.cls(0)
    if self.mode == "PLAY": # if文の追加
        pyxel.circ(self.x, self.y, self.r, 8)
        pyxel.rect(self.bar_x, self.bar_y, self.bar_width, self.bar_height, 11)
        pyxel.rect(self.cpu_x, self.cpu_y, self.bar_width, self.bar_height, 14)

        pyxel.text(20, 4, f"YOU: {self.score_player:04}", 11)
        pyxel.text(100, 4, f"CPU: {self.score_cpu:04}", 14)
琴葉葵

インデントの処理を間違えないように注意しないとだね!!

琴葉茜

この変更の後にそのまま動かすと、背景だけのウィンドウになるはずだよ

解説画像1
琴葉葵

まだ「"TITLE"」の時の処理を作っていないもんね!

琴葉茜

ということで次は、タイトル画面を作っていこうか!
今回はタイトル画面中に「スペースキー」を押すとプレイ画面に切り替わるようにしよう

琴葉茜

まずは関数「draw」の方から
シンプルに真ん中に文字が表示されるようにしよう

# 関数「draw」の最後に追加
elif self.mode == "TITLE":
    pyxel.text(40, 40, "--- HOCKEY GAME ---", 7)
    pyxel.text(40, 80, "PRESS SPACE TO START", 6)
解説画像2
琴葉葵

ちゃんと文字が表示されてる!

琴葉茜

次に、関数「update」の方でスペースキーを押したら「self.mode」が「"PLAY"」に変化するようにしよう

# 関数「update」の最後に追加
elif self.mode == "TITLE":
    # スペースで開始
    if pyxel.btnp(pyxel.KEY_SPACE):
        self.reset_game()
        self.mode = "PLAY"
琴葉葵

あれ?よく見たらキーボード入力のプログラムが「pyxel.btn」じゃなくて「pyxel.btnp」になってる

琴葉茜

よく観察してるね!実はキーボード入力に関するPyxelの関数には「pyxel.btn()」「pyxel.btnp()」「pyxel.btnr()」の3種類があるんだよね

関数名 意味 主な使い方 使う場面の例
pyxel.btn(k) キーが押されている間ずっと
(「押しっぱなし」に反応)
連続して反応させたいとき 上下キーでプレイヤーを移動
pyxel.btnp(k) キーが押された瞬間だけ
(「1回押したとき」に反応)
一度きりの処理 スタート画面からゲーム開始、選択切り替え
pyxel.btnr(k) キーが離された瞬間だけ
(「指を離したとき」に反応)
特殊・上級向け 連打防止、チャージ攻撃など
琴葉葵

ほうほう、使い分けは意識しとかないとだね

琴葉茜

これでタイトル画面からプレイ画面に切り替え出来るようになったと思うけど、どうかな??

琴葉葵

うん!ちゃんとスペースキーを押したら切り替わったよ!

琴葉葵

ここまで出来たら、ゲームの終わり方も作りたくなるね

琴葉茜

いいね!あとは、ゲームの終了条件とその時の画面も作って、3種類の画面が切り替わるようにしようか!

琴葉葵

3つ目の画面と機能もタイトル画面を作った時と同じで「self.mode」の中身がどうなってるかで作れば良いのかな

琴葉茜

そのとおり!今回は「self.mode」が「"GAMEOVER"」の時にどちらが勝ったか表示されるようにしようか

琴葉茜

ゲームの終了条件と勝敗はひとまず、「どちらかの得点が5点になったらゲーム終了」「得点が5点になった方が勝ち」ってことにしよう

琴葉葵

シンプルな条件で良いね!

琴葉茜

最初に、終了条件を関数「update」の条件「self.mode == "PLAY"」の時の処理に追加しよう
ゴール判定の処理後に追加するのが無難かな

elif self.mode == "PLAY":

    # 途中省略

    # ウィンドウの端に当たったらリセット
    if self.x <= self.r: # 左側が当たった場合はCPUの得点
        self.score_cpu += 1
        self.reset_hockey()
    elif self.x >= pyxel.width - self.r:
        self.score_player += 1 # 右側が当たった場合はプレイヤーの得点
        self.reset_hockey()
    if self.y <= self.r or self.y >= pyxel.height - self.r:
        self.vy *= -1

    # 勝利条件チェック
    if self.score_player >= 5 or self.score_cpu >= 5:
        self.mode = "GAMEOVER"
琴葉茜

関数「update」内を変更したついでに、「self.mode」が「"GAMEOVER"」の時にスペースキーを押すとタイトル画面に戻るようにしよう

# 関数「update」の最後に追加
elif self.mode == "GAMEOVER":
    if pyxel.btnp(pyxel.KEY_SPACE):
        self.mode = "TITLE"
琴葉葵

ここの追加はタイトル画面の時と同じだね!

琴葉茜

あとはゲーム終了時の文字を表示すればOK
どちらが勝ったかは、得点の大きさで判断すれば良いよ

# 関数「draw」の最後に追加
elif self.mode == "GAMEOVER": # 終了画面
    if self.score_player > self.score_cpu:
        winner = "YOU"  
    else:
        winner = "CPU"
    pyxel.text(60, 40, f"{winner} WIN!!", 10)
    pyxel.text(20, 70, "PRESS SPACE TO RETURN TO TITLE", 7)
解説画像3
琴葉葵

おおー、いい感じ!

琴葉茜

ひとまず、これで画面の移動はバッチリだね!

この時点での全体のプログラム

import pyxel
import random

class App:
    def __init__(self):
        pyxel.init(160, 120, title="ホッケーゲーム")
        self.mode = "TITLE"

        self.reset_game()

        pyxel.run(self.update, self.draw)

    def reset_game(self):
        # ホッケーの初期化
        self.reset_hockey()

        # プレイヤー(左側)のバー
        self.bar_height = 20 # バーの高さ
        self.bar_width = 5 # バーの幅
        self.bar_x = 10 # プレイヤーのx座標
        self.bar_y = (pyxel.height - self.bar_height) // 2 # プレイヤーのy座標

        # CPU(右側)のバー
        self.cpu_x = pyxel.width - 10 - self.bar_width # CPUのx座標
        self.cpu_y = (pyxel.height - self.bar_height) // 2 # CPUのy座標

        # スコア用
        self.score_player = 0
        self.score_cpu = 0

    # ホッケーの初期化
    def reset_hockey(self):
        self.x = pyxel.width // 2
        self.y = pyxel.height // 2
        self.vx = random.choice([-1, 1])
        self.vy = random.choice([-1, 1])
        self.r = 4

    def update(self):
        if self.mode == "PLAY": # プレイ画面
            self.x += self.vx
            self.y += self.vy
            
            # プレイヤーバーの操作(上下キー)
            if pyxel.btn(pyxel.KEY_UP):
                self.bar_y -= 2
            if pyxel.btn(pyxel.KEY_DOWN):
                self.bar_y += 2

            self.bar_y = max(0, min(pyxel.height - self.bar_height, self.bar_y))

            # CPUバーの自動追尾
            if random.random() > 0.2:  # 80%の確率で追尾
                if self.y > self.cpu_y + self.bar_height // 2:
                    self.cpu_y += 1.2
                elif self.y < self.cpu_y + self.bar_height // 2:
                    self.cpu_y -= 1.2
            self.cpu_y = max(0, min(pyxel.height - self.bar_height, self.cpu_y))

            # ウィンドウの端に当たったらリセット
            if self.x <= self.r: # 左側が当たった場合はCPUの得点
                self.score_cpu += 1
                self.reset_hockey()
            elif self.x >= pyxel.width - self.r:
                self.score_player += 1 # 右側が当たった場合はプレイヤーの得点
                self.reset_hockey()
            if self.y <= self.r or self.y >= pyxel.height - self.r:
                self.vy *= -1

            # 勝利条件チェック
            if self.score_player >= 5 or self.score_cpu >= 5:
                self.mode = "GAMEOVER"

            # プレイヤーバーの衝突判定
            if (
                self.x + self.r >= self.bar_x and
                self.x - self.r <= self.bar_x + self.bar_width and
                self.y + self.r >= self.bar_y and
                self.y - self.r <= self.bar_y + self.bar_height
            ):
                self.vx = abs(self.vx) + 1

            # CPUの衝突判定
            if (
                self.x - self.r <= self.cpu_x + self.bar_width and
                self.x + self.r >= self.cpu_x and
                self.y + self.r >= self.cpu_y and
                self.y - self.r <= self.cpu_y + self.bar_height
            ):
                self.vx = -(abs(self.vx) + 1)
        elif self.mode == "TITLE": # タイトル画面
            # スペースで開始
            if pyxel.btnp(pyxel.KEY_SPACE):
                self.reset_game()
                self.mode = "PLAY"
        elif self.mode == "GAMEOVER": # 終了画面
            if pyxel.btnp(pyxel.KEY_SPACE):
                self.mode = "TITLE"

    def draw(self):
        pyxel.cls(0)
        if self.mode == "PLAY": # プレイ画面
            pyxel.circ(self.x, self.y, self.r, 8)
            pyxel.rect(self.bar_x, self.bar_y, self.bar_width, self.bar_height, 11)
            pyxel.rect(self.cpu_x, self.cpu_y, self.bar_width, self.bar_height, 14)

            pyxel.text(20, 4, f"YOU: {self.score_player:04}", 11)
            pyxel.text(100, 4, f"CPU: {self.score_cpu:04}", 14)
        elif self.mode == "TITLE": # タイトル画面
            pyxel.text(40, 40, "--- HOCKEY GAME ---", 7)
            pyxel.text(40, 80, "PRESS SPACE TO START", 6)
        elif self.mode == "GAMEOVER": # 終了画面
            if self.score_player > self.score_cpu:
                winner = "YOU"  
            else:
                winner = "CPU"
            pyxel.text(60, 40, f"{winner} WIN!!", 10)
            pyxel.text(20, 70, "PRESS SPACE TO RETURN TO TITLE", 7)

App()