[NES] Prince of Persiaのレベルエディタを作成しています。 第4章 彼は走っている! またはクローゼットの中のスケルトン

第1章第2章第3 第4 第5章エピローグ



免責事項


ゲームを編集している場合、その詳細をすべて揃える必要があります。 元のバージョンの断片が出て、私たちが入れた新しい意味と矛盾する場合、ゲームのバージョンを世界に見せることは愚かでしょう。 ゲームレベルの編集では、開発者が最初に投資したプロットのひねりを変更することはできません。



少し遅れて、プロットを修正します。





何を編集しますか?


先に進む前に、ゲームを再描画するように制限したいと思います。 実際、すべてを変更したい場合は、最初からゲームを書く方が簡単です。 しかし、当初の計画どおり、私はまだ古いベースを維持しながら、何か新しいことをしたいと思っています。 編集するものを決定します。



ゲームの精神を維持するために、ブロックのテキスト、音楽、タイプ、構成を元の形のままにすることにしました。 すべてのオブジェクトはかなり単純なデータへのポインターの配列に関連付けられているため、これは他のすべてと同じように簡単に変更できます。



ところで、私はその場所にマウスを置いたままにしました。



コード


ゲームの動的な要素は、以前のように単純なつぶしで簡単に変更することはできません。 したがって、コードを積極的に研究するときが来ました。 私たちはその要素のいくつかに精通しました。 それでは、ゲーム中に何が起こるか見てみましょう。



デバッガーで「ステップオーバー」ボタンを押すと、必然的にメインループに移動します。メインループのメインコンポーネントは次のようになります。

label_CC1A: $CC1A:20 F7 F2 JSR $F2F7 $CC1D:20 10 D0 JSR $D010 $CC20:20 1E 86 JSR $861E $CC23:20 00 CB JSR $CB00 $CC26:20 E8 A4 JSR $A4E8 $CC29:20 10 D0 JSR $D010 $CC2C:20 04 8B JSR $8B04 $CC2F:20 10 D0 JSR $D010 $CC32:20 F9 80 JSR $80F9 $CC35:20 FC D8 JSR $D8FC $CC38:20 DF BA JSR $BADF $CC3B:20 00 CB JSR $CB00 $CC3E:20 12 9F JSR $9F12 $CC41:20 DD A3 JSR $A3DD $CC44:4C 1A CC JMP $CC1A
      
      





このループの上には、部屋またはレベル間を移動するときに呼び出される多くのプロシージャの呼び出しもあります。 上記のコードにリストされている手順は、ゲームの状態を変更し、さまざまな種類のチェックも実行します。 これらのうち、以下は重要ではありません。

-$ F2F7-VBlank割り込みの特定の状態の変化を待っています。

-$ D010、$ CB00-対応する銀行にある手続きを呼び出す前に銀行を切り替える。



残りのすべてのプロシージャのコードは提供しません。すべてのブランチを考慮すると、さらに10個のポストが作成されるためです。 今興味のあるものだけをあげます。 私は擬似コードに翻訳しませんが、コメントをします。



A4E8。 彼は走っている!


このプロシージャを呼び出す前に、バンク#02-$ D010をオンにするプロシージャが呼び出されます。 セル$ FFF2に番号#02を書き込むことでこのバンクをオンにし、次の手順に進みます。

 $A4E8:AD E4 04 LDA $04E4 = #$01 $A4EB:D0 13 BNE $A500 ;;    $04E4. ;;   0,      $A4ED:60 RTS ;; ...  $A4EE:4C FF A4 JMP $A4FF $A4F1:A9 00 LDA #$00 $A4F3:8D E3 04 STA $04E3 = #$44 $A4F6:8D E0 04 STA $04E0 = #$76 $A4F9:8D E4 06 STA $06E4 = #$0A $A4FC:4C 14 DC JMP $DC14 ;;         ;; ,       -  . $A4FF:60 RTS ;;      $DC14. ;;             label_A500: $A500:AD 0A 04 LDA $040A = #$01 $A503:29 30 AND #$30 $A505:D0 EA BNE $A4F1 ;;       , ;;        $A507:AE E3 04 LDX $04E3 = #$44 ;;        $04E3 $A50A:BD 0F A7 LDA $A70F,X @ $A763 = #$AA ;;    ROM- ,     $04E3 $A50D:C9 FF CMP #$FF $A50F:F0 E0 BEQ $A4F1 ;;      #FF, ;;        $A511:CD E0 04 CMP $04E0 = #$76 ;;        $04E0 $A514:D0 0E BNE $A524 ;;   , ... (. ) $A516:A9 00 LDA #$00 ;;    ... $A518:8D F4 04 STA $04F4 = #$00 $A51B:8D E0 04 STA $04E0 = #$76 $A51E:EE E3 04 INC $04E3 = #$44 ;;      2 ( ) $A521:EE E3 04 INC $04E3 = #$44 label_A524: $A524:BD 10 A7 LDA $A710,X @ $A764 = #$01 $A527:8D 0A 04 STA $040A = #$01 ;;      ROM-  $040A $A52A:EE E0 04 INC $04E0 = #$76 ;;    $A52D:60 RTS ;; 
      
      







したがって、ここには、いくつかの状態を持ついくつかのセル、インデックスを持つ別のセル、およびROMファイル内の特定の配列があります。 このコードは、フラグがセル$ 04E4に設定されている場合にのみ実行されることを示しています。 それを入れてみましょう。



ゲームを再開し、最初のレベルに入り、ユニットを$ 04E4に入れます。





2回目が通過し、...彼は自分で走ります ボタンを押す必要はありません。

デモのプレイを待つと、セル$ 04E4にすでに1つあることがわかります。デモ中にこのセルをリセットすると、デモのプレイが消え、コントロールが手に渡されます。 非常に便利です:デモはメインパートを渡し、その後、$ 04E4をリセットして自分でゲームを続行できます。



これで、ゲームに基づいて、コードを解析できます。 明らかに、$ DC14プロシージャにより、ゲームメニューが表示されます。 $ A70F配列には、それぞれ2バイトの構造体がいくつか格納され、#FFトークンで終わります。 さらに、最初のバイトは一定の時間間隔であり、その終わりにインデックスが増加し、次の構造の2番目のバイトがセル$ 040Aに転送されます。 彼はどういう意味ですか?

配列を見てみましょう:

64 00 . A0 01 . 28 00 . E6 84 . E6 02 ... FF





まず、#64 cuを期待します (従来の時間単位)、#A0 U.V.E。、#28などのように#FFマーカーまで。 最初のバイトペアの時間を#FEに変更すると、デモプレイでは、開始後長時間キャラクターがアイドル状態になっていることがわかります。 そこに#02を置くと、彼は左に曲がり、しばらくの間、壁にくり抜かれます。 したがって、2番目のバイトは、指定された時間間隔中に実行するアクションを記述します。 ゲームが決定されているため、特定のアクションのシーケンスを厳密に設定すれば十分であり、それ自体が「プレイ」されます。

実際、2番目のバイトはコントローラーのボタンを押すことの模倣であり、最初のビットはボタンを右にエンコードし、2番目のビットは左のボタンをエンコードします。 これらのビットの組み合わせにより、デモプレイ中のゲームパッドの擬似状態が決まります。 この配列を編集することにより、デモプレイを新しいレベルに調整できます。

セル$ 040Aの値で次に何が起こるかを追跡すると、$ 060Eにあるデータ構造に到達します。 次のように説明されています。

 struct CHARACTER { char bCharType; unsigned short X; unsigned short Y; unsigned short ptrAction; char bDirection; char bActionIndex; char bPoseIndex; char bReserved[4]; };
      
      





この構造に到達すると、特定の構造へのポインターで構成される「アクション」の配列があり、それが画面上のスプライトディスプレイのシーケンスに展開されることがわかります。 「アクション」とは、キャラクターが画面上で実行することを意味します。実行、ジャンプ、しゃがみ、その他のアクションです。 同様に、「ポーズ」インデックスを持つすべて:彼が立っているか座っているかは、構造のこのメンバーによって決定されます。 将来的にはこの構造が必要になりますが、今のところ、レベルからレベルへの移行方法を見つける必要があります。



迷路を駆け抜ける


ここで、以前と同様に、開始点-セル$ 70があります。 レベルからレベルに移動すると、コードが表示されます(以前と同様に、セル$ 70に書き込む条件によってブレークポイントでキャッチします)。

 $86F3:AD 35 07 LDA $0735 = #$00 $86F6:D0 0D BNE $8705 $86F8:A5 70 LDA $0070 = #$00 $86FA:18 CLC $86FB:69 01 ADC #$01 $86FD:C9 0E CMP #$0E $86FF:90 02 BCC $8703 $8701:A9 00 LDA #$00 $8703:85 70 STA $0070 = #$00 ;; <<<  $8705:A9 00 LDA #$00 $8707:8D 01 20 STA $2001 = #$18 $870A:85 15 STA $0015 = #$18 $870C:4C 1D CB JMP $CB1D
      
      





ここで、ユニットがセル$ 70に追加され、最後のバンクに移動することがわかります。 さらに、チェックがあります。数値が13より大きい場合、この値をゼロにします。 つまり、出力をレベル14にすると、最初に戻ります。 これは、次のレベルに移行するための開始手順です。 次に、パスワードが表示され、次のレベルに進みます。 それがどこから呼び出されたのか見てみましょう。



ハードコード


x86アーキテクチャの場合と同様に、ここでは、JSR命令の引数を命令ポインターレジスタにプッシュし、リターンアドレスをスタックにプッシュすることにより、プロシージャが呼び出されます。 スタックには次のものがあります。



22, CC, ...





したがって、$ CC22の命令に先行する命令は次のように呼び出します。

 $CC20:20 1E 86 JSR $861E
      
      





かさばり、すべてをトレースするのは面白くありません。 ブレークポイントを$ 70での書き込みから読み取りに切り替えて、デバッグを開始します。 最初に、おなじみの$ C0D5プロシージャで停止します-それは私たちにとって興味深いものではありませんが、次の停止でここに来ます:

 $D0B8:A5 70 LDA $0070 = #$0B $D0BA:C9 0B CMP #$0B $D0BC:D0 0C BNE $D0CA $D0BE:A5 51 LDA $0051 = #$16 $D0C0:C9 16 CMP #$16 $D0C2:D0 06 BNE $D0CA $D0C4:20 10 D0 JSR $D010 $D0C7:4C F3 86 JMP $86F3
      
      





セル内の値は、番号#0B == 11(レベルは最初から番号付けされていることを思い出します)と比較され、次にセル$ 51から読み取ります。ここで、#16 = 22になります。レベルマップを見ると)、51ドルで、最後に検索しなかった部屋番号があります。 これらのチェックが実行されると、アドレス$ 86F3、つまり移行を開始する手順に進みます。 キャラクターが部屋を左に出ると、開発者はこの部屋から別のレベルに移動するためにコードを簡単に入力しました。

キャラクターが奈落の底に落ちたときに、6番目のレベルから7番目のレベルへの遷移を探す場合、同様のテストを見つけることができます。 つまり、CMP命令を編集するだけで十分であり、別の部屋にはこれらのプロパティがあります。 ちょうど条件が満たされる必要があります。次のレベルへの移行は、左(このチェックの場合)または転倒(2番目の場合)に行われます。



新しいレベルの起動コードを検討した結果、最初のレベルでは、キャラクターの座標もコードにハードコーディングされた値で上書きされるため、調査の開始時にレベルヘッダーを変更したときに移動できないことがわかります。 レベル13でのガードの敗北後、同様のチェックが実行され、道を開けることができます。 それらはすべて、同様の方法で非常に簡単に見つかります。



スケルトンをキャビネットから取り出し、壁をペイントします


エディターはほぼ完成です。 レベルからレベルへ、または部屋から部屋への移行モーメントを調べると、編集可能な残りのパラメーターが見つかります。 このようなデータを検索する方法はすでに検討されているため、ここでは詳しく説明しません。 一般的な説明のみを行い、最後の章の最後に、ゲームの構造とその説明に関するROMの変位のリストを示します。



0x13BEB: 00 00 24 00 00 00 00 00 00 00 00 24 00 00



ガードが描画されるタイルのセットのコードを含む配列(ガード自体またはスケルトン)。 この構造は、レベルのタイプを説明する構造に似ており、レベルの正常性の量を決定する構造に似ています。



パレットは、ポインタの2つの配列とパレット自体の実際の配列から構築されます。 パレット自体は、32バイトのシーケンスとしてPPUに送信される形式で正確に保存されます。 2番目の配列には7つのポインターしか含まれていないため、レベルに受け入れられるパレットが表示され、最初のポインターは6番目になります。 そして、最初の配列は(レベルの数に応じて)14個のポインターで構成され、それぞれが2番目の配列の1つまたは別の要素につながります。



ほぼ準備完了




この段階で、エディターのサンプルシェルを既にスケッチしました。細部は1つだけでした。 後で、ステータスバーのマジックナンバーの意味を説明します。



「リフレクション」と呼ばれる最後の5番目の章では、王子の反射を制御する方法を学びます。 ゲームのコースに実質的に影響を与えなかった二次的な要素から、それを主要なものの1つに変えることができます。



All Articles