kumak1’s blog

kumak1のイラストや技術ログ

UnityとPlayMakerを触り始めたのでカスタムアクション作った

ゲームを継続して作っている。 じっくりと腰を据えて遊ぶ大作・・!というのも憧れはするけども、そういうのは時間も資金も潤沢な専業の会社に任せるほかない。

  • 数分脳汁を垂らしながらプレイするもの
  • 何かのながらでやるもの
  • ちょっとした時間つぶしでやるもの

こういったものが少人数開発だと狙うべき所かなぁという感触をもっている。 (自分自身のスキル的にも限界ががが) となると、いかの条件がでてくる。

  • PC・スマホ双方ビルドできる
  • 2D・3D両方共 同じ感覚で作れる
  • エコシステムが活発なもの

こうなると、Unity だなぁという感じになった。 (神こロしでは JavaScript のゲームライブラリを魔改造したけども、3Dやエコシステムに難ありだった)

Unity だけでも作りやすくはある。 んだけども、せっかくなのでロジックをもっとビジュアライズして、全体の処理をイメージしやすくしたり、 バグを発見・潰しやすくするために PlayMaker というプラグインを導入することにした。

こんな感じに矢印を繋げれば処理を作る事ができる。

f:id:kumak1:20160914221458p:plain

構成要素は大体以下の3つ。

  • ステート
    • 今どんな状態か(歩く・走る etc..)
  • イベント
    • どんな時にアクションを動作させるか(十字キーを押した・壁にぶつかった etc..)
  • アクション
    • どんな行動を起こすか(ステートの移動 ・HPを減らす etc)

イベント・アクションは初期状態で相当な数が用意されていて、これだけで大体のことができてしまう。 けれど、それらは単機能でかつ組み合わせるにも煩雑な過程を経る必要がある(一旦オブジェクト内変数に値を待避させる)など、逆に面倒なことがちょこちょことある。

けれど上手くつくられているもので、これらは自由に追加することが可能だ。 用意されている処理を Edit Script などからエディタで開き、参考にしながらオレオレ イベント・アクションを追加してサイキョーの開発環境にしていける。 今回試しに作ったのは以下2つ。

  • BoolOperatorTest
    • はじめから用意されている BoolTest の拡張。
    • BoolTest は以下の値をセットでき、変数の状態に応じてイベント発火できる。
      • 変数(bool)
      • true 時に動作するイベント
      • false 時に動作するイベント
    • この拡張では、以下の値をセットでき、より柔軟に変数の状態に応じてイベント発火できる。
      • 変数1(bool)
      • 変数2(bool)
      • 論理演算子
      • true 時に動作するイベント
      • false 時に動作するイベント
  • FsmMultipleStateTest
    • はじめから用意されている FsmStateTest の拡張
    • FsmStateTest 以下の値をセットでき、ステートの状態に応じてイベント発火できる。
      • 対象オブジェクト(fsm)
      • 予測するステート名(string)
      • true 時に動作するイベント
      • false 時に動作するイベント
    • この拡張では、以下の値をセットでき、より柔軟に変数の状態に応じてイベント発火できる。
      • 対象オブジェクト(fsm)
      • 予測するステート名(string)をカンマ区切りで複数
      • true 時に動作するイベント
      • false 時に動作するイベント

既存のものをちょいちょいと組み合わせるだけで、より自分が使いやすいものをシュッと作れて、スッと利用できるので大変よい感じである。 いくらGUIで大体作れるからといっても、効率良くしたり、少ない記述で見通し良くしようとしたらば、結局は設計とプログラムは必要なのだなとしみじみしたのであった

BoolOperatorTest.cs

using UnityEngine;
namespace HutongGames.PlayMaker.Actions
{
    [ActionCategory(ActionCategory.Logic)]
    [Tooltip("Performs boolean operations on 2 Bool Variables.")]
    public class BoolOperatorTest : FsmStateAction
    {
        public enum Operation
        {
            AND,
            NAND,
            OR,
            XOR
        }

        [RequiredField]
        [Tooltip("The first Bool variable.")]
        public FsmBool bool1;

        [RequiredField]
        [Tooltip("The second Bool variable.")]
        public FsmBool bool2;

        [Tooltip("Boolean Operation.")]
        public Operation operation;

        [Tooltip("Event to send if the Bool variable is True.")]
        public FsmEvent isTrue;

        [Tooltip("Event to send if the Bool variable is False.")]
        public FsmEvent isFalse;

        [UIHint(UIHint.Variable)]
        [Tooltip("Store the result in a Bool Variable.")]
        public FsmBool storeResult;

        [Tooltip("Repeat every frame while the state is active.")]
        public bool everyFrame;

        public override void Reset()
        {
            bool1 = false;
            bool2 = false;
            operation = Operation.AND;
            storeResult = null;
            everyFrame = false;
            isTrue = null;
            isFalse = null;
        }

        public override void OnEnter()
        {
            DoBoolOperator();

            if (!everyFrame)
            {
                Finish();
            }
        }

        public override void OnUpdate()
        {
            DoBoolOperator();
        }

        void DoBoolOperator()
        {
            var v1 = bool1.Value;
            var v2 = bool2.Value;

            switch (operation)
            {
                case Operation.AND:
                    storeResult.Value = v1 && v2;
                    break;

                case Operation.NAND:
                    storeResult.Value = !(v1 && v2);
                    break;

                case Operation.OR:
                    storeResult.Value = v1 || v2;
                    break;

                case Operation.XOR:
                    storeResult.Value = v1 ^ v2;
                    break;
            }

            Fsm.Event(storeResult.Value ? isTrue : isFalse);
        }
    }
}

FsmMultipleStateTest

using UnityEngine;

namespace HutongGames.PlayMaker.Actions {
    [ActionCategory(ActionCategory.Logic)]
    [ActionTarget(typeof(PlayMakerFSM), "gameObject,fsmName")]
    [Tooltip("Tests if an FSM is in the specified State.")]
    public class FsmMultipleStateTest : FsmStateAction {
        [RequiredField]
        [Tooltip("The GameObject that owns the FSM.")]
        public FsmGameObject gameObject;

        [UIHint(UIHint.FsmName)]
        [Tooltip("Optional name of Fsm on Game Object. Useful if there is more than one FSM on the GameObject.")]
        public FsmString fsmName;

        [RequiredField]
        [Tooltip("Check to see if the FSM is in this state.")]
        public FsmString stateName;

        [Tooltip("Event to send if the FSM is in the specified state.")]
        public FsmEvent trueEvent;

        [Tooltip("Event to send if the FSM is NOT in the specified state.")]
        public FsmEvent falseEvent;

        [UIHint(UIHint.Variable)]
        [Tooltip("Store the result of this test in a bool variable. Useful if other actions depend on this test.")]
        public FsmBool storeResult;

        [Tooltip("Repeat every frame. Useful if you're waiting for a particular state.")]
        public bool everyFrame;

        // store game object last frame so we know when it's changed
        // and have to cache a new fsm
        private GameObject previousGo;

        // cach the fsm component since that's an expensive operation
        private PlayMakerFSM fsm;

        public override void Reset() {
            gameObject = null;
            fsmName = null;
            stateName = null;
            trueEvent = null;
            falseEvent = null;
            storeResult = null;
            everyFrame = false;
        }

        public override void OnEnter() {
            DoFsmStateTest();

            if (!everyFrame)
            {
                Finish();
            }
        }

        public override void OnUpdate() {
            DoFsmStateTest();
        }

        void DoFsmStateTest() {
            var go = gameObject.Value;

            if (go == null)
            {
                return;
            }

            if (go != previousGo)
            {
                fsm = ActionHelpers.GetGameObjectFsm(go, fsmName.Value);
                previousGo = go;
            }

            if (fsm == null)
            {
                return;
            }

            storeResult.Value = stateName.Value.Split(',').Any(s => s == fsm.ActiveStateName);

            Fsm.Event(storeResult.Value ? trueEvent : falseEvent);
        }


    }
}

製作期間半年ちょいでブラウザゲーム「神こロし」をリリースした。

ひょんなことがきっかけで、「神こロし」というブラウザゲームを作っているし、更新している事への備忘録

きっかけ

日本ではイラストレーターで知られるD.Kさんが、なにやら個人でゲームを作っていることをTwitterで知る。 どうやらキャライラストを募集していて、寄稿するとゲーム内に登場できるみたい。

Pixiv Fantasia とかやってた私としては参加せざるを得ないので、もにょもにょとした気持ちをツイート。 したら、なぜかプログラムの話題になって、あれよあれよと制作側になる。

体制は?

D.K さんがコンセプチュアルデザイン、マップデザイン、アートワーク。 私は開発だとかゲームバランスとか最終意思決定。 実際はゲーム制作進行のイロハを丁寧に教わってます・・

どんな風に作ったの?

2人でワヤワヤ話して方向性を決めて、D.Kさんが以下の様なマップを作る

うまいこと実装していくというサイクル

つくっていて思うこととか

単純に作っていて楽しいし、 いろんな絵描きの人つながり増えたし、(今までニコ動関連しかつながり広がってなかった) ゲームをキッカケとして盛り上がったりお友達になっているのをみるとほっこりする。 「繋がり」っていいなと

技術者としてのメリット

そこそこな規模で、自分で好きなようにできるプロダクトを持つのは初めてなのですごい勉強になってる。 開発も、インフラも、デザイナも、ディレクションも、カスタマーサポートも全部自分。 「職場で身近な人はこうやってたよな・・」で手探りでやっているが、 他業種の人の気持ちになれて大変よい。 人に優しくなれる。

これからについて

地味なアップデートを繰り返している。 この世界観とかを引き継いだ3Dゲームを作る話にもなっているので、頑張っていくぞという気持ちがある。

capistranoでデプロイ先にユーザ指定してwheneverを適用させたい

この記事は Pepabo Advent Calendar 2015 - Qiita の19日目の記事です。

capistranoRuby で簡潔によしなにしてデプロイしてくれるので大変便利ですね。拡張ライブラリも豊富ですし Ruby の経験がほとんどなくてもそれとなく記述できて素晴らしいです。(ぺちぱーなもので・・) whenever も cron を Ruby で書けるっていいですね。バージョン管理できるし、レビューも受けやすいし、なにより crontab -r がもう怖くないw 基本的にすんなりと設定できるのですが、掲題の「capistranoでデプロイ先にユーザ指定してwheneverを適用」の時にちょっと詰まったので、備忘録がてらに記事にします。

なんでユーザ指定したいの

ユーザを分ける事自体に疑問を持つ方は、こちらの記事を読むと良さそう。 用は、デプロイする人はデプロイするだけ、バッチ実行する人はバッチ実行するだけと分けたいのです。 セキュリティ的にも、あとで調査等々する場合にも役に立ちます。 www.atmarkit.co.jp

前提

デプロイ先に以下が導入済みだとします。

  • rbenv 導入済み
  • .ruby-version ファイルで Ruby のバージョンを記録
  • バッチ実行用ユーザ(batch)がいる

やりたいこと

  1. capistrano でデプロイし、以下を実行
  2. デプロイ先で、bundle install
  3. デプロイ先で、whenever 実行

具体的には

whenever を素で実行する際の -u オプションを、capistrano でデプロイ時に適用したいわけです。

% bundle exec whenever --help
Usage: whenever [options]
    -i [identifier],                 Default: full path to schedule.rb file
        --update-crontab
    -w, --write-crontab [identifier] Default: full path to schedule.rb file
    -c, --clear-crontab [identifier]
    -s, --set [variables]            Example: --set 'environment=staging&path=/my/sweet/path'
    -f, --load-file [schedule file]  Default: config/schedule.rb
    -u, --user [user]                Default: current user
    -k, --cut [lines]                Cut lines from the top of the cronfile
    -r, --roles [role1,role2]        Comma-separated list of server roles to generate cron jobs for
    -v, --version

では capistrano の設定結果から。

Capfile.rb

# Include tasks from other gems included in your Gemfile
require 'capistrano/rbenv'
require 'capistrano/bundler'
require 'whenever/capistrano'

rbenv, bundler, whenever の gem を追加

deploy.rb

# rbenv の設定
set :rbenv_ruby, File.read('.ruby-version').strip # rbenvが.ruby-versonのRubyを使うようになる
set :rbenv_path, '/usr/local/rbenv'               # デプロイ先のrbenvのpathを指定

# bundler の設定
set :bundle_path, -> { release_path.join('vendor/bundle') } # sharedよりもreleaseのパス指定の方が、切り戻しが必要な際に楽

# whenever の設定
set :whenever_update_flags, ->{ "--update-crontab #{fetch :whenever_identifier} --set #{fetch :whenever_variables} --user batch" }

わけわからないのは多分 whenever の設定だけかと思います。 ライブラリのソースコードを見てみると、whenever の実行オプションを指定している箇所があります。 whenever/whenever.rake at 334cfa01f373006cc032e23907b1777a8ea3f3b0 · javan/whenever · GitHub

これに --user batch を付け加えているだけです、簡単だー。 バッチユーザがデプロイユーザと同じグループに所属しているならばこれでOKですね。 もしダメな場合、以下のような荒技を使えば大丈夫・・ デプロイユーザにsudoer権限が必要になりますが、これでなんとかなってしまいます。

set :whenever_command, ->{ [:sudo, "#{fetch(:rbenv_path)}/bin/rbenv", :exec, :bundle, :exec, :whenever] }

ぷらすあるふぁ

実はこれをいじっていた当初は whenever をユーザ指定して実行がなかなかできず、task を半ば自前で作ってしまっていました。 (調度 set :whenever_update_flags のところで。tsushikazu 先輩の優しいアドバイスで解決)

「やりたいことができそうなのにできない」時は、コードリーディングをすると大体のことは解決できるので、怖がらず・面倒くさがらずにGitHubを覗くのが近道みたいです。 綺麗なコードを読むのはエンジニアの成長には欠かせませんしね。 まだまだ若手感を出して頑張っていこう。

vagrantの共有フォルダのマウントオプションについて

最近、趣味で Laravel を使い始めて気付いたのでメモ。

私はvagrantで開発する際、アプリのコードはマウントして作業しています。 Laravel ではちょうど以下のようなディレクトリ構造ですね。

work
├─ application
│   ├─ app
│   │   ├─ strage
│   │    ...
│   │   
│   ├─ bootstrap
│   └─ public
└─ vagrant
     ├─ Vagrantfile
      ...

Laravel を動作させるには、work/application/app/strage ディレクトリの権限を緩めに(777とか)する必要があります。 ココでつまりました。

緩めに設定したはずなのに、動作させたときに生成されるファイル(セッションとかログとか)がいちいち書き込み禁止で生成・エラーになってしまうのです。。

Vagrantfile のマウントの記述は以下でした。

config.vm.synced_folder "../application", "/var/www/application"

解決方法

マウントオプションを設定して、ここから権限を緩める必要があったようです。

config.vm.synced_folder "../application", "/var/www/application", mount_options: ['dmode=777','fmode=777']

ネット上ではあまりこれに言及したTipsがなかったので、誰かの役に立てば・・ (基本的な事過ぎなのかもしれなくて恥ずかしいですが

vagrantのprivate_networkとforwarded_portのメモ

Vagrantfile を書いていると、ipとportの関係をうっかり間違えてしまうことがあるので備忘メモ

config.vm.network :private_network, ip: "192.168.0.10"
config.vm.network :forwarded_port, guest: 3306, host: 13306

上記のように、ipとportをVMで割り当てると、 localhost でアクセスする場合はフォワードしたポートを利用し、 private_network でアクセスする場合は本来のポートでアクセスする。

イメージとしてはこんな感じ f:id:kumak1:20150525230248p:plain

Webエンジニアの「金の弾丸」について

「金で解決できることは、ある」

汚い大人になってしまった感がモリモリありますが、割と大事。

Web開発って基本的に、フリーのOS、フリーのミドルウェア、フリーのツール、を使って開発をする、いわゆるOSSの文化が一般的。

が、なんかどうも個人的にもにょっとするところがあるのです。

続きを読む

css spriteを簡単に作れるPhotoshopスクリプト「Psprite」を作成・公開しました。

こんばんは。

最近はプログラムにかまけて、絵をほとんど描いていない人です。描きたいんですけどね、8月末の引っ越しが終わったらね。

さて、本エントリはタイトルの通り、Photoshopスクリプトを作ってみました。

写真でも、絵でも、Web制作でも大活躍なアプリケーションですが、個人的にcss spriteを作る時にいまいち使いにくい。「スライス」とかよくわかんないw。スクリプト自体は他の方も、というかLIGさんが作っていたりしますがいまいち使いにくい。喧嘩を売っていくスタイル。

そこで、「レイヤー毎」or「グループ毎」に css sprite のサンプルページを書き出すスクリプトを書きました。以下のメリットがあるかなと思います。

  • サンプルが確認しやすい
  • 表示されてるレイヤー or グループだけcss spriteを生成する
  • グループの中のレイヤーも全部対象で一括処理

続きを読む