今回は、レジェンド オブ ブラッド ゲームの JS 模倣 (コード付き) を紹介します。レジェンド オブ ブラッド ゲームの JS 模倣の 注意事項 は何ですか? 以下は実際的なケースです。
ゲームの最初のバージョンは 2014 年に開発されました。ブラウザは html+css+js を使用し、サーバーは asp+php を使用し、通信は ajax を使用し、データ ストレージは access+mySql を使用します。ただし、いくつかの問題がありました (当時はノードの使い方を知らなかったので、ASP で複雑なロジックを記述するのは非常に困難でした。当時は Canvas 上での記述がほとんどなく、DOM レンダリングは簡単にパフォーマンスのボトルネックに達する可能性がありました)。 、放棄されました。その後、キャンバスを使用したバージョンがリメイクされました。この記事は 2018 年に書かれたものです。
1.開発前の準備
比較的複雑な PC ゲームを実装するのに Javascript を使用する理由
1. PC側のオンラインゲームをjsで実装することが可能です。 PC や携帯電話のハードウェア構成のアップグレード、ブラウザの更新、さまざまな H5 ライブラリの開発により、js でオンライン ゲームを実装することはますます困難になってきています。ここでの難しさは主に 2 つの側面にあります。1 つはブラウザのパフォーマンス、もう 1 つは非常に複雑なロジックを含むゲームの反復に耐えられるほど JS コードを拡張できるかどうかです。
2. 現在のjsゲームの中には大規模なものが少ないので参考にしてください。マルチプレイヤー接続、サーバー側のデータ ストレージ、複雑なインタラクションを伴うほとんど (ほぼすべて) のゲームは、Flash を使用して開発されています。しかし、flash は結局衰退しつつありますが、js は急速に発展しており、ブラウザさえあれば実行できます。
2001 年の伝説的なゲームを選んだ理由
もちろん、最初の理由は古いゲームへの思い入れです。もう 1 つのより重要な理由は、他のゲームのプレイ方法を知らないか、プレイ方法は知っているが素材 (画像、サウンド) を持っていないことです。効果など)。ゲームのマップ、キャラクターやモンスターのモデル、アイテムや装備の図を収集し、それらを処理して解析してから js 開発に使用するのに多大な労力を費やすのは時間の無駄だと思います。
以前にレジェンド ゲームのマテリアルをいくつか収集し、幸運にもレジェンド オブ レジェンド クライアント リソース ファイル (github アドレス) を抽出する方法を見つけたので、コードを直接書き始めることができ、準備時間をいくらか節約できます。
起こり得る困難
1. ブラウザのパフォーマンス: これが最も難しい点です。ゲームが 40 フレームを維持したい場合、js が計算するために各フレームに残る時間は 25 ミリ秒のみです。また、レンダリングは通常、計算よりも多くのパフォーマンスを消費するため、js に残される実際の時間はわずか約 10 ミリ秒です。
2. 不正行為対策: ユーザーがインターフェイスを直接呼び出したり、ネットワーク要求データを改ざんしたりすることを防ぐにはどうすればよいですか?目標は、js を使用してより複雑なゲームを実装することであり、オンライン ゲームではこれを考慮する必要があるため、比較的成熟したソリューションが必要です。これはこの記事の焦点ではありません。
2.全体のデザイン
ブラウザ側
画面描画にはキャンバスを使用します。
dom(p)+css と比較して、canvas はより複雑なシーンのレンダリングとイベント管理を処理できます。たとえば、次のシーンには、プレイヤー、動物、地面にあるアイテム、および一番下のマップの画像の 4 つの画像が含まれています。 (実際には、地面の影、文字、動物、物体をマウスでポイントしたときに表示される対応する名前、地面の影もあります。読みやすさのため、内容についてはあまり考慮しません。 )
このとき、「動物をクリックすると動物を攻撃、アイテムをクリックするとアイテムを拾う」という効果を実現したい場合は、動物とアイテムのイベントを監視する必要があります。 dom メソッドを使用する場合、いくつかの難しい問題に対処する必要があります:
a. レンダリングの順序は イベント処理 の順序とは異なり (z-index が小さい場合は、最初にイベントを処理する必要がある場合があります)、追加の処理が必要です。例えば上記の例では、モンスターやアイテムをクリックする際にキャラクターをクリックしやすいため、キャラクターに対して「クリックイベント貫通」処理を行う必要があります。さらに、イベント処理の順序は固定されていません。キャラクターの解放を必要とするスキル (ゲーム内の治療など) を持っている場合、この時点でキャラクターはイベント監視を行う必要があります。したがって、要素がイベントを処理する必要があるかどうか、およびイベントが処理される順序はゲームの状態によって異なります。 DOM のイベント バインディングはニーズを満たすことができなくなりました。
b. プレーヤーのモデル、プレーヤーの名前、プレーヤーのスキル効果など、関連する要素を同じ dom ノード に配置するのは困難です。これらは
に配置する必要があります。 または コンテナ内では管理が簡単です (このようにして、複数の要素の位置を親要素から継承でき、位置を個別に扱う必要がありません)。しかし、この方法では、z-index を扱うのが難しくなります。たとえば、プレーヤー A がプレーヤー B の上にある場合、A は B によって隠されます。 したがって、A の z-index を小さくする必要がありますが、プレーヤー A の名前が B の名前や影によって隠されてはなりませんが、これは達成できません。 。簡単に言えば、 DOM 構造の保守性は画面表示の効果を犠牲にし、その逆も同様です。
c. パフォーマンスの問題。たとえ効果が犠牲になったとしても、レンダリングに DOM を使用すると、必然的に多くのネストされた関係が生じ、すべての要素のスタイルが頻繁に変更され、ブラウザーの再描画やリフローが継続的にトリガーされます。
キャンバスレンダリングロジックとプロジェクトロジックの分離
Canvas のさまざまなレンダリング操作 (drawImage、fillText など) など) がプロジェクト コードと一緒に配置されるため、後でプロジェクトを保守できなくなることは避けられません。いくつかの既存のキャンバス ライブラリを検討し、vue のデータ バインディング+ デバッグ ツールと組み合わせて、新しいキャンバス ライブラリ Easycanvas (github アドレス) を作成しました。vue と同様に、プラグイン Elements を介してキャンバスのデバッグをサポートします。
この方法では、ゲーム全体のレンダリング部分がはるかに簡単になります。必要なのは、ゲームの現在の状態を管理し、サーバーによってソケットから返されたデータに基づいてデータを更新することだけです。 「データの変更によるビューの変更」は Easycanvas の責任です 。たとえば、下の図のアイテムをラッピングするプレーヤーの実装では、ラッピング コンテナの位置とバックパック内の各要素の配置規則を指定するだけで、ラップされた各アイテムを配列にバインドします。この配列を管理します。はい (Easycanvas は、データを画面にマッピングするプロセスを担当します)。
たとえば、5 行 8 列の合計 40 個のアイテムのスタイルは、次の形式で Easycanvas に渡すことができます (インデックスはアイテムのインデックス、アイテムの x 方向の間隔は 36、y 方向の間隔は 32) )。そして、このロジックは、項目の配列がどのように変更されたり、パッケージがどこにドラッグされたりしても、各項目の相対位置は固定されます。 Canvas 上でのレンダリングに関しては、プロジェクト自体を考慮する必要がないため、メンテナンス性が向上します。
りーキャンバスレイヤードレンダリング
仮定: ゲームは 40 フレームを維持する必要があり、ブラウザは幅 800、高さ 600、面積 480,000 (以下、画面領域として 480,000 と呼びます) です。
同じキャンバスを使用してレンダリングする場合、このキャンバスのフレーム番号は 40 で、1 秒あたり少なくとも 40 の画面領域を描画する必要があります。ただし、複数の要素が同じ座標点で重なっている可能性があります。たとえば、下部の UI、ヘルス バー、ボタンが重なっており、それらが結合してシーン マップをブロックしています。したがって、これらを合計すると、ブラウザの 1 秒あたりの描画量は、ゆうに 100 画面領域を超える可能性があります。
この描画は、キャンバス全体のあらゆる場所でビューが更新されるため、最適化するのが困難です。それは、プレイヤーや動物の動き、ボタンの特殊効果、特定のスキルの効果の変化である可能性があります。この場合、プレイヤーが動かなくても、服が「風になびく」効果(実際にはスプライトアニメーションが次の絵に再生されます)やポーションの瓶が現れるなどの影響でキャンバス全体が再描画されます。地面。なぜなら ゲームの特定のフレームが前のフレームと区別できないことは、ゲーム画面の一部であっても変化しないことはほとんど不可能です。ゲーム画面全体が常に更新されます。
なぜなら、ゲームの特定のフレームが前のフレームと区別できないことはほとんど不可能であり、画面は常に更新されるからです。
そこで今回は3枚のキャンバスを重ねる配置を採用しました。 Easycanvasのイベント処理は配信に対応しているため、先頭のキャンバスがクリックされても、クリックを終了する要素がなければ後続のキャンバスもイベントを受け取ることができます。 3 つのキャンバスは、UI、地面 (マップ)、エルフ (キャラクター、動物、スキルの特殊効果など) を担当します:
この階層化の利点は、レイヤーごとの最大フレーム数を必要に応じて調整できることです。
例えば UI レイヤーですが、多くの UI は通常動かず、動いたとしてもあまり精密な描画を必要としないため、フレーム数は適切に、たとえば 20 フレームに減らすことができます。このようにして、プレイヤーの体力が 100 から 20 に減少した場合、ビューは 50 ミリ秒以内に更新され、50 ミリ秒の切り替えはプレイヤーには感じられません。体力とかあるから UI層のデータは短時間に複数回連続して変化することが難しく、50msの遅延は人間が認識しにくいため、頻繁な描画は必要ありません。 。 1 秒あたり 20 フレームを保存すると、おそらく 10 画面領域の描画を保存できるでしょう。
もう 1 つの例は、プレイヤーが移動したときにのみマップが変化する地面です。このようにして、プレーヤーが動いていない場合、フレームごとに 1 つの画面領域を保存できます。プレーヤーが移動する際の滑らかさを確保する必要があるため、地上での最大フレーム レートは低すぎてはなりません。地上フレームが 30 フレームの場合、プレイヤーが静止している場合、1 秒あたり 30 の画面領域を保存できます (このプロジェクトでは、マップはほぼ画面いっぱいに描画されます)。また、他のプレイヤーや動物の動きによって地面が変化することはなく、地面レイヤーを再描画する必要もありません。
スプライトレイヤーの最大フレームレートを下げることはできません。このレイヤーはキャラクターの動きなどゲームの核となる部分を表示するため、最大フレームレートを40に設定します。 このように、1 秒あたりに描画される領域は、プレーヤーが動いているときは 80 ~ 100 画面領域になりますが、プレーヤーが動いていないときは 50 画面領域しかありません。ゲームでは、プレイヤーは立ち止まったままモンスターと戦い、入力し、アイテムを整理し、スキルを解放するため、長時間にわたって地面の描画がトリガーされず、パフォーマンスが大幅に節約されます。
サーバー側
目標は js を使用してマルチプレイヤー オンライン ゲームを実装することであるため、サーバーはノードとソケットを使用してブラウザと通信します。このもう 1 つの利点は、マップ上の特定の座標点に障害物があるかどうかを判断するなど、一部の共通ロジックを両端で再利用できることです。
Node 側のプレーヤーやシーンなどのゲーム関連データはすべてメモリに保存され、定期的にファイルと同期されます。 Node サービスが開始されるたびに、データがファイルからメモリに読み取られます。このように、プレーヤーの数が増えると、ファイルの読み取りと書き込みの頻度が急激に増加し、パフォーマンスの問題が発生します。 (その後、安定性を向上させるために、読み取りおよび書き込みプロセス中のサーバーの再起動によって引き起こされるファイルの損傷を避けるために、「メモリ ファイル バックアップ」方式を使用して、ファイルの読み取りおよび書き込み用のバッファが追加されました)。
Node側はインターフェース、データ、インスタンスなど複数の層に分かれています。 「インターフェイス」はブラウザとの対話を担当します。 「データ」とは、薬の名前や効果、モンスターの速さや体力などの静的なデータであり、ゲームルールの一部です。 「インスタンス」とは、例えばあるプレイヤーの薬が「薬データ」のインスタンスです。別の例として、「鹿インスタンス」は属性「現在の血液量」を持ち、鹿 A は 10、鹿 B は 14 で、「鹿」自体は「初期血液量」のみを持ちます。
3. シーンマップの実装
マップシーン
マップ シーンの部分から始めましょう。これはレンダリングに依然として Easycanvas に依存しています。
考える
プレイヤーは常に画面中央に固定されているため、プレイヤーの動きは実質的にマップの動きとなります。たとえば、プレイヤーが左に走ると、マップは右に移動します。先ほども述べたように、プレイヤーは 3 つのキャンバスの中間層にいて、マップは最下層に属しているため、プレイヤーはマップをブロックする必要があります。
これは合理的なように思えますが、マップ内に木がある場合、「プレイヤーのレベルは常に木よりも高い」は間違いです。現時点では、2 つの大きな解決策があります:
地図は階層化されており、「地上」と「地上」が分離されています。たとえば、下の図では、左側が地面に、右側が地面に、プレイヤーを 2 つのレイヤーの間に配置し、中央にキャラクターを挟むように重ねて描画します。 これで問題は解決したように見えますが、実際には 2 つの新しい問題が発生します。 1 つ目は、プレイヤーが「地上」のもの (木など) によってブロックされる場合があるということです。もう 1 つは、「地上」のものをブロックできる必要がある場合です。 (たとえば、この木の下に立つと、頭が木を遮ってしまいます)。もう 1 つの問題は、レンダリングのパフォーマンス コストが増加することです。プレイヤーは常に変更されるため、「地面」レイヤーは頻繁に再描画する必要があります。これにより、大きな地上地図のレンダリングをできるだけ節約するため、元のデザインが崩れ、キャンバスの階層化がより複雑になります。
地図は階層化されておらず、「地上」と「地上」が一緒に描かれています。プレーヤーが木の後ろにいる場合、以下に示すように、プレーヤーの透明度を 0.5 に設定します。 これには欠点が 1 つだけあります。それは、プレイヤーの体が不透明または半透明のいずれかであり (マップ上を歩くモンスターにもこの効果があります)、完全に現実的ではありません。理想的な効果は、プレイヤーの体の一部が隠れるシーンがあることだからです。ただし、これはパフォーマンスに優れており、コードの保守も簡単です。現在、このソリューションを使用しています。
では、「地図」画像のどの部分が木であるかをどのように判断するのでしょうか?通常、ゲームには大きなマップ記述ファイル (実際には配列) があり、0、1、2 などの数字を使用して、通過できる場所、障害物がある場所、乗り換えポイントなどを識別します。熱血伝説の「説明ファイル」は48×32を最小単位として記述されているため、熱血伝説におけるプレイヤーのアクションは「チェス盤」のような感覚になります。ユニットが小さいほどスムーズになりますが、ユニットが占める体積は大きくなり、この記述を生成するプロセスに時間がかかります。
本題に入りましょう。
達成しました
私は友人に、レジェンド オブ レジェンド クライアントで「ブナ州」のマップをエクスポートするのを手伝ってもらいました。そのサイズは幅 33,600、高さ 22,400 で、私のコンピューターの数百倍です。コンピューターの爆発を防ぐには、コンピューターを複数のチャンクに分割して読み込む必要があります。凡例の最小単位は 48x32 であるため、マップを 480x320 の 4900 (70x70) の画像ファイルに分割します。
この記事の事例を読んだ後は、この方法を習得したと思います。さらに興味深い情報については、php 中国語 Web サイトの他の関連記事に注目してください。
推奨読書:
FileReaderは、写真をアップロードする前にローカルプレビューを実装します
vue-dplayer hls再生を実装する手順の詳細な説明
以上が伝説のゲームの JS 模倣 (コード付き)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。