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