はじめに
以前公開した記事で、文字を透過できないという問題の対処法を初心者向けに解説しました。その発展として、Fontクラスを継承することで、renderする時に透明度を設定できるよう改造していきます。
実装する機能
タイトルの通り透明化機能を作るのですが、Fontクラスのrenderをオーバーライドするので、太字など任意の機能が追加できます。今回は、ついでに枠線を付ける機能も付けようと思います。
事前知識
そもそもpygameでフォントのインスタンスを作成する際には、pygame.font.Fontとpygame.font.SysFontの二種類の指定方法があります、前者はttfファイルを用意してそのフォントパスを引数に渡し、後者はあらかじめpygame側で決められている名前を渡すという違いがあります。今回はAlphaFontという名前でpygame.font.Fontに当たるクラスを作成し、pygame.font.SysFontに当たるものは以下の関数で実装します。
def AlphaSysFont(name, size):
return AlphaFont(pygame.font.match_font(name), size)
コード解説
class AlphaFont(pygame.font.Font):
def render(self, text, antialias=True, color=None, background=None, border=None, alpha = 255):
surface = pygame.Surface(self.size(text), pygame.SRCALPHA)
if background is not None:
surface.fill(background)
font_surface = None
if color is not None:
font_surface = super().render(text, antialias, color, None)
surface.blit(font_surface, (0, 0))
if border is not None:
# マスクを取得して輪郭を抽出
target = font_surface if font_surface else super().render(text, antialias, (0, 0, 0), None)
mask = pygame.mask.from_surface(target)
# 輪郭に沿って点を描画(太さを出すなら draw.circle にする)
for component in mask.connected_components():
for point in component.outline():
surface.set_at(point, border)
surface.set_alpha(alpha)
return surface
文字の色、背景色、縁の色をそれぞれ指定し、surfaceに張り付けていくことで、ベースとなる画像を作成します。そのうえで、surface.set_alphaを用いて画像自体に任意の透明度を設定することで、透過を実現しています。
Noneが指定されているものはfillやblitを行わないため反映されません。colorにNoneを設定した場合は輪郭を抽出する元画像がないため、黒色で文字画像を作成し、そこから輪郭を抽出します。
輪郭を抽出する際、単にoutlineをする場合は閉じた個所を一カ所抽出して終わりになってしまうため、connected_componentsを用いて複数の閉じた個所を抽出するため二重ループを使っています。
使用例
通常のFontやSysFontとは引数が増えているだけで、使い方は大きく変わらないので、以下のように使えます。コピペして動かせば、記事の最初に貼ってある画像と同じものが表示されるはずです。
コードを見る
import pygame, sys
pygame.init() #おまじない
screen = pygame.display.set_mode((512, 512)) #ウィンドウの作成
pygame.display.set_caption('template') #ウィンドウのタイトルを設定
clock = pygame.time.Clock() #ゲームループのfpsを設定するためのやつ
class AlphaFont(pygame.font.Font):
def render(self, text, antialias=True, color=None, background=None, border=None, alpha = 255):
surface = pygame.Surface(self.size(text), pygame.SRCALPHA)
if background is not None:
surface.fill(background)
font_surface = None
if color is not None:
font_surface = super().render(text, antialias, color, None)
surface.blit(font_surface, (0, 0))
if border is not None:
# マスクを取得して輪郭を抽出
target = font_surface if font_surface else super().render(text, antialias, (0, 0, 0), None)
mask = pygame.mask.from_surface(target)
# 輪郭に沿って点を描画(太さを出すなら draw.circle にする)
for component in mask.connected_components():
for point in component.outline():
surface.set_at(point, border)
surface.set_alpha(alpha)
return surface
def AlphaSysFont(name, size):
return AlphaFont(pygame.font.match_font(name), size)
def draw_gradient(surface, start_color, end_color):
"""グラデーションを描画"""
height = surface.get_height()
width = surface.get_width()
for x in range(width):
ratio = x / width
r = int(start_color[0] * (1 - ratio) + end_color[0] * ratio)
g = int(start_color[1] * (1 - ratio) + end_color[1] * ratio)
b = int(start_color[2] * (1 - ratio) + end_color[2] * ratio)
pygame.draw.line(surface, (r, g, b), (x, 0), (x, height))
def main():
font = AlphaSysFont('MS Gothic', 64)
text = font.render('こんにちは', True, (255, 0, 0), (255, 255, 0), (0, 255, 0), 64)
text2 = font.render('pygame', True, (0, 255, 0), None, None, 128)
text3 = font.render('縁だけ', True, None, None, (255, 255, 255), 64)
while True:
draw_gradient(screen, (0, 0, 255), (255, 0, 0))
screen.blit(text, (100, 100))
screen.blit(text2, (100, 200))
screen.blit(text3, (100, 300))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
pygame.display.update()
clock.tick(30)
if __name__ == '__main__':
main()
まとめ
文字に透明度を設定したい時かつ、それを多用する場合にはクラスを作ってしまった方が楽かも?
最後に
ここまで読んで下さりありがとうございました。今回追加した機能以外にも、頑張ればいろいろなデコレーションを加えられるので、自分好みにカスタマイズしたものを一度作ってみるのは大いにありですね。ついでにmaskで縁を付けてみたものの、フォントサイズが小さいと微妙な仕上がりだったりするので、1pxずらして4方向にblitするとか、他の方法がいいかもしれません。(それだと縁だけにはできませんが)
