step0:星空を飛ぶ宇宙船
フェーズ 4 で描いた宇宙船を、実際に画面へ表示します。 やることは 2 つだけです。
pyxel.load で assets.pyxres を読み込むpyxel.blt で宇宙船を画面に貼る絵を画面に出すには、2 つの段階を踏みます。
| 段階 | やること | いつ |
|---|---|---|
読み込むpyxel.load |
.pyxres ファイルの中身を、Pyxel のイメージバンク(絵の置き場)へ運び込む |
起動時に 1 回だけ |
貼るpyxel.blt |
イメージバンクから絵を切り出して、画面の指定した位置にコピーする | 毎フレーム(1 秒に 30 回) |
ここを取り違えると動きません。ファイルを読むのは重い処理なので、
毎フレームやってはいけません。逆に、画面は毎フレーム cls で消されるので、
貼るほうは毎回やらないと宇宙船は一瞬で消えます。
.pyxres はハードディスクの中のファイルです。そのままでは Pyxel は絵を使えません。
pyxel.load は、その中身をメモリ上のイメージバンクへ運び込む作業です。
一度運び込んでしまえば、あとは何度でも高速に使い回せます。
blt に渡す 8 つの数字
pyxel.blt は引数が 8 個あります。多く見えますが、
「どこへ」「どこから」「どれだけ」「何を抜くか」の 4 セットに分かれているだけです。
pyxel.blt(x, y, img, u, v, w, h, colkey)
| 引数 | 意味 | 今回の値 |
|---|---|---|
x, y | 画面のどこへ貼るか(左上のかど) | 72, 80 |
img | どのイメージバンクから取るか(0〜2) | 0 |
u, v | 台紙のどこから切り出すか | 0, 0 |
w, h | 何ドットぶん切り出すか | 16, 16 |
colkey | 塗らない色(透過色)。省略可 | 0 |
u・v は台紙の中の座標、x・y は画面の中の座標ですu と v
台紙側の横・縦を表す座標です。画面側の x・y と区別するために、
わざと別の文字を使う習わしになっています。u が横、v が縦です。
今回の宇宙船は台紙の左上に描いたので u = 0、v = 0 です。
x = 72、y = 80 は宇宙船の中心ではなく左上のかどの位置です。
16 ドットの絵を画面の横中央に置きたいときは、
(160 - 16) / 2 = 72 のように絵の幅を引いてから半分にします。
リソースエディタで宇宙船を描いたとき、周りは黒(色番号 0)のままでした。
このまま貼ると、16 × 16 の黒い四角ごと画面に貼られてしまい、
宇宙船の後ろを流れるはずの星が四角く隠れます。
そこで blt の 8 番目の引数 colkey(カラーキー=透過色)に
0 を渡します。すると Pyxel は色番号 0 のドットだけ塗らずに飛ばすようになります。
draw の中では、先に書いた命令ほど奥、後に書いた命令ほど手前になります。
紙に絵の具を重ね塗りするのと同じで、あとから塗ったものが上にきます。
だから draw は「奥のものから順に」書きます。
星 → 宇宙船 → 文字、の順です。
もし宇宙船を星より先に描くと、宇宙船の上を星が通り過ぎてしまいます。
| API | 意味 |
|---|---|
pyxel.load(ファイル名) |
.pyxres を読み込み、絵・音をイメージバンクなどへ入れる。
pyxel.init のあとに書く |
pyxel.blt(x, y, img, u, v, w, h, colkey) |
イメージバンク img の (u, v) から w × h ドットを
切り出し、画面の (x, y) へ貼る。colkey の色は塗らない |
pyxel.COLOR_BLACK |
色番号 0(黒)を表す定数。0 と書いても同じ |
colkey は省略できます
pyxel.blt(x, y, 0, 0, 0, 16, 16) のように 7 個で呼ぶこともできます。
その場合は透過なし(全ドットをそのまま貼る)になります。
「省略できる引数」がある関数は Pyxel に多いので、覚えておいてください。
変更は 3 か所だけです。まず変更点を確認してから、下の全文と見比べてください。
STAR_SPEED_MAX の下)SHIP_U = 0 # 台紙のどこから切り出すか(横)
SHIP_V = 0 # 台紙のどこから切り出すか(縦)
SHIP_WIDTH = 16 # 宇宙船の幅
SHIP_HEIGHT = 16 # 宇宙船の高さ
__init__ に読み込みと初期位置を足す pyxel.init(SCREEN_WIDTH, SCREEN_HEIGHT, title="Star Voyager")
pyxel.load("assets.pyxres") # ← 追加:絵と音のファイルを読み込む
self.stars = []
self.make_stars()
# ↓ 追加:宇宙船の位置を覚えておく
self.ship_x = (SCREEN_WIDTH - SHIP_WIDTH) // 2 # 横は画面の中央
self.ship_y = 80 # 縦は下のほう
pyxel.run(self.update, self.draw)
draw に blt を足す # 2. 宇宙船(星より手前) ← 星のループと文字のあいだに追加
pyxel.blt(
self.ship_x, self.ship_y, # 画面のどこに貼るか
0, # どのイメージバンクから取るか
SHIP_U, SHIP_V, # 台紙のどこから切り出すか
SHIP_WIDTH, SHIP_HEIGHT, # 何ドットぶん切り出すか
pyxel.COLOR_BLACK, # この色は塗らない(透過色)
)
# star_voyager.py
# step0 フェーズ5:宇宙船を画面に出す
import random
import pyxel
SCREEN_WIDTH = 160
SCREEN_HEIGHT = 120
TITLE = "STAR VOYAGER"
GUIDE = "PRESS ESC TO QUIT"
STAR_COUNT = 40 # 星の数
STAR_SPEED_MIN = 0.3 # 星の速さの下限(ドット/フレーム)
STAR_SPEED_MAX = 1.8 # 星の速さの上限
SHIP_U = 0 # 台紙のどこから切り出すか(横)
SHIP_V = 0 # 台紙のどこから切り出すか(縦)
SHIP_WIDTH = 16 # 宇宙船の幅
SHIP_HEIGHT = 16 # 宇宙船の高さ
class App:
"""このゲーム全体を受け持つ箱"""
def __init__(self):
"""箱が作られるときに 1 回だけ呼ばれる。ここで準備をすませる"""
pyxel.init(SCREEN_WIDTH, SCREEN_HEIGHT, title="Star Voyager")
pyxel.load("assets.pyxres") # 絵と音のファイルを読み込む
self.stars = [] # 星をぜんぶ入れるリスト
self.make_stars()
self.ship_x = (SCREEN_WIDTH - SHIP_WIDTH) // 2 # 横は画面の中央
self.ship_y = 80 # 縦は下のほう
pyxel.run(self.update, self.draw)
def make_stars(self):
"""星を STAR_COUNT 個ぶん作って self.stars に入れる"""
for i in range(STAR_COUNT):
x = random.randint(0, SCREEN_WIDTH - 1)
y = random.randint(0, SCREEN_HEIGHT - 1)
speed = random.uniform(STAR_SPEED_MIN, STAR_SPEED_MAX)
self.stars.append([x, y, speed])
def star_color(self, speed):
"""速い星ほど明るい色にして、奥行きを表す"""
if speed > 1.2:
return pyxel.COLOR_WHITE
if speed > 0.7:
return pyxel.COLOR_GRAY
return pyxel.COLOR_DARK_BLUE
def text_center(self, y, s, col):
"""文字列 s を、画面の横中央に来るように描く"""
x = (SCREEN_WIDTH - len(s) * pyxel.FONT_WIDTH) // 2
pyxel.text(x, y, s, col)
def update(self):
"""毎フレームの更新処理。星を下へ動かす"""
for star in self.stars:
star[1] += star[2] # y に速さを足す
if star[1] >= SCREEN_HEIGHT: # 画面の下に出たら
star[0] = random.randint(0, SCREEN_WIDTH - 1) # x を引き直して
star[1] = 0 # いちばん上に戻す
def draw(self):
"""毎フレームの描画処理。奥のものから順に描く"""
pyxel.cls(0)
# 1. 星(いちばん奥)
for star in self.stars:
pyxel.pset(star[0], star[1], self.star_color(star[2]))
# 2. 宇宙船(星より手前)
pyxel.blt(
self.ship_x, self.ship_y, # 画面のどこに貼るか
0, # どのイメージバンクから取るか
SHIP_U, SHIP_V, # 台紙のどこから切り出すか
SHIP_WIDTH, SHIP_HEIGHT, # 何ドットぶん切り出すか
pyxel.COLOR_BLACK, # この色は塗らない(透過色)
)
# 3. 文字(いちばん手前)
self.text_center(20, TITLE, pyxel.COLOR_YELLOW)
self.text_center(34, GUIDE, pyxel.COLOR_GRAY)
App()
python step0/star_voyager.py
pyxel.load は pyxel.init のあとに書く
順番が逆だと動きません。pyxel.init が「Pyxel の置き場所(イメージバンクなど)を用意する」
命令だからです。置き場所が無いところに荷物は運び込めません。
pyxel.init(...) # 1. 置き場所を用意する
pyxel.load("assets.pyxres") # 2. そこへ運び込む
"assets.pyxres" はどこを探しにいくのか
ここは間違えやすいところです。pyxel.load に渡す相対パスは、
実行したスクリプト(star_voyager.py)が置いてあるフォルダを基準に探されます。
いま自分がいるフォルダ(カレントディレクトリ)は関係ありません。
learning_pyxel/ ← ここでコマンドを打っても
└── step0/
├── star_voyager.py ← 基準になるのはこのファイルの場所
└── assets.pyxres ← なので "assets.pyxres" で見つかる
つまり python step0/star_voyager.py と打っても、
step0 に移動してから python star_voyager.py と打っても、
どちらも pyxel.load("assets.pyxres") で読めます。
"step0/assets.pyxres" とは書きません。
open とは違う挙動です
普通の open("assets.pyxres") はカレントディレクトリ基準で探すため、
リポジトリのルートから実行すると見つかりません。
pyxel.load は「ゲームのファイルはスクリプトと一緒に置くもの」という前提で、
親切に振る舞ってくれています。
self.ship_x なのか。ただの ship_x ではだめか
今回は画面の中央に置いたきりで動かさないので、実は定数でも足ります。
それでも self. を付けてインスタンスの属性にしたのには理由があります。
次のフェーズ 6 で、この値をキー入力で書き換えるからです。
| 置き場所 | 性質 | 向いているもの |
|---|---|---|
SHIP_WIDTH = 16(クラスの外の定数) |
ずっと変わらない | 絵のサイズ、画面サイズ、色 |
self.ship_x(インスタンスの属性) |
ゲーム中に変化する。update で書き換え、draw で読む |
位置、速度、得点、残機 |
self. が付いた変数は update と draw の両方から見えて、
しかもフレームをまたいで値が残ります。これがクラスを使う最大の理由です。
「変わるものは self. に、変わらないものは定数に」と覚えてください。
pyxel.blt(self.ship_x, self.ship_y, 0, SHIP_U, SHIP_V, SHIP_WIDTH, SHIP_HEIGHT, 0)
と 1 行で書いてもまったく同じ意味です。ただ、数字が 8 個並ぶと何番目が何なのか読めません。
Python ではカッコの中は自由に改行できるので、意味のまとまりごとに改行して
コメントを付けられます。行末に \ などは不要です。
最後の引数のうしろにコンマ(,)が付いているのも文法違反ではなく、
あとで引数を足しやすくするための書き方です。
pyxel.COLOR_BLACK と 0 は同じもの
pyxel.COLOR_BLACK の中身はただの 0 です。
それでも名前のほうを書くのは、読んだときに意味が分かるからです。
pyxel.blt(..., 0) だと最後の 0 が
「バンク番号? 座標? 色?」と迷いますが、
pyxel.COLOR_BLACK なら一目で色だと分かります。
SHIP_WIDTH = 16 と定数にしたのも同じ理由です。
1 つずつ変えて実行し、変えたら元に戻して次へ進んでください。
| やってみること | 確かめたいこと |
|---|---|
最後の pyxel.COLOR_BLACK, の行を消す( SHIP_HEIGHT, のコンマも消す) |
宇宙船が黒い四角ごと貼られる。透過色の役目がはっきり分かる |
pyxel.COLOR_BLACK を pyxel.COLOR_WHITE に変える |
白いドット(コックピットの支柱)だけが抜けて星が透ける |
SHIP_WIDTH を 8 にする |
宇宙船の左半分だけが表示される。「切り出す大きさ」の意味が分かる |
SHIP_U を 8 にする |
切り出す位置が右へずれて、右半分+台紙の余白が貼られる |
blt の行を、星のループより前に移動する |
星が宇宙船の上を通るようになる。描画順=重なり順を確認できる |
self.ship_y を 110 にする |
宇宙船が画面の下からはみ出す。はみ出しても落ちない(エラーにならない) |
pyxel.load(...) の行を消す |
宇宙船が消える。読み込まないとイメージバンクは空のまま |
| 症状 | 原因と対処 |
|---|---|
| 宇宙船が表示されない(エラーなし) | pyxel.load を書き忘れているか、
blt を pyxel.run より後ろに書いている。
または draw の中ではなく __init__ に blt を書いている |
| 宇宙船が黒い四角付きで表示される | 8 番目の引数 colkey を渡していない |
| 宇宙船のところだけ真っ黒な穴があく | リソースエディタで背景を黒(0)以外の色で塗りつぶしている。
エディタで背景を 0 に戻す |
OSError / ファイルが見つからない系のエラー |
assets.pyxres が star_voyager.py と同じ step0 フォルダに
無い。"step0/assets.pyxres" と書いてしまっている場合も同じエラーになる |
TypeError: blt() missing ... argument |
引数の数が足りない。x, y, img, u, v, w, h の 7 個は必須 |
| 変な絵(台紙の別の場所)が貼られる | SHIP_U・SHIP_V が 0 になっていない。
または宇宙船を台紙の左上以外に描いている |
| 星が宇宙船の上を通る | blt が星のループより前にある。後ろへ移動する |
| 絵が更新されない(前の絵のまま) | リソースエディタで Ctrl + S による保存を忘れている |
pyxel.btn でキー入力を受け取り、
矢印キーで宇宙船を動かします。
今回作った self.ship_x / self.ship_y を
update の中で書き換えるだけです。画面の外へ出ないようにする処理も学びます。