カンバスたれ、計算機

Pythonでゲーム用にビットマップフォントを読み込む話

Pyxel というPythonのライブラリがありまして。これは『レトロゲームエンジン』をうたっていて、 いくつかの単純な命令を用いてレトロゲームを作れる(ドット絵やピコピコBGM作る機能まで付いてる)ものなのですが

この子、外部フォントを読み込んで描画する機能が無いんですね。 issueの#27 を見るに、フォント画像を用意してください、フォントファイルの読み込みはPyxelの範囲を超えます。というスタンスのようで。

なので書きました。単純にPillowでビットマップに描画してイメージバンクに転写して、コピーして使うだけ。

もちろんビットマップに描画したものを通常の画像ファイルとして保存してしまえば、実行時にフォントファイルは必要なくなります。

半角カタカナは位置計算が面倒なので今回は入れてませんが、技術的には何の支障もなく使えます。

(ちなみに、もしPyxelの素の環境にあるライブラリしか使ってはいけない縛りがあったとしても、Pillow(PIL)とnumpyはPyxelの依存ライブラリにあるので使えます。)

このコードがいつか誰かのたすけになることを願って

(ゼロピクセルフリーフォントさんところのフォントはどれもゲーム創作欲をガンガンに焚きつけてくるので、今回書いたコードは自分でもしっかり使うかと思われます)


スペシャルサンクス:

x0y0pxFreeFont ゼロピクセルフリーフォント
今回使用したフォント x8y12pxTheStrongGamer ザ・ストロングゲーマー
Pyxel
Pythonでレトロゲーム作るやつ 公式見ればだいたいわかる
Zen of Python 日本語訳
プログラマが持つべき心構え (The Zen of Python) - Qiita
  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
from PIL import Image, ImageFont, ImageDraw
import pyxel
import string

class Font:
    def __init__(self, file, size, alphabet):
        import numpy as np
        self.file = file
        self.size = size
        self.alphabet = alphabet

        px_w, px_h = size
        # pt_w, pt_h = (int(n * 0.75) for n in meta['size']) # 96 dpi
        ## load custom font
        font = ImageFont.truetype(file, size=px_h) # it must be pt_h, but px_h brings better result
        img = Image.new('1', size=(256, 256))
        draw = ImageDraw.Draw(img)
        coords = {}
        x, y = 0, 0
        for c in alphabet:
            if x + px_w > 256:
                x = 0
                y += px_h
            draw.text((x, y), c, font=font, fill=1)
            coords[c] = (x, y)
            x += px_w
        self.coords = coords
        self.img = img
        self.data = np.array(img.getdata()).reshape(256, 256)


def draw_font(img, font, col=7):
    img_bank = pyxel.image(img)
    for y in range(256):
        for x in range(256):
            img_bank.set(x, y, col if font.data[y][x] else 0) 

def text(font, x, y, s):
    w, h = font.size
    left = x

    for ch in s:
        if ch == '\n':
            x = left
            y += h
            continue

        if ch == ' ':
            x += w
            continue
        
        if ch in font.coords.keys():
            u, v = font.coords[ch]
            pyxel.blt(x, y, 0, u, v, w, h, 0)
#             for y_ in range(h):
#                 for x_ in range(w):
#                     if font.data[v+y_, u+x_]:
#                         pyxel.pix(x+x_, y+y_, col)
        x += w


def update():
    pass

def draw():
    pyxel.cls(0)
    text(font, 0, 0, """
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
みにくいよりうつくしいほうがいい。
Explicit is better than implicit.
あんじするよりめいじするほうがいい。
Simple is better than complex.
ふくざつであるよりはへいいであるほうがいい。
Complex is better than complicated.
それでも、こみいっているよりはふくざつであるほうがまし。
Flat is better than nested.
ネストはあさいほうがいい。
Sparse is better than dense.
みっしゅうしているよりはすきまがあるほうがいい。
Readability counts.
よみやすいことはぜんである。
Special cases aren't special enough to break the rules.
とくしゅであることはルールをやぶるりゆうにならない。
Although practicality beats purity.
しかし、じつようせいをもとめるとじゅんすいさがうしなわれることが
ある。""".strip())

pyxel.init(255, 255, caption="よくある手法で外部フォント")

fontfile = 'x8y12pxTheStrongGamer.ttf'
letter_size = (8, 12)
ascii_chars = string.punctuation + string.digits + string.ascii_letters
ひらがな = "".join(chr(c) for c in range(ord('ぁ'), ord('ゔ')+1))+"ー"
カタカナ = "".join(chr(c) for c in range(ord('ァ'), ord('ヶ')+1))+"ー"
# 半角カタカナ = "".join(chr(c) for c in range(ord('ァ')-6, ord('゙')+2))
alphabet = ascii_chars + ひらがな + カタカナ + "、。「」"
font = Font(fontfile, letter_size, alphabet)draw_font(0, font)
# font.img.save('x8y12pxTheStrongGamer.png')

pyxel.run(update, draw)