フェーズ 2 タイトル文字を表示する

step0:星空を飛ぶ宇宙船

トップページstep0 概要 / フェーズ 2

ゴール

黒い画面の中央上部に STAR VOYAGER が黄色で表示され、 その下に灰色で操作説明の文字が出る。どちらも画面の横中央にきちんと揃っている

STAR VOYAGER PRESS ESC TO QUIT 画面の中心線
2 つの文字列が、長さが違っても両方とも中央に揃っている状態を目指します

1. 考え方

文字も「描くもの」

Pyxel では文字も図形と同じ描画です。したがって書く場所は draw の中、 そして pyxel.cls より後です。cls は画面全体を塗りつぶすので、 先に文字を描いてから cls すると塗りつぶされて消えてしまいます

描いた順に上へ重なる Pyxel の描画は「上から絵の具を重ねる」イメージです。後に書いた命令ほど手前に描かれます。 draw の中の命令の順番は、そのまま画面の重なり順になります。

文字の座標は「左上」

pyxel.text(x, y, s, col)x, y は、文字列の左上の角の位置です。 中心でも左下でもありません。そして Pyxel の内蔵フォントは 1 文字がぴったり 4×6 ドットと決まっています。

S T A R (x, y) ここが基準 4 ドット 6 4 文字 × 4 ドット = 16 ドット 文字列の表示幅は len(s) * 4 (文字数 × 1 文字の幅)で求まる
内蔵フォントは等幅(すべての文字が同じ幅)なので、文字数から表示幅を計算できます

中央に揃える計算

「中央に置く」は、Pyxel が用意してくれてはいません。自分で x 座標を計算します。 考え方は単純で、画面幅から文字列の幅を引いて、余った左右の余白を半分ずつに分けるだけです。

STAR VOYAGER 画面幅 160 12 文字 × 4 = 48 余白 56 余白 56 x = (160 - 48) // 2 = 56
左の余白の大きさが、そのまま文字列を描き始める x 座標になります

2. このフェーズで使う API

API・定数意味
pyxel.text(x, y, s, col)文字列 s を色 col で、左上が (x, y) になるように描く
pyxel.FONT_WIDTH内蔵フォント 1 文字の幅。中身は 4
pyxel.FONT_HEIGHT内蔵フォント 1 文字の高さ。中身は 6
pyxel.COLOR_YELLOW ほか色番号に付けられた名前。COLOR_YELLOW10 と同じ意味

3. コード

step0/star_voyager.py を、次の内容に書き換えてください。

# star_voyager.py
# step0 フェーズ2:タイトル文字を表示する

import pyxel

SCREEN_WIDTH = 160   # 画面の横幅(ドット)
SCREEN_HEIGHT = 120  # 画面の縦幅(ドット)

TITLE = "STAR VOYAGER"
GUIDE = "PRESS ESC TO QUIT"


def text_center(y, s, col):
    """文字列 s を、画面の横中央に来るように描く"""
    x = (SCREEN_WIDTH - len(s) * pyxel.FONT_WIDTH) // 2
    pyxel.text(x, y, s, col)


def update():
    """毎フレームの更新処理。このフェーズではまだ何もしない"""
    pass


def draw():
    """毎フレームの描画処理"""
    pyxel.cls(0)                                    # 画面を黒で塗りつぶす
    text_center(20, TITLE, pyxel.COLOR_YELLOW)      # タイトル
    text_center(34, GUIDE, pyxel.COLOR_GRAY)        # 操作説明


pyxel.init(SCREEN_WIDTH, SCREEN_HEIGHT, title="Star Voyager")
pyxel.run(update, draw)

4. 実行して確認する

python step0/star_voyager.py

確認ポイント

5. コードの解説

大文字だけの変数 = 定数のつもり

SCREEN_WIDTH = 160
TITLE = "STAR VOYAGER"

Python には「変更できない値」を作る仕組みはありませんが、 全部大文字の名前を付けたものは途中で書き換えないという約束(慣習)があります。 これを定数と呼びます。

理由は 2 つあります。

用語:マジックナンバー コードの中に理由の説明なく直接書かれた数字のこと。読み手に意味が伝わらず、 変更にも弱いため、名前を付けて定数にするのが定石です。

引数を受け取る自作関数

def text_center(y, s, col):
    x = (SCREEN_WIDTH - len(s) * pyxel.FONT_WIDTH) // 2
    pyxel.text(x, y, s, col)

フェーズ 1 の updatedraw はカッコの中が空でしたが、 今回は y, s, col の 3 つを受け取ります。これを引数と呼びます。 呼ぶ側が値を渡し、関数の中ではその値が変数として使えます。

なぜ関数にまとめるのか。 中央揃えの計算は、タイトルと操作説明の 2 か所で必要です。 同じ計算を 2 回書くと、片方だけ直して食い違うという事故が起きます。 1 か所にまとめておけば、直すときも 1 か所で済みます。

引数の順番は自分で決める text_center(y, s, col) の順番は、pyxel.text(x, y, s, col) から x を取り除いた並びに合わせています。元の API と似た並びにしておくと混乱しません。

len(s)

文字列 s文字数を返す Python の組み込み関数です。 len("STAR") なら 4。リストに使えば要素の個数を返します。

// — 整数の割り算

Python の割り算には 2 種類あり、この違いは Pyxel では特に重要です。

書き方結果
112 / 256.0小数(float)
112 // 256整数(int)
113 // 256整数(小数点以下は切り捨て)

/割り切れても必ず小数になります。画面のドットは整数のマス目なので、 座標の計算には // を使って整数のまま扱うほうが素直です。

/ を使ってもエラーにはなりません Pyxel の座標は小数も受け付けます(内部で切り捨てられます)。 ただし print で座標を確認したときに 56.0 と表示されて紛らわしいので、 整数で扱えるところは整数にしておきます。

pyxel.COLOR_YELLOW

10 と書いても同じですが、名前で書くほうが読んだときに色が分かります。 16 色すべてに名前が付いています。

BLACK(0)  NAVY(1)   PURPLE(2)  GREEN(3)
BROWN(4)  DARK_BLUE(5)  LIGHT_BLUE(6)  WHITE(7)
RED(8)    ORANGE(9) YELLOW(10) LIME(11)
CYAN(12)  GRAY(13)  PINK(14)   PEACH(15)
名前と見た目が一致しない色があります COLOR_GREEN(3) は青緑、COLOR_CYAN(12) は青紫寄り、 COLOR_RED(8) は赤紫です。名前を鵜呑みにせず、 フェーズ 1 のパレット図で実際の色を確かめてください。

y 座標を 20 と 34 にした理由

文字の高さは 6 ドット(pyxel.FONT_HEIGHT)です。 2034 の差は 14 なので、文字と文字の間に 8 ドットのすき間ができます。 数字に深い意味はなく、見た目のバランスで決めています。好きに変えてかまいません。

6. 試してみよう

やってみること確かめたいこと
TITLE の文字列を長さの違うものに変える 文字数が変わっても、自動で中央に揃い続ける
draw の中で pyxel.cls(0)いちばん最後に移動する 文字が塗りつぶされて何も見えなくなる(=順番が重要な理由)
text_center// 2// 4 に変える 文字が左寄りになる。式の意味を実感できる
色を pyxel.COLOR_GREENpyxel.COLOR_RED に変える 名前から想像する色と実際の色が違うことを確認できる

確認できたら、コードは元に戻しておいてください。

7. うまくいかないときは

症状原因と対処
何も表示されない(真っ黒のまま) pyxel.cls(0)text_center より後ろにある。clsいちばん先頭に置く
NameError: name 'text_center' is not defined def text_center の定義が draw より後ろにある、または名前のつづりが違う
TypeError: text_center() missing 1 required positional argument 呼び出し時の引数が足りない。text_center(20, TITLE, pyxel.COLOR_YELLOW) と 3 つ渡す
文字が中央からずれる SCREEN_WIDTHpyxel.init に渡した幅が食い違っている
TabError: inconsistent use of tabs and spaces タブとスペースが混ざっている。備忘録の該当項目を参照
次のフェーズ フェーズ 3 では星を画面に散らし、手前へ流れるアニメーションにします。 ここで初めて update に中身が入り、画面が「動き」ます。