見出し画像

[BGA Studio] Hello BGA! その1

前回の記事でプロジェクトのフォルダ構成を説明したので、今回は順番に手札を1枚選んで出していき、出し切ったら上がりになるだけの単純なゲーム挙動を実装して、BGA Studioでゲームをどうやって開発するのかを簡単に説明していきます。

なお、BGA Studioの規約に従うならば、プログラム内のコメントとゲーム内テキストは全て英語で記述する必要があります。これは、テキスト翻訳のベース言語が英語であるということ以外に、ゲームのサポート対応を誰かが行う時、プログラムが誰でも読める言語で書かれている必要があるためです。

ちなみに、今回の連載で制作したプロジェクトはこちらにあります。ご自由にどうぞ。

準備するもの

・ボードゲームプロジェクト
前々回の記事で説明した手順で、作成したプロジェクトフォルダをFTP経由でダウンロードして、手元にコピーしておきます。

・トランプ画像
間に合わせのカード画像素材として利用します。FTPサーバにあるサンプルプロジェクトのhearts内にimg/cards.jpgがあるので、これをダウンロードしておくとよいでしょう。

画像1

このファイルは52枚の表面のカードが一つの画像になっています。
BGAでは、たくさんの画像ファイルをユーザにダウンロードさせるのは推奨していません。そのため、このように同じサイズの画像を一つにまとめて扱うようにしています。

・テキストエディタ
BGA Studioでは、MySQL, PHP, Javascript, HTML, CSSファイルを扱います。
どのようなエディタや開発環境を使うにせよ、FTPサーバにアップロードしないとゲームに反映されません。
FTP機能も持つエディタにするなり、IDEにするなり、ご自由にお選びください。

ゲーム情報を設定する

gameinfos.inc.phpへは、ゲームの分類などの情報から、プレー可能人数、同率順位を許容するか、といったゲームルールについて記述を行います。
詳しい設定ルールはファイル本体にコメントで丁寧に記載されているため、後々そちらを確認していただくとして、ここでは一人でもとりあえずテストできるようにするために、プレー人数を変更しておきます。

...

// Board game geek ID of the game
'bgg_id' => 15000,


// Players configuration that can be played (ex: 2 to 4 players)
'players' => [ 1,2,3,4 ],    

// Suggest players to play with this number of players. Must be null if there is no such advice, or if there is only one possible player configuration.
'suggest_player_number' => null,

...

状態遷移の作成

プログラムを書き始める前に、まずはゲームの状態遷移を作成します。

今回作ろうとしているのは、順番になったらカードを出していくだけのゲームなので、状態は以下の4つです。

1.ゲームのセットアップ → 2.へ移動
2.各プレイヤーの手番 → 3.へ移動
3.手番の遷移・ゲーム終了判定 → 2.か4.へ移動
4.ゲーム終了

これらをstates.inc.phpに記入します。

$machinestates = [
   // The initial state. Please do not modify.
   1 => [
       "name" => "gameSetup",
       "description" => "",
       "type" => "manager",
       "action" => "stGameSetup",
       "transitions" => [ "" => 2 ]
   ],

   // Note: ID=2 => your first state
   2 => [
   		"name" => "playerTurn",
   		"description" => clienttranslate('${actplayer} must play a card'),
   		"descriptionmyturn" => clienttranslate('${you} must play a card'),
   		"type" => "activeplayer",
   		"possibleactions" => [ "playCard" ],
   		"transitions" => [ "playCard" => 3 ]
   ],

   // Go to next player or finish this game.
   3 => [
       "name" => "nextPlayer",
       "type" => "game",
       "action" => "stNextPlayer",
       "updateGameProgression" => true,
       "transitions" => [ "nextPlayer" => 2, "endGame" => 99 ]
   ],

   // Final state.
   // Please do not modify (and do not overload action/args methods).
   99 => [
       "name" => "gameEnd",
       "description" => clienttranslate("End of game"),
       "type" => "manager",
       "action" => "stGameEnd",
       "args" => "argGameEnd"
   ]
];

各状態は番号で区別されており、各状態から別の状態への遷移があるかどうかも、番号で記述しています。状態に割り当てる番号は連番である必要はありません。状態3の番号を50にしたり、200にしても、他の状態からの遷移が正しく設定されていれば動作します。

BGA Studioでは一部の状態番号は用途が決まっているため(1=初期化ステート、99=ゲーム終了ステート)、注意が必要です。

各テキストがclienttranslate関数にラッピングされていますが、これは翻訳対象のテキストであることを示しています。テキスト内で使われているactplayer, youは、それぞれ自動的に手番プレイヤー名あなたに翻訳されます。

サーバ:テーブルを定義する

状態の次は、ゲームに使うカード用のテーブル定義をdbmodel.sqlへ記述します。
今回はカードを使うので、BGA Studioで用意されているDeckモジュールに合う形でカード用のテーブルを定義します。

-- All cards' state informations.
CREATE TABLE IF NOT EXISTS `cards` (
 `card_id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
 `card_type` varchar(16) NOT NULL,
 `card_type_arg` TINYINT UNSIGNED NOT NULL,
 `card_location` VARCHAR(16) NOT NULL,
 `card_location_arg` int(11)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

プレイヤー情報など、他のテーブル定義は書いていませんが、それらはBGAのフレームワークが自動的に定義してくれています。

サーバ:初期化処理を記述する

状態とカード情報テーブルを定義したので、次はサーバ側の初期化処理を[ゲーム名].game.phpへ記述します。

ゲームの初期化処理を書く前に、カード(Deck)モジュールのインスタンス生成処理をコンストラクタに記述します。これによって、クライアントから要求があった時ならいつ(ゲームの初期化処理以外の時)でも、カードの操作ができるようになります。

   public function __construct()
   {
       ...
       
       $this->cards = self::getNew("module.common.deck");	//Deckモジュールを指定してインスタンス生成
       $this->cards->init("cards");	//'cards'テーブルを使うよう指定して初期化
   }

ゲームの初期化処理は、クラス内のsetupNewGameに記述します。
最初から、ある程度の初期化処理は書かれているので、後ろの方にカード(Deck)の初期化を追記します。

   protected function setupNewGame( $players, $options = array() )
   {    
       ...
       $cards = [];

       for ($cardNo = 1; $cardNo <= 52; $cardNo++) {
           $cards[] = [
               'type' => 0,
               'type_arg' => $cardNo,
               'nbr' => 1
           ];
       }

       $this->cards->createCards($cards, 'deck');
       $this->cards->shuffle('deck');
       
       foreach ($players as $playerID => $player) {
           $this->cards->pickCards(5, 'deck', $playerID);
       }
       ...
       $this->activeNextPlayer();
   }

普通のトランプであればスートと数字で種類・数値を分けるところですが、今回は特に何も考えずに番号を割り当て、各々1枚存在するということにして、初期化(createCards)を行っています。初期化されたカードは、最初は全て山札(deck)に置かれます。
これだけだと1~52までが順番に並んだ山札になってしまうので、shuffleでカードを切りました。

その後、各プレイヤーがpickCardsによって、10枚ずつ山札(deck)から初期手札を引いています。

サーバ:ゲーム状態を返すようにする

BGAのゲームは、プレイヤーがいつWebページを開きなおしてもいいように作らなくてはなりません。ゲーム開始時も含めて、ゲームの今の状態を返すサーバ関数は、getAllDatas関数です。

protected function getAllDatas()
{
  $result = array();

  // Get information about players
  // Note: you can retrieve some extra field you added for "player" table in "dbmodel.sql" if you need it.
  $sql = "SELECT player_id id, player_score score FROM player ";
  $result['players'] = self::getCollectionFromDb( $sql );

  $currentPlayerID = self::getCurrentPlayerId();    // !! We must only return informations visible by this player !!

  $result['player_cards'] = array_values($this->cards->getCardsInLocation("hand", $currentPlayerID));

  return $result;
}

ほとんどは最初から定義されている部分です。最後のplayer_cardsのくだりは新たに追加しました。
getAllDatasはゲームの状態を返す関数ですが、全プレイヤーの隠された情報(見えない手札等)を返すわけにはいかないので、応答を要求してきたプレイヤー(getCurrentPlayerId関数)の手札の情報だけを返すようにしています。

サーバ:Webページテンプレートを変更する

手札カードをWebブラウザ上で選択できるようにするためのUIを、HTMLテンプレート([ゲーム名]_[ゲーム名].tpl)内に用意します。

元々書かれている

This is your game interface. You can edit this HTML in your ".tpl" file.

という箇所を、以下で置き換えます。

<div id="player_hand" class="whiteblock">
  <h3 id="inhand_header" style="display:table;width:100%;"><span style="display:table-cell;">{IN_HAND}:</span></h3>
  <div id="player_cards" class="card_inhand"></div>
</div>

id=player_cardsのDOM要素がカードを配置するノードで、その上に「手札」と書かれたヘッダを用意します。ヘッダ部分には{IN_HAND}と記述していますが、これは、置換対象のテキストであることを表しています。
実際のテキストは、[ゲーム名].view.php内のbuild_page関数内に記述します。

function build_page( $viewArgs )
{
  ...
  
  $this->tpl['IN_HAND'] = self::_("My hand");
}

テキストはself::_()でくくったテキストを渡していますが、これは翻訳対象のテキストであることを示しています。クライアント側でも似たような書き方をして、翻訳対象であることを示します。

テンプレートの編集はここまでで終了です。

クライアント:画像ファイルを配置する

プレイヤーが遊ぶ時に利用するトランプの画像ファイルを配置します。場所はimgフォルダ内であれば問題ありません。

画像2

最初にコピーしたcards.jpgをimgフォルダ内に配置しておきます。

クライアント:カード(Stock)モジュールを使用する

次は、[ゲーム名].jsを編集し、Stockモジュールを使って手札を配置できるようにします。

まずは、Dojoの外部Javascript読み込み対象にStockを追加します。

define([
  ...
  "ebg/counter",
  "ebg/stock"
],
function (dojo, declare) {
  ...
});

BGA Studioで提供しているモジュールとDojo Toolkitのモジュールは、細かいパス指定なしで読み込むことができます。

次に、Webページを開いてサーバからゲーム情報を受け取った時に実行されるsetup関数を変更して、Stockの利用準備を行います。

setup: function(gamedatas) {
  ...
  
  this.playerHand = new ebg.stock();
  this.playerHand.create(this, $('player_cards'), 936 / 13, 384 / 4);
  this.playerHand.image_items_per_row = 13;
  this.playerHand.centerItems = true;
  this.playerHand.setSelectionMode(0);
  
  for(var cardNo = 1; cardNo <= 52; cardNo++)
  {
    this.playerHand.addItemType(cardNo, cardNo, g_gamethemeurl + 'img/cards.jpg', cardNo - 1);
  }
  
  for(var cardPos in gamedatas.player_cards){
    var card = gamedatas.player_cards[cardPos];
    this.playerHand.addToStockWithId(card.type_arg, card.id, 'player_hand');
  }
  ...
},

詳細はStockのページに書かれていますが、ここでも軽く解説していきます。
なお、setup関数に渡される引数gamedatasは、サーバ側のgetAllDatas関数が返すデータです。

  this.playerHand = new ebg.stock();
  this.playerHand.create(this, $('player_cards'), 936 / 13, 384 / 4);
  this.playerHand.image_items_per_row = 13;
  this.playerHand.setSelectionMode(0);

Stockのインスタンス生成と初期化を行っています。create関数にて、カードUIを配置するdiv要素と、カード1枚当たりのサイズを設定しています。image_items_per_rowに渡しているのは、画像ファイル(img/cards.jpg)が1列辺り何枚のカード画像を持っているかの情報です。今回の場合はスートごとに列になっているため、13を設定しています。13を超える番号のカードは、次の列を参照します。
setSelectionMode関数へは、カードの選択モードを渡しています。ここでは0(選択不可)を指定しています。これについては後ほど。

次は、カードの種類を指定する処理です。

  for(var cardNo = 1; cardNo <= 52; cardNo++)
  {
    this.playerHand.addItemType(cardNo, cardNo, g_gamethemeurl + 'img/cards.jpg', cardNo - 1);
  }

52枚のカードの種類と画像の対応を、Stockへ登録しています。あくまでカードの種類と画像を結びつけるだけの処理で、実際の手札を初期化するわけではありません。

cardNoを2つ指定していますが、第二引数はカードを並べ替える際の参照値です。
カードの画像ファイル指定は急に出てきたグローバル変数g_gamethemeurlを参照していますが、これはBGAにおけるゲームの配置場所を表しています。
最後の引数は、画像ファイル上のどの場所にあるカード画像を参照するのかを指定しています。一列辺りの枚数はimage_items_per_rowに渡した数となり、左上から0番目, 1番目, 2番目, ... となります。例えば、「28」を渡したら、参照画像はクラブの4になります。

  for(var cardPos in gamedatas.player_cards){
    var card = gamedatas.player_cards[cardPos];
    this.playerHand.addToStockWithId(card.type_arg, card.id, 'player_hand');
  }

この部分は、プレイヤーの手札を初期化しています。Deckテーブル定義より、type_argにカード種類、idにカードidが入っているので、そのまま使います。

まとめ

Hello BGA!が思っていたよりもはるかに長い記事になりそうなので、準備が済んだ段階で区切ることにしました。続きはこちらです。

この記事が気に入ったらサポートをしてみませんか?