2021/10/04(月) [n年前の日記]
#1 [pygame] PygameでタイルマップBGを描画
Python + Pygame でタイルマップのBGを描画できるか試してみた。
環境は、Windows10 x64 21H1 + Python 3.9.7 64bit + Pygame 2.0.1。
こんな感じになった。800x600ドットのウインドウ上で描画。カーソルキー、あるいはWASDキーでスクロールができる。
手抜きをして、タイルチップ単位 = 8x8ドット単位でスクロールさせてるから、一般的な2Dゲームのようにドット単位で動かすにはもうちょっとアレコレ処理を入れないといかんのだけど…。でもまあ、とりあえず、Pygame を使った場合でも、こういった描画はできそうだと分かった。
ただ、このやり方では、1280x720のウインドウサイズで描画した際に60FPSにならなかった…。AMD Ryzen 7 1700 (3GHz,8コア,16スレッド) + GeForce GTX 1060 6GB の環境で、2D、しかもBGを1枚しか表示してないのに、60FPSが出ないのはダメだよな…。相変わらず、Python + Pygame は遅い…。
もっとも、8x8ドット単位で処理してるのがいかんのではという気もする。32x32ドットや64x64ドットにして、ループ回数を減らすべきかも。もしくは、BG描画用の大きな Surface を用意して、スクロールに応じて書き換えが必要な部分だけ変更して、ウインドウへの描画はそのBG用Surfaceを使って行う、てな感じの処理にすれば負荷が違ってくるのかもしれない。
環境は、Windows10 x64 21H1 + Python 3.9.7 64bit + Pygame 2.0.1。
こんな感じになった。800x600ドットのウインドウ上で描画。カーソルキー、あるいはWASDキーでスクロールができる。
手抜きをして、タイルチップ単位 = 8x8ドット単位でスクロールさせてるから、一般的な2Dゲームのようにドット単位で動かすにはもうちょっとアレコレ処理を入れないといかんのだけど…。でもまあ、とりあえず、Pygame を使った場合でも、こういった描画はできそうだと分かった。
ただ、このやり方では、1280x720のウインドウサイズで描画した際に60FPSにならなかった…。AMD Ryzen 7 1700 (3GHz,8コア,16スレッド) + GeForce GTX 1060 6GB の環境で、2D、しかもBGを1枚しか表示してないのに、60FPSが出ないのはダメだよな…。相変わらず、Python + Pygame は遅い…。
もっとも、8x8ドット単位で処理してるのがいかんのではという気もする。32x32ドットや64x64ドットにして、ループ回数を減らすべきかも。もしくは、BG描画用の大きな Surface を用意して、スクロールに応じて書き換えが必要な部分だけ変更して、ウインドウへの描画はそのBG用Surfaceを使って行う、てな感じの処理にすれば負荷が違ってくるのかもしれない。
◎ ソースと使用画像。 :
ソースは以下。
_02_tilemapbg.py
処理としては、以下のようなことをしている。
使用画像は以下。

_road_chip.png
読み込むタイルマップデータ(.json)は以下。Tiled で配置して、json でエクスポートした。
_road_layout.json
Tiled で編集し直したい場合は、以下の Tiled用ファイルを使えばよいかと…。
_road_layout.tmx
_road_chip.json
ソース、画像、Tiled用ファイルは、CC0 / Public Domain ってことで。
_02_tilemapbg.py
import json
import pygame
import sys
SCRW, SCRH = 800, 600
pygame.init()
screen = pygame.display.set_mode((SCRW, SCRH), pygame.DOUBLEBUF)
# load Tiled json file
bglayout = "road_layout.json"
with open(bglayout, 'r') as f:
data = json.load(f)
map_w = int(data["width"])
map_h = int(data["height"])
tile_w = int(data["tilewidth"])
tile_h = int(data["tileheight"])
# get tilemap, convert start ID 1 to 0.
bgtilemap = list(map(lambda x: x-1, data["layers"][0]["data"]))
# load bg chip image
imgname = "road_chip.png"
img = pygame.image.load(imgname).convert_alpha()
# split bg chip image
bgchips = []
w, h = tile_w, tile_h
xc = img.get_width() // w
yc = img.get_height() // h
for yi in range(yc):
for xi in range(xc):
x, y = xi * w, yi * h
bgchips.append(img.subsurface(pygame.Rect(x, y, w, h)))
xofs, yofs = 0, 0
xofs_max = max(0, map_w - (SCRW // tile_w))
yofs_max = max(0, map_h - (SCRH // tile_h))
running = True
clock = pygame.time.Clock()
# Main loop
while running:
# update
# check event
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
running = False
if ev.type == pygame.KEYDOWN:
if ev.key == pygame.K_ESCAPE or ev.key == pygame.K_q:
# Push ESC or Q key
running = False
# scroll BG
pygame.event.pump()
pressed = pygame.key.get_pressed()
if pressed[pygame.K_LEFT] or pressed[pygame.K_a]:
xofs -= 1
if xofs < 0:
xofs = 0
if pressed[pygame.K_RIGHT] or pressed[pygame.K_d]:
xofs += 1
if xofs > xofs_max:
xofs = xofs_max
if pressed[pygame.K_UP] or pressed[pygame.K_w]:
yofs -= 1
if yofs < 0:
yofs = 0
if pressed[pygame.K_DOWN] or pressed[pygame.K_s]:
yofs += 1
if yofs > yofs_max:
yofs = yofs_max
# draw start
screen.fill((40, 60, 200)) # clear screen
# draw tilemap BG
xc = SCRW // tile_w
yc = SCRH // tile_h
for yi in range(yc):
for xi in range(xc):
xii = xi + xofs
yii = yi + yofs
if xii >= map_w or yii >= map_h:
continue
i = bgtilemap[yii * map_w + xii]
x = xi * tile_w
y = yi * tile_h
screen.blit(bgchips[i], (x, y))
pygame.display.flip()
clock.tick_busy_loop(60)
cap = "Tilemap BG - %5.2f FPS" % (clock.get_fps())
pygame.display.set_caption(cap)
pygame.quit()
sys.exit()
処理としては、以下のようなことをしている。
- マップエディタ Tiled からエクスポートした json を読み込んで、タイルマップの配列(一次元配列)を得る。
- タイルセット画像を読み込んで、8x8ドット単位で分割。
- タイルマップ配列に基づいて、8x8ドット単位で、タイルセット画像を描画。
使用画像は以下。

読み込むタイルマップデータ(.json)は以下。Tiled で配置して、json でエクスポートした。
_road_layout.json
Tiled で編集し直したい場合は、以下の Tiled用ファイルを使えばよいかと…。
_road_layout.tmx
_road_chip.json
ソース、画像、Tiled用ファイルは、CC0 / Public Domain ってことで。
◎ 余談。今後の課題。 :
こういった感じのタイルマップを自動生成できるようにしたいのだけど、さて、何をどうすればいいのやら。うーん。
道路の自動生成ができれば、道路が無い部分にビルを配置することだってできそうだなと。更に、2Dで道路やビルを配置できるなら、ソレを3Dで処理して…。例えば blender + Python で、道路+ビル街を生成するスクリプトを書けたりしないかなと。要するに、POV-Ray 用の CityGen みたいなことができないかと。
いやまあ、blenderなら、めっちゃリアルでゴイスなビル街を生成してくれる有償アドオンがあるらしいのだけど…。
_Blender の都市生成アドオン「SceneCity」 - 100光年ダイアリー
_SceneCity: 3D city generator addon for Blender 2.9+
この手のツール、アドオン、プラグインは結構作られているようで…。
_3Dソフトで都市を自動生成 - 100光年ダイアリー
ゴイスな映像を作りたいなら、買って使ったほうが早いはず。
道路の自動生成ができれば、道路が無い部分にビルを配置することだってできそうだなと。更に、2Dで道路やビルを配置できるなら、ソレを3Dで処理して…。例えば blender + Python で、道路+ビル街を生成するスクリプトを書けたりしないかなと。要するに、POV-Ray 用の CityGen みたいなことができないかと。
いやまあ、blenderなら、めっちゃリアルでゴイスなビル街を生成してくれる有償アドオンがあるらしいのだけど…。
_Blender の都市生成アドオン「SceneCity」 - 100光年ダイアリー
_SceneCity: 3D city generator addon for Blender 2.9+
この手のツール、アドオン、プラグインは結構作られているようで…。
_3Dソフトで都市を自動生成 - 100光年ダイアリー
ゴイスな映像を作りたいなら、買って使ったほうが早いはず。
[ ツッコむ ]
以上、1 日分です。