プチコンTips

セーブ機能を付ける方法

 自作ゲームにおいてハイスコアなどは変数に記録していてもCLEAR命令によって消去されてしまいます。そのため消えては困るデータはSAVE命令によって保存する必要があります。
 プチコンプログラムにおいてよく使われているのはMEM$とGRPを使った2つの方法なのでそれぞれについて使用方法を書いていきます。

《 SAVE、LOAD時のシステム音とダイアログ表示について 》

 SAVEやLOADの際のシステム音(ピーガガガ音)やダイアログ表示がウザイという人はSYSBEEP=FALSEもしくはSYSBEEP=0で消すことができます。またダイアログにおいてはLOAD "(ファイル名)",0のように末尾に0かFALSEを付けることで出ないようにできます。
 一端変更して元に戻すという場合は次のようにSYSBEEPの値を別の変数に入れておくといいです。

A=SYSBEEP
SYSBEEP=0
LOAD "(ファイル名)",0
SYSBEEP=A

 何も無かったかのようにLOADが完了して元の状態に戻ります。
 ただし、これでもセーブデータがないときのエラーダイアログは出てしまいます。あとSAVEの際はダイアログを出ないように設定することはできません。これは悪意のあるプログラムを作ることができてしまうため恐らくこれからもバージョンアップでSAVE時のダイアログ無しに設定できるようにはならないでしょう。


MEM$を使った記録方法


◎数値を記録する方法

 MEM$はユーザーのセーブデータ記録用に開放されているシステム変数です。このMEM$にセーブしたいデータを入れてSAVE "MEM:(ファイル名)"とすればセーブ機能の搭載が可能になります。特に難しいことは何もないのですが、下記の2つの点に注意する必要があります。

 (1)MEM$でセーブできるのは文字列のみ(最大256文字)
 (2)複数のデータをセーブする場合は元に戻せるように桁揃えをする

(1)例えば変数Hにハイスコアが入っている場合は数値変数の状態ではMEM$で記録できませんのでMEM$=STR$ (H)のようにして文字列の形にすることでSAVE "MEM:HISCORE"のように記録できるようになるということです。
 このセーブデータをロードする時はLOAD "MEM:HISCORE"のようにするだけです。ただし、この場合はMEM$にデータが入っているため変数Hにハイスコアを入れる場合には文字列変数を数値に変換しなくてはならないのでH=VAL (MEM$)とする必要があります。(※プチコンのファイルはフォルダ管理ができないため不特定多数の人に向けて公開するプログラムの場合にはHISOCREのようにありふれたファイル名は重複の可能性があるためできるだけ避けて「QRコードの注意点」における公開する側の注意点(4)でも書いたようにファイル名の先頭3〜4文字にユーザー識別用の文字列を付けるのがベターといえる)

 あとデータをLOADできない時(SAVEされてない時)のことを考えて必要ならばハイスコアのデフォ値をMEM$に入れておくと処理が楽になります。例えば、ハイスコアの初期値が100点ならばMEM$="100":LOAD "MEM:HISCORE":H=VAL (MEM$)のようにします。(MEM$はCLEAR命令で初期化されないため最初にMEM$=""などの初期設定をしておかないと誤動作をしてしまう場合がある)
 こうすることで、システム変数RESULTを使ってIF文で条件判断をする必要性が無くなります。

 なお、セーブする数値が整数であれば問題ありませんが、小数を含む場合には有効数字の関係でSTR$を使って文字列にする段階で小数第3位までに丸められた数字になってしまいます。
 セーブする際にわずかに値が変わってしまうのが許容できるならば問題ありませんが、それでは困るという場合には文字列にする前に数値を整数部分と小数部分に分けます。小数部分を取り出す方法はこちらの方法を使うと簡単です。取り出した小数部分は4096倍してそれを文字列化してセーブします。ロードの時は整数部分+(小数部分÷4096)とすれば元に戻せますが、その際には下記の(2)のように複数のデータを1つのMEM$に保存する方法を参考にしてちゃんと元に戻せるようにしておく必要があります。(保存する値が128以上にならないことが分かっているならば整数と小数を分けずにそのまま4096倍するのが最も簡単)
 また、32bit整数演算変換ルーチンを用いて変数の値を整数としてみなすことで簡単にセーブできます。この場合はすでにINT$にセーブすべき値(INTに入った数値を4096倍した値)を文字列にしたものが入っているのでそれを保存するだけで良いです。ロードしたときはINT$INTに変換するルーチン(逆変換ルーチン)の方を使ってください。INTにはINT$を数値化したものを4096で割ったものが入っています)

(2)MEM$には(1)で書いたように256文字分記録できます。そのためハイスコアだけではなく上位5位までのスコアを記録したいというなどの場合も対応が可能です。例えば1位〜5位までのスコアが配列変数H(1)〜H(5)に入っている場合には下記の方法によってMEM$経由でセーブが可能になります。

《 リスト1 》
MEM$=""
FOR I=1 TO 5
 MEM$=MEM$+STR$ (H(I))
NEXT
SAVE "MEM:HISCORE"

 しかし、このセーブデータはLOADしても配列変数H()に入れる手段がありません。
 例えば1位1234点、2位567点、3位345点、4位210点、5位98点という場合にはMEM$の内容は"123456734521098"となりますがこの"123456734521098"という文字列を見ただけで1位から5位までのスコアは判別が不可能でありこの状態で記録しても元に戻すことができないのです。
 仮にこのゲームではスコアが最高でも5桁までしか到達しないというのであればすべて5桁のスコアになるように記録していくことで元に戻すことが可能になります。(※そのゲームの上限スコアが分からないならばプチコンで扱える数字が、524287までなので6桁あれば十分だし、小数点を含む数値も記録する必要があるのならばさらに4桁プラスすればよい)
 5桁に足りない場合には頭に0を付けて5桁になるようにしなくてはなりません。(最低でも1桁あるので先頭に"0"を4つ加えれば5桁以上になり、その右から5桁分取り出せばちょうど5桁の数になる)

 ※BASICでは0の代わりにスペースでも問題ない場合が多いですが、プチコンの場合は仕様上スペースを先頭に置くとVAL関数では0を返すため桁揃えには0を使ってください。("  456"ではなく"00456"のようにするということ)
  スペースで桁揃えをする場合には先頭ではなく後ろの方に置けば問題はないです。("  456"ではなく"456  "のようにするということ)

《 リスト2 》 セーブ 【mkII専用】
MEM$=""
FOR I=1 TO 5
 MEM$=MEM$+RIGHT$ ("0000"+STR$ (H(I)),5)
NEXT
SAVE "MEM:HISCORE"
RIGHT$はmkII専用の命令であるため初代対応にするためにはMID$を使って同様の動作を行うようにする必要がある
MEM$=MEM$+RIGHT$ ("0000"+STR$ (H(I)),5) → H$="0000"+STR$ (H(I)):MEM$=MEM$+MID$ (H$,LEN (H$)-5,5)


 こうすることで、MEM$には"0123400567003450021000098"という文字列が記録されるため5桁ごとに区切れば元に戻すことが可能になります。

《 リスト3 》 ロード
LOAD "MEM:HISCORE"
FOR I=1 TO 5
 H(I)=VAL (MID$ (MEM$,I*5-5,5))
NEXT

 ただし、保存する値に負数が含まれる場合には、例えば-345という数字を符号無しで符号+5桁の数として桁揃えする際に単純に先頭に0を羅列して"00-345"としてしまうとVAL関数では0を返してしまいます。そのため負数の値を含む場合には数値の後にスペースを入れて桁揃えをする("-345 "のようにする)のが良いでしょう。(符号で1文字分使ってしまうため符号込みで5桁にするならばスペースは1文字分で良い)
 少し面倒くさいですが、負数を含まないのが分かっていれば特に気にする必要はないし、負数かどうかを考えるのが面倒ならば下記の区切りを入れて記録する方法を使えば負数が混在していても何ら問題はありません。

 なお、セーブする数値が整数であるならば数値を文字列にする場合にはHEX$を用いると「文字列化」と「桁揃え」を同時に行えるためSTR$を使って文字列化するよりも簡単になります。変数Sにスコアが入っていて4桁区切りにしたい場合にはHEX$(S,4)でOKです。しかも、16進数なので4桁であっても0〜65535(16の4乗-1)までの数値が保存可能になるため桁数を考えた場合に保存できる上限の数値が大きくなります。
 非常に簡単なのはいいですが、負数の場合には少しだけ注意が必要です。それは、VAL("&H"+MEM$)では基本的には正数しか返さないためです。(※
下記のような例外があり)
 負数は補数で表すことができ、4桁で表したときには"FFFF"が-1、"FFFE"が-2になります。負数を含む場合には4桁の数では-32768("8000")〜+32767("7FFF")までが表現可能になります。それは(桁揃えが4桁の場合)32767を超えているかを判定するだけで負数に戻すことができます。

 ◎MEM$にHEX$で4桁に揃えられた負数を含む数値を文字列化したものが入っている時に元に戻す方法
  H=VAL("&H"+MEM$):IF H>32767 THEN H=H-65536


 なお、桁揃えが5桁の場合はこの方法ではプチコンで扱える上限数を超えてしまうため負数を判定できないのですが、例外として16進数で5桁を数値化する場合に限りVAL関数で補数は負数で返してくれます。したがって、セーブする数値が負数になる場合があるときは上記のような負数判定を不要にするため桁揃えは5桁にするのが最も簡単です。(5桁ならばプチコンで扱えるすべての整数を表現できる)
 数値をセーブする場合には普段はHEX$を使って文字列化を行い、小数を含む値を保存が必要な時のみ上記の方法を用いるのが良いでしょう。

 ◎MEM$で数値をセーブする方法についてのまとめ
  • HEX$で文字列化
    • 数値は自動的に整数化が行われる(小数にならないなら問題なし)
    • 桁揃えは自動的に可能
    • 4桁以下だと負数判定が別途必要(負数にならないなら問題なし)
    • 5桁だと負数判定は不要
    • 小数を保存するならば4096倍して整数化を行う

  • STR$で文字列化
    • 数値は自動的に小数第3位で丸められる(整数もしくは丸められてもOKなら問題なし)
    • 桁揃えをする場合は自前で行う(負数の場合は符号の分の1桁変わることに注意)
    • 正確な値を保存するならば4096倍して整数化を行う

    小数を含む正確な値を保存するならば32bit整数演算変換ルーチンを用いるのが最も簡単

◎区切りを入れて記録する方法

 複数の数値をMEM$を使ってセーブする場合には頭に0を付けて桁揃えをして保存しましたが、これが文字列ならば記録したい文字列の後ろにスペースを入れてLEFT$を使用して桁揃え(文字数揃え)をする必要があります。ただし、このような余分なスペースを付けた場合に数値と違って簡単に取り除くことはできないため好ましくないと思っている人もいることでしょう。
 実は、桁揃えをしなくても"1234/567/345/210/98"のようにセーブデータの中で他には使用していない特定の文字を区切りに使って保存すれば元に戻すことは可能です。

《 リスト4 》 セーブ
MEM$=""
FOR I=1 TO 5
 MEM$=MEM$+STR$ (H(I))+"/"
NEXT
SAVE "MEM:HISCORE"

《 リスト5 》 ロード 【mkII専用】
LOAD "MEM:HISCORE"
A=-1
FOR I=1 TO 5
 B=A+1
 A=INSTR (B,MEM$,"/")
 H(I)=VAL (MID$ (MEM$,B,A-B))
NEXT
INSTRはmkII専用の命令であるため初代対応にするためにはMID$とIF文を使って同様の動作を行うようにする必要がある

 この区切り文字を入れる方法はハイスコアなどの数値をセーブする場合にはあまりメリットはないですが、最初に書いたように文字列を保存する場合に桁揃えを行うのは都合が悪いという時に有効活用できます。スペースが邪魔になる場合はそれを削除する手間を考えるならば最初から桁揃え無しでセーブした方が楽にできます。あと桁数(文字数)が異なるデータが混在する時も有効活用できるでしょう。
 ただし、区切り文字の分だけ記録できる量が減ってしまうというデメリットがあります。

◎データを短縮して記録する方法

 RPGなどで記録するデータが多い場合はMEM$の最大256文字という制限が大きくなってくるためもう少し効率よく保存をする必要があります。例えば123という数値を3文字分使って記録するのではなく1文字分として記録する方法があるのです。そのためにはCHR$を使います。
 この方法を用いると1文字で0〜255までの数値が記録できます。(2文字ならば0〜63535まで記録可能だけど上記と同じく2文字単位で桁揃えして記録しておかないと元に戻すことができないので注意)

《 リスト6 》 セーブ
MEM$=""
FOR I=0 TO 9
 MEM$=MEM$+CHR$ (P(I))
NEXT
SAVE "MEM:SAVEDATA"

《 リスト7 》 ロード
LOAD "MEM:SAVEDATA"
FOR I=0 TO 9
 P(I)=ASC (MID$ (MEM$,I,1))
NEXT

これによって0〜255の値を取るP(0)〜P(9)の10個のパラメータがあっても10文字分のデータで済むようになります。(普通に10進数で桁揃えをすれば1文字で3桁分になるため30文字必要になるし、HEX$を使っても20文字必要になる)


グラフィック面(GRP)を使った記録方法


◎基本的な記録方法

 プチコンは初代においてはグラフィック面が2面、mkIIにおいてはグラフィック面が4面使用できます。この中から使用していないグラフィック面にデータを記録することでグラフィック面(GRP:)経由でセーブを行うというものです。
 プチコンのグラフィック面は256x192ドット256色で構成されています。1ドットには1バイト分の情報を入れることが可能になるため256x192バイト(=49152バイト=48KB)のデータを保存可能になります。そのため最大256文字(256バイト)となっているMEM$では一度に保存できる容量が小さすぎるという場合に有効活用できます

 グラフィック面に記録するためにはGPSETを使います。読み出しはGSPOITでできます。上記MEM$を使ったセーブ方法の冒頭部分で書いたように変数の内容はCLEARで消去されるのと同様にグラフィック面の情報もGCLSによってGPAGEで指定されたグラフィック面の情報は消去されてしまいます。mkIIに搭載されているACLS命令を使えばGPAGE 0、1両方のグラフィック面の情報が消去されます。(ページ2、3に関してのみACLSでは消去されない)
 したがって、(ページ1のグラフィック面を保存する場合には)SAVE "GRP1:(ファイル名)"のようにグラフィック面の情報をセーブする必要があります。

 具体的なセーブ、ロードの方法を書いていきます。
 H(0)〜H(99)に1位から100位までのスコアが記録されているという場合には下記のようになります。(※セーブやロード後にGPAGE命令によって使用するGPAGE 0として使用するグラフィック面を元に戻すのを忘れないようにしてください。あと以下のサンプルはすべてそのままでも動作しますが、事前にGPAGE 1:GCLSをしているとセーブデータ公開時にQRコードの枚数を減らせるのでオススメです。)

《 リスト8 》 セーブ
GPAGE 1
FOR I=0 TO 99
 A=H(I)/256
 B=H(I)%256
 GPSET I*2,0,A
 GPSET I*2+1,0,B
NEXT
SAVE "GRP1:SAVEDATA"

《 リスト9 》 ロード
GPAGE 1
LOAD "GRP1:SAVEDATA"
FOR I=0 TO 99
 A=GSPOIT (I*2,0)
 B=GSPOIT (I*2+1,0)
 H(I)=A*256+B
NEXT

 1つのドットでは0〜255の数値しか記録できませんが、1つのデータに2ドット分使用することで0〜65535までの数値を記録することが可能になっています。3ドット分使えば0〜16777215の数値が記録できるため整数ならばプチコンで扱えるすべての範囲をまかなうことが可能です。
 1つのドットで0〜255まで記録できる「256進数」であるため2ドットで分で扱う場合にはこのようにまず最初に256で割ってその商を求めそして次に余り(剰余)を求める必要があります。本来ならば商は整数であるためA=H(I)/256ではなくA=FLOOR (H(I)/256)とする必要があるのですが、GPSETの指定座標は小数部分が無視されるためこの場合はFLOORによって整数化する必要はないのです。

 この例では1つのデータに2ドット使用であり、それが100個分で合計200ドット使用であったためGPSETで指定するY座標は0で問題ありませんでした。これがデータ数が増えて合計256ドット(256バイト)を越えるデータ量になる場合にはGPSETで指定するY座標は256バイトごとに変化していきます。

《 リスト10 》 セーブ
GPAGE 1
FOR I=0 TO 999
 A=H(I)/256
 B=H(I)%256
 GPSET (I*2)%256,I/128,A
 GPSET (I*2+1)%256,I/128,0,B
NEXT
SAVE "GRP1:SAVEDATA"

《 リスト11 》 ロード
GPAGE 1
LOAD "GRP1:SAVEDATA"
FOR I=0 TO 999
 A=GSPOIT ((I*2)%256,I/128)
 B=GSPOIT ((I*2+1)%256,I/128)
 H(I)=A*256+B
NEXT

 0〜65535の数値1000個をセーブ、ロードするプログラムですが、GPSET、GSPOITのY座標を128で割っていることが分かるでしょう。1データ1バイトならば256で割る必要性があるのですが、1データ2バイトならば128個で256バイトになるため128で割る必要性があるということです。
 1つのデータが1ドットや2ドットのように256の約数であればFOR文のカウンタから計算して書き込む場所を求めてやれば良かったのですが、これが3ドット単位だったり、単一形式のデータを大量にセーブするのではなく1ドット単位、2ドット単位、4ドット単位などが混在になると単純計算ではいかないためトータルで何ドット目に記録しているのかというのを数えるカウンタを用意する必要があります。
 カウンタは次以降のサンプルにあるようにGPSETを1回実行するごとにインクリメントを行えば良いだけなので何も難しく考える必要はありません。

◎文字列を記録する方法

 数値ではなく文字列をセーブする場合はASCで数値化、数値を文字にするためにはCHR$でできるため問題ありません。文字列を記録する場合には使用していない0〜255のうち使用していないものを終了コードに割り当てると良いでしょう。これによってデータの区切りが分かるようになり、セーブデータを元に戻すことが可能になります。
 例えばM$(0)〜M$(99)に文字列が記録されており区切りコードを&H0Dに設定した場合は下記のようにできます。

《 リスト12 》 セーブ
GPAGE 1
A=0
FOR I=0 TO 99
 FOR J=0 TO LEN (M$(I))-1
  B=ASC (MID$ (M$(I),J,1))
  GPSET A%256,A/256,B
  A=A+1
 NEXT
 GPSET A%256,A/256,&H0D
 A=A+1
NEXT
SAVE "GRP1:SAVEDATA"

《 リスト13 》 ロード
GPAGE 1
A=0
LOAD "GRP1:SAVEDATA"
FOR I=0 TO 99
 M$(I)=""
 @LOOP
  B=GSPOIT (A%256,A/256)
  A=A+1
  IF B!=&H0D THEN M$(I)=M$(I)+CHR$ (B):GOTO @LOOP
NEXT

 あくまで文字列をセーブデータとして保存するだけであり、GRPは一時的に使用しているだけですが、ゲーム内で使用するメッセージを予めGRPに保存してそれを配布するという方法をとれば文字列変数に確保可能な256文字制限を超えることも可能です。
 セーブデータであればシーケンシャルアクセスで問題なかったのですが、ゲーム内で使用されるメッセージの順番は固定ではないためランダムアクセスができるようにどこに文字列を埋め込んだのかを示したテーブルを用意しなくてはなりません。
 しかし、これによって1枚のGRPで区切りコードを含めて49152文字分記録可能になるだけではなくRPGやアドベンチャーゲームなどではBASICのプログラムリストを見てストーリーがばれてしまうという問題も緩和できます。もっとも、DATA文に生のデータを入れるのではなく文字コードをずらしていくという簡易的な暗号化によってある程度改善可能なのですが、それでもDATAでは使えない文字種があるためそれを避ける必要があります。しかし、GRPではそのようなことを考える必要はありません。  実際にどのようにするのかという詳細は今回のセーブデータというテーマの範疇から外れるため割愛させて頂きます。要望があればそのうち「ゲームデータの作成方法」という感じの内容で書きたいと思います。

◎リプレイデータを記録する方法

 GRPではたくさんのデータを記録できるためゲーム内で1フレームごとに押したボタンの情報をGRPに書き込んでいけばゲームのリプレイデータの記録や再生をすることも可能になります。変数Bにゲーム内で使用しているBUTTON関数の値が入っている場合は下記のようにすれば記録、再生ができます。

《 リスト14 》 リプレイ記録
@RPREC
 GPAGE 1
 GPSET 0,0,A/256
 GPSET 1,0,A%256
 GPSET A%256,A/256,B%256
 A=A+1
 GPAGE 0
 RETURN

《 リスト15 》 リプレイ再生
@RPPLAY
 GPAGE 1
 IF A>GSPOIT (0,0)*256+GSPOIT (1,0)THEN @RPEND
 B=GSPOIT (A%256,A/256)
 A=A+1
@RPEND
 GPAGE 0
 RETURN

 このプログラムはGPAGE 0をゲーム中で使用していてGPAGE 1は未使用という場合においてその未使用のページにリプレイデータを記録していくというものです。使用する場合には初期設定でA=2を入れておいてください。(ゲーム中で変数Aを使用している場合はカウンタの変数をAから変えてください)
 ゲームのメインルーチン内でB=BUTTON()を使用している直後にGOSUB @RPRECを加えるだけで自動的にリプレイデータが記録されていきます。ただし、1フレームあたり0〜255の値で記録しているためゲーム内で使用するのは十字ボタンとABXYボタンのゲームに限ります(不意にそれ以外のボタンを押してもエラーにならないようにB%256としている)
 リスト14を見てのように変数Bの値を書き込んでいる3番目のGPSETに加えて1番目と2番目のGPSET(座標0,0と1,0)では随時カウンタの値も書き込んでいます。1フレームあたりGPSETを3つ実行しているため若干重いですが、それでも処理落ちギリギリのゲームでない限りは全く問題はないし、途中でBREAKしてもCLEAR命令によってカウンタ(変数Aの値)が消去されても記録しているGRPデータが残っている限りはリプレイデータから読み取ることが可能というメリットがあります。。
 リプレイデータを再生する時は先ほどのGOSUB @RPRECからGOSUB @RPPLAYへと変更するだけです。

 このルーチンはあくまでGRPへの保存とそこからの読み出し部分のみなので(記録しているページの)GCLSを行ったりACLSを行ったりしたら消えてしまうため保存の必要性がある場合は別途SAVEやLOADによって保存してください。
 49150バイト記録できるため60fps(VSYNC 1)で動作しているゲームの場合は819秒(13分39秒)記録することが可能です。ただし、ゲームでRND関数が使用されている場合はこのリプレイでは再現できません。プチコンのRND関数はシードを与えることができず、再現性がないためユーザーの手によって疑似乱数ルーチンを用意する必要があります(そのプレイ時にRND関数で生成した数値をすべて記録しておけば再現は可能だけど乱数1つ当たり数バイト必要なのに加えて乱数を使った箇所すべてにおいてIF文で条件判断をして再生ルーチンにジャンプさせる必要があるため修正点が非常に多くなってしまう)RND関数代わりに使える疑似乱数ルーチンを作りましたので良かったら活用してください。(自作プログラムへの自由な組み込みOK)
 ボタンは押した状態や離した状態が数フレーム続く場合が多いためランレングス圧縮が有用です。上手く圧縮できればGRP1枚にかなり長時間(1時間以上)のリプレイデータの保存が可能です。(保存する際にBUTTON関数の値とその値が維持されているフレーム数の2バイトでワンセットで記録すれば良いけど256フレーム超連続で同じBOTTON関数の値となる場合にはデータを複数に分ける必要がある)

 上下画面をグラフィックで使用しているためリプレイデータを記録できないという場合でもmkIIだったら裏画面に記録する(描画画面と表示画面を変えることができる)ことで実現が可能です。例えば私の作品である「JUMPING ISLAND」にリプレイデータの記録を組み込む方法を書いていきます。(ただし、今回はウリであるタッチパネルの操作には対応させず、ボタン操作のみ対応させている)
 まずは、下記のリスト16を入力し適当なファイル名で保存してください。(リスト16は汎用的に使えるので別途保存していると便利です)

《 リスト16 》 リプレイ記録・再生ルーチン 【mkII専用】
@RPREC
 GPAGE 1,3,1
 GPSET 0,0,_A/256
 GPSET 1,0,_A%256
 GPSET _A%256,_A/256,B%256
 _A=_A+1
 GPAGE 0
 RETURN

@RPPLAY
 GPAGE 1,3,1
 IF _A>GSPOIT (0,0)*256+GSPOIT (1,0)THEN @RPEND
 B=GSPOIT (_A%256,_A/256)
 _A=_A+1
@RPEND
 GPAGE 0
 RETURN

 次にQRコード経由などで入力した「JUMPING ISLAND」のリストをLOADしてリスト16をAPPENDしてください。そして、以下の変更点を行ってください。

《 「JUMPING ISLAND」のリプレイ記録・再生 改造方法 》

4行 ACLSCLS:SPCLR:VISIBLE ,,,,1,1に変える
29行の末尾に_A=2を加える

リプレイ記録時 41行のB=BUTTON()の直後にGOSUB @RPRECを加える
リプレイ再生時 41行のB=BUTTON()の直後にGOSUB @RPPLAYを加える

 ※リプレイ記録を行っても処理落ちはしないので常時リプレイ記録状態にしても問題ありません。
  再生する必要がある場合のみ再生用に変更すると良いです。

 いちいちリプレイの記録と再生を繰り返すごとにリストを書き換えるのが面倒ならばメニュー画面(タイトル画面)からリプレイ再生を選べるようにしてIF文によってGOSUB @RPRECGOSUB @RPPLAYを分岐するだけでいいので簡単です。また、GRPに記録したリプレイデータは消えてしまう(というか、1回実行するごとに上書きしている)ため常時記録用とハイスコア記録用に分けてハイスコアが出たら自動的にハイスコア用の領域にコピーするようにすると良いでしょう。(mkIIならばGSPOITで1ドットずつ読み出さなくてもGCOPYで一発コピーできる)
 またこのリプレイデータはステージごとに分かれておらず別のステージでプレイしても上書きされてしまいます。このゲームは1ステージあたり960フレーム分記録できれば良いので1つのGRPデータにステージ1〜ステージ9まで別々に記録することが可能です。ハイスコア用と常時記録用に分けて合計10個のリプレイデータをGRP上に記録するようにするのがベストでしょう。
 リスト16のリプレイ記録、再生ルーチンはボタン操作のみの対応でタッチパネルの操作には対応されていませんが、これは押したボタンとともにタッチされたX座標、Y座標も記録する(つまり、カウンタを別にして1フレームあたり3バイト分記録する)だけで解決できます。「画面タッチされているか否か」も記録する必要があるのですが、タッチされてない時はX座標、Y座標の値をそれぞれ255で記録しておけば再生時にX座標、Y座標の値が両方255ならばタッチされてないと判断することが可能になります。
 下記リスト17にタッチパネル対応版を用意しましたので良かったら活用ください。(変数TにTCHSTの値、変数TXにTCHXの値、変数TYにTCHYの値が入っている場合はそのまま活用できますが、変数名が異なるときは変えてください)

《 リスト17 》 リプレイ記録・再生ルーチン(タッチパネル対応版) 【mkII専用】
@RPREC
 GPAGE 1,3,1
 IF !T THEN TX=255:TY=255
 GPSET 0,0,_A/256
 GPSET 1,0,_A%256
 GPSET _A%256,_A/256,B%256
 GPSET (_A+1)%256,(_A+1)/256,TX
 GPSET (_A+2)%256,(_A+2)/256,TY
 _A=_A+3
 GPAGE 0
 RETURN

@RPPLAY
 GPAGE 1,3,1
 IF _A>GSPOIT (0,0)*256+GSPOIT (1,0)THEN @RPEND
 B =GSPOIT (_A%256,_A/256)
 TX=GSPOIT ((_A+1)%256,(_A+1)/256)
 TY=GSPOIT ((_A+2)%256,(_A+2)/256)
 _A=_A+3
 T=(TX+TY<510)
@RPEND
 GPAGE 0
 RETURN

 「JUMPING ISLAND」はリスト短縮をしている関係上このタッチパネル対応のリプレイ記録・再生ルーチンを組み込む場合は変更箇所がかなり多くなってしまっています。実際に組み込んだリプレイ対応バージョンを用意しましたのでそれを参考にしてみてください。


 最後になりましたが、GRPはプチコン内部に1つ保存するだけで48KB(49152バイト+ヘッダ12バイト=49164バイト)も必要になってきます。そのためGRPとMEM$は上手く使い分けるのがベターでしょう。MEM$で保存されたデータは実際は528バイトもあるのだけどそれは1文字が2バイトで記録されているためであり、これは初代プチコンが2バイト文字で保存していたためmkIIでは記録されたデータの互換性を保つためだと思われます。
 GRPとMEM$の間のサイズ記録できるものがあると便利なのでその間にあるCHRリソース(スプライトであるSPU、BGキャラであるBGU)に保存することも考える人がいるかもしれません。SPUは8つに分割されており、1つあたり8192バイトとなっているためヘッダ込みで8204バイトで済みGRPよりも保存のための容量は少なくて済みます。

 しかし、1バイト単位で記録できるGRPとは異なりSPUは1つのキャラデータ単位でしか保存できません。8x8ドットのキャラが最小単位となっているため16進数64文字分(=32バイト)が最小単位となります。そのためあまり使い勝手はよくありません。
 というわけで、8KB以下のGRPリソースのセーブデータをSPUなどのCHRリソースに変換するプログラム「CHRセーバー」「CHRローダー」を作ったのでこれを使えば手軽にCHRリソースをセーブデータとして活用できます。CHRをリプレイデータの記録に使用する場合は1つにつき2分16秒分記録でき、しかも、GRP1つ分の保存領域に6つ分記録できるのでお得です。(データ圧縮を行えば1つのCHRリソースに数10分のリプレイデータを記録することも可能)
 mkIIは保存領域が少なくなっている人も多いためMEMでは足らないようなセーブデータにはGRPではなくCHRを使うとプレイしてくれる人が増えると思います。(むやみにGRPリソースを使わないのはプレイしてくれる人への配慮になる)

 プチコン内部に保存する場合には無圧縮ですからGRPとSPUの差は積み重なれば大きくなってきます。内部保存領域の最小単位は私の検証では4KBと推測されており、3つ以上のMEMリソースに分割するくらいならば1つのSPUに保存した方が内部保存領域を圧迫せずに済みます。したがって、GRP、SPU、MEMの使い分けが重要となってきます。しかし、QRコードはzip圧縮がかかっているためGRPも使用している部分が少なければQRコードの枚数はそれほど増えません。実際、リスト8で保存したセーブデータはプチコン内部で49164バイト、PTCに書き出した時点で49200バイトあるにも関わらずQRコード1枚で収まりました。


RETURN/RETURN *MAIN

inserted by FC2 system