大ファンの株式会社 刀の森岡さんが、西武園ゆうえんちの改革に乗り出すそうです。あのUSJをハリーポッターでV字回復させた方です。西武園ゆうえんちは、古い遊園地ということを活かし昭和のレトロ空間をコンセプトにするとの事。
というわけで、今回はpyxel(ぴくせる)というpythonのレトロゲームエンジンで2Dゲームを作成してみました。
作成したゲームは次のようなものです。
一言で表すなら、爆弾を上下に移動して爆弾を避けるゲームです。
スペースキーを押すと上昇、離すと加工するフラッピーバード的なゲームです。
200行程度のプログラムで作成することができます。
Pyxel(ぴくせる)だけでゲームが作れる!
まずはPyxelの特徴ですが、pyxelはpyxelだけで、ゲームに必要な絵、音楽もつくることができます!
自分でゲームを作る際に問題になるのが「絵がかけない」、「音楽が作れない」という問題です。
キャラクターが作れないとゲームにならないし、絵が描けてもBGMがないゲームは味気ないです。
そんな悩みを一掃してくれるのがPyxelです。
上の方に貼り付けてあるゲーム画像の絵は全てオリジナルで描いたものです。
キャラクタも爆弾も爆発も雲も自分で書きました。
下手なんですけど2Dのレトロ風ゲームと言うことで味のある感じになります。
Pyxelでゲーム素材を作る方法
pyxelは標準で「pyxeleditor(ぴくせるエディター」という画像と音楽を作るツールが含まれており、オリジナルのゲーム素材
を作成することが可能です。
ゲーム内で使用する画像は四角のドットを並べて作成します。
音楽を作る場合はピアノロールを使って音を並べていくだけでOKです。どーでしょうか?いけそうじゃあないですか?
作成したサウンドは同時に4チャンネル重ねることも可能です。
Pyxelで理解すべき3つの関数について
素材が完成したらプログラムを組んでいきます。
Pyxelは基本的なプログラムのベースは次の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import pyxel class App: def __init__(self): pyxel.init(160, 120) self.x = 0 pyxel.run(self.update, self.draw) def update(self): self.x = (self.x + 1) % pyxel.width def draw(self): pyxel.cls(0) pyxel.rect(self.x, 0, 8, 8, 9) App() |
Appクラスの中の3つのクラスに処理を記述していきます。
それぞれの関数の役割は次の通りです。
・def __init__関数
Appクラスのコンストラクタです。
App()を実行したとき最初に呼ばれる関数のため、画面のサイズやプレイヤーの初期位置等、
ゲームの初期設定を記述します。
・def update関数
フレーム更新ごとに呼ばれる処理を書きます。
キャラクターの移動や、キー入力時の処理を記述します。
・def draw関数
ゲーム画面を描画する処理を書きます。
updateで更新した座標をdraw関数で描写するように記述していきます。
Pyxelで作成したゲーム素材を利用する方法
pyxelで作成したゲーム素材は次の手順で利用することができます。
1.素材ファイルを読み込む
コンストラクタ内で作成した素材を読み込みます
1 2 |
def __init__(self): pyxel.load("assets/my_resource.pyxres") |
pyxel.load関数の引数に作成したpyxresのファイルパスを渡すことで、
ゲーム内に素材をロードすることができます。
2. 画像を表示したいところでblt関数を呼び出す
1 2 3 4 |
def draw(self): #キャラクタ表示 if not self.GAMEOVER: pyxel.blt(self.player_x, self.player_y, 0, 0, 0, 16, 16, 0) |
bltやbltmを利用して作成した素材から画像を描画することができます。
8つの引数はそれぞれ次のようになります。
1 |
blt(x, y, img, u, v, w, h, [colkey]) |
x: 画像を表示するx座標
y: 画像を表示するy座標
img: 画像番号(イメージバンク番号)
u: イメージバンクからの位置(x)
v: イメージバンクからの位置(y)
w: 取得するイメージの幅
h: 取得するイメージの高さ
音はMusic番号を指定してあげればOKです。
1 2 |
#音再生 pyxel.playm(0, loop=True) |
サンプルゲームのプログラム内容
今回作成したpyxelのプログラムは次の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
import pyxel from random import randint class App: def __init__(self): pyxel.init(160, 120) pyxel.load("assets/my_resource.pyxres") #STARTFLAG self.START = False #GAMEOVERフラグ self.GAMEOVER = False #スコア用変数 self.score = 0 #プレイヤー初期地位 self.player_x = 20 self.player_y = 60 #重力系変数 self.gravity = 2.5 self.MAX_GRAVITY = self.gravity self.POWER = 0.25 #爆弾の生成用 self.bomb = [(i * 60, randint(0, 104)) for i in range(3,15)] #遠い雲 self.far_cloud = [(10, 25), (70, 35), (120, 15), (44, 45), (67, 75), (110, 95)] #近い雲 self.near_cloud = [(20, 15), (40, 75), (110, 40)] #音再生 pyxel.playm(0, loop=True) pyxel.run(self.update, self.draw) def update(self): #終了する if pyxel.btnp(pyxel.KEY_Q): pyxel.quit() #スペース押された場合 if pyxel.btn(pyxel.KEY_SPACE) or pyxel.btn(pyxel.GAMEPAD_1_START): self.START = True if self.GAMEOVER and (pyxel.btn(pyxel.KEY_ENTER or pyxel.btn(pyxel.GAMEPAD_1_START))) : self.reset() if not self.START or self.GAMEOVER: return #プレイヤーの更新 self.update_player() #爆弾の表示 for i, v in enumerate(self.bomb): self.bomb[i] = self.update_bomb(*v) # スコア if not self.GAMEOVER: self.score += 1 def draw(self): if self.GAMEOVER: MESSAGE =\ """ GAMEOVER PUSH ENTER RESTART """ pyxel.text(51, 40, MESSAGE, 1) pyxel.text(50, 40, MESSAGE, 7) return #背景表示 pyxel.bltm(0, 0, 0, 0, 0, 20, 16, 0) #雲の表示(遠い) offset = (pyxel.frame_count // 8) % 160 for i in range(2): for x, y in self.far_cloud: pyxel.blt(x + i * 160 - offset, y, 0, 0, 56, 22, 8, 12) #キャラクタ表示 if not self.GAMEOVER: pyxel.blt(self.player_x, self.player_y, 0, 0, 0, 16, 16, 0) # 爆弾表示 for x, y in self.bomb: pyxel.blt(x, y, 0, 32, 0, 16, 16, 7) #雲の表示(近く) offset = (pyxel.frame_count // 2) % 160 for i in range(2): for x, y in self.near_cloud: pyxel.blt(x + i * 160 - offset, y, 0, 32, 56, 22, 8, 12) #スコア表示 s = "SCORE {:>4}".format(self.score) pyxel.text(5, 4, s, 1) pyxel.text(4, 4, s, 7) if not self.START: MESSAGE ="PUSH SPACE KEY" pyxel.text(61, 50, MESSAGE, 1) pyxel.text(60, 50, MESSAGE, 7) return #プレイヤー更新関数 def update_player(self): if pyxel.btn(pyxel.KEY_SPACE) or pyxel.btn(pyxel.GAMEPAD_1_A): if self.gravity > -self.MAX_GRAVITY: self.gravity = self.gravity - self.POWER else: if self.gravity < self.MAX_GRAVITY: self.gravity = self.gravity + self.POWER self.player_y = self.player_y + self.gravity if( 0 > self.player_y ): self.player_y = 0 if( self.player_y > pyxel.height -16 ): self.player_y = pyxel.height - 16 #爆弾更新 def update_bomb(self, x, y): if abs(x - self.player_x) < 12 and abs(y - self.player_y) < 12: self.GAMEOVER = True pyxel.blt(x, y, 0, 48, 0, 16, 16, 0) pyxel.blt(self.player_x, self.player_y, 0, 16, 0, 16, 16, 0) pyxel.stop() x -= 2 if x < -40: x += 240 y = randint(0, 104) return (x, y) def reset(self): #STARTFLAG self.START = True #GAMEOVERフラグ self.GAMEOVER = False #スコア用変数 self.score = 0 #プレイヤー初期地位 self.player_x = 20 self.player_y = 60 #重力系変数 self.gravity = 2.5 self.MAX_GRAVITY = self.gravity self.POWER = 0.25 #爆弾の生成用 self.bomb = [(i * 60, randint(0, 104)) for i in range(3,15)] #遠い雲 self.far_cloud = [(10, 25), (70, 35), (120, 15), (44, 45), (67, 75), (110, 95)] #近い雲 self.near_cloud = [(20, 15), (40, 75), (110, 40)] #音再生 pyxel.playm(0, loop=True) App() |
シンプルなゲームのため、複雑な処理はありません。
ボタンを押すと上昇し、離すと下降する処理は次のように記述しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#重力系変数 self.gravity = 2.5 self.MAX_GRAVITY = self.gravity self.POWER = 0.25 ---省略--- def update_player(self): if pyxel.btn(pyxel.KEY_SPACE) or pyxel.btn(pyxel.GAMEPAD_1_A): if self.gravity > -self.MAX_GRAVITY: self.gravity = self.gravity - self.POWER else: if self.gravity < self.MAX_GRAVITY: self.gravity = self.gravity + self.POWER self.player_y = self.player_y + self.gravity if( 0 > self.player_y ): self.player_y = 0 if( self.player_y > pyxel.height -16 ): self.player_y = pyxel.height - 16 |
上下するキャラの処理の流れ
1.スペースキーが押された場合はPOWERを徐々に増やし重力(gravity)からPOWERを引くことで
キャラクターが浮き上がるように設定しています。(10行目、15行目)
2.ボタンを話した場合はgravityが徐々に増加していくので、徐々に落ちる速度が上昇します。
(13行目、15行目)
背景の雲を動かす方法
背景の雲の移動はプレイヤーから見て奥行きの近い雲と遠い雲の2パターン作成しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#遠い雲 self.far_cloud = [(10, 25), (70, 35), (120, 15), (44, 45), (67, 75), (110, 95)] #近い雲 self.near_cloud = [(20, 15), (40, 75), (110, 40)] ---省略--- #雲の表示(遠い) offset = (pyxel.frame_count // 8) % 160 for i in range(2): for x, y in self.far_cloud: pyxel.blt(x + i * 160 - offset, y, 0, 0, 56, 22, 8, 12) ---省略--- #雲の表示(近く) offset = (pyxel.frame_count // 2) % 160 for i in range(2): for x, y in self.near_cloud: pyxel.blt(x + i * 160 - offset, y, 0, 32, 56, 22, 8, 12) |
雲の動きの流れ
1.雲の初期位置を決めるリストの作成します。なお、リストの要素数が雲の数となります
(2行目、5行目)
2.経過フレームを定数で割った商を横幅と同じ160で余剰したときの結果分右にずらす。
(10、13行目、18、21行目)
ちょっと難しい処理ですが、雲が右から左に向かって少しずつ移動するような処理になっています。
Pyxelで作ったゲームを他の人に遊んでもらう
通常、pythonで作成したゲームを他の人のパソコンで遊んでもらうには、作成したゲームプログラムと
pythonの実行環境が必要になりますが、Pyxelではpythonの環境がない場合でも遊べるように
変換してくれる機能があります。
使い方は以下の通りです。
1 |
pyxelpackager python_file |
python_fileは自分が作成したゲームファイル名が入ります。
素材ファイルはassetsフォルタを作成し、その中に入れておく必要がありますが
うまく作成できるとdistフォルダの中に実行ファイルが作成されます。
ファイルを他のパソコンにコピーして起動すれば同じゲームが遊べます。
ちなみに今回作成したファイルリンクを貼っておきますので
よろしければ試してみてください。
※上の例ではMacで作成したので、同じOSの「Mac」で動かすことは可能ですが、
windowsやLinuxでは動かないので注意してください。
もしwindowsやLinuxで動くものを作りたい場合はpyxelの環境をそれぞれのOSで作成して
再度pyxelpackagerを実行してください。
今回はpyxelでゲームを作成してみましたが、画像だけでなく、音楽も簡単に作ることができるので
すぐにオリジナルのゲームを作成することができます。
単独実行ファイルを出力ができるため自分が作ったゲームを友達に遊んでもらうことも気軽にできます。
是非一度試してみてください。