目標
前回のデータを使って、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
うまく変換されてるように見えるけど、各所見ていくとNGな箇所が見つかる。

今回、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キャラクタ)の板を上から並べて描画しています。

実行
pyxel run xeviscr.py
結果
描画時の処理を計測して比較してみました。(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


