とあるプログラマの備忘録

都内某所に住むプログラマが自分用に備忘録を残すという趣旨のブログです。はてなダイアリーから移動しました!

Phalcon Migrations Tips

ちょっとPhalconを使わなければならない状態になったので、使ってみているのですが、 Phalcon結構素直でいいFWだと思います。

Phalconはだんだん好きになってきたのですが、
この案件今まで一人でやっていたらしく、migrationとか使ってないといわれました。

そんじゃPhalconMigrationでもつかうかと気軽におもったのですが
資料がない。

あってもup系の記事や記述ばかりでdownどうすんねん!!
ってのがわからなかったのでしょうがないからソースを読んでやっとわかったので、 同じ轍を踏んでいる人が少しでも参考になるように記載しておきます。

とりあえずソースよませろや!って方はこちらを読めば大体挙動がわかると思います。

GitHubは偉大
github.com

・テーブルはわかりやすいようにmigration_testを指定していますが指定しなければALLになります
・configを環境ごとに分けている場合は--configオプションにconfigのパスを指定してください

・初期generateします

$ phalcon migration generate  --config=./app/config/config_local.php

~skip

 Success: Version 1.0.0 was successfully generated

app/migrations/1.0.0が出来上がりました
これはすべてのテーブル情報が含まれています。

・migration_testテーブルを作成します

f:id:raharu0425:20161201134940p:plain

こんなテーブルにしました。

・migration_testテーブルだけgenerateします

$ phalcon migration generate  --config=./app/config/config_local.php --table=migration_test

~skip

 Success: Version 1.0.1 was successfully generated

app/migrations/1.0.1ができあがりました。

さて、この1.0.1でテーブルをつくったのだから、1.0.0にロールバックした時にはこのテーブルは消えてほしいので
app/migrations/1.0.1/migration_test.phpのmorph()内のソースをupにコピーして、downにdropTableの記述をします
これはrun時にはmorph() -> up()と処理が走り、rollback時には down() -> morph()と処理が走っているので、
morph() に書いたままだと、downで消したテーブルをmorphが作り直してしまうためです

<?php

use Phalcon\Db\Column;
use Phalcon\Db\Index;
use Phalcon\Db\Reference;
use Phalcon\Mvc\Model\Migration;

/**
 * Class MigrationTestMigration_101
 */
class MigrationTestMigration_101 extends Migration
{
    /**
     * Define the table structure
     *
     * @return void
     */
    public function morph()
    {

    }

    /**
     * Run the migrations
     *
     * @return void
     */
    public function up()
    {
        $this->morphTable('migration_test', array(
                'columns' => array(
                    new Column(
                        'id',
                        array(
                            'type' => Column::TYPE_INTEGER,
                            'unsigned' => true,
                            'notNull' => true,
                            'autoIncrement' => true,
                            'size' => 11,
                            'first' => true
                        )
                    ),
                    new Column(
                        'name',
                        array(
                            'type' => Column::TYPE_VARCHAR,
                            'default' => "",
                            'notNull' => true,
                            'size' => 128,
                            'after' => 'id'
                        )
                    )
                ),
                'indexes' => array(
                    new Index('PRIMARY', array('id'), null)
                ),
                'options' => array(
                    'TABLE_TYPE' => 'BASE TABLE',
                    'AUTO_INCREMENT' => '1',
                    'ENGINE' => 'InnoDB',
                    'TABLE_COLLATION' => 'utf8_general_ci'
                ),
            )
        );

    }

    /**
     * Reverse the migrations
     *
     * @return void
     */
    public function down()
    {
        self::$_connection->dropTable('migration_test');
    }

}

1.0.0にロールバックします
downは--versionオプションで前バージョンに戻した際に呼び出されます。

$ phalcon migration run  --config=./app/config/config_local.php --version=1.0.0

~ skip
Success: Version 1.0.0 was successfully migrated

migration_testテーブルが消えていれば成功です

例えばageカラムを追加した場合のdownはこのように記述します

<?php

use Phalcon\Db\Column;
use Phalcon\Db\Index;
use Phalcon\Db\Reference;
use Phalcon\Mvc\Model\Migration;

/**
 * Class TestMigrationMigration_102
 */
class TestMigrationMigration_102 extends Migration
{
    /**
     * Define the table structure
     *
     * @return void
     */
    public function morph()
    {
    }

    /**
     * Run the migrations
     *
     * @return void
     */
    public function up()
    {
        $this->morphTable('test_migration', array(
                'columns' => array(
                    new Column(
                        'id',
                        array(
                            'type' => Column::TYPE_INTEGER,
                            'unsigned' => true,
                            'notNull' => true,
                            'autoIncrement' => true,
                            'size' => 11,
                            'first' => true
                        )
                    ),
                    new Column(
                        'name',
                        array(
                            'type' => Column::TYPE_VARCHAR,
                            'notNull' => true,
                            'size' => 128,
                            'after' => 'id'
                        )
                    ),
                    new Column(
                        'age',
                        array(
                            'type' => Column::TYPE_INTEGER,
                            'size' => 11,
                            'after' => 'name'
                        )
                    )
                ),
                'indexes' => array(
                    new Index('PRIMARY', array('id'), null)
                ),
                'options' => array(
                    'TABLE_TYPE' => 'BASE TABLE',
                    'AUTO_INCREMENT' => '1',
                    'ENGINE' => 'InnoDB',
                    'TABLE_COLLATION' => 'utf8_general_ci'
                ),
            )
        );

    }

    /**
     * Reverse the migrations
     *
     * @return void
     */
    public function down()
    {
        $this->morphTable('test_migration', array(
                'columns' => array(
                    new Column(
                        'id',
                        array(
                            'type' => Column::TYPE_INTEGER,
                            'unsigned' => true,
                            'notNull' => true,
                            'autoIncrement' => true,
                            'size' => 11,
                            'first' => true
                        )
                    ),
                    new Column(
                        'name',
                        array(
                            'type' => Column::TYPE_VARCHAR,
                            'notNull' => true,
                            'size' => 128,
                            'after' => 'id'
                        )
                    )
                ),
                'indexes' => array(
                    new Index('PRIMARY', array('id'), null)
                ),
                'options' => array(
                    'TABLE_TYPE' => 'BASE TABLE',
                    'AUTO_INCREMENT' => '1',
                    'ENGINE' => 'InnoDB',
                    'TABLE_COLLATION' => 'utf8_general_ci'
                ),
            )
        );

    }

}

これやり方違うよ!とかご意見マサカリお待ちしています!

AnsibleでFailed to connect to the host via sshと言われた

ansible-playbook -i provisioning/hosts provisioning/site.yml

したら

fatal: [192.168.183.100]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh.", "unreachable": true}

って怒られた場合

Andibleはhostsに書かれているものを~/.ssh/configから情報引っ張ってきてSSH接続しに行くとのことなので、 まずは疑うべきは.ssh/configとAnsibleのほhostsだと思う。

・対応したホスト名で.ssh/configができているか?
ssh [hosts]でそもそもログインできるのか?
ssh/configのポート間違ってない?
ssh/configの秘密キー間違ってない?

あたりを調べると良さそうだ

Unity z軸に関係なくダメージを最前面に出したい

ダメージをz軸に関係なく最前面に出したいとおもって
SortingLayerを弄っていたのですが、うまく行かず。。

結局カメラを追加する形になりました。

やりたいこと

f:id:raharu0425:20150827105747p:plain

これをこうしたい。

f:id:raharu0425:20150827105840p:plain

z軸に関わらず前面に表示されるレイヤーを作成する

FrontObjという名前で作成しました

f:id:raharu0425:20150827110002p:plain

カメラを作成する

FrontObjCameraという名前で作成します

f:id:raharu0425:20150827110222p:plain

注意点

Clear FlagsをDepth Onlyにしないといけません。

もともとあったはずのMainCameraをDepth0
新しく追加したFrontObjCameraをDepth1にします

いらないコンポーネントは外しておきましょう

カメラのマスクレイヤーを設定する

Main Camera

f:id:raharu0425:20150827110828p:plain

FrontObjCamera

f:id:raharu0425:20150827110727p:plain

カメラを増やして2つとも全部描画させるともちろんポリゴン数も頂点数も2倍になります。
マスク設定がかぶらないようにします。

これで期待通りの描画になりました。

f:id:raharu0425:20150827111341p:plain

もともSortingLayerがz軸を無視して描画順序を変更してくれるとばかり思っていたのですが、
うまくいきませんでした。
シェーダーをかける人はシェーダーでなんとでもできるかもしれません。

他の方法やご意見マサカリとう歓迎しております!!

Unity ScriptableObjectはバトルデータに向いていると思う

久しぶりのブログです。
アプリ作成は進んでいるのですが、いかんせんブログのネタになる作業をしていない&見せられないよ!
という事であまりブログが書けてません。。むしろブログ書きたいのに。。。

今日の話はScriptableObjectが結構使えそうだったのでその話です。

ScriptableObjectについてはこちらの記事が丁寧に書かれていますのでこちらを参考にしてください。

tsubakit1.hateblo.jp

それでは問題です。

ステージ毎に敵の出現ポイントやタイミングを変更したいのですが、どうしたらいいでしょうか?

よくある事です、 ○秒後に○の敵が出現その後○秒後にボスが出現といった
いわゆるバトルデータ(シナリオデータ)をどうやって実現しましょうか?というお話です。

幾つかすぐに出てくる答えとしては、

csvjsonでデータを作成してなんやかんやパースすればええやん
・シナリオDBでも作って適当に出せるようにしたらええやん
・最低データのみ保持してランダムで出現させればええやん

全部正解だと思います。 作ろうとしているものの用途に合わせて上記で選ぶのも良いでしょう。 ですが、Unityの場合はScriptableObjectがこれに適合している気がします。

なぜなら、Inspector上で値を変更しながらバトルデータを作成できるから!

です

という事で早速作成します。

まずは構造を決めます。

[Scripts/QuestBattleData]として保存します

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[System.Serializable]
public class QuestBattleData : ScriptableObject {

    //適当なパラメータ
    public int param1;
    public Rect param2;

    //敵が出現する場所とID
    public List<EnemyDetail> entryEnemy;
    
}

[System.Serializable]
public class EnemyDetail{
    public int enemy_id;
    public Vector3 enemy_pos;
}

構造を型にアセットを作成できるようにします。
[Editor/CreateBattleData]として保存します

using UnityEngine;
using UnityEditor;
using System.Collections;

public class CreateBattleData : MonoBehaviour {
    [MenuItem("Assets/Create/CreateBattleData")]
    public static void CreateAsset()
    {   
        QuestBattleData item = ScriptableObject.CreateInstance<QuestBattleData>();

        //アセットを保存するパス
        string path = AssetDatabase.GenerateUniqueAssetPath("Assets/Resources/QuestBattleData/" + typeof(QuestBattleData) + ".asset");
        
        AssetDatabase.CreateAsset(item, path);
        AssetDatabase.SaveAssets();
        
        EditorUtility.FocusProjectWindow();
        Selection.activeObject = item;
    }
}

これでAssets/Resources/QuestBattleData/フォルダを作成して、
Project > Createメニューを開くと一番下に出てきます。

f:id:raharu0425:20150821184403p:plain

これで作成されたアセットはこんなInspectorになってます。

f:id:raharu0425:20150821184517p:plain

あとはQuestBattleDataとしてスクリプトにアタッチすればOK

public QuestBattleData battleData;

f:id:raharu0425:20150821185329p:plain

スクリプトからQuestBattleDataを参照する事が可能です。
また、Resouces/に入れておけば動的にアタッチする事も出きますし、
.assetなのでこのままAssetBundle化してLoadしてもよいでしょう。

そして何より、

Inspector上で動的にゲームバランスを整えながらバトルデータを作成できます!

これにより、CSVJsonのようにパーサーを用意しなくても良いですし、
DBデータでマスタデータを更新して検証DBに反映してとかやらなくても良いし、
値を追加変更する場合も影響範囲がわかりやすいという状態です(スバラっ!)

という事で今回はScriptableObjectのお話でした、
他に良い方法があるよ!とかこんな使い方があるよ!?とか
ご意見マサカリ歓迎しております。

Unity PloyWorldを使ってステージを作成してみる

さて、なんかゲームの大枠ができつつあるのですが、
仮ばっかりでチープさが抜けません!

なのでちょっと一個くらいステージ背景を作ろうとおもいます。

最初Unity付属のTerrainをそのまま使用したところFPSがひどいことになったので、
Terrainをローポリ化してくれる+オブジェクトやらなSkyboxやらも全部丸っとつけて
$50だもってけドロボー的な素晴らしいアセット購入しました。

Asset Store

これの素晴らしいところは他のハイポリモデルも
この世界観にあった感じでローポリ化してくれるらしいのですが、
自分が作ってるVoxleは元々ローポリなので世界観にバッチリです!!

兎にも角にも自分的は$50分働いてもらわないといけないので早速何か作ってみます。

SkyBox変えよう

まずは形から入る私ですが、とりあえずSkyBoxを変えることになりました。
PloyWorldは素晴らしいことに10種類以上のSkyBoxが入っています!(スバラ!!

今回は「Sky_factedにします」

f:id:raharu0425:20150730203114p:plain

Terrainで適当に地形を作る

これはもうStandard Assets様に頼って適当作りますが、
この時点で頂点数をふやしてもあまり意味がないのでこんな設定にしておきます。

f:id:raharu0425:20150730203244p:plain

初期値は結構な数字になていますのでスマホ向けに考えているのならここを抑えましょう。

こんな感じになった

f:id:raharu0425:20150730203417p:plain

まぁよくある山です、これはこのままで使うと

f:id:raharu0425:20150730204440p:plain

ポリゴン16k、、これはちょっと多いですね、
それに頂点数も10kですか、もっと抑えたいです。

これでも結構使いますね、だいぶ下げたんですけどね。

PolyWorld登場

この状態からWindow > Quantum Theory > PolyWorld Terrainを押すとこんあ画面が出てくるので

f:id:raharu0425:20150730204639p:plain

作成したTerrainを選択してGenerateボタンを押します。
今回はHalfを選択してみます

するとエラーが出ます(笑)

f:id:raharu0425:20150730204757p:plain

Terrainって名前はダメだからなんか違う名前にしてよ鬼のおにーちゃんいぇーい、ピースピース
と言われました。

しょうがないので名前も適当にTerrainMountainにして山っぽさを出します。

もう一回ジェネレートすると。

f:id:raharu0425:20150730205147p:plain

こんなかんじになるのでしたのチェックボックスのUse Associated Meshを押します。

その後Use Custom Shadearsボタンをおして、 Bake Vertex Colorsボタンを押します。

f:id:raharu0425:20150730205313p:plain

最後に、Hide Terrainにチェックをつけると。。?

f:id:raharu0425:20150730205421p:plain

いい感じになりました。
ただし
ポリゴン数は設定通り半分になりましたが、頂点数はふえました(笑)
カクついた分頂点数がふえた?Terrainが元々やってくれてたBatchingに負けた?

f:id:raharu0425:20150730205449p:plain

どちらにせよこの山の時にhalfじゃ期待以上の軽量化を狙えなさそうです。

なので1/4にしてみました。

f:id:raharu0425:20150730210121p:plain f:id:raharu0425:20150730205846p:plain

うん確かに1/4であれましたが、果たしてこの背景にどこまでユーザーはクオリティを求めるでしょうか?(笑
今回は背景をローポリでスマホに耐える形でというのが前提だったので、
1/4を採用しようとおもいます。

今後このPoly Worldをつかっていろいろやってみたいとおもいます。

ご意見マサカリ等募集しておりますm()m

Unity キャラクターの見え方をなんとかしてみる

元はこんな感じ。 まだステージができていないのでなんともチープですが、この段階でもきっとできることがあるはずです。

f:id:raharu0425:20150730183724p:plain

・影が欲しい
・ちょっとアンチエイリアスががパッキリしすぎてる

影が欲しい

通常であればLightから影を作るのでしょうが、
これだとアクティブなオブジェクトが増えてくると負荷になりそうなので、
今回はただの丸影をつけたいとおもいます。

StandardAssetからEffetsをインポートして、
キャラクターのプレハブにBlobShadowProjectorをアタッチします。

f:id:raharu0425:20150730184428p:plain

f:id:raharu0425:20150730184437p:plain

あとはBlobShadowProjectorをキャラクターの頭上に移動すれば丸影ができるのですが、
このままだとキャラクター本体にも影が落ちてしまうので、

Inspectorのf:id:raharu0425:20150730184528p:plain

Ignore Layerを使って回避しましょう。

これで丸影ができました

f:id:raharu0425:20150730184631p:plain

アンチエイリアスがパッキリしすぎている

元々そういうモデルやんみたいな根も葉もないことを言われるとそれまでですが、
もう少し境界をぼやっとさせたいわけです。

これにはカメラにAntialiasingをアタッチして値を調整しましょう。

f:id:raharu0425:20150730184858p:plain

こうすると

f:id:raharu0425:20150730184913p:plain

わかるかなー?微妙ですが、雰囲気はバッチリです。

現状できるのはこのくらいでしょうか?
微々たるところがゲームのクオリティに繋がると信じています(笑)

Unity UniRxをつかってピンチやスワイプの処理を書いてみる その2

前回はこんな記事を書いたところ、

raharu0425.hatenablog.com

raharu0425.hatenablog.com

Bufferつかった方がらくですよー!とお告げをいただきましたので、つかってみました。

Bufferとは引数分コレクションが溜まったらSubscribeするという便利機能らしいので
前回の機能をかきなおしてみました。

スワイプ

    public void MapSwipe()
    {

       #if UNITY_EDITOR
        var drug = Observable.EveryUpdate ().Select (pos => Input.mousePosition);
        var stop = Observable.EveryUpdate ().Where(_ => Input.GetMouseButtonUp(0)).First();
       #elif UNITY_IOS || UNITY_ANDROID
        var drug = Observable.EveryUpdate ().Select (pos => Input.GetTouch(0).position);
        var stop = Observable.EveryUpdate ().Where(_ => Input.touchCount != 1).First();
       #endif

        IDisposable onDrug = drug.Buffer (3)
            .TakeUntil(stop)
            .Subscribe (colPos => {

                float delPosx = colPos.Last().x - colPos.First().x;
                float delPosz = colPos.Last().y - colPos.First().y;
                float Speed = 0 -(5 * (MainCamera.fieldOfView / 60));
                MainCamera.gameObject.GetComponent<Rigidbody>().velocity = new Vector3(delPosx, 0, delPosz) * Speed;

        });
    }

ピンチ

   public void MapPinch()
    {
       #if UNITY_EDITOR
        var pinch = Observable.EveryUpdate ().Select (pos_dist => Input.mousePosition.x);
        var stop = Observable.EveryUpdate ().Where(_ => Input.GetMouseButtonUp(0)).First();
       #elif UNITY_IOS || UNITY_ANDROID
        var pinch = Observable.EveryUpdate ()
            .Select (pos_dist => Vector2.Distance(Input.GetTouch (0).position, Input.GetTouch (1).position));
        var stop = Observable.EveryUpdate ().Where (_ => Input.touchCount != 2).First();
       #endif

        
        IDisposable onPinch = pinch.Buffer(3)
            .TakeUntil(stop)
            .Subscribe(distanceParam => {
                float diff = distanceParam.Last() - distanceParam.First();

               #if UNITY_EDITOR
                MainCamera.fieldOfView -= diff;
               #elif UNITY_IOS || UNITY_ANDROID
                MainCamera.fieldOfView -= diff / 10;
               #endif
            });

    }

ふむふむ、前のソースより可読性が上がっていい感じです。
挙動も問題ないのでこっちで行ってみようと思います!