WebGLとasyncioマルチプレイヤーオンラインシューティングゲーム、パート2



この記事では、きれいな戦車モデルをシーンにインポートし、 websocket



asyncio



を使用してプレイヤーとボットを互いに同期させ、部屋間でバランスをとることから、ブラウザーベースの3D



シューティングゲームの作成について説明しようとしました。



はじめに

1.ゲームの構造

2.モデルとブレンダーをインポートする

3. babylon.jsとモデル自体を使用してゲームにモデルをロードする

4. babylon.jsの動き、ミニマップ、およびゲームサウンド

5. Webソケットとゲームの同期

6.プレイヤーとその調整

7.部屋とオブジェクトのPythonによるプレイヤーのバランス調整

8. Asyncioおよびボットの動作生成

9. Nginxおよびソケットプロキシ

10. memcacheを介した非同期キャッシング

11.あとがきとロードマップ



Python3



WebGL



、および単なるゲームの非同期アプリケーションのトピックに興味のある方は、猫をお願いします。



はじめに



この記事自体は、 aiohttpasyncio



を使用して非同期アプリケーションを作成するトピックの続きとして考えられていました。 最初の部分aiohttp( asyncio )



上にdjango



ようなモジュール構造を作成する方法に専念していた場合、 2番目の部分ではもっと創造的なことをやりたいと思いました。



当然のことながら、 3D



おもちゃを作ることは私にとっては面白そうで、ゲーム以外では非同期プログラミングが必要な場合があります。



登録せずに、お互い、ボット、部屋、簡単なチャット、簡単な風景をたたくことができるおもちゃを用意します。 ゲームモデルとして、最もシンプルで馴染みのあるいくつかの戦車と、同時に指標となるゲームモデルを取り上げます。 さて、フレームワークの一部として、 memcached



キャッシュを思い出しましょう。



わかりやすくするために、一部のプロセスは、リモートでインフォグラフィックを連想させる写真で表されます。これは、ネットワークプログラミングが、送信されているものと呼び出されているものを矢印で示している場合、常に簡単で便利に見えるとは限らないためです。

インフォグラフィックと通常の形式の両方のコード例は、一般的な作業スキームの理解を深めるために、非常に頻繁に簡略化されます。 さらに、最新の修正を含む完全なコードはgithubで表示できます。



しかし、これは完全なゲームではないことを理解する必要があります-彼がgit clone



を書いてATMに行ったという意味で。 むしろ、ゲームフレームワーク、1つのボトルでのasyncio



およびwebgl



デモを作成する試みです。 ショーケース、評価、徹底的にテストされたセキュリティなどはありませんが、一方で、 open sourse



開発されたopen sourse



プロジェクトでは、暇なときに十分に機能したようです。



2モデルとブレンダーのインポート



当然、おもちゃにはキャラクターの3D



モデルが必要であり、風景や建物などをシミュレートするモデルが必要です。

キャラクターは、人、戦車、飛行機です。 2つのオプションがあります-モデルを描画し、完成したものをインポートします。 最も簡単な方法は、専門サイトの1つで既製のモデルを見つけることです(たとえば、 hereまたはhere) 。 戦車や他のモデルを描くプロセスに興味がある人、YouTubeにはたくさんのビデオがあり、ハブにはこのトピックに関する資料があります



インポートプロセス自体について詳しく説明します。 blender



にインポートされた形式の中で、 .die .obj .3ds



が最もよく見られます。



インポート/エクスポートには多くのニュアンスがあります。 たとえば、 .3ds



をインポートする場合、原則として、モデルはテクスチャなしでインポートされますが、マテリアルは既に作成されています。 この場合、ディスクテクスチャから各マテリアルをロードするだけです。 .obj



場合、原則として、テクスチャに加えて、 .mtl



ファイルが存在する場合、それが存在する場合、通常は問題の可能性は低くなります。



モデルをシーンにエクスポートした後、 webgl



表示に問題があるという警告とともに、 chrome



クラッシュすることがあります。 この場合、たとえば、衝突、アニメーションなどがある場合、ブレンダーで余分なものをすべて削除するようにしてください。



さらに、最も重要なポイントの1つです。 マップ内を移動するモデルについては、

モデルを構成するすべてのオブジェクトを接着する必要があります。 そうしないと、それらを移動することはできません。視覚的には、タンクの代わりにマシンガンのみが駆動するように見えます。また、ツリーに必要な座標を設定しようとすると、マップ上の位置は木の切り株のみを変更し、他のすべてはマップの中央に残ります。



この問題を解決するには2つの方法があります。

1)モデルのすべての詳細を組み合わせて、1つのオブジェクトにします。 この方法は少し高速ですが、 UV



スキャンの形式で1つのテクスチャがある場合にのみ機能します。 これを行うには、シフト付きのアウトライナを使用してすべてのオブジェクトを選択します。それらは特徴的なオレンジ色で強調表示され、 object



メニューでjoin



アイテムを選択します。





2)次のオプションは、親子の原則に従ってすべての詳細をリンクすることです。 この場合、各ディテールに独自のテクスチャがあったとしても、テクスチャに問題はありません。 これを行うには、右クリックして親オブジェクトと子を順に選択し、 ctrl+P



を押してメニューからobject



を選択します。 その結果、オフラインモードでは、モデルを構成するすべてのオブジェクトが同じ親に属していることがわかります。





それでも、私はそのような発言を挿入したいと思います。多くの場合、いくつかのモデルは、不明な理由により、ブレンダーにインポートされないか、何らかのジャンクの形でインポートされます。 また、非常に多くの場合、モデルに付属している一部のテクスチャは適用したくない場合があります。 そのような場合、何もできません。他のオプションに進む必要があります。



3. babylon.jsとモデル自体を使用してゲームにモデルをロードする



モデル自体のロードは非常に単純に見え、ディスク上のメッシュの場所を示します。



 loader = new BABYLON.AssetsManager(scene); mesh = loader.addMeshTask('enemy1', "", "/static/game/t3/", "t.babylon");
      
      





その後、モデルがすでにロードされていることを任意の場所で確認し、必要に応じてその位置を設定して変換することができます。



 mesh.onSuccess = function (task) { task.loadedMeshes[0].position = new BABYLON.Vector3(10, 2, 20); };
      
      





この場合の最も一般的な操作の1つは、オブジェクトのクローン作成です。たとえば、ツリーがあります。各ツリーを個別にロードしないように、一度ロードして、異なる座標でシーン全体にクローンを作成できます。



 var palm = loader.addMeshTask('palm', "", "/static/game/g6/", "untitled.babylon"); palm.onSuccess = function (task) { var p = task.loadedMeshes[0]; p.position = new BABYLON.Vector3(25, -2, 25); var p1 = p.clone('p1'); p1.position = new BABYLON.Vector3(10, -2, 20); var p2 = p.clone('p2'); p2.position = new BABYLON.Vector3(15, -2, 30); };
      
      





AssetsManager



の場合、クローニングも重要な役割を果たします。 彼はシーンの主要部分がロードされるまで単純なスプラッシュ画面を描画し、 loader.onFinish



に入れたloader.onFinish



ことを確認します。



 var createScene = function () { . . . } var scene = createScene(); loader.onFinish = function (tasks) { engine.runRenderLoop(function () { scene.render(); }); };
      
      







さまざまな理由により、ゲーム中にそれ以上ロードすることは避けなければなりません。 したがって、すべてのキャラクターは初期化中にロードされ、すでにソケットの処理や、プレーヤーの外観と動作を担当するクラスで、必要な機器などのクローンを作成します。 スキームは次のようになります。





さらに、モデル自体について少し説明したいと思います。このバージョンのカードは既成のソリューションというよりも実験的なものですが、全体像を理解するのに害はありません。



この場合、キャラクターは2種類の戦車、T-90とエイブラムスによって表されます。 勝利と敗北のゲームロジックはないため、フレームワークの場合は、個々のケースごとにこれらすべてを発明する必要があることが暗示されています。 だから今は選択肢がなく、最初の人は常にエイブラムスをプレイし、ボットと他のすべてのプレイヤーはT-90として表示されます。



マップ自体には、特定のレリーフがあります。heightMapと呼ばれるbabylon.js



を使用して非常に簡単に作成されます。このため、土壌テクスチャに黒と白の画像を適用する必要があります。丘では、特性の一部をパラメータで指定できます。暗い部分と白い部分の間の遷移がぼやけているほど、勾配は緩やかになります。



  var ground = BABYLON.Mesh.CreateGroundFromHeightMap("ground", "/static/HMap.png", 200, 200, 70, 0, 10, scene, false); var groundMaterial = new BABYLON.StandardMaterial("ground", scene); groundMaterial.diffuseTexture = new BABYLON.Texture("/static/ground.jpg", scene); ground.material = groundMaterial;
      
      







さらに、家の形の小さな側近、その近くの給水塔、いくつかの木といくつかの草があります。

芝生は、最大の低ポリで、テクスチャがその上にあるプレーンで出てきました。 そして、この飛行機はさまざまな場所で傾いています。 一般的に、ポリゴンモデルが低ければ低いほど、パフォーマンスは向上しますが、明らかな理由により、エンターテイメントが損なわれます。

もちろん、設定「グラフィック品質」で選択を行うことはできますが、私たちの場合はできません。

草とは異なり、バナナのヤシにはかなりの数のピークがあるため、マップ上に数個のピース​​のみを残すことにしました。

マップ上の頂点が多いほど、 FPS



は低くなります。





家は少し離れており、衝突のある透明な立方体で覆うことが決定されました。



そして最後に残したものは3つのカラフルなキューブです。これらはすべてのユーザーに対して同期されておらず、単純なターゲットを表しています。 それぞれにヒットすると、点灯して消えます。





4. babylon.jsの動き、ミニマップ、およびゲームサウンド



動きについて言えば、これは主に最初の人に関するものです。他のプレイヤーやボットの動きは常に位置の変化であり、ほとんどの場合、サーバーからのソケットを使用してブロードキャストされます。



それ自体では、動きは単にカメラとプレーヤーの目に見える部分を制御するだけです。 たとえば、プレーヤーが右に曲がるので、カメラを私たちが見ている場所に対して右に回すか、シーンを目的の程度に回転させる必要があります。 また、たとえば、敵を倒すためのいくつかの手段を描いたモデルも有効にする必要があります。

基本的に一人称の動きのためにbabylon.js



で作られたゲームでは、2つのカメラがあります:





例:



 //FollowCamera var camera = new BABYLON.FollowCamera("camera1", new BABYLON.Vector3(0, 2, 0), scene); camera.target = mesh; ``````javascript //FreeCamera var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3( 0, 2, 0), scene); mesh.parent = camera;
      
      





移動中、ヒット中、ショット中、移動中などにいくつかの音があるはずです。 babylon.js



には優れたサウンド管理APIがあります 。 サウンドコントロールのトピックは非常に広範囲であるため、いくつかの小さな例を検討します。

初期化の例:



 var music = new BABYLON.Sound("Music", "music.wav", scene, null, { playbackRate:0.5, //  . volume: 0.1, //  . loop: true, //      . autoplay: true //      . });
      
      





ショットの音の例-クリックすると、インターロックが解除されていることを確認し、シングルショットの音を再生します。



 var gunS = new BABYLON.Sound("gunshot", "static/gun.wav", scene); window.addEventListener("mousedown", function (e) { if (!lock && e.button === 0) gunS.play(); });
      
      





矢印を前後に押して、それに応じて再生を開始した場合に、機器の動きの音を停止します。 キーリリースイベントで、再生を停止します。



  var ms = new BABYLON.Sound("mss", "static/move.mp3", scene, null, { loop: true, autoplay: false }); document.addEventListener("keydown", function(e){ switch (e.keyCode) { case 38: case 40: case 83: case 87: if (!ms.isPlaying) ms.play(); break; } }); document.addEventListener("keyup", function(e){ switch (e.keyCode) { case 38: case 40: case 83: case 87: if (ms.isPlaying) ms.pause(); break; } });
      
      





ミニマップ



シューティングゲームには、プレイヤーだけを見ることができるミニマップ、または一度にすべてを見ることができるミニマップが必要です。この場合、よく見ると、シェルを見ることができます。 babylon.js



これを実装する方法はいくつかあります。 おそらく最も簡単な方法は、別のカメラを作成し、マップの上に配置し、このカメラからのビューを必要なコーナーに配置することです。





私たちの場合、 freeCamera



を使用して、 freeCamera



を一番上に配置するように伝えます。



 camera2 = new BABYLON.FreeCamera("minimap", new BABYLON.Vector3(0,170,0), scene);
      
      





Y



座標が大きいほど、ビューは完全になりますが、マップの詳細は細かくなります。

次に、カメラに画像をスクリーンに配置する方法をカメラに伝えます。



 camera2.viewport = new BABYLON.Viewport(x, y, width, height);
      
      





最後に、両方のカメラをシーンに追加する必要があります(カメラが1台の場合、これを行う必要はありません)。



 scene.activeCameras.push(camera); scene.activeCameras.push(camera2);
      
      





5. Webソケットとゲームの同期



ゲームの基本構造全体がwebsoket



構築され、プレイヤーは何らかのアクション、マウスの回転、またはキーストロークを実行し、プレイヤーの位置座標がサーバーに送信されるイベントがこの動きでハングアップします。部屋。



最初は、 FreeCamera



を使用するため、親オブジェクトであるため、その座標を使用します。 例:





次の図では、サーバーへの接続を開いて新しいプレーヤーを作成してから、ユーザーのアクションに応答するときにメッセージを交換するまでのプロセス例を示しています。







6.サーバー側のデバイス



上記では、非常に簡潔な簡単な図を見ましたが、今度はサーバー側の動作について詳しく説明します。 ソケットハンドラーの関数では、次のメッセージを受信した後、そのaction



を確認し、次にプレーヤーが実行したaction



を確認し、これに従って目的の関数、イベントハンドラーを呼び出します。



 async def game_handler(request): . . . async for msg in ws: if msg.tp == MsgType.text: if msg.data == 'close': await ws.close() else: e = json.loads( msg.data ) action = e['e'] if action in handlers: handler = handlers[action] handler(ws, e) . . .
      
      





たとえば、 move



が来た場合、それはプレイヤーがいくつかの座標に移動したことを意味します。 このアクションの関数ハンドラーでは、これらの座標をPlayer



クラスに割り当てるだけで、それらの座標が処理されて戻され、さらに現在の部屋の他のすべてのプレーヤーに送信されます。



 def h_move(me, e): me.player.set_pos(e['x'], e['y'], e['z']) mess = dict(e="move", id=me.player.id, **me.player.pos_as_dict) me.player.room.send_all(mess, except_=(me.player,))
      
      





もちろん、現在Player



クラスが座標で行うことはすべて、単純にそれらをボットに渡して、焦点を合わせます。 そして理想的には、座標でマップ全体をチェックする必要があります。プレーヤーが障害に遭遇した場合、たとえば不正行為を避けるために、クライアントでスクリプトが変更された場合、壁から漏れないようにする必要があります。



 class Player(list): . . . def __init__(self, client, room, x=0, y=0, z=0, a=0, b=0): list.__init__(self, (x, y, z, a, b)) self._client = client self._room = room Player.last_id += 1 self._id = Player.last_id room.add_player(self) . . . def set_rot(self, a, b): self[3:5] = a, b def getX(self): return self[0] . . . def setX(self, newX): self[0] = newX . . . x = property(getX, setX) . . . @property def pos_as_dict(self): return dict(zip(('x', 'y', 'z'), self.pos))
      
      





Player



クラスでは、 property



を使用して、座標をより便利に処理します。 興味深いのは、ハブラでこのテーマに関する良い資料があったことです。



7.部屋ごとのプレイヤーのバランス



ゲームのかなり重要な部分は、異なる氏族、部屋、惑星、国などでプレイヤーを育てることです。 それ以外の場合、すべてが1枚のカードに収まらないからです。 私たちの場合、システムはまだ非常にシンプルです-1つの部屋に設定で設定されているよりも多くのプレイヤーがいる場合(デフォルトではボットで4)、新しい部屋を作成し、残りのプレイヤーをそこに円で送信します。



現時点では、ショーケースとレーティングがないため、何らかのベースを使用する意味がないため、どの部屋のどのプレーヤーなどに関するすべての情報がメモリに保存されます。



ルーム番号は、プレイヤーがルート/pregame



あるスタートページからゲームに入るときに割り当てられます。 ボタンが押されると、 ajax



トリガーされます。結果が成功した場合は、プレーヤーが目的の部屋にリダイレクトされます。



 if (data.result == 'ok') { window.location = '/game#'+data.room;
      
      





サーバー側では、すべての部屋とその中のプレイヤーのリストを含むrooms



ディクショナリid



ます。プレイヤーの数が指定された値を超えていない場合は、部屋id



クライアントに返します。 プレイヤーの数が多い場合は、新しい部屋を作成します。



 def check_room(request): found = None for _id, room in rooms.items(): if len(room.players) < 3: found = _id break else: while not found: _id = uuid4().hex[:3] if _id not in rooms: found = _id
      
      





Room



については、 Room



クラスを担当します。 彼の一般的な作業スキームは次のようになります。





Player



クラスと相互作用することがわかります。おそらく、スキーム全体は完全に線形に見えませんが、最終的には、このようなチェーンを非常に便利に書くことができます。



 #        me.player.room.send_all( {"e" : "move", . . . }) #     me.player.room.players #     me.player.room.add_player(self) #     me.player.room.remove_player( me.player )
      
      





私は、私の同僚について、この提起された質問について少し話をしたいと思います。 me



は、イベントを提供する関数のパラメーターとして渡されるソケットです。



 def h_new(me, e): me.player = Player(me, Room.get( room_id ), x, z)
      
      





ここで、実際には、翻訳者が言うように、しゃれ。 Pythonのすべてがオブジェクトであることを知っているので。

これはより明確に起こることです:



 player = Player( Room.get(room_id), x, z) player.me = me me.player = player
      
      





player



モジュールとme



オブジェクトの.player



という2つのリンクを取得します。両方とも等しく、少なくとも1つのリンクが存在する限り存在するメモリ内の同じオブジェクトを参照します。



これはさらに簡単な例で見ることができます:



 >>> a = {1} >>> b = a >>> b.add(2) >>> b {1, 2} >>> a {1, 2}
      
      





この例では、 b



a



は1つの共通値への単なる参照です。



 >>> a.add(3) >>> a {1, 2, 3} >>> b {1, 2, 3}
      
      





さらに調査します。



 >>> class A(object): ... pass ... >>> a = A() >>> a.player = b >>> a.player {1, 2, 3} >>> b {1, 2, 3} >>> a.__dict__ {'player': {1, 2, 3}}
      
      





オブジェクトのプロパティは単なる構文糖です。 この場合、それらは単に__dict__



辞書に格納されます



その結果、リンクa



1つを強制終了しましa



、その代わりに、新しく作成したオブジェクトに属する別のリンクを作成し、実際にこのオブジェクトの辞書__dict__



ます。



 >>> a <__main__.A object at 0x7f3040db91d0>
      
      





8. Asyncioおよびボットの動作生成



通常のゲームでは、少なくとも1つのボットが必要です。このゲームも例外ではありません。 もちろん、ボットができるのは同心円を描くことだけで、プレイヤーがいる座標に徐々に近づいていきます。 新しいプレーヤーが入ると、ボットはそれに注意を向けます。

ボットが移動する座標に関する部屋内のすべてのプレイヤーにメッセージを送信する行。



 mess = dict(e="move", bot=1, id=self.id, **self.pos_as_dict) self.room.send_all(mess, except_=(self,))
      
      





次のようにクライアント部分とボットとの相互作用の一般的なスキームは次のとおりです。





クラスではRoom



、私たちは、__init__



クラスのインスタンスを作成しますBot



。そして、すでにdef __init__



クラスでBot



、私たちはasyncio.async(self.update())



各パス上で実行されなければならないタスクをperedaom。

を含む関数を呼び出してもawait



、関数自体は開始されませんが、ジェネレーターオブジェクトが作成されます。class



この関数は開始しないため、宣言された関数を呼び出すだけでなく、このクラスが提供するオブジェクトを作成します。await



ジェネレーターでメソッドが呼び出されると、含む関数の呼び出しが発生します.__next__()



。この場合- next



デコレータにありasync



-コルーチンを初期化します。

簡単に言えば、100ミリ秒ごとにボットの新しい座標を使用してクライアントにメッセージを送信し、0.5秒ごとにボットの座標を更新します。



無限ループでタスクを操作する簡単な例:



 import asyncio async def test( name ): ctr = 0 while True: await asyncio.sleep(2) ctr += 1 print("Task {}: test({})".format( ctr, name )) asyncio.ensure_future( test("A") ) asyncio.ensure_future( test("B") ) asyncio.ensure_future( test("C") ) loop = asyncio.get_event_loop() loop.run_forever( )
      
      





入れたすべての関数は、2秒でasyncio.ensure_future



指定さasyncio.sleep(2)



た遅延で円で実行されます)。これの実用的な用途は非常に広範囲であり、ゲーム用のボットに加えて、たとえばトレーディングシステム用のボットだけを書くことができます。私の主観的な意見では、これは場所での開発を簡素化し、非常に価値があり、動物園を回避します。



9. Nginxおよびソケットプロキシ



そしてゲームに関して言及する最後のことは、Nginx



私たちのプロジェクトが確実に動作するwebsocket



確信している場合の正しい設定ですhttp



最初に思い浮かぶのは、この構成のようなものです:



 server { server_name aio.dev; location / { proxy_pass http://127.0.0.1:8080; } }
      
      





また、ローカルで正常に動作しますが、1つの根本的な欠点があります。ソケットは、この設定では外部サーバーで動作し5.5.5.10



なくなりloalhost



ます。

したがって、次のアイデアは書くことです。



 server { server_name aio.dev; location / { proxy_pass http://5.5.0.10:8080; } }
      
      





しかし、彼女はPythonのパフォーマンス性能の低いので、あまりにも、欠陥があるnginx



桁違いによると、いずれの場合にproxy_pass



しなければならないhttp://127.0.0.1:8080





ので、機会を取るNginx



ソケットをプロキシ、数年前に登場し-aを、:



 server { server_name aio.dev; location / { proxy_pass http://127.0.0.1:8080; } location /ws { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }
      
      





ソケットを初期化するときのアドレスには、ポート80を指定します。これは、明示的に指定しない限り、デフォルトでポート80でリッスンするように構成されているvar uri = "ws://aio.dev:80/ws"



ためNginx



ですlisten





この構成では、それがために働くだろうNginx



番目、そして便利にアクセス可能となりwebsoket



、S、およびhttp







10.非同期キャッシュ。



このゲームはフレームワークのプラグインコンポーネントの1つとして記述されているため、小さなイノベーションについて少し説明したいと思います。



そのmemcached



ため、で関数を使用するページキャッシングの例を考えてみましょうasync def



。多かれ少なかれ訪問したクラシックサイトでは、メインページ、ニュースページなどの訪問したページをキャッシュできるほか、ページが変更されたときにキャッシュ時間が切れる前にキャッシュをリセットできるようにする必要があります。幸いなことに、memcached 非同期ドライバーは既にsvetlovによって作成されており、デコレーターを作成し、いくつかの小さな問題を解決するために残っています。



それ自体では、たとえば、次のように、任意の関数に対してデコレータの形でかなり馴染みのある方法でキャッシングを行うことが決定されました。ビーカーデコレータでは、キャッシュ時間を設定し、特にmemcachedのキーとなる名前を設定する必要があります。



データmemcached



シリアル化されて使用されpickle



ます。そしてもう1つのニュアンス-フレームワークは最上部aiohttp



記述CIMultiDict



れているため、シリアル化されていません。同じキーを持つ機能を備えた辞書の実装であり、作成Cython



により高速に記述されていますaiohttp







  dct = CIMultiDict() print( dct ) <CIMultiDict {}> dct = MultiDict({'1':['www', 333]}) print( dct ) <MultiDict {'1': ['www', 333]}> dct = MultiDict([('a', 'b'), ('a', 'c')]) print( dct ) <MultiDict {'a': 'b', 'a': 'c'}> dct = dict([('a', 'b'), ('a', 'c')]) print( dct ) {'a': 'c'}
      
      





したがって、そこに格納された値はでシリアル化できませんでしたpickle



したがって、それらを取得して再梱包する必要がありましたが、時間が経つにつれてCIMultiDict



シリアル化可能になることを願っていpickle



ます。



 d = MultiDict([('a', 'b'), ('a', 'c')]) prepared = [(k, v) for k, v in d.items()] saved = pickle.dumps(prepared) restored = pickle.loads(saved) refined = MultiDict( restored )
      
      





完全なキャッシングコード
 def cache(name, expire=0): def decorator(func): async def wrapper(request=None, **kwargs): args = [r for r in [request] if isinstance(r, aiohttp.web_reqrep.Request)] key = cache_key(name, kwargs) mc = request.app.mc value = await mc.get(key) if value is None: value = await func(*args, **kwargs) v_h = {} if isinstance(value, web.Response): v_h = value._headers value._headers = [(k, v) for k, v in value._headers.items()] await mc.set(key, pickle.dumps(value, protocol=pickle.HIGHEST_PROTOCOL), exptime=expire) if isinstance(value, web.Response): value._headers = v_h else: value = pickle.loads(value) if isinstance(value, web.Response): value._headers = CIMultiDict(value._headers) return value return wrapper return decorator
      
      







キャッシングは、デコレータを一番上に記述し、キャッシュの有効期限と名前を指定するだけで適用できます。



 from core.union import cache @cache('list_cached', expire=10 ) async def list_tags(request): return templ('list_tags', request, {})
      
      





あとがき



この段階では、それはむしろ、可能性を実証するために、可能性の高いゲームの初期の実施段階であるとWebGL



し、asyncio



製品版より。しかし、フレームワークの将来のバージョンでは、すべてが理想にはるかに近づくことを願っています。空中、地上、そして単に個々のプレイヤーとして、プレイヤーが戦闘のキャラクターを選択したり動的に変更したりできるスペースサガの形でゲームを作りたいと思います。



私は常にスターウォーズのキャラクターが好きだったので、歴史的なものよりも未来的な方法ですべてをやりたいです。

設定でゲームの任意のレベルをローカライズし、それをメインのレベルにし、可能な限りゲームのカードとキャラクターを動的に変更する機能を備えています。



以下は、主に、ある程度言える程度に現在存在し、最も可能性の高い、さらなる作業が行われる主な欠点です。



ping


サーバーは各プレーヤーからのpingを監視し、ヒット、移動などの速度を同期しようとする必要があります。少なくともおよそ。新しいプレイヤーが参加するとき、彼は対戦相手がほぼ同じpingを持っている部屋に入る必要があります。商業用ですが、さまざまなオプションが思い浮かびます。



不正行為


当然、クライアントを決して信頼するべきではありません。通常のサーバーでは、マップ全体を完全に理解し、プレーヤーのすべての動き、弾丸の軌道をチェックする必要があります。そうでなければ、ゲームの最小の人気で、多様な不正行為の繁栄は避けられないでしょう。



ロードマップ-ゲーム



クライアント:







サーバー:







ロードマップ-フレームワーク全体





何かを書くのを忘れて、どこかで彼は用語と間違えられるかもしれません。したがって、すべての文法的およびその他のエラーをPMに書き込むようお願いします。



前編

babylon.jsに関する記事を確認し、 githubのthree.js

ライブラリと比較する

ドキュメントを読む

無料の大規模な選択を主要なサイトの一つとは、3Dモデルを支払った

ブレンダーのための無料の3Dモデルとウェブサイトを

回転

babylon.jsの中の音と作業

音の可視化例

asynioにネロの世界

スリープ

タスクと作業

ステッチ通話

PEP-0492

のブログ Svetlovの aiohttp著者

memcachedの非同期ドライバ

babylon.jsのための更新されたドキュメント

githubの上の文書aiohttp

aiohttpドキュメントreadthedocsのための

歩留まり文からドキュメント

ライブラリのリスト- AIO-LIBS

の別のより包括的なリスト



:発電機の詳細

http://www.dabeaz.com/generators/Generators。 pdf

http://www.dabeaz.com/coroutines/Coroutines.pdf

http://www.dabeaz.com/finalgenerator/FinalGenerator.pdf




All Articles