キャンバスなしでゲームを作成する

Blizzard HeartStoneカードゲームに出会ったことがあります。 それを試してみると、そのようなものはhtml5テクノロジーを使用して作成でき、クロスプラットフォームになることができるというアイデアが生まれました。 私の意見では、そのようなことはまだウェブサイトの開発にのみ関与している人々によって行うことができます。



だから私たちが持っているもの:





実際には、それだけです。 これで計画を実行するのに十分です。



この例では、 PHPで作成された既製のWebSocketsサーバーを使用しました。



メソッド名と引数を渡すために、json形式でサーバーとデータを交換します。



必要な最も基本的なことは、クライアントとサーバー、つまりアプリケーション(この場合はゲーム)を拡張できるインターフェースとの間に責任ある対話を確立することです。



クライアントでは、次のようになります。



var socketInfo = {}; socket = new WebSocket('ws://localhost:8000'); socket.onopen = function (e){ socketInfo.method = "connect"; socket.send(JSON.stringify(socketInfo)); console.log('   ws://localhost:8000'); } socket.onclose = function (e){ console.log(' !'); } socket.onmessage = function (e){ if (typeof e.data === "string"){ var request = JSON.parse(e.data); Actions[request.function](request.args); }; }
      
      





これは、WebSocket機能の標準的な説明です。 onmessageメソッドに注目しましょう。



パラメータとして、サーバーから送信されたJSON形式の文字列を受け取ります。 この行には、クライアントで実行する必要がある関数の名前と、それに渡すパラメーターが含まれています。



名前に特定の変数が含まれる関数を実行するには、この関数が配列の要素である必要があります(オプションとして、グローバル配列ウィンドウ)。 この例では、これはActions配列です。



  var Actions = { myId: function(id){ localStorage.setItem("myId", id); }, log: function(str){ console.log(str); }, ....... }
      
      





当然、文字列もjson形式でサーバーに送信します。 メソッドの名前と引数の2つのフィールドが含まれています。

同様に、サーバーは結果の文字列を解析します。



 function websocket_onmessage($keyINsock, $str){ global $Users; $json = json_decode($str); $method = strval($json->{'method'}); $args = $json->{'args'}; if (!isset($args)) $args = $keyINsock; if (!empty($method)) $Users->$method($keyINsock, $args); }
      
      







websocket_onmessage関数はサーバーへのリクエストを処理し、接続IDと送信された文字列をそれぞれ引数として受け取ります。 次に、Usersクラスのオブジェクトを操作します。



プレーヤーを接続する例について説明しましょう。



  public function frAccept($myid, $opId){ $this->opponents[$myid] = $opId; $this->opponents[$opId] = $myid; $args = array($myid, $opId); $arrOut = array('function' => 'frAccept', 'args' => $args); $arrOutJSON = json_encode($arrOut); websock_send($opId, $arrOutJSON); websock_send($myid, $arrOutJSON); }
      
      





各メソッドの最初のパラメーターは、リクエストが送信された接続の識別子です。これが$ myIdと呼ばれる理由です。 この例では、2人のプレーヤーを接続する方法について説明します(2番目のパラメーターは、戦闘への招待を受け入れたプレーヤーのIDです)。 簡単にするために、データベースを使用しません。したがって、さらに作業するために、相手の識別子を配列に入れます(これらはLocalStorageのクライアントにも格納されますが、なぜそれらを前後に駆動します)。



対戦相手が書き留めたすべての人が、この情報を両方のプレイヤーに提供します。



  frAccept: function(id){ fight_start(id); console.log('Fight starting'); } ....... function fight_start(ids){ $('body').load('fight.html'); curplayer = ids[0]; localStorage.setItem("opponent", ids[0]); if (ids[0] == localStorage.getItem("myId")) localStorage.setItem("opponent", ids[1]); }
      
      





curplayer変数には、それが誰の番であるかに関する情報が格納されます。



ゲーム全体は通常のdivブロックで構築されています。



 <meta http-equiv="Cache-Control" content="no-cache" /> <div id="to_area"></div> <div id="wrapper"> <div id="opHand"></div> <div id="area"> <div id="timer"> <div class="bg"></div> </div> <div id="opUnits"> <div class="nexus card" data-health="30" id="" data-attack="0"> <div class="health"> <div class="inner">30</div> </div> </div> <div class="bg"></div> </div> <div id="myUnits"> <div class="nexus card" data-health="30" data-attack="0"> <div class="health"> <div class="inner">30</div> </div> </div> <div class="bg"></div> </div> <div id="next"> </div> </div> <div id="myhand"></div> </div>
      
      







このようなフレームワークを使用して、ゲームの要素を操作し始めます。



開始するには、4枚の初期カードを取得します。 どうやってやるの? サーバーに4回尋ねる:「カードを渡す」。 引数として必要な数のカードを追加することにより、この操作を簡素化しました。get_cards(4);



  function get_cards(num){ socketInfo.method = "get_cards"; socketInfo.args = num; socket.send(JSON.stringify(socketInfo)); }
      
      





サーバーがメソッド名と引数で受け取ったjson文字列を解析することはすでにわかっています。 その結果、get_cardsメソッドを使用します。



  public function get_cards($myid, $num){ global $Cards; for ($i=0; $i < $num; $i++){ $card = $Cards->getRandom(); $args = array( "player_id" => $myid, "card" => $card ); $arrOut = array('function' => 'player_get_card', 'args' => $args); $arrOutJSON = json_encode($arrOut); websock_send($myid, $arrOutJSON); websock_send($this->opponents[$myid], $arrOutJSON); } }
      
      





ループでは、デッキからランダムなカードを要求し($ Cards-> getRandom()はカードのjson表現を返します:名前、攻撃、ライフ数、写真)、結果を両方のプレイヤーに送信します。 相手について忘れないでください、彼は我々がカードを引いたことを確認しなければなりません。



どのプレイヤーが現在のマップを取得したかは、識別子の比較によって決定されます。



  player_get_card: function(args){ if (!me(args.player_id)) { $('#opHand').append('<div class="card" id="'+args.card.id+'" />'); return; }else{ var card = card_construct(args.card); $('#myhand').append(card); return; } }
      
      





したがって、#opHandは相手の手、#myhandは私たちの手です。 それが私たちの手にあるカードです。



それで、カードを手に入れました。それをテーブルに置く時間です。 これを行うには、基本的なjquery関数を作成します。



  $('#myhand').on('click',".card",function(){ //      if (!me(curplayer)) return; to_area($(this)); });
      
      





私たちの手でカードをクリックすると、カードはアリーナに送られます:to_area()。



  function to_area(card){ //     card.appendTo('#myUnits'); socketInfo.method = 'opGetCard'; socketInfo.args = card.attr("id"); socket.send(JSON.stringify(socketInfo)); }
      
      





敵はこのすべてを見なければならないことを忘れないでください。



  public function opGetCard($myid, $card_id){ global $Cards; $card = $Cards->getById($card_id); $arrOut = array('function' => 'opGetCard', 'args' => $card); $arrOutJSON = json_encode($arrOut); websock_send($this->opponents[$myid], $arrOutJSON); }
      
      







次に、最も重要なこと-攻撃を考慮する必要があります。 1枚のカードで、相手のカードを攻撃します。 これを行うには、次のものが必要です。





アグレッサーカードを選択するには、アクティブクラスを操作します。



  $('#area').on('click',"#myUnits .card",function(){ //////////////////      if(!me(curplayer) || $(this).hasClass('nexus'))return; if ($(this).hasClass('active')) { $(this).removeClass('active'); }else{ $('#area #myUnits .card.active').removeClass('active'); $(this).addClass('active'); } });
      
      





攻撃者にはアクティブなクラスがあります。つまり、カードが選択されています。 これで、被害者を選択できます。



  $('#area').on('click',"#opUnits .card",function(){ //////////////////       $agressor = $('#area #myUnits .card.active'); if ($agressor.length < 1) { return false; }; attack_request($agressor,$(this)); });
      
      





そのため、被害者のカードをクリックすると、攻撃者と被害者という2つの引数を関数に話す名前attack_requestで転送します。 つまり、誰と誰が攻撃するのか。



関数自体はラッパーであり、攻撃があるという情報のみをサーバーに送信します



  function attack_request(agressor, victim){ ////////////   socketInfo.method = 'attack_request'; socketInfo.args = agressor.attr("id")+'-'+victim.attr("id"); socket.send(JSON.stringify(socketInfo)); }
      
      





サーバーの機能はエコーモードで動作し、情報を自分と相手に送り返します。 これは、2人のプレイヤー間のインターネット速度の差を可能な限り中和するために行われます(ゲーム内に1動きにつき2分をカウントするタイマーがあるため)。



  public function attack_request($myid, $args){ $arrOut = array('function' => 'attack', 'args' => $args); $arrOutJSON = json_encode($arrOut); websock_send($myid, $arrOutJSON); websock_send($this->opponents[$myid], $arrOutJSON); }
      
      





私たちが持っているマップは、一意の識別子によって明確に識別されているため、攻撃者と被害者のIDがあり、この攻撃を引き出すことができます。



  attack: function (args){ id = args.split('-'); var alias = $('#'+id[0]).data('alias'); onBeforeAttack(alias,id); } .................. function onBeforeAttack(alias, id){ //     if(Units[alias] == undefined){ var uFile = 'js/units/'+alias+'.unit.js'; var xmlhttp = getXmlHttp(); xmlhttp.open('GET', uFile, false); xmlhttp.send(null); if(xmlhttp.status == 200){ $('body').append('<script src="'+uFile+'"></scipt>'); }else{ Units[alias] = new defaultUnit(); } } Units[alias].attack(id[0], id[1]); }
      
      







攻撃をレンダリングする前に、このタイプのユニットに関する情報を含むオブジェクトがあるかどうかを確認する必要があることに注意してください。 拡張性の瞬間をすぐに考えてみました。 結局のところ、近接ユニットと射程の両方があり、それらの攻撃が異なって見えるようにしたいと思います。 しかし、これは未来のためです。 これまでのところ、デフォルトオブジェクトのみがあり、ユニットオブジェクトの説明を含む対応するファイルがない場合に使用されます。



これは次のようなものです。



  function defaultUnit(){} defaultUnit.prototype.attack = function(agressor_id, victim_id){ var victim = $('#'+victim_id); var agressor = $('#'+agressor_id); victim.css('z-index',5); agressor.css('z-index',10); var $ypos = victim.offset().top - agressor.offset().top; var $xpos = victim.offset().left - agressor.offset().left; agressor.animate({top:$ypos, left:$xpos}, 100).delay(200).animate({top:0, left:0}, 100); if (isNaN(victim.data('attack'))) victim.data('attack',0); if (isNaN(agressor.data('attack'))) agressor.data('attack',0); victim.data('health',victim.data('health')-agressor.data('attack')); agressor.data('health',agressor.data('health')-victim.data('attack')); refreshCards(); }
      
      





攻撃をレンダリングした後、refreshCards()を呼び出します。これは、アリーナ内のすべてのカードのライフ数を変更します(大量スペルもあるため、正確にすべてです)。



  refreshCards = function(){ ////    $('#myUnits .card.active').removeClass('active'); var rest = setTimeout(function(){ $('#area .card').each(function(){ refresh_card($(this)); if ($(this).data('health') < 1) { death($(this)); }; }); clearTimeout(rest); },1000); } function refresh_card(card){ //////////   $('.health .inner',card).html(card.data('health')); $('.attack .inner',card).html(card.data('attack')); }
      
      





ライフの数が1未満になった場合、カードの「死」を開始し、本質的に保護する必要がある本館が死んだ場合、プレイヤーの1人が負けた場合のイベントもトリガーします。



  function death(unit){ //////////  if (unit.hasClass('nexus')) { var lose = localStorage.getItem("opponent"); if (!me(curplayer)) lose = localStorage.getItem("myId"); socketInfo.method = 'lose'; socketInfo.args = lose; socket.send(JSON.stringify(socketInfo)); }; unit.addClass('die'); var dt = setTimeout(function(){ unit.remove(); clearTimeout(dt); },2000); }
      
      





実際、ゲームで作成したすべての基本アクションを説明するつもりはありませんでした。コンセプトを説明しました。



デモを表示するには、2つのブラウザー/タブが必要です(モバイルでは腐っているように見えますが、お勧めしません)。



141.8.196.181/new-cards



All Articles