はじめに
ゲームでよくあるスクラッチくじを作ってみたので、その解説をしていきます。タイトルの通り削る部分を作っただけなので、演出などは各自で付け足してください。
実装方針
- 削るための層(上)
- 背景となるマークや文字を表示する層(下)
まず上記の二つの層を作り、上のレイヤーを削ることで隠されていた部分が徐々に見えるようにします。
事前知識
肝心の「削る」というのが、pygame上では、どの操作に対応するか先に説明しておきます。
pygameでSurfaceに対して書換を行う方法としてblitとdrawがありますが、今回はdrawを用います。不透明な図形を扱う分には違いが分かりずらいのですが、drawはblitと違い、指定した色で指定した領域の画像情報を上書きするので(ざっくり)、無色透明で上書きすれば、削ったりくりぬいたりできるという理屈です。
↑元となるSurfaceに対して半透明の四角をdrawした場合(左)とblitした場合(右)
コード解説
import pygame, sys, random
pygame.init()
WIDTH, HEIGHT = 600, 400
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("スクラッチくじ")
clock = pygame.time.Clock()
font = pygame.font.SysFont('MS Gothic', 54)
# 背景用サーフェス
background = pygame.Surface((WIDTH, HEIGHT))
background.fill((80, 112, 255))
# 透明度付きのマスクサーフェス
mask_surface = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
class ScratchArea:
def __init__(self, rect):
self.rect = rect
result = random.choice(['あたり', 'はずれ'])
self.text = font.render(result, True, (255, 0, 0))
# 文字の表示位置を計算
x, y, w, h = self.rect
tw, th = self.text.get_size()
tx = x + (w - tw)//2
ty = y + (h - th)//2
self.text_pos = (tx, ty)
self.draw()
# 初期不透明ピクセル数を取得
init_mask = pygame.mask.from_surface(
mask_surface.subsurface(self.rect), threshold=1
)
self.initial_count = init_mask.count()
def update(self):
# 現在の不透明ピクセル数を再取得
current_mask = pygame.mask.from_surface(
mask_surface.subsurface(self.rect), threshold=1
)
current_count = current_mask.count()
# 不透明ピクセル数の差分から削った割合を計算
scratched = self.initial_count - current_count
scratched_percent = scratched / self.initial_count * 100
# 一定割合以上削れた時の処理
if scratched_percent >= 75:
pygame.draw.ellipse(mask_surface, (0, 0, 0, 0), self.rect)
def draw(self):
x, y, w, h = self.rect
pygame.draw.ellipse(background, (255, 255, 255, 255), self.rect)
pygame.draw.ellipse(background, (255, 255, 0, 255), (x-5, y-5, w+10, h+10), 6)
background.blit(self.text, self.text_pos)
pygame.draw.ellipse(mask_surface, (192, 192, 192, 255), self.rect)
scratch_area = ScratchArea((300, 100, 200, 200))
# マウスドラッグの軌跡(線分リスト)
drawing = False
path = []
def main():
global drawing, path
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# 左クリック押したら描画開始
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
drawing = True
path = [list(event.pos)]
# 左クリック離したら描画終了
elif event.type == pygame.MOUSEBUTTONUP and event.button == 1:
drawing = False
path = []
# ドラッグ中は軌跡を追加
elif event.type == pygame.MOUSEMOTION and drawing:
path.append(list(event.pos))
# 軌跡に沿ってマスクを透明化
for i in range(len(path) - 1):
pygame.draw.line(mask_surface, (0, 0, 0, 0), path[i], path[i+1], 10)
scratch_area.update()
screen.blit(background, (0, 0)) # 背景
screen.blit(mask_surface, (0, 0)) # マスクを描画
pygame.display.update()
clock.tick(60)
if __name__ == '__main__':
main()
コードの各部分の役割はコメントを確認していただければと思います。全体としては、マウスでドラッグされている間の軌道を記録しておき、その軌道上に無色透明な線をdrawすることで、スクラッチ部分を削り取る構成になっています。
また、ある程度削り終わった時、綺麗に削り取る処理を追加するために、不透明なピクセルの割合を出す処理も実装しています。この部分は毎ループ呼び出す必要があります。削り終わったかどうか返すようにしておけば、ゲームループ側で受け取って、当たりの演出を付け加えるなども可能です。
まとめ
画像をくりぬきたい時は、無色透明な図形をdrawしよう
最後に
ここまで読んで下さりありがとうございました。今回は簡単な図形や塗りつぶししか使っていませんが、当然Surfaceであれば削ったりくりぬいたりできるので、用意した画像次第ではかなり本格的なものが作れる想定です。ぜひ自作ゲームの1要素として組み込んでみてください。

