集めた数を数える変数を用意し、画面に文字で描く
お菓子を集めた数を数えて、画面の左上に SCORE 0 のように表示します。
書き足すのは全部で 4 行だけの、短いフェーズです。
新しく出てくるのは pyxel.text() と f 文字列の 2 つです。
「いくつ集めたか」も、男の子の位置と同じでフレームをまたいで覚えておきたい情報です。
なので __init__ で self.score = 0 と用意します。
フェーズ 3 で書いた check_candy() の if の中が、
ちょうど「いま取った」瞬間です。ここに 1 行足すだけで数えられます。
pyxel.text()pyxel.text(x, y, 文字列, 色番号)
blt() と同じで、x と y は文字の左上の位置です。
色は pyxel.COLOR_WHITE(= 7)のような色番号を渡します。
self.score は数値ですが、pyxel.text() に渡せるのは文字列です。
そこで、数値を文字列の中に埋め込みます。それが f 文字列です。
self.score = 3
f"SCORE {self.score}" # → "SCORE 3" という文字列になる
文字列の前に f をつけると、{ } の中身がその場で計算されて
文字に置きかわります。f を書き忘れると、
{self.score} がそのまま文字として表示されてしまいます。
| 機能 | 役割 |
|---|---|
pyxel.text(x, y, s, col) | 文字列 s を (x, y) に色 col で描く |
pyxel.COLOR_WHITE | 白(色番号 7)。7 と書いても同じ |
f"... {変数} ..." | 文字列の中に変数の中身を埋め込む(f 文字列) |
+= 1 | いまの値に 1 を足して入れなおす |
__init__ に 1 行足します。
self.boy_x = SCREEN_WIDTH // 2 - BOY_SIZE // 2
self.boy_y = SCREEN_HEIGHT // 2 - BOY_SIZE // 2
self.score = 0 # ← 足す
self.place_candy()
pyxel.run(self.update, self.draw)
check_candy() に 1 行足します。
def check_candy(self):
"""お菓子に重なったら、スコアを増やして次の場所へ置きなおす"""
if is_hit(self.boy_x, self.boy_y, BOY_SIZE,
self.candy_x, self.candy_y, CANDY_SIZE):
self.score += 1 # ← 足す
self.place_candy()
draw() のいちばん最後に 1 行足します。
def draw(self):
"""フレーム毎の描画処理"""
pyxel.cls(pyxel.COLOR_BLACK)
pyxel.blt(self.candy_x, self.candy_y, 0, 16, 0,
CANDY_SIZE, CANDY_SIZE, pyxel.COLOR_BLACK) # お菓子
pyxel.blt(self.boy_x, self.boy_y, 0, 0, 0,
BOY_SIZE, BOY_SIZE, pyxel.COLOR_BLACK) # 男の子
pyxel.text(4, 4, f"SCORE {self.score}", pyxel.COLOR_WHITE) # ← 足す
実行して、左上に SCORE 0 が出て、お菓子を取るたびに数字が増えれば完成です。
# step1 フェーズ3:… のままになっているはずです。
毎フェーズの最後にここを直す習慣にしておくと、あとで見返したときに助かります。
変わったのは 4 行だけですが、全体を載せておきます。
# candy_hunt.py
# step1 フェーズ4:集めた数をスコアとして表示する
import random
import pyxel
SCREEN_WIDTH = 160
SCREEN_HEIGHT = 120
BOY_SIZE = 8 # 男の子の絵の大きさ(縦横とも 8 ドット)
BOY_SPEED = 2 # 1 フレームで進むドット数
CANDY_SIZE = 8 # お菓子の絵の大きさ
def is_hit(x1, y1, size1, x2, y2, size2):
"""2 つの四角が重なっていれば True を返す"""
return (x1 < x2 + size2 and x2 < x1 + size1 and
y1 < y2 + size2 and y2 < y1 + size1)
class App:
def __init__(self):
"""起動時の設定"""
pyxel.init(SCREEN_WIDTH, SCREEN_HEIGHT, title="Candy Hunt")
pyxel.load("candy_hunt.pyxres")
self.boy_x = SCREEN_WIDTH // 2 - BOY_SIZE // 2
self.boy_y = SCREEN_HEIGHT // 2 - BOY_SIZE // 2
self.score = 0
self.place_candy()
pyxel.run(self.update, self.draw)
def update(self):
"""フレーム毎の更新処理"""
self.move_boy()
self.check_candy()
def move_boy(self):
"""矢印キーで男の子を動かす"""
if pyxel.btn(pyxel.KEY_LEFT):
self.boy_x -= BOY_SPEED
if pyxel.btn(pyxel.KEY_RIGHT):
self.boy_x += BOY_SPEED
if pyxel.btn(pyxel.KEY_UP):
self.boy_y -= BOY_SPEED
if pyxel.btn(pyxel.KEY_DOWN):
self.boy_y += BOY_SPEED
# 画面の外へ出さない
self.boy_x = max(0, min(self.boy_x, SCREEN_WIDTH - BOY_SIZE))
self.boy_y = max(0, min(self.boy_y, SCREEN_HEIGHT - BOY_SIZE))
def place_candy(self):
"""男の子と重ならない場所へ、お菓子を置きなおす"""
while True:
self.candy_x = random.randint(0, SCREEN_WIDTH - CANDY_SIZE)
self.candy_y = random.randint(0, SCREEN_HEIGHT - CANDY_SIZE)
if not is_hit(self.boy_x, self.boy_y, BOY_SIZE,
self.candy_x, self.candy_y, CANDY_SIZE):
break
def check_candy(self):
"""お菓子に重なったら、スコアを増やして次の場所へ置きなおす"""
if is_hit(self.boy_x, self.boy_y, BOY_SIZE,
self.candy_x, self.candy_y, CANDY_SIZE):
self.score += 1
self.place_candy()
def draw(self):
"""フレーム毎の描画処理"""
pyxel.cls(pyxel.COLOR_BLACK)
pyxel.blt(self.candy_x, self.candy_y, 0, 16, 0,
CANDY_SIZE, CANDY_SIZE, pyxel.COLOR_BLACK) # お菓子
pyxel.blt(self.boy_x, self.boy_y, 0, 0, 0,
BOY_SIZE, BOY_SIZE, pyxel.COLOR_BLACK) # 男の子
pyxel.text(4, 4, f"SCORE {self.score}", pyxel.COLOR_WHITE)
App()
数値を文字列にまぜる方法はいくつかありますが、いまの Python では f 文字列がいちばん素直です。
| 書き方 | 結果 | ひとこと |
|---|---|---|
f"SCORE {self.score}" | SCORE 3 | これを使う |
"SCORE " + str(self.score) | SCORE 3 | 同じ結果。長くなる |
"SCORE " + self.score | TypeError。文字列と数値は + でつなげない | |
"SCORE {self.score}" | SCORE {self.score} | f を忘れた形。そのまま表示される |
{ } の中には式が書けます
変数だけでなく、計算もそのまま書けます。
f"SCORE {self.score}" # → SCORE 3
f"NEXT {5 - self.score % 5}" # → 計算結果が入る
f"{self.score:3}" # → 幅 3 で右ぞろえ(" 3")
最後の :3 のような書き方は書式指定といって、
桁をそろえたいときに便利です。いまは使いませんが、頭の隅に置いておいてください。
self.score += 1 の意味self.score += 1 # これは次と同じ意味
self.score = self.score + 1
「いまの値を読んで、1 足して、また入れなおす」という 3 段階を短く書いたものです。
-=(フェーズ 2 の self.boy_x -= BOY_SPEED)も同じ仲間です。
draw() ではなく check_candy() で増やすのか
draw() の中で self.score += 1 と書くと、
毎フレーム(1 秒に 30 ずつ)増えてしまいます。
フェーズ 2 で決めた「update() で決めて、draw() で描く」という
役割分担が、ここでも効いています。
place_candy() でお菓子が別の場所へ移っているので、
同じお菓子で 2 回数えることはありません。備忘録の
「1 回だけ処理する書き方」で扱った
「状態が変わった瞬間だけ処理する」のと似た形になっています。
フェーズ 3 の「あとに描いたものが上に重なる」がここでも効きます。
pyxel.text() を blt() より前に書くと、
お菓子や男の子がスコアの上を通ったときに文字が隠れてしまいます。
描く順番は 背景 → ゲームの中身 → 画面表示(スコアなど)が基本です。
これまで BOY_SPEED や CANDY_SIZE は定数にしてきましたが、
pyxel.text(4, 4, ...) の 4 はそのまま書いています。
基準は「同じ数字が 2 か所以上に出てくるか」です。
なんでも定数にすると、かえって「この名前は何だったか」と探す手間が増えます。 迷ったら、2 回目に同じ数字を書くときに定数へ格上げすると決めておけば十分です。
Pyxel の標準フォントは1 文字 4 ドットの送りで、
実際に色が付くのは横 3 ドット、高さは大文字で 5 ドット、
g や y のように下に伸びる文字を含めると 6 ドットです。
pyxel.text(4, 4, "スコア", 7) と書いても、何も描かれません
(エラーにもなりません)。標準フォントは英数字と記号だけです。pyxel/examples/assets/umplus_j12r.bdf)を読み込む方法があります。
font = pyxel.Font("umplus_j12r.bdf")
pyxel.text(4, 4, "スコア 5", 7, font)
ただしフォントファイルを配布物に同梱する必要が出てくるので、
この step では英数字だけで作ります。
お菓子や男の子がスコアの真下を通ると、白い文字が読みにくくなることがあります。 黒で 1 ドットずらして描いてから、白で本体を描くと、縁取りができて読みやすくなります。
text = f"SCORE {self.score}"
pyxel.text(5, 5, text, pyxel.COLOR_BLACK) # 影
pyxel.text(4, 4, text, pyxel.COLOR_WHITE) # 本体
同じ文字列を 2 回書かずに済むよう、いったん text という変数に入れています。
COLOR_BLACK)なので、黒い影は背景に溶けて見えません。
効果を確かめたいときは、一時的に pyxel.cls(pyxel.COLOR_NAVY)(濃紺)などに
変えてみてください。影が効いてくるのは明るい背景を敷いたときや、
文字の下を明るい絵が通るときです。
step1 は黒背景で進めるので、この飾りは入れなくても構いません。
| 症状 | 見るところ |
|---|---|
SCORE {self.score} とそのまま表示される |
文字列の前の f が抜けています |
TypeError: can only concatenate str (not "int") to str |
"SCORE " + self.score と書いています。
f 文字列にするか、str(self.score) で文字列に変換します |
AttributeError: 'App' object has no attribute 'score' |
__init__ の self.score = 0 を書き忘れているか、
pyxel.run() よりあとに書いています |
| スコアが猛烈な勢いで増える | self.score += 1 が if の外、または draw() の中にあります。
check_candy() の if の中(place_candy() と同じ深さ)に置きます |
| スコアが増えない | self.score += 1 ではなく self.score + 1 になっていませんか。
これは計算するだけで、結果をどこにも入れていません |
| 文字が表示されない | 色が背景と同じ黒(0)になっていないか確認します。
また、日本語を渡していると何も出ません |
| 男の子がスコアの上に重なって文字が消える | pyxel.text() が blt() より前にあります。いちばん最後に描きます |
遊びの手ごたえが出てきました。次はいよいよお化けを 1 体出して、男の子を追いかけさせます。 「相手の座標と自分の座標を見くらべて、近づく向きへ動く」という、 追跡の基本の考え方が登場します。