以前の記事
【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,y
はenemy
の持つ変数を、u,v
は16,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.player
とself.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で倉庫番ゲームを作る(前編)