1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Pyxel】Pyxelを使ってゼビウスの背景スクロールを実現する(その2)

Last updated at Posted at 2025-12-08

目標

前回のデータを使って、PyxelEditor上に展開して背景スクロール実現を試みる。

PyxelEditorに関して、
横8ドット縦8ドットのキャラクタデータを作成するイメージデータは1枚で最大1024キャラクタまで登録可能。マップデータに相当するタイルマップはこのイメージデータ番号指定で1枚しか使用できない。

つまり、今回圧縮したキャラクタデータが1024以下でなければPyxelEditor上に置くことができないことになる。

今回のデータを見てみるとキャラクタデータ数は1196キャラクタになってるのでPyxelEditorでは不可能ということになる。
(※対象となる画像によってデータ数は変動します)

データ変換

一応、キャラクタデータ数を最大1024として変換してみた。
(本来はデータ変換後のキャラクタデータ数が1024以下であることが望ましい。)

前回のdeflate.pyの最後に以下を追加。

#-----------------------------------
#PyxelEditor上のキャラクタデータに展開する
#横256ドット縦256ドット
#つまり、横256/8=32キャラクタ、縦256/8=32の合計32*32=1024キャラクタ設定可能
#総数1024キャラクタ以下なら1image分保存可能

#キャラクタデータをchardata.txtに出力
with open('chardata.txt', mode='w') as f:
	_d = [0 for _dd in range(4*8)]
	for n in range(int(_char_number/32)+1):
		for y in range(8):				#1キャラ縦サイズ
			print("[",end="", file=f)			#1ドット行ずつ括る

			if( n >= (int(_char_number/32))):
				mx = _char_number % 32	#横最大数で最後の残りは端数になる
				if( mx == 0 ):			#端数無ければ最大数は最大値に設定
					mx = 32
			else:
				mx = 32

			for x in range(32):
				_d = _char_tbl[(n*32) + x]
				if( x >= mx ):		#無い部分には0を入れる
					for i in range(8):
						print(0,end=", ", file=f)
				else:
					for i in range(4):
						print(0x10+(_d[(y*4)+i]>>4),end=", ", file=f)		#上位バイト
						print(0x10+(_d[(y*4)+i]&0x0f),end=", ", file=f)		#下位バイト
			print("],", file=f)
	print("[0]", file=f)			#終端

	f.close()

#タイルマップデータをtilemap.txtに出力
with open('tilemap.txt', mode='w') as f:
	print("imgsrc = 2", file=f)		#imageセットした値をセット(今回は"2")

	set = 0
	for n in range(int(_map_number/CHAR_X_MAX)+1):		#Y方向
		print("[",end="", file=f)		#キャラ行ずつ括る

		if( n >= (int(_map_number/CHAR_X_MAX))):
			mx = _map_number % CHAR_X_MAX	#横最大数で最後の残りは端数になる
			if( mx == 0 ):					#端数無ければ最大数は最大値に設定
				mx = CHAR_X_MAX
		else:
			mx = CHAR_X_MAX

		for x in range(CHAR_X_MAX):						#X方向:128
			#登録最大数こえた?
			if( ((n*CHAR_X_MAX) + x) >= _map_number ):
				set = 1
				break

			_d = _map_tbl[(n*CHAR_X_MAX) + x]
			if( x >= mx ):		#無い部分には0を入れる
				print(0,end=", ", file=f)
				print(0,end=", ", file=f)
			else:
				print(_d & 0x01f,end=", ", file=f)				#下位バイト:座標x
				print(( _d >> 5 ) & 0x01f,end=", ", file=f)		#上位バイト:座標y

		#終了?
		if( set == 1 ):
			print("],", file=f)
			break

		#横不足サイズ分を0で埋める
		for x in range(256 - CHAR_X_MAX):
			print(0,end=", ", file=f)
			print(0,end=", ", file=f)

		print("],", file=f)

	print("[0]", file=f)			#終端
	f.close()

PyxelEditorのリソースファイル"xevi.pyxres"を"xevi.zip"にリネームしてzip解凍すると、"pyxel_resource.toml"が出力される。

"pyxel_resource.toml"をテキストエディタで開き、
以下の[images]の"data = [[0]]"の"[0]"を部分に
キャラクタデータ"chardata.txtの内容に入れ替える。

[[images]]
width = 256
height = 256
data = [[0]]

同様に以下の[tilemaps]の"data = [[0]]"の"[0]"を部分に
タイルマップデータ"tilemap.txtに入れ替える。
(最初の1行は下記記述のimgsrcなので削除、終端コードの手前の"[],"を削除する)

[[tilemaps]]
width = 256
height = 256
imgsrc = 0
data = [[0]]

イメージデータを3枚目に格納した時は上記 "imgsrc=2" に変更する。
(タイルマップがひとつのイメージデータで構成されてる所以)

"pyxel_resource.toml"を保存して、"pyxel_resource.toml"をzip圧縮。
"pyxel_resource.zip"を"xevi.pyxres"にリネームしてエディタ起動

pyxel edit xevi

イメージデータ
pyxel_image.png

タイルマップ
pyxel_tile.png

うまく変換されてるように見えるけど、各所見ていくとNGな箇所が見つかる。
pyxel_tile_NG.png

今回、PyxelEditorにデータを置いて背景スクロールを実現させることが目標なので、NG箇所はそれっぽいキャラクタデータに置き換えて作成することにする。
(置き換えなくてもいいけど、見栄え的に・・・)

リソースを元に背景スクロール

前回の"xeviscr.py"の描画関数(draw())を下記に置き換える。

#----------
#描画(リソース使用)
def draw():

	global _dx
	global _dy
	global _area_number
	global _startup

	#画面クリア
	pyxel.cls(0)
	
	if( _startup != 0 ):
		_area_number = 0
		_dy = MAP_HEIGHT - 1	#スクロール位置
	
	_ddy = _dy & 0x07

	for _yp in range(SCREEN_CHAR_HEIGHT+1):			#画面縦キャラクタサイズ
		_yofs = _dy + (_yp*8)

		if( ( _area_number == 0 ) and ( _yofs >= MAP_HEIGHT ) ):
			#森描画(マップの下から6キャラクタ分をループさせて使用)
			_yofs -= MAP_HEIGHT
			_v = ( 256 - 6 + ( int( _yofs / 8 ) % 6 ) ) * 8
			pyxel.bltm( 0, _yp*8 + (8 - _ddy) - 8, 0, _area_x[_area], _v, SCREEN_WIDTH, CHAR_HEIGHT )

		else:
			_area = _area_number
			if( ( _area_number > 0 ) and ( _yofs >= MAP_HEIGHT ) ):
				#上から描画していくので、はみでた部分を差し引いて前のエリアを記述していく
				_yofs -= MAP_HEIGHT
				_area -= 1
	
			_v = int( _yofs / 8 ) * 8
			pyxel.bltm( 0, _yp*8 + (8 - _ddy) - 8, 0, _area_x[_area], _v, SCREEN_WIDTH, CHAR_HEIGHT )

	if( _startup != 0 ):
		_startup = 0

エリアの繋ぎ目部分はこんな感じで、タイルマップにおいて、縦8ドット(1キャラクタ)横224ドット(画面横サイズ、28キャラクタ)の板を上から並べて描画しています。
tsunagi_zu.png

実行

pyxel run xeviscr.py

結果

前回同様スクロールスピード8での動作は以下
tilemapscr.gif

描画時の処理を計測して比較してみました。(import time使用)
処理時間多めの箇所を抜粋すると、
前回:0.015
今回:0.0016
処理時間は1/10となリました。

前回は8ドット x 8ドットのキャラクターサイズでpsetフル活用だったので時間がかかっていたのも当然の結果。
今回はタイルマップからの描画なのでゲーム化するなら当然こちらの方が良いですが問題はキャラクタデータ、地道に手作業するなら問題ないですが簡単にはいかないようです。

後日談

減色ファイル"deccol.py"において、減色させる箇所を以下のように変更。

#k平均法を使用する
im_q = im.quantize(colors=16, method=1, kmeans=504, dither=0)

キャラクタデータ数を限りなく1024に近い数値にした結果、k平均法を504回かけるとキャラクタデータ数は1019となり、PyxelEditorのキャラクタデータ最大数以内におさまりました。

以下の動作確認に反映させました。
(リソース使用スイッチを切り替え前後でほぼ変化が無いことを確認)

動作確認

上記からさらに操作可能にしたものの動作確認は以下でできます。
HTMLなのでPyxelが無くても動きます。
Xevious Scroll 2

応用?

パレットモードのPNGファイルからPyxelEditor上のキャラクタデータを作成する。
PyxelEditorのImage Dataは最大横256ドット縦256ドット。
PIL使って減色&リサイズしてこのサイズに収めれば格納できることになる。

#パレットモードのPNGファイルからPyxelEditor上のキャラクタデータを作成する
#最大サイズ:横256ドット縦256ドット
import struct
import zlib

#PNGファイルを開く(ファイル名は仮)
with open('basechar.png', mode='rb') as pngf:
	#読み込み
	data = pngf.read()
	pngf.close()

#PNGデータ展開
#[PNGシグネチャ]
offset = 8
#[IHDR(25 bytes)]
width = struct.unpack_from(">I", data, offset + 8)		#横
height = struct.unpack_from(">I", data, offset + 12)	#縦
offset += 25
#[PLTE]
length = struct.unpack_from(">I", data, offset + 0) 
#Length(4) + chunk(4) = 8
offset += 8
#パレットデータ格納
pdata = list(struct.unpack_from(">" + str(length[0]) + "B", data, offset))
#CRC(4) = 4
offset += length[0] + 4

#パレットファイル出力
with open('palet.txt', mode='w') as f:
	for n in range(int(length[0]/3)):
		for x in range(3):			#RGB
			print('{:02x}'.format(pdata[n*3+x]),end="", file=f)
		print("", file=f)			#改行
	f.close()

#[IDAT]
length = struct.unpack_from(">I", data, offset) 
ctype = struct.unpack_from(">4s", data, offset + 4)
idata = list(struct.unpack_from(">" + str(length[0]) + "B", data, offset + 8))
#Length(4) + chunk(4) + CRC(4) = 12
offset += length[0] + 12

#複数のIDATを連結する
while True:
	length = struct.unpack_from(">I", data, offset) 
	ctype = struct.unpack_from(">4s", data, offset + 4)
	#IENDを拾ったら終了
	if( ctype[0].decode() == 'IEND' ):
		#終了
		break
	tdata = list(struct.unpack_from(">" + str(length[0]) + "B", data, offset + 8))
	idata = idata + tdata
	offset += length[0] + 12

#IDATはdeflate圧縮がかかっているのでzlibによる解凍
idata = zlib.decompress(bytearray(idata))

#Pyxel Image出力
#行頭に必ず0x00が入るので横サイズ+1とし、1~(横サイズ+1)の範囲で抽出する
width2 = int(width[0]/2)+1
with open('chardata.txt', mode='w') as f:
	for y in range(height[0]):				#1キャラ縦サイズ
		print("[",end="", file=f)			#1ドット行ずつ括る
		for x in range(1,width2):			#横サイズ
			print( idata[y*width2+x]>>4,end=", ", file=f)		#上位バイト
			print( idata[y*width2+x]&0x0f,end=", ", file=f)		#下位バイト

		print("],", file=f)
	print("[0]", file=f)					#終端

	f.close()

これ使えばキャラクタ(スプライト)もPyxelEditorに載せられると思いましたがそうは簡単にはいかず、結局多大なる手直しが必要になることがわかったのでした・・・orz

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?