【Unity】タイピングゲームの制作 (文字列の取得・設定)

というわけで、前回からかなり間が空いてしまいましたが、気まぐれと勢いで作っているタイピングゲームの制作日記(その2)です。

前回の記事はこちら

【Unity】タイピングゲームの制作 (入力とマッチング)

20170323_01

今回は「入力対象となる文字列の取得・設定」編です!
前回はスクリプト上で決め打ちだったので、タイピングゲームでも何でもありませんでした。
文字列(言葉)の管理と取得、入力すべきアルファベットの認識までやってみます。

ちなみに Unity とタイトルに入れつつ、Unity っぽいことはあまりやっておりません。
C# のスクリプト作成くらいでしょうか。
どうしても裏側の話題になってしまうので、興味がある方だけお付き合い下さい。

入力対象とする文字列の取得

当たり前ですが、入力対象となる文字列は毎回ランダムに設定する必要があります。
平仮名の50音からランダムで取得して組み合わせても言葉にならないので、どこかで言葉をまとめて管理しないと厳しいです。
どのようなタイピングゲームにするのかにも寄りますが、一般的な単語や文章、ことわざ、四字熟語何かが設定されていることが多いです。
キャラゲーよりのタイピングソフトは台詞なんかも混じっていたりしますね。

で、当初は YAML ファイルに追加していくことを考えましたが、言葉が多くなってくると管理が大変になります。
最低でも「画面への表示名」と「入力する文字列」を設定する必要があります。
また、「単語ごとのカテゴリ」とか「入力数」「難易度」とかも設定できるようにしておくと後々役に立ちそう。

・・・という場合、データベースで管理するのが一番よさそうです。
取得時に条件指定やグルーピングが出来るので、言葉の選定に無駄なロジックを挟まなくてもいけそうですね。

本ブログは「さくらのVPS」上に立てており、WordPress を使用しています。
なので VPS 上の MySQL サーバに専用のデータベースを作成し、言葉管理用のテーブルを作成します。

CREATE TABLE words (
    id integer not null AUTO_INCREMENT PRIMARY KEY,
    target_word varchar(200) not null,
    logical_word varchar(200) not null
) ENGINE=InnoDB;

主キーはないよりあったほうが管理しやすいでしょう。
「target_word」が入力対象とする平仮名、「logical_word」が画面上に表示する言葉の文字列です。
適当に単語を挿入しておきます。

INSERT INTO words VALUES (NULL, 'かんがるー', 'カンガルー');
INSERT INTO words VALUES (NULL, 'きりん', 'キリン');
INSERT INTO words VALUES (NULL, 'うさぎ', 'ウサギ');
INSERT INTO words VALUES (NULL, 'あざらし', 'アザラシ');
INSERT INTO words VALUES (NULL, 'はわいもんくあざらし', 'ハワイモンクアザラシ');
INSERT INTO words VALUES (NULL, 'ちちゅうかいもんくあざらし', 'チチュウカイモンクアザラシ');

MySQL サーバと通信して取得する過程は長いので割愛しますが、「Unity MySQL」とかでぐぐると結構ヒットするので問題ないはず。
自分は「WWW クラスでサーバにリクエストを送り」「PHP から PDO で SQL を実行し」「レスポンスの JSON をパースして保存」という手順で実装しました。
C# 単独で MySQL に接続する dll もあるっぽいので、そのあたりはお好みでよさそうです。

1つハマったこととして、データベースから取得したマルチバイト文字が16進表記になってしまう問題が発生しました。
こういう時は大抵文字コードにブレがあったりするものですが、PDO での接続やデータベース・テーブルは UTF-8、PHP の内部エンコーディングも UTF-8 です。

$dsn = 'mysql:host=host;dbname=dbname;charset=utf8mb4';
$pdo = new PDO($dsn, $this->db_user, $this->db_password);

どうも PDO インスタンス生成時の dsn がよろしくなかったようで、charset に「utf8mb4」を指定したら解決しました。
「utf8」では16進になってしまいます。
また、MySQL をあまり使っていない人は「UTF-8」と指定しがちですが、小文字ハイフンなしの「utf8」が正しい指定です。
dsn に無効な charset を渡すと無視される(と思われる)ので注意しましょう。

あとはひたすら台詞を挿入していくだけなのですが、これが非常に面倒です。
ひとまず動作確認に必要な分だけ突っ込んでいますが、本格的に追加していく際は方法を考える必要がありそうです。
とはいえ「平仮名」と「表示名」で分けて保存する関係上、気合で用意するしかないかもしれません。

実際に入力するアルファベットの表示

これで「表示名」や「入力する文字列」は取得できます。
が、画面上に入力するアルファベットが表示されないのは不親切であり、そのアルファベットをスクリプト側で把握していなければ、入力時のマッチングも行えません。

そこで、平仮名50音と「入力するアルファベット」を対応付ける YAML ファイルを作成します。
単に「入力すべきアルファベットを入れる」の場合、複数通りの打ち方のある文字の制御が厳しくなってしまいます。(「ぁ」は「xa」か「la」など)
後々のことを考えるとアルファベットで直接定義するのは望ましくないです。

また、対応付けのキーとなる平仮名も、そのまま「あ」とか使用するのは避けたいところ。
もし文字コードが異なる環境で動作させた場合、平仮名が文字化けして対応付けが出来ないことになると思われます。
ということで、キーは50音を16進数の文字コードへ変換したものを使います。

実際に作った YAML がこちらです。

E38181: xa,la
E38182: a
E38183: xi,li
E38184: i
E38185: xu,lu
E38186: u
E38187: xe,le
E38188: e
E38189: xo,lo
E3818A: o
E3818B: ka,ca
E3818C: ga
E3818D: ki
E3818E: gi
E3818F: ku,cu
E38190: gu
E38191: ke
E38192: ge
E38193: ko,co
E38194: go
E38195: sa
E38196: za
E38197: si,shi
E38198: zi
E38199: su
E3819A: zu
E3819B: se
E3819C: ze
E3819D: so
E3819E: zo
E3819F: ta
E381A0: da
E381A1: ti
E381A2: di
E381A3: xtu,ltu
E381A4: tu
E381A5: du
E381A6: te
E381A7: de
E381A8: to
E381A9: do
E381AA: na
E381AB: ni
E381AC: nu
E381AD: ne
E381AE: no
E381AF: ha
E381B0: ba
E381B1: pa
E381B2: hi
E381B3: bi
E381B4: pi
E381B5: hu,fu
E381B6: bu
E381B7: pu
E381B8: he
E381B9: be
E381BA: pe
E381BB: ho
E381BC: bo
E381BD: po
E381BE: ma
E381BF: mi
E38280: mu
E38281: me
E38282: mo
E38283: xya,lya
E38284: ya
E38285: xyu,lyu
E38286: yu
E38287: xyo,lyo
E38288: yo
E38289: ra
E3828A: ri
E3828B: ru
E3828C: re
E3828D: ro
E3828E: xwa
E3828F: wa
E38293: nn
E383BC: '-'

50音を濁音・小文字込みで網羅し、伸ばし用のハイフンを入れたマッピングファイルです。
カンマ区切りのものは複数通りの入力が可能なものです。

データベースから取得した平仮名を1文字ずつ判定し、上のマッピングファイルに従って「どのアルファベットを打つか」を設定していきます。
平仮名を16進へ変換する必要があるので、以下のようなユーティリティクラスを作成してみました。

using System;
using System.Collections.Generic;
using System.Text;

public class EncodeUtility {

    /// <summary>
    /// 指定した文字列を16進数コードに変換し、文字ごとに配列へ格納して返す
    /// </summary>
    public static List<string> convertHex(string text) {
        List<string> hexes = new List<string>();

        foreach (char c in text) {
            // 入力文字列のバイトコード取得
            byte[] text_bytes = Encoding.UTF8.GetBytes(c.ToString());

            // バイトコードを16進数に変換
            string hex = BitConverter.ToString(text_bytes);
            hex = hex.Replace("-", "");

            hexes.Add(hex);
        }

        return hexes;
    }
}

平仮名1文字ずつが List で返ってくるので、マッピングファイルから入力するアルファベットを判定していきます。
(長くなるので関数だけ記載します)

/// <summary>
/// 入力対象の16進コード配列をアルファベット文字列に変換する
/// 候補がカンマ区切りで複数ある場合、最初のアルファベットを設定する
/// </summary>
public string getInputAlphabet(List<string> hexes) {
    string alphabet = "";

    foreach (string hex in hexes) {
        // マッピングから16進に一致する値を取得
        string mapping_value = alphabetMapping.getValue(hex);

        // 候補が複数ある場合は最初のアルファベットを取得
        int separate_index = mapping_value.IndexOf(SEPARATER);
        if (0 < separate_index) {
            string value = mapping_value.Substring(separate_index);
            alphabet += mapping_value.Replace(value, "");
        } else {
            alphabet +=  mapping_value;
        }
    }

    return alphabet;
}

かなり端折っていますが、alphabetMapping は List 型で、yaml からパースしたマッピングのキーと値を管理します。
YAML に関するあれこれは以下の記事で書いているので、そちらをご参照下さい。

【Unity】メッセージやパラメータをYAMLファイルで定義・参照する

マッピングの List から16進文字コードを元にアルファベットを取得、IndexOf() でセパレータごとに区切り、最初の値を設定します。
あとはアルファベットを全部結合したものを返すだけです。

これでアルファベットの設定はできますが、複数通りの入力への対応や、小文字が入る場合の入力方法 (「しゃ」なら「sya」「sixya」など) が出来ていないので、完全とは言い難い代物です。
そのあたりの解決がタイピングソフト実装の最難関かなーと思っています。

まとめ

かなり適当な記事になってしまいましたが、文字列の取得と設定について考えてみました。
今回までで、「入力欄生成」「入力文字のマッチング」「文字列の設定・表示」まで完成しています。
細かい部分はさておき、やっとそれっぽくなってきました!

次回はゲームっぽく、プレイヤーキャラクターと敵キャラクターの制御あたりを作ろうかと考えています。
単に言葉だけを打つなら代わりはいくらでもあるので、「ゲームにする」のは重要ですね。

【Twitter】ランパの台詞botを作成してみる その3

というわけで、今回は「ランパの台詞bot」制作その3です!
少しずつ作っていた台詞集ですが、ゲーム開始からエンディングまでの台詞を集め終わりました!

前回同様、改良点や苦労した点を載せていきます。
ちょっと長めの記事ですが、今回でほぼ完成までいけた・・・かと思います!

前回の記事は↓になります!

【Twitter】ランパの台詞botを作成してみる その2

ツイートした台詞を一定期間再ツイートしないようにする

かなりレパートリーは豊富になりましたが、それでも短期間に連続してツイートしてしまうことがあります。
以下は1月19日のツイートです。

種類が豊富なのに短期間で同じ台詞を言わせるのはいただけないです。
そんなわけで、1回ツイートした台詞は少し期間を空け、連続でツイートしないようにします。

これを実現するにはサーバ側でデータを取っておくか、過去のツイートを取得して判定するしかありみあせん。
幸い、WordPress 用に MySQL が入っているので、それを使ってサーバ側で判定してみます。
当初「台詞をデータベースで管理する」ことは想定していなかったので、台詞ごとにシーケンシャルなIDなんていいものはありません。
当然ながら一意に識別できるコードなんてものもありません。

そこで使用するのがハッシュ値を計算する md5() です!
これで台詞をハッシュ値に変換し、識別しやすい形に置き換えて保存します。
普通に台詞を格納するのも手ですが、最大130文字近くになる台詞がある上、マルチバイト文字をそのまま入れて比較・・・というのは避けたかったためです。

下準備として、データベースとテーブルを作成しておきます。
端末上から直接作ってしまいましょう。

CREATE DATABASE twitter_bot;

use twitter_bot;

CREATE TABLE rampa_tweet_history (
    id INT PRIMARY KEY AUTO_INCREMENT NOT NULL,
    message TEXT NOT NULL,
    create_date DATETIME NOT NULL
);

データベース名やテーブル名は適当です。
id は不要かもしれませんが、一応作っておきます。
create_date は一定期間以上経過した台詞を弾くために必要です。

前回作成したPHPファイルを修正していきます。
それなりに長くなりましたが、以下がソースになります!

<?php

require_once("twitteroauth-master/autoload.php");
require_once('twitteroauth-master/src/TwitterOAuth.php');

use Abraham\TwitterOAuth\TwitterOAuth;

/**
 * ランパの台詞集から台詞を抽出・ツイートするクラス
 */
class TwitterRampaBot {

    private $customer_key;
    private $customer_secret;
    private $access_token;
    private $access_token_secret;

    private $db_user = 'db_user';
    private $db_password = 'db_password';
    private $interval_spec = 'P3D';

    const file_path = 'rampa_serifu.txt';
    const max_tweet_len = 140;

    /**
     * コンストラクタ
     */
    public function __construct()
    {
        $this->customer_key = "customer_key";
        $this->customer_secret = "customer_secret";
        $this->access_token = "access_token";
        $this->access_token_secret = "access_token_secret";

        mb_internal_encoding('UTF-8');
    }

    /**
     * つぶやきを送信する
     */
    public function tweet()
    {
        $message = $this->getTweetWord();

        // 文字数チェック
        if (self::max_tweet_len < mb_strlen($message)) {
            trigger_error('ツイート可能な文字数を超えています。', E_USER_ERROR);
            exit;
        }

        // サーバにつぶやきを送信
        $connection = new TwitterOAuth($this->customer_key, $this->customer_secret, $this->access_token, $this->access_token_secret);
        $connection->post("statuses/update", array("status" => $message));
    }

    /**
     * ツイート用の文字列を取得・整形する
     *
     * @return string
     */
    private function getTweetWord()
    {
        // テキストファイルから文字列読み込み
        $input_lines = file(self::file_path);
        $words = array();

        // データベース接続
        try {
            $dsn = 'mysql:host=localhost;dbname=twitter_bot;charset=utf8';
            $pdo = new PDO($dsn, $this->db_user, $this->db_password);
        } catch (\Exception $e) {
            trigger_error('データベースへの接続に失敗しました。', E_USER_ERROR);
            exit;
        }

        // 先頭 # の行を取得するまでループ
        while (true) {
            $line_num = rand(0, count($input_lines));

            // 先頭が # で始まる行を抽出
            if (isset($input_lines[$line_num]) && preg_match('/^\# .*$/', $input_lines[$line_num])) {
                $words = array();

                // # 以降を場面として取得
                $scene = $this->excludeLineFeed($input_lines[$line_num]);
                $scene = str_replace('# ', '', $scene);

                // 複数行の台詞を全て取得するまでループ
                for ($i = $line_num + 1; ; $i++) {
                    // 文字列か空or改行コードのみの場合は終了
                    if (empty($input_lines[$i]) || preg_match('/^' . PHP_EOL . '$/', $input_lines[$i])) {
                        break;
                    }

                    $words[] = $this->excludeLineFeed($input_lines[$i]);
                }

                // 台詞と発言場面を設定
                $message = '';
                foreach ($words as $word) {
                    $message .= $word . PHP_EOL;
                }
                $message .= '【' . $scene . '】';

                // 台詞が一定期間内にツイートされているかチェック
                if (!$this->checkMessage($pdo, $message)) {
                    continue;
                }

                // ツイートする台詞を保存
                $this->insertMessage($pdo, $message);

                break;
            }
        }

        $pdo = null;

        return $message;
    }

    /**
     * 台詞がデータベース内に保存されているかチェックする
     *
     * @param PDO $pdo
     * @param string $message
     */
    private function checkMessage(PDO $pdo, $message)
    {
        $sql = 'SELECT count(id) FROM rampa_tweet_history WHERE message = :message AND create_date > :create_date';
        $date = new DateTime();
        $date->sub(new DateInterval($this->interval_spec));

        $pdo_stat = $pdo->prepare($sql);
        $pdo_stat->bindParam(':message', md5($message));
        $pdo_stat->bindParam(':create_date', $date->format('Y-m-d H:i:s'));
        $pdo_stat->execute();

        // ツイートされていた場合は台詞を再取得
        if (0 < $pdo_stat->fetchColumn()) {
            return false;
        }

        return true;
    }

    /**
     * 台詞をデータベースへ挿入する
     *
     * @param PDO $pdo
     * @param string $message
     */
    private function insertMessage(PDO $pdo, $message)
    {
        $sql = 'INSERT INTO rampa_tweet_history (message, create_date) VALUES (:message, :create_date)';
        $date = new DateTime();

        $pdo_stat = $pdo->prepare($sql);
        $pdo_stat->bindParam(':message', md5($message));
        $pdo_stat->bindParam(':create_date', $date->format('Y-m-d H:i:s'));
        $pdo_stat->execute();
    }

    /**
     * 指定した文字列から改行コードを除外する
     *
     * @param string $str
     * @return string
     */
    private function excludeLineFeed($str)
    {
        return str_replace(PHP_EOL, '', $str);
    }
}

$twitterRampaBot = new TwitterRampaBot();
$twitterRampaBot->tweet();

exit;

?>

※ツイートの取得や送信部分は前回の記事をご参照下さい。

MySQL への接続には PDO_MySQL を使用しました。
明確な理由はありませんが、ガチガチに書くわけでもないので、PDO が手っ取り早いかなーと。
仕事ではDB関連はフレームワークに任せてしまうので、別の意味で苦戦していたりもします。

ポイントは checkMessage() 内の処理です。
一定期間は interval_spec で定義し、P3D を設定しておきます。
これを DateTime::sub() で指定することで、「現在時間から3日前の日時」になります。
このあたりの指定方法は公式マニュアルに書いてあります。
http://php.net/manual/ja/dateinterval.construct.php

「データベース内に台詞が存在するか」だけ欲しいので、実行する SQL は「SELECT COUNT(id)」です。
execute() 実行後、fetchColumn() を実行することでレコード数を取得することができます。
あとは if で評価して真偽を返し、false であれば continue で再度台詞を取得します。true であれば台詞をハッシュ化してデータベースに保存します。

作りたての状態で実行すると、データベース接続が出来なかったり、日付型の比較でしくじったりするので、事前にテストを行うことをおすすめします。
端末上から直接PHPファイルを指定して実行すればOKです。
その場合、「サーバにつぶやきを送信」の部分をコメントアウトし、echo や var_dump() を使用しましょう。
テスト用として別ファイルで作ってしまうと安全です。

乱数の生成方法の変更

そもそもPHPの rand() はいろいろと問題があるようで、これを使用していること自体がまずいみたいです。
より良い乱数生成と数倍の速度を持つという mt_rand() で置き換えます。
使い方は変わらないようなので、本当に置き換えるだけ!

$line_num = mt_rand(0, count($input_lines));

「より良い乱数」になったかどうかはまだ分かりません。
ただ rand() では特定の台詞のみ偏って選出されたりしたので、そのあたりの改善が期待できる・・・かもしれません。

上で記載した「一定期間内にツイートした台詞は除外」機能と合わせれば、少なくとも同じ台詞が続いてしまうことは避けらます。
データベースやPHPのモジュールにもよりますが、気になっている方は試してみてはいかがでしょうか。

苦労したこと

前回に引き続き、bot製作に関して苦労したことを紹介します。

抜けが無く収集できているか心配になる

抜けがないように収集したつもりですが、やはり不安な部分もあります。
例えばランパの変身が可能なエリアで、且つ該当の変身をまだ取得していない場合に特殊な台詞があります。

今のボクのチカラじゃここではへんしんできないみたいだ・・・
ぼうけんしてフシギな石を見つけたら、またへんしんできるようになるかも・・・?
気になるところがあるかもしれないけれど、ひとまず今は先にすすんで、あとでここにもどってみようよ

今のボクのチカラじゃここではへんしんできないみたいだ・・・
ぼうけんしてフシギな石を見つけたら、またへんしんできるようになるかも・・・?
ひとまず今はちがうみちをすすんでみて・・・あとでここにもどってみようよ、スタフィー

この際、場所によって微妙に台詞が違ったりします。
自分は後半で気付きましたが、もしかしたら前半の隠しエリアなどにも設定されているかもしれません。
その場合、ストーリーを最初からやり直すことになりかねません。

手動で収集・入力することに限界を感じる瞬間ですが、メインとなるストーリー上のイベントはすべて搭載しているので、台詞botとしては妥協ラインかなーとも思います。
裏を返せばそれだけの台詞パターンがある位、ゲーム自体の作りが丁寧ということですね。
攻略本で変身するエリアだけを確認する等、漏れがないような施策を考えていきたいところです。

かなり長い台詞がある

例として、以下はステージ8-3後半の会話です。
途中キョロスケが1~2回話す以外、ほぼランパのみが喋り続けます。

す、スタフィー・・・なかまが・・・なかまが・・・

みんなっ、しっかりっ!!へんじをしてーーー!!
ダメだ・・・みんなチカラをダイールにすいとられてうごけないみたい・・・

ダイール・・・何てことを・・・
ゆるさない!ゆるさないぞーぉおお!!!
何があっても・・・みんなをもとの元気なすがたにもどすんだ!!!

ボクをダイールからまもるために、みんなはぎせいになった・・・
みんなは・・・なかまは・・・かぞくどうぜんなんだ
ボクには・・・パパもママもいないから・・・みんながかぞくなんだ!

・・・うん、ボクが生まれてすぐ・・・パパもママもしんじゃったんだって
すごく小さかったからよくわからなかったけど
さみしくないようにって、みんながやさしくしてくれた

ボク、こうやってスタフィーたちとであえてわかったんだ
スタフィーも王子、ボクも王子、おなじ王子なのに、スタフィーはいつもボクをたすけてくれた
このままじゃいけないんだ!スタフィーのように、強くやさしく・・・みんなをまもれる王子にならなきゃ!

ねぇ、スタフィー ボクやるよ!
ダイールをたおして・・・なかま・・・かぞくをとりもどすんだ!!!
ぜったいこのままでおわらせるわけにはいかない!さあ!行こう!

台詞自体はランパの過去や成長、決意が見て取れる大変よろしいものです。
ただし、bot的には「ツイート可能な文字数は140まで」という制限に引っ掛かります。
仕方ないので文としての意味合いが切れない程度に分けていますが、それでも140文字以内に収まるか怪しくなる台詞もあります。
上の例で言えば、「ボク、こうやってスタフィーたちと~」から3行で129文字です。

この例に限らず、稀に非常に長い台詞があります。(7-4など)
こればかりはどうしようもないので、やはり文としての意味合いを保ちつつ分けています。
一部強引な分け方をしていますが、「こんな台詞がある」というbotであり、所謂「名言集」ではないので・・・と言い訳しておきます。

余談ですが、その割にはエンディングは超あっさりで、ダイール撃破後にランパが発した明確な台詞は以下の2つのみです。

スタフィー!だいじょうぶ?

スタフィー!ほんとうにありがとう
またあそびにきてね

後者はスタッフロール後の一番最後の台詞で、「また遊んでね!」的な意味合いが含まれていると思われるため、実質1つのみです。
まあエンディングなのでナレーションベースということもありますが、ちょっと驚きました。

「ランパのあつめもの」の存在を忘れる

キョロスケ様のかばんメニューに「あつめもの」があります。
このメニュー、ストーリーの進行具合に応じてランパのコメントがあります。

DSC_0306

が、このメニュー自体の存在をステージ5まで忘れていました。
更新タイミングは(たぶん)ステージクリア時とエンディング後なので、ステージ5からメモっていけばやり直す範囲も抑えられますが、完全にノーマークだったので精神的ダメージが大きかったです。

ちなみに「今日のゲスト」の各台詞と、クリア後にあつめものを見た際の「ランパのにちじょう」シリーズはすべて回収しました。

DSC_0326

ただし、「ランパのにちじょう (海)」だけ異常に出現率が低く逃しそうになりました。
内容は上画像の通りですが、さらっとすごいことを言っているので意図的に低確率なのかもしれません。
隕石を弾き返すほどの回転をランパが習得できるとは思えませんが・・・。

「キョロスケ様のキャラ診断」なるフラッシュがある

公式サイトに「キョロスケ様のキャラ診断」なるものがあります。
公式自体見たことがなかったので、海外のフォーラムで初めて知ったりもします。

現在でも公式サイトにリンクがありますが、画面下部にひっそりと表示されているだけです。
気合でランパっぽくなるような選択肢を選びます。

starfy_sindan_rampa2

やはりというか、キャラクター毎のコメントがあります。
ゲーム外の台詞ですが、「ゲーム内の」と限定して作っているわけでもないので、とりあえず台詞集に追加しておきました。
ちなみにハマグリ曰く、「真面目で素直な優等生タイプ」らしいです。

まとめ

そんなわけで、ランパの台詞bot作成(ほぼ)完了です!
プレイしていると、キャラクターやストーリーが懐かしく感じて、意外とハマってしまったりします。

現在の台詞パターンは193種類です!
1アクションゲームとしてみると相当な量ではないでしょうか。
あとは上述した「あつめもの」をステージ1~4分入れれば完成かと思います!

PHPカンファレンス2016 (関東) に行ってきました!

というわけで、やや久々の行ってきましたシリーズです。
今日はプログラマっぽく、PHPカンファレンス2016に行ってきました!
こういった技術系のイベントは行ったことがありませんでしたが、今後ITな生活を送る上では重要かと思い、申し込んでみました!

山梨発で、朝10時までに大田区産業プラザに到着する必要があります。
いつも通り高速バスで行きますが、身延線だと間に合わないため、竜王駅から出ている便で向かいました。

cimg2100_r

京急蒲田駅から産業プラザまでは徒歩5分ほどです。
大きな建物なので分かりやすかったです。

cimg2102_r

1Fにセッション用スペースとスポンサーのブースがありました。
セッションの合間に軽く回ってみました。

cimg2103_r

PHP技術者認定試験のブースです。
最近受けて準上級になったので印象深いです。

cimg2104_r

↑のうまい棒です。
ペチゾーが欲しいところでしたが、くじはハズレでした。
そんな運命力はなかったようです。
合格体験記のものと同一らしいので、書いて送って獲得したほうが楽ですね。

cimg2106_r

レバテックのブースにあった問題です。
パッと見ではかなり厳しい…というかusort()がある時点で半分諦めていたりします。
その場で答えられた人はなかなかのPHPerかと思われます。

セッションやLTの内容は割愛しますが、「ほほう!」というものもあれば、「ふーん…」というものもありました。
概念すら知らなかった技術や言語があったので、そういう意味では有意義でした。
ただ一口にPHPといっても、フレームワークからツールまで幅広いので、全てを把握するのは困難ですね。

cimg2108_r

上は最も印象に残った Cygames のセッションの様子です。
ホールに入りきらず、自分も外側に立ってみていたような状態です。
運営側の工夫やサーバ構成などが聞けたので面白かったです。

そんなわけで、短めですがPHPカンファンレス2016にお邪魔してきました!
今後もこういった技術系のセミナーやイベントに顔を出していきたいところです。

【PHP】Symfony2のFormBuilderにデフォルト値を設定する方法

※投稿時の Symfony のバージョンは 2.7.9 、EC-CUBE のバージョンは3.0.10です。

というわけで、今回は PHP のフレームワーク Symfony2 に関するメモです。
仕事で使っている関係上いろいろと詰まることがあり、今日もちょっとしたことで詰まってしまいました。
主に「EC-CUBE3」で使っているので、それを元にして書いておきます。

shot2ss20160714205855672

やりたかったことは「FromBuilder の時にデフォルト値を設定する」です。
(プレースホルダとは別で、画面表示時からフォームに値が設定されている状態です)
普通なら getForm() した後に setData() すれば問題ないのですが、EC-CUBE のフックポイント的に FormBuilder の状態でいじれると便利だったりします。

テストとして、会員登録画面の「生年月日」欄の年をデフォルトで「2000」にしようと思います。
そこでこんなコードを書きましたが、フォームにデフォルト値として設定されません。

$Customer = $app['eccube.repository.customer']->newCustomer();

$builder = $app['form.factory']->createBuilder('entry', $Customer);
$builder->get('birth')->get('year')->setData(2000);

ぐぐったらスタックオーバーフローにそんな話題がありました。

http://stackoverflow.com/questions/18870866/symfony-2-3-form-getdata-doesnt-work-in-subforms-collections

setDataLocked() なる関数でロックしてあげないと getForm() 時に消える(?)らしいです。
ならこれでロックすればOKですね。

$Customer = $app['eccube.repository.customer']->newCustomer();

$builder = $app['form.factory']->createBuilder('entry', $Customer);
$builder->get('birth')->get('year')->setData(2000)->setDataLocked(true);

shot2ss20160714205839398

今度はデフォルト値としてしっかり入りました!
値を変えて確認画面に行ってもちゃんと変わったので、大丈夫かと思われます。

ちなみに getForm() 後であれば普通に $form->get(‘フィールド名’)->setData() で設定できます。
但し handleRequest() してしまうと変えられなくなります。
また、「プラグインからフックして設定する」などの理由がなければ Type クラスの $buider->add() のオプションで指定しまう方が確実です。

まとめ

そんなわけで、Symfony2 の setData() に関するお話でした。
Symfony2 は初見ではとっつきにくい割に日本語サイトが少ないので厳しいです。
EC-CUBE3 のフレームワークとしても使われているので、もっと日本語サイトも増えてくれるといいなーなんて思います。

ゲーム開発とは関係ありませんが、今後も何か詰まったことがあったらメモして忘れないようにしたいです。
特に PHP は一生付き合っていく言語になりそうなので、尚更ですね。
そもそもゲーム開発だけでは話題が続かなくなってきているので、いろいろ話題を織り交ぜていくブログに方向転換しようかと思います。

カテゴリー PHP