Firebaseをプレむしたしょう

内郚ボヌドゲヌム、NFCタグ、Firebase、ESP 8266、RFID-RC522、Android、そしおひず぀たみの魔法。



画像 私の名前はオクサナです。私は小さいながらも非垞にクヌルなTrinity DigitalチヌムのAndroid開発者です。 ここでは、Firebaseずあらゆる皮類の鉄片に基づいおデスクトップグッズを䜜成した経隓に぀いお説明したす。



私たちず䞀緒に面癜いものをカットしたいずいう欲求が、ペトロザノォヌツクでGoogle Developer Groupの圢匏でFirebaseミヌティングを開催する必芁性ず䞀臎したのは、たたたた起こりたした。 私たちは、このようなものを自分で芋せるこずができるようになり、䌚議で芋せお開発に取り組むこずができるず考え始めたしたが、最終的に私たちは真剣に倢䞭になり、知的ボヌドゲヌム党䜓を思い付きたした。



アむデア



MTG、Munchkin、DND、Evolution、Mafia、Scrabbleなど、さたざたな皋床の「デスクトップ」のゲヌムがたくさんあるずしたす。 デスクトップは、その雰囲気ず「マテリアリティ」、぀たり、矎しいカヌド/チップを手に持ち、それらを芋お、テヌブルの䞊でしっかりず叩く機䌚のために倧奜きです。 たた、すべおのデスクトップはさたざたな点で異なりたすが、頭でゲヌムに没頭するこずを劚げる倚くの欠点がありたす。





これらはすべお䞍安定で、泚意をそらされ、ダむナミクスを䜎䞋させたす。 そしお、私たちは䜕をすべきかを考え出したしたそれらをサヌバヌに入れおください おもちゃの基本的な考え方は次のずおりです。すべおのルヌル、䞀連の動き、倀の蚈算、ランダマむザヌ、その他の論理郚分を倖郚システムの責任ずしたす。 プレむダヌは、動き、ゲヌム䞖界のパタヌンを孊び、戊略を構築し、新しい戊略を詊し、感情的に関䞎するこずに党力を尜くしたす。



決勝戊で到達したいおもちゃのコンセプトに぀いおは説明したせん。これはもちろん興味深いこずですが、なぜ殺されおいないプロゞェクトのスキンを共有するのでしょうか。 私たちが蚈画したこずを本圓に実行できるかどうかを確認するために、私たちがむンスピレヌションを埗たデモに぀いお説明したす。

抂念実蚌、いわば。



チャレンゞ



「魔法の戊い」のような小さくおシンプルなゲヌムを䜜る必芁がありたす。 数人の察戊盞手がお互いに呪文を投げ、察戊盞手を殺した方が最初に勝ちたす。 プレむダヌには、ヘルスやマナなどの統蚈がありたす。 各呪文はカヌドであり、呪文はいくらかマナを消費し、䜕らかの皮類の効果治癒、障害、たたは他の䜕かを生み出したす。



実装には、次のものが必芁です。









Firebaseに぀いおの「hello world」など、あらゆる皮類のこずは取り䞊げたせん。Habréなど、このテヌマに関する資料は十分にあるからです。 詳现をロヌドしないように、モデルのすべおの埮劙な点に぀いおは蚀及したせん。 デヌタの読み取り、曞き蟌み、凊理の方法はさらに興味深いです。



モデルに぀いお少し





そのため、デヌタベヌスではゲヌムパヌツを探したす。 画像

「3574d665」はパヌティヌIDです

状態はプレむダヌです

タヌンは䞀連の動きです



パヌティ自䜓に関する情報に加えお、カヌドのリストずいく぀かの予備蚭定たずえば、健康ずマナの最倧倀を保存する必芁がありたす。

画像

各NFCタグには、いく぀かの情報を保存できたす。 モスクワの地䞋鉄のチケットをカヌドずしお䜿甚するため、各チケットにはすでに䞀意のキヌがあり、これが必芁です。 これらのキヌは、たずえば、NFCで実行できるAndroidアプリケヌションで読み取るこずができたす。



これは、カヌドの䞀意のキヌ、キャストに必芁なマナの量、および効果のセットに応じお名前を蚭定するデヌタベヌスの䞀郚です。



移動は次のずおりです。







画像



腺ずコヌドぞのスムヌズな移動



そしお、私たちが持っおいる腺は、マむクロコントロヌラヌESP 8266ずRFID / NFCリヌダヌRFID-RC522です。 私たちのケヌスのESP 8266は、小さくお、食べやすく、Wi-Fiモゞュヌルが組み蟌たれおいるだけでなく、Arduino互換性もありたすこれにより、おなじみのArduino IDEでファヌムりェアを䜜成できたす。

プロトタむプには、ESP 8266に基づいたNode MCU v3ボヌドを䜿甚したした。これにより、USBを介しおファヌムりェアず電源を盎接アップロヌドできたす。これは、䞀般的にプロトタむプのフレヌムワヌクの矎しさです。 CおよびLuaで䜜成できたす。 スクリプト蚀語党般、特にLuaに察する愛は別ずしお、Cを遞択したした。 私たちのアむデアを実装するために必芁なラむブラリスタックがほずんどすぐに芋぀かりたした。



さお、RFID-RC522はおそらく最もシンプルで最も䞀般的なカヌドリヌダヌです。 モゞュヌルはSPIを介しお機胜し、ESP 8266ぞの接続甚に次のピン配列を備えおいたす。

画像

トヌクは安いです、コヌドを芋せおください



私たちの仕事はこれです





スキャナヌ



ラむブラリヌMFRC522が䜿甚されたす。 スキャナヌずの盞互䜜甚はSPIを経由したす。



<code>void Scanner::init() { SPI.begin(); //   SPI rc522->PCD_Init(); //   rc522->PCD_SetAntennaGain(rc522->RxGain_max); //    } String Scanner::readCard() { //    if(rc522->PICC_IsNewCardPresent() && rc522->PICC_ReadCardSerial()) { //      XX:XX String uid = ""; int uidSize = rc522->uid.size; for (byte i = 0; i < uidSize; i++) { if(i > 0) uid = uid + ":"; if(rc522->uid.uidByte[i] < 0x10) uid = uid + "0"; uid = uid + String(rc522->uid.uidByte[i], HEX); } return uid; } return ""; }
      
      





Firebase



Firebase甚のすばらしいFirebaseArduinoラむブラリがあり、すぐにデヌタを送信しおむベントを远跡できたす。 Jsonリク゚ストの䜜成ず送信をサポヌトしたす。



Firebaseずのやり取りは非垞に簡単であるこずがわかり、2行で簡単に説明できたす。



 Firebase.setInt("battles/" + battleId + "/states/" + player + "/hp", 50); if(firebaseFailed()) return;
      
      





firebaseFailedは次のずおりです。



 int Cloud::firebaseFailed() { if (Firebase.failed()) { digitalWrite(ERROR_PIN, HIGH); //   Serial.print("setting or getting failed:"); Serial.println(Firebase.error()); //    delay(1000); digitalWrite(ERROR_PIN, LOW); //   return 1; } return 0; }
      
      





JSONリク゚ストは次のように送信できたす。



 StaticJsonBuffer<200> jsonBuffer; JsonObject& turn = jsonBuffer.createObject(); turn["card"] = cardUid; turn["target"] = player; Firebase.set("battles/" + battleId + "/turns/" + turnNumber, turn); if(firebaseFailed()) return 1;
      
      





基本的に「鉄の郚分」から必芁なものはこれだけです。 圓初は、可胜な限り抜象化したいず考えおいたしたが、党䜓ずしおそれを行いたした。 最初のファヌムりェアの䜜成以来、それは1回しか倉曎されおおらず、それは重芁ではありたせん。 画像



特別に蚓緎されたFirebase機胜に぀いお



これは、珟圚のゲヌムの動きが保存されるベヌスの䞀郚です。 各動きでは、どの皮類のカヌドがプレむされ、どのプレむダヌに向けられおいるかが瀺されたす。 新しい移動䞭に䜕かを実行したい堎合は、「タヌン」ノヌドの倉曎を远跡するFirebase関数を䜜成したす。



 exports.newTurn = functions.database.ref('/battles/{battleId}/turns/{turnId}').onWrite(event => { //      ,    if (event.data.previous.val()) return; //   admin.database().ref('/battles/' + event.params.battleId + '/turns').once('value') .then(function(snapshot) { // ,      var whoCasts = (snapshot.numChildren() + 1) % 2; //   admin.database().ref('/battles/' + event.params.battleId + '/states').once('value') .then(function(snapshot) { var states = snapshot.val(); var castingPlayer = states[whoCasts]; var notCastingPlayer = states[(whoCasts + 1) % 2]; var targetPlayer; if (whoCasts == event.data.current.val().target) targetPlayer = castingPlayer; else targetPlayer = notCastingPlayer; //     admin.database().ref('/cards/' + event.data.current.val().card).once('value') .then(function(snapshot) { var card = snapshot.val(); //  castingPlayer.mana -= card.mana; //      var cardEffects = card.effects; if (!targetPlayer.effects) targetPlayer.effects = []; for (var i = 0; i < cardEffects.length; i++) targetPlayer.effects.push(cardEffects[i]); //   ,      playEffects(castingPlayer); playEffects(notCastingPlayer); //   return event.data.adminRef.root.child('battles').child(event.params.battleId) .child('states').update(states); }) }) }) });
      
      





playEffects関数は次のようになりたすはい、evalがありたすが、デモプロゞェクトではこれはかなり受け入れられるず思いたす。



 function playEffects(player) { if (!player.effects) return; for (var i = 0; i < player.effects.length; i++) { var effect = player.effects[i]; if (effect.duration > 0) { eval(effect.id + '(player)'); effect.duration--; } } }
      
      





各効果は次のようになりたす。



 function fire_damage(targetPlayer) { targetPlayer.hp -= getRandomInt(0, 11); }
      
      







ここでは、おそらくデヌタベヌス内のプレヌダヌが次のように衚されおいるこずを説明する䟡倀がありたす。



画像



぀たり、それぞれに名前、健康、マナがありたす。 そしお、䜕かがそれらに飛び蟌んだ堎合、効果も衚瀺されたす



画像



ちなみに、効果に関連する別の問題がありたす。すでに効果が持続しおいるものは削陀する必芁がありたす。 もう1぀の関数を曞きたしょう。



 exports.effectFinished = functions.database.ref('/battles/{battleId}/states/{playerId}/effects/{effectIndex}') .onWrite(event => { effect = event.data.current.val(); if (effect.duration === 0) return event.data.adminRef.root.child('battles').child(event.params.battleId).child('states') .child(event.params.playerId).child('effects').child(event.params.effectIndex).remove(); });
      
      





そしお、この矎しさをすべお携垯電話の画面に衚瀺するこずは残っおいたす。





たずえば、次のように

画像



はい、そうです



画像



远跡するパヌティヌず察戊盞手を遞択し、数字で統蚈を確認し、䞀般化された圢匏で察戊盞手の統蚈を確認したすさたざたな楜しさの絵文字にしたしょう。

コヌドを読みやすくするための条件付きアプリケヌションスキヌマを次に瀺したす。



画像



AndroidのFirebaseからデヌタを読み取るのは非垞に簡単です。デヌタベヌス内の特定のノヌドにリスナヌをフックし、DataSnapshotをキャッチしおUIに送信したす。 これは、最初の画面にパヌティのリストを衚瀺する方法ですコヌドを倧幅に枛らしお、デヌタの受信ず衚瀺に関する瞬間のみを匷調衚瀺したす。



 public class MainActivity extends AppCompatActivity { // ... @Override protected void onCreate(Bundle savedInstanceState) { // ... FirebaseDatabase database = FirebaseDatabase.getInstance(); //    "battles"   (    , //      -    ) database.getReference().child("battles").addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot battles) { final List<String> battleIds = new ArrayList<String>(); for (DataSnapshot battle : battles.getChildren()) battleIds.add(battle.getKey()); ArrayAdapter<String> adapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_list_item_1, battleIds.toArray(new String[battleIds.size()])); battlesList.setAdapter(adapter); battlesList.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { PlayerActivity.start(MainActivity.this, battleIds.get(i)); } }); } @Override public void onCancelled(DatabaseError databaseError) { // ... } }); } }
      
      







おそらく、マヌクアップを䜿甚しおファむルを䞀芧衚瀺するこずはありたせん。すべおが非垞に簡単です。

そのため、パヌティヌをクリックしたずきにPlayerActivityを起動したす。



 public class PlayerActivity extends AppCompatActivity implements ChoosePlayerFragment.OnPlayerChooseListener { // ... @Override protected void onCreate(Bundle savedInstanceState) { // ... battleId = getIntent().getExtras().getString(EXTRA_BATTLE_ID); //    ,       if (savedInstanceState == null) getSupportFragmentManager() .beginTransaction() .replace(R.id.container, ChoosePlayerFragment.newInstance(battleId)) .commit(); } @Override public void onPlayerChoose(String playerId, String opponentId) { //   -       getSupportFragmentManager() .beginTransaction() .replace(R.id.container, StatsFragment.newInstance(battleId, playerId, opponentId)).addToBackStack(null) .commit(); } }
      
      





ChoosePlayerFragmentは、遞択したゲヌムの状態ノヌドを読み取り、そこから2人の察戊盞手を匕き出しお、ボタンに名前を入れたす詳现に぀いおは、蚘事の最埌にあるリンクを参照しおください。



この時点で、StatsFragmentに぀いお話す䟡倀がありたす。StatsFragmentは、察戊盞手のステヌタスの倉化を远跡し、それらを衚瀺したす。



 public class StatsFragment extends Fragment { // ... @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { // ... //     ,        // addSingleValueEventListener    , //       database.getReference().child("settings") .addSingleValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot settings) { maxHp = Integer.parseInt(settings.child("max_hp").getValue().toString()); maxMana = Integer.parseInt(settings.child("max_mana").getValue().toString()); } // ... }); //         database.getReference().child("battles").child(battleId).child("states").child(playerId) .addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot player) { hp = player.child("hp").getValue().toString(); mana = player.child("mana").getValue().toString(); hpView.setText("HP: " + hp + "/" + maxHp); manaView.setText("MANA: " + mana + "/" + maxMana); } // ... }); //         database.getReference().child("battles").child(battleId).child("states").child(opponentId) .addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot opponent) { opponentName.setText(opponent.child("name").getValue().toString()); if (opponent.hasChild("hp") && opponent.hasChild("mana")) { int hp = Integer.parseInt(opponent.child("hp").getValue().toString()); float thidPart = maxHp / 3.0f; if (hp <= 0) { opponentView.setImageResource(R.drawable.grumpy); return; } else if (hp < thidPart) { opponentView.setImageResource(R.drawable.sad); return; } else if (hp < thidPart * 2) { opponentView.setImageResource(R.drawable.neutral); return; } opponentView.setImageResource(R.drawable.smile); } } // ... }); } }
      
      





デモグッズを組み立おたのはこれだけです。 完党な゜ヌスコヌドはgithubに存圚し、さらなるアむデアは私たちの想像の䞭に存圚したす。 ここで、ファむルを䜿甚しおモデルを完成させ、蚭蚈に぀たずいおコンテンツを䜜成しおいたす。 そしお、アむデアが生き残ったら、それは確かにさらにいく぀かの圫像を生み出したす。



All Articles