Edited at

Pyxelでパックマンぽいゲームを作る 前編


はじめに

みなさんどうもこんにちは。高知工科大 Advent Calendar 2018の10日目を担当するstmnです。よろしくお願いします。今回はPyxelを使って、パックマンぽいゲームを作っていこうと思います。

前編では敵判定はなしで、クッキーとパワークッキーを取った際にスコアが増えるようになる所まで実装します。実装すると下記のgifのようなものができます。

pyxel-181210-223432.gif


事の始まり(茶番)

本題読みたい人は飛ばしてください

今年のAdvent Calendar、なにかこっかな〜

ん?


面白そう!ゲーム系のプログラム書いた事ないけど、触ってみたいしやってみるか!

ってノリで担当日の2日前から書いてみました。


Pyxel(ピクセル)って?

Pyxel(ピクセル)は和製のレトロゲームエンジンで以下のような特徴があります。


  • ドットゲーム絵ゲームが作れる

  • 記述言語はPython

  • マルチプラットフォーム

  • 機能がシンプルで覚える量が少ない

  • 日本語ドキュメントがある(作者が日本人)

  • etc...

詳細な特徴については、

を見ていただけたらいいと思います。


作成目標

パックマンのようなゲームを作ることを目標にします。

ただし、BGMやSEに関しては、知識が全くない為触れません。作品で使うドット絵は全て、ライブラリに付属するPyxel Editorを使うようにします。

また、ゲームプログラムとして設計に甘いところが多々あると思いますが、優しい目で見守っていただいて、気づいたことがあれば、アドバイス頂ければ幸いです。


動作環境

今回の実装は以下のような環境とディレクトリ構成で行なっていきます。


動作環境

OS      : macOS Mojave 10.14.1

Python : 3.6.3
pyxel : 0.9.4
glfw : 3.2.1


ディレクトリ構成

.

├── app.py // mainプロジェクト
└── pacman.pyxel  // pyxeleditorで作成した情報を持つファイル


ドット絵作成

はじめに、ゲームに使うドット絵を記述します。

コンソールで以下のコマンドを打ってPyxel Editor を起動させます。


コンソール


$ pyxeleditor pacman.pyxel

その後、ドット絵の記述とタイルマップの作成を行なっていきます。


イメージエディタ

まずは、イメージエディタでゲーム内で出現させるドット絵を描いていきます。ちなみに私はこのように描いてみました。

スクリーンショット 2018-12-09 23.06.53.png

このエディタで描く事のできる最小の単位は1ドットで、8ドット毎に薄く藍色の線が引かれています。また、1つのイメージには256×256ドットまでドット絵を描くことができ、右側の白い四角の位置をずらすことで書きたい場所に描くことができます。

今回、ゲーム内で使うドット絵は全て8×8ドットで描いてます。

スクリーンショット 2018-12-10 20.07.54.png


タイルマップエディタ

次にゲームの背景となるタイルマップを作成します。タイルマップは先ほど作成したドット絵をペタペタと貼りながら作成していきます。

今回はこのような形で作成しました。

スクリーンショット 2018-12-09 23.07.04.png

画像だけ貼られてもイメージが湧きにくいと思うので、説明付きの画像を下に貼っておきます。

ちなみに、タイルマップの全体図が真っ黄色なのは、一番最初にイメージの左上を全てのタイルに設定する仕様になっているので、自分の描いた丸い黄色い球体が全体に配置されているからです。

スクリーンショット 2018-12-10 20.27.05.png


プログラム作成

では、ここからコードを書いていきます。


タイルマップの表示

まずは作成したタイルマップを表示してみましょう。


app.py

import pyxel

class App:
def __init__(self):
# ゲームの設定。widthとheightの単位はドット
# init(width, height, [caption], [fps])
pyxel.init(128,128,caption="パックマンぽいゲーム", fps=25)

# 作成したドット絵やタイルマップの情報を読み込む
pyxel.load('pacman.pyxel')
# 毎フレーム毎にupdateとdrawを呼び出す
pyxel.run(self.update, self.draw)

# ゲーム内で扱う情報を更新したり、キー入力の処理などを行う
def update(self):
# qキーが押されたらゲームを終了する。
if pyxel.btnp(pyxel.KEY_Q):
pyxel.quit()

# ゲーム内で描画されるドット絵の処理をする
def draw(self):
# 真っ黒に背景をする
pyxel.cls(0)
# タイルマップを描画する
self.tilemap_draw()

# タイルマップの描画処理
def tilemap_draw(self):
base_x = 0
base_y = 0
tm = 0
u = 0
v = 0
w = 16
h = 16
# 指定したtm(template)番号の(u,v)座標から
# サイズ(w,h)の大きさを(base_x,base_y)座標に描画する
pyxel.bltm(base_x,base_y,tm,u,v,w,h)
App()



実行

では実際に実行してみたいと思います。

$ python app.py

すると、下記のようなゲームウィンドが表示されると思います。

スクリーンショット 2018-12-10 16.47.09.png

これで、タイルマップをゲームウインドウに表示することができました。


パックマンの実装

次は、パックマンを実装していきたいと思います。


パックマンクラスの作成

パックマンには以下の情報を保持させます。


  • パックマンの描画される座標

  • パックマンの進む方向

  • パックマンの向いてる向き

  • パックマンのいる座標

  • 描画するかどうかのフラグ

  • 獲得スコア

  • パワークッキーの効果中か(次回実装)

最初に話したように、前編では敵に対しての処理を扱わないので、最後の項目以外を表現できるようにクラス変数を定義していきたいと思います。


app.py

import pyxel

# 追加
class Pacman:
def __init__(self):
"""
pacman自体を生成するクラス
"""

# 描画されるドットの座標
self.dot_x = 8
self.dot_y = 8

# パックマンの進む方向の情報
self.x_change_quantity = 0
self.y_change_quantity = 0

# 0方向なし, 1 上, 2を下, 3を右, 4を左
self.vectol = 0

# 今いるタイルを座標化する
self.tile_x = 1
self.tile_y = 1

# どのパックマンをプロットするかの情報
self.plot_pacman_x_coordinate = 0

# スコア
self.score = 0

class App:
#・・・・・・・・・

App()



パックマンの動作の実装

それではパックマンの動作ロジックと描画機能を実装していきたいと思います。


app.py

import pyxel

class Pacman:
#・・・・・・・・・

class App:
def __init__(self):
#・・・・・・・・・

def update(self):
#・・・・・・・・・
# 追加
self.update_pacman_state()

def draw(self):
#・・・・・・・・・
# 追加
self.draw_pacman()

# 追加
def update_pacman_state(self):
# 描画が終わったら、次の移動判定をする
if self.pacman.dot_x % 8 == 0 and self.pacman.dot_y % 8 == 0:
# 進行方向を確認
# 壁に行こうとしてたら
if self.tilemap_state.get(self.pacman.tile_x + self.pacman.x_change_quantity, self.pacman.tile_y + self.pacman.y_change_quantity) == 33:
# その場所から動かないように値に変える
self.pacman.x_change_quantity = 0
self.pacman.y_change_quantity = 0
# Wキーが押されていた場合に
elif pyxel.btn(pyxel.KEY_W):
# 次に移動する先が、背景、クッキー、パワークッキーなら
if self.tilemap_state.get(self.pacman.tile_x, self.pacman.tile_y - 1) == 5 or self.tilemap_state.get(self.pacman.tile_x, self.pacman.tile_y - 1) == 64 or self.tilemap_state.get(self.pacman.tile_x, self.pacman.tile_y - 1) == 65:
# 上に行くように設定
self.pacman.x_change_quantity = 0
self.pacman.y_change_quantity = -1
# 描画する向きを上に設定
self.pacman.vectol = 1
# Sキーが押されていた場合に
elif pyxel.btn(pyxel.KEY_S):
# 次に移動する先が、背景、クッキー、パワークッキーなら
if self.tilemap_state.get(self.pacman.tile_x, self.pacman.tile_y + 1) == 5 or self.tilemap_state.get(self.pacman.tile_x, self.pacman.tile_y + 1) == 64 or self.tilemap_state.get(self.pacman.tile_x, self.pacman.tile_y + 1) == 65:
# 下に行くように設定
self.pacman.x_change_quantity = 0
self.pacman.y_change_quantity = 1
# 描画する向きを下に設定
self.pacman.vectol = 2
# Dキーが押されていた場合に
elif pyxel.btn(pyxel.KEY_D):
# 次に移動する先が、背景、クッキー、パワークッキーなら
if self.tilemap_state.get(self.pacman.tile_x + 1, self.pacman.tile_y) == 5 or self.tilemap_state.get(self.pacman.tile_x + 1, self.pacman.tile_y) == 64 or self.tilemap_state.get(self.pacman.tile_x + 1, self.pacman.tile_y) == 65:
# 右に行くように設定
self.pacman.x_change_quantity = 1
self.pacman.y_change_quantity = 0
# 描画する向きを右に設定
self.pacman.vectol = 3
# Aキーが押されていた場合に
elif pyxel.btn(pyxel.KEY_A):
# 次に移動する先が、背景、クッキー、パワークッキーなら
if self.tilemap_state.get(self.pacman.tile_x - 1, self.pacman.tile_y) == 5 or self.tilemap_state.get(self.pacman.tile_x - 1, self.pacman.tile_y) == 64 or self.tilemap_state.get(self.pacman.tile_x - 1, self.pacman.tile_y) == 65:
# 左にいくように設定
self.pacman.x_change_quantity = -1
self.pacman.y_change_quantity = 0
# 描画する向きを左に設定
self.pacman.vectol = 4

# タイルの座標を進行方向に合わせて変える
self.pacman.tile_x += self.pacman.x_change_quantity
self.pacman.tile_y += self.pacman.y_change_quantity

# 毎フレーム毎に描画する座標を変更していく
self.pacman.dot_x += self.pacman.x_change_quantity
self.pacman.dot_y += self.pacman.y_change_quantity

# 追加
def draw_pacman(self):
# 移動しない場合は描画したパックマンの状態をキープ
if self.pacman.x_change_quantity == 0 and self.pacman.y_change_quantity == 0:
pass
else:
# 口の動きを表現するために、2フレームに1回
# 丸の状態を表示する
if pyxel.frame_count % 2 == 0:
self.pacman.plot_pacman_x_coordinate = 0
elif self.pacman.vectol == 1:
# 上向きに開いた口のイメージの座標を設定
self.pacman.plot_pacman_x_coordinate = 8
elif self.pacman.vectol == 2:
# 下向きに開いた口のイメージの座標を設定
self.pacman.plot_pacman_x_coordinate = 16
elif self.pacman.vectol == 3:
# 右向きに開いた口のイメージの座標を設定
self.pacman.plot_pacman_x_coordinate = 24
elif self.pacman.vectol == 4:
# 左向きに開いた口のイメージの座標を設定
self.pacman.plot_pacman_x_coordinate = 32
# イメージ0に登録されている(self.pacman.plot_pacman_x_coordinate,0)の座標から
# 8×8サイズを参照にして、(self.pacman.dot_x,self.pacman.dot_y)の座標に描画する
pyxel.blt(self.pacman.dot_x,self.pacman.dot_y,0,self.pacman.plot_pacman_x_coordinate,0,8,8)

App()



実行

ここまで実装すればクッキーは消えませんが、パックマンが動くようになります。

$ python app.py

pyxel-181210-215351.gif

次はクッキーに触れた時の処理を実装していきます。


クッキーの処理

パックマンが移動する際に、その座標にクッキーかパワークッキーがある場合はスコアと特定の値追加して、読み込んだタイルマップの情報を変化させてあげる必要があります。

また、今の実装ではスコアを表示することができないので表示するように変更を行い、スコアを表示できるだけのスペースを用意します。


app.py

import pyxel

class Pacman:
#・・・・・・・・・

class App:
def __init__(self):
#・・・・・・・・・
# 変更
# 変更前:pyxel.init(128,128,caption="パックマンぽいゲーム", fps=25)
pyxel.init(128,140,caption="パックマンぽいゲーム", fps=25)

def update(self):
#・・・・・・・・・
# 追加
self.update_tilemap()

def draw(self):
#・・・・・・・・・

# 追加
def update_tilemap(self):
# ノーマルクッキーはスコア30点追加
# パワークッキーはスコア100点追加

# ノーマルクッキーに触れたら
if self.tilemap_state.get(self.pacman.tile_x,self.pacman.tile_y) == 65:
self.pacman.score += 30

# パワークッキーに触れたら
elif self.tilemap_state.get(self.pacman.tile_x,self.pacman.tile_y) == 64:
self.pacman.score += 100

# パックマンがノーマルクッキーとパワークッキーを踏んだら、何もないブロックに変える
pyxel.tilemap(0).set(self.pacman.tile_x,self.pacman.tile_y,5,refimg=0)

App()



実行

では実行してみしょう。

$ python app.py

pyxel-181210-223432.gif

ちゃんとクッキーとパワークッキーは消えて、スコアも追加されています。


終わりに

以上で前編は終わりです。どうだったでしょうか?

実際にコードを書いてみて、ゲームを作った事のない人でも短時間で理解できて、かなりいい感じに作れる良さげなゲームエンジンだと思いました。ゲームプログラムを書いてる人なら、もっと使いこなせるんじゃないかと思います。


後編について

後編では敵の移動と当たり判定などを扱っていきたいと思います。