0
6

More than 3 years have passed since last update.

【Python】Pyxelで簡単な迷路ゲームを作る - 敵を出現させる -

Last updated at Posted at 2021-01-11

以前の記事
【Python】Pyxelでゲームを作るための最初の一歩
【Python】Pyxelでゲームを作る - エディターを使う -
【Python】Pyxelで簡単な迷路ゲームを作る

はじめに

迷路をたどってゴールを目指すゲームに、敵キャラクターを出現させてみます。
ぶつかるとゲームオーバーになるようにして、少しゲームっぽくしてみたいと思います。

準備

怖い顔の敵キャラクターを追加。

壁を増やして迷路っぽくします。

ベースとなるコード。まだ敵は現れません。

import pyxel

class Player:
    def __init__(self, x, y):
        self.x = x
        self.y = y

        self.dx = 0
        self.dy = 0
        self.move_count = 0

    def move(self):
        self.x += self.dx
        self.y += self.dy

class App:
    def __init__(self):
        pyxel.init(128, 128)

        pyxel.load("assets/sample.pyxres")

        self.player = Player(16, 16)

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

    def update_move(self):
        if self.player.move_count == 0:
            if pyxel.btnp(pyxel.KEY_LEFT):
                self.player.dx = -1
            elif pyxel.btnp(pyxel.KEY_RIGHT):
                self.player.dx = 1
            elif pyxel.btnp(pyxel.KEY_UP):
                self.player.dy = -1
            elif pyxel.btnp(pyxel.KEY_DOWN):
                self.player.dy = 1

            if self.player.dx != 0 or self.player.dy != 0:
                self.player.move_count = 16

            if pyxel.tilemap(0).get(
                    self.player.x / 8 + self.player.dx * 2,
                    self.player.y / 8 + self.player.dy * 2) == 6:
                self.player.dx = 0
                self.player.dy = 0
                self.player.move_count = 0
        else:
            self.player.move()
            self.player.move_count -= 1

            if self.player.move_count == 0:
                self.player.dx = 0
                self.player.dy = 0

    def update(self):
        self.update_move()

    def draw(self):
        pyxel.cls(0)

        pyxel.bltm(0, 0, 0, 0, 0, 16, 16, 0)

        pyxel.blt(self.player.x, self.player.y, 0, 0, 0, 16, 16, 0)

        if pyxel.tilemap(0).get(self.player.x / 8,self.player.y / 8) == 2:
            pyxel.rect(0, 0, 128, 16, 8)
            pyxel.text(40, 5, "GAME CLEAR!!", 7)

App().run()

敵を表示させる

class App:
    def __init__(self):
        pyxel.init(128, 128)

        pyxel.load("assets/sample.pyxres")

        self.player = Player(16, 16)

        # 敵キャラクターのインスタンスを生成
        self.enemy = Player(16, 96)
# 中略
    def draw(self):
        pyxel.cls(0)

        pyxel.bltm(0, 0, 0, 0, 0, 16, 16, 0)

        pyxel.blt(self.player.x, self.player.y, 0, 0, 0, 16, 16, 0)

        # 敵キャラクターの描画
        pyxel.blt(self.enemy.x, self.enemy.y, 0, 0, 16, 16, 16, 0)

敵キャラクターの座標を管理するために、Playerクラスのインスタンスを生成します。
初期位置のy座標は、画面の一番下が128なので、そこから2マス上がった96にします。
そして描画関数の中に、敵を描画する機能を追記します。
引数(x,y,img,u,v,w,h,colkey)のうち、x,yenemyの持つ変数を、u,v16,16を指定します。

敵を動かす

敵はランダムに動くことにします。キャラクターの移動はupdate_move関数で管理しているので、これを修正していきます。
よく見ると、最初のブロックの「押されたキーによって動く方向を決める」部分以外はそのまま使えそうです。
したがって、「動く方向を決める」部分を別の関数として切り出し、自分のプレイヤーの移動か敵キャラクターの移動かによって、別々の関数を呼び出すようにします。
「動く方向を決める」関数は以下のようにします。
(なお、randomモジュールを使うので、先頭にimport randomと書いておきます。)

    # 自分のプレイヤーはキーによって動く方向を決める
    def move_manual(self, player):
        if pyxel.btnp(pyxel.KEY_LEFT):
            player.dx = -1
        elif pyxel.btnp(pyxel.KEY_RIGHT):
            player.dx = 1
        elif pyxel.btnp(pyxel.KEY_UP):
            player.dy = -1
        elif pyxel.btnp(pyxel.KEY_DOWN):
            player.dy = 1

    # 敵のキャラクターはランダムに動く方向を決める
    def move_auto(self, player):
        rand = random.randint(1, 4)
        if rand == 1:
            player.dx = -1
        elif rand == 2:
            player.dx = 1
        elif rand == 3:
            player.dy = -1
        elif rand == 4:
            player.dy = 1

次に、update_move関数を修正します。
今まではPlayerクラスのオブジェクトが1個しかなかったので、self.playerを直接操作していました。
今回は自分と敵の2個のオブジェクトがあるので、どのオブジェクトを動かすか選択できるようにします。
ということで、移動させるPlayerクラスのオブジェクトと、マニュアル移動かオート移動かを示すブール値を引数にしました。

    def update_move(self, player, is_manual):
        # マニュアル移動かオート移動かによって呼び出す関数を分岐
        if player.move_count == 0:
            if is_manual:
                self.move_manual(player)
            else:
                self.move_auto(player)

            # ここから下は変更なし(ただしself.playerをplayerに変更している)
            if player.dx != 0 or player.dy != 0:
                player.move_count = 16

            if pyxel.tilemap(0).get(
                    player.x / 8 + player.dx * 2,
                    player.y / 8 + player.dy * 2) == 6:
                player.dx = 0
                player.dy = 0
                player.move_count = 0
        else:
            player.move()
            player.move_count -= 1

            if player.move_count == 0:
                player.dx = 0
                player.dy = 0

最後に、update関数の中を修正します。

    def update(self):
        self.update_move(self.player, True)
        self.update_move(self.enemy, False)

これで敵キャラクターが動くようになりました。

敵とぶつかったらゲームオーバー

敵とぶつかったらゲームオーバーになるようにしてみます。
ぶつかったかどうかは、自分と敵の位置座標の関係によって判定します。いわゆる「当たり判定」というやつですね。

    # 敵とぶつかったか判定する
    def check_hit(self, player, enemy):
        dx = player.x - enemy.x
        dy = player.y - enemy.y
        if -16 < dx < 16 and -16 < dy < 16:
            return True
        else:
            return False

x,y座標ともに、-16〜16の間であれば、ぶつかったと判定されます。
(-16 < dx < 16のように比較演算子をつなげて書けることを初めて知りました。便利ですね。)
ちなみに引数をなくしてself.playerself.enemyとしても良いのですが、敵が複数になったりした場合に融通が効かなくなるので、判定するオブジェクトを引数に指定するようにしています。

    def draw(self):
        pyxel.cls(0)

        pyxel.bltm(0, 0, 0, 0, 0, 16, 16, 0)

        pyxel.blt(self.player.x, self.player.y, 0, 0, 0, 16, 16, 0)

        pyxel.blt(self.enemy.x, self.enemy.y, 0, 0, 16, 16, 16, 0)

        if pyxel.tilemap(0).get(self.player.x / 8,self.player.y / 8) == 2:
            pyxel.rect(0, 0, 128, 16, 8)
            pyxel.text(40, 5, "GAME CLEAR!!", 7)

        # 敵とぶつかったらゲームオーバー
        if self.check_hit(self.player, self.enemy):
            pyxel.rect(0, 0, 128, 16, 8)
            pyxel.text(40, 5, "GAME OVER...", 7)

drawの最後に、ゲームオーバーか判定する箇所を追加しました。
これで敵にぶつかるとゲームオーバーになります。

仕上げ

ゲームをクリアしたときもそうですが、ゲームオーバーになってもゲームが続行されてしまいます。
ゲームクリアorゲームオーバーになったらゲームが終了し、動かなくなるようにします。

    def __init__(self):
        pyxel.init(128, 128)

        pyxel.load("assets/sample.pyxres")

        self.player = Player(16, 16)

        self.enemy = Player(16,96)

        # ゲーム終了を示すフラグ
        self.game_end = False

# 中略
    def update(self):
        # ゲーム終了でない場合のみ更新する
        if self.game_end == False:
            self.update_move(self.player, True)
            self.update_move(self.enemy, False)

    def draw(self):
        pyxel.cls(0)

        pyxel.bltm(0, 0, 0, 0, 0, 16, 16, 0)

        pyxel.blt(self.player.x, self.player.y, 0, 0, 0, 16, 16, 0)

        # 敵キャラクターの描画
        pyxel.blt(self.enemy.x, self.enemy.y, 0, 0, 16, 16, 16, 0)

        if pyxel.tilemap(0).get(self.player.x / 8,self.player.y / 8) == 2:
            pyxel.rect(0, 0, 128, 16, 8)
            pyxel.text(40, 5, "GAME CLEAR!!", 7)
            self.game_end = True

        # 敵とぶつかったらゲームオーバー
        if self.check_hit(self.player, self.enemy):
            pyxel.rect(0, 0, 128, 16, 8)
            pyxel.text(40, 5, "GAME OVER...", 7)
            self.game_end = True

ゲームオーバーになると更新がストップし、ゲームが続行できなくなりました。

結構難易度が高くなりました。
以下、コード全文です。関数名が適当なのでちょっと読みにくいかもしれません。

import pyxel
import random

class Player:
    def __init__(self, x, y):
        self.x = x
        self.y = y

        self.dx = 0
        self.dy = 0
        self.move_count = 0

    def move(self):
        self.x += self.dx
        self.y += self.dy

class App:
    def __init__(self):
        pyxel.init(128, 128)

        pyxel.load("assets/sample.pyxres")

        self.player = Player(16, 16)
        self.enemy = Player(16, 96)

        self.game_end = False

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

    def update_move(self, player, is_manual):
        if player.move_count == 0:
            if is_manual:
                self.move_manual(player)
            else:
                self.move_auto(player)

            if player.dx != 0 or player.dy != 0:
                player.move_count = 16

            if pyxel.tilemap(0).get(
                    player.x / 8 + player.dx * 2,
                    player.y / 8 + player.dy * 2) == 6:
                player.dx = 0
                player.dy = 0
                player.move_count = 0
        else:
            player.move()
            player.move_count -= 1

            if player.move_count == 0:
                player.dx = 0
                player.dy = 0

    def move_manual(self, player):
        if pyxel.btnp(pyxel.KEY_LEFT):
            player.dx = -1
        elif pyxel.btnp(pyxel.KEY_RIGHT):
            player.dx = 1
        elif pyxel.btnp(pyxel.KEY_UP):
            player.dy = -1
        elif pyxel.btnp(pyxel.KEY_DOWN):
            player.dy = 1

    def move_auto(self, player):
        rand = random.randint(1, 4)
        if rand == 1:
            player.dx = -1
        elif rand == 2:
            player.dx = 1
        elif rand == 3:
            player.dy = -1
        elif rand == 4:
            player.dy = 1

    def check_hit(self, player, enemy):
        dx = player.x - enemy.x
        dy = player.y - enemy.y
        if -16 < dx < 16 and -16 < dy < 16:
            return True
        else:
            return False

    def update(self):
        if self.game_end == False:
            self.update_move(self.player, True)
            self.update_move(self.enemy, False)

    def draw(self):
        pyxel.cls(0)

        pyxel.bltm(0, 0, 0, 0, 0, 16, 16, 0)

        pyxel.blt(self.player.x, self.player.y, 0, 0, 0, 16, 16, 0)
        pyxel.blt(self.enemy.x, self.enemy.y, 0, 0, 16, 16, 16, 0)

        if pyxel.tilemap(0).get(self.player.x / 8,self.player.y / 8) == 2:
            pyxel.rect(0, 0, 128, 16, 8)
            pyxel.text(40, 5, "GAME CLEAR!!", 7)
            self.game_end = True

        if self.check_hit(self.player, self.enemy):
            pyxel.rect(0, 0, 128, 16, 8)
            pyxel.text(40, 5, "GAME OVER...", 7)
            self.game_end = True

App().run()

(2021/1/12追記)
コメントにてコードの改善が提案されています。ご覧ください。

参考

https://github.com/kitao/pyxel/blob/master/README.ja.md
Pyxelでパックマンぽいゲームを作る 前編
Pyxelで倉庫番ゲームを作る(前編)

0
6
2

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
6