PlayCanvasチートシート Unityと比べてみよう エディタ拡張編 その2

前回の記事で触れてなかったところなどを追加したバージョン

前回の記事
seiroise.hatenablog.com

Unityを使ったことのある人がPlayCanvasに手を出したとき「Unityのアレをやりたいんだけど・・・」という状況が結構あった。
今回はその中でもPlayCanvas版のエディタ拡張的要素であるスクリプト属性についてまとめる。


参考

スクリプト属性についてのリファレンス
Script Attributes | Learn PlayCanvas


注意

PlayCanvasではスクリプトを書いた後にEditorのParseボタンを押さないと更新されないので注意しよう。
また、エディタ上の値をいじったあと、入力を確定させないとLaunch後に反映されないので必ず確定させるよう注意しよう。


目次

数値

Unity

public int hp = 80;


PlayCanvas

ScriptAttribute.attributes.add("hp", {
    type: "number",
    default: 80
});

console.log(this.hp); //80

f:id:seiroise:20170509201333g:plain
typeオプションに"number"を指定して数値に、defaultオプションでデフォルトの値を決めてる。


ちなみに日本語でもいける。でもソースコード中に文字列以外で日本語があると精神上よろしくない。
なのでtitleオプションで変更する方が心臓に負担がかからないかも。(あとタブがずれる)

ScriptAttribute.attributes.add("お金", {
    type: "number",
    default: 100
});

console.log(this.お金); //100


titleオプションを使ったバージョン

ScriptAttribute.attributes.add("money", {
    type: "number",
    title: "お金",
    default: 100
});

console.log(this.money); //100

単位の表示

Unityだとエディタ拡張しないとできないので割愛


PlayCanvas

NumberAttribute.attributes.add("weight", {
    type: "number",
    placeholder: "kg",
    default: 60
});

console.log(this.weight); //150(kgは出力されない)

f:id:seiroise:20170509201553g:plain
(薄い

スライダー

Unity

[Range(10, 200)]
public float height = 60;


PlayCanvas

NumberAttribute.attributes.add("height", {
    type: "number",
    default: 175.8,
    min: 100,
    max: 250
});

console.log(this.height); //175.8

f:id:seiroise:20170508194339g:plain

minとmaxの二つのオプションを必ず指定すること。

文字列

Unity

public string id;


PlayCanvas

StringAttribute.attributes.add("id", {
    type: "string",
});

console.log(this.id); //

f:id:seiroise:20170509201844g:plain


defaultオプションで初期値の指定もできる

StringAttribute.attributes.add("name", {
    type: "string",
    default: "太郎"
});

console.log(this.name); //"太郎"


UnityのTextArea属性みたいに複数行入力を可能にするオプションはないみたい

真偽値

Unity

public bool isHeal;


PlayCanvas

BooleanAttribute.attributes.add("isHeal", {
    type: "boolean"
});
console.log(this.isHeal); //false

f:id:seiroise:20170509202123g:plain


defaultオプションで初期値の指定もできる

BooleanAttribute.attributes.add("isTrigger", {
    type: "boolean",
    default: true
});
console.log(this.isTrigger); //true

配列

Unity

public string[] characters;

PlayCanvas

Test.attributes.add("characters", {
    type: "string",
    array: true,
});

f:id:seiroise:20170508194848g:plain
重複は許されない(gifだと"太郎")

列挙

Unity

public enum Value {
    valueOne = 1,
    valueTwo = 2,
    valueThree = 3
}

public Value value;

PlayCanvas

Test.attributes.add("value", {
    type: "number",
    enum: [
        {"valueOne": 1},
        {"valueTwo": 2},
        {"valueThree": 3}
    ]
});

f:id:seiroise:20170508195512g:plain

ベクトル

Vec2

Unity

public Vector2 direction;


PlayCanvas

VectorAttribute.attributes.add("direction", {
    type: "vec2"
});
console.log(this.direction); //[0, 0]

f:id:seiroise:20170509202054g:plain


defaultオプションに配列を指定することで初期値の指定もできる。
Vec3とVec4も同様に初期値の指定ができる。

VectorAttribute.attributes.add("direction", {
    type: "vec2",
    default: [1, 2]
});
console.log(this.direction); //[1, 2]


さらにplaceholderオプションに配列を指定することで要素ごとの単位の表示もできる。
これもVec3とVec4も同様に指定できる。

VectorAttribute.attributes.add("direction", {
    type: "vec2",
    default: [1, 2]
    placeholder: ["cm", "cm"]
});

Vec3

Unity

public Vector3 position;


PlayCanvas

VectorAttribute.attributes.add("position", {
    type: "vec3"
});
console.log(this.position); //[0, 0, 0]

Vec4

Unity

public Vector4 rotation;


PlayCanvas

VectorAttribute.attributes.add("rotation", {
    type: "vec4"
});
console.log(this.rotation); //[0, 0, 0, 0]

rgb

たしかUnityだとエディタ拡張しないとできないので割愛。(できたっけ?)


PlayCanvas

ColorAttribute.attributes.add("backgroundColor", {
    type: "rgb"
});

console.log(this.backgroundColor); //[1, 1, 1]

f:id:seiroise:20170508193922g:plain


defaultオプションに配列を指定することで初期値の指定もできる。
このとき有効なのは0~1のみで0~255でも"#FF5588"などもだめ。
rgbaも同様に指定できる。

ColorAttribute.attributes.add("mainColor", {
    type: "rgb",
    default: [0.2, 0.5, 1]
});

console.log(this.mainColor); //[0.2, 0.5, 1]

rgba

Unity

public Color vertexColor;


PlayCanvas

ColorAttribute.attributes.add("vertexColor", {
    type: "rgba",
});

曲線

Curve

Unity
おそらく一番近いのはAnimationCurve

public AnimationCurve wave;


PlayCanvas

CurveAttribute.attributes.add("wave", {
    type: "curve"
});

f:id:seiroise:20170508195950g:plain

CurveSet

多分Unityにはない?かな
curvesオプションに値を指定することでCurveSetになる。

CurveAttribute.attributes.add("xyzCurve", {
    type: "curve",
    curves: ["x", "y", "z"]
});

f:id:seiroise:20170509194613g:plain

GradientCurve

Unity

public Gradient gradient;


PlayCanvas

CurveAttribute.attributes.add("gradient", {
    type: "curve",
    color: "rgba"
});

f:id:seiroise:20170509195229g:plain


colorオプションに"r"などを指定することでその色だけの曲線を設定することもできる

ツールチップ

Unity

[Tooltip("握力")]
public int grip;


PlayCanvas

NumberAttribute.attributes.add("grip", {
    type: "number",
    description: "握力",
    default: 30
});

f:id:seiroise:20170509202151g:plain

PlayCanvasチートシート Unityと比べてみよう エディタ拡張編 その1

上位互換
seiroise.hatenablog.com

PlayCanvasのエディタ拡張的な要素である「スクリプト属性」について
あんまり日本語の解説がなかったのでUnityと比較してまとめてみる。

参考

pc.ScriptAttribute

Unity

public Color color;

PlayCanvas

Attribute.attributes.add("color", {
    type: "rgba"
});

こんな感じになる
f:id:seiroise:20170508181803p:plain
f:id:seiroise:20170508193922g:plain

スライダー

Unity

[Range(100f, 250f)]
public float height = 175.8f;

PlayCanvas

Test.attributes.add("height", {
    type: "number",
    placeholder: "cm",
    default: 175.8,
    min: 100,
    max: 250
});

こんな感じになる
f:id:seiroise:20170508194339g:plain

配列

Unity

public string[] characters;

PlayCanvas

Test.attributes.add("characters", {
    type: "string",
    array: true,
});

こんな感じになる
f:id:seiroise:20170508194848g:plain
ちなみに重複は許されない(gifだと"太郎"のところ)

列挙

Unity

public enum Value {
    valueOne = 1,
    valueTwo = 2,
    valueThree = 3
}

public Value value;

PlayCanvas

Test.attributes.add("value", {
    type: "number",
    enum: [
        {"valueOne": 1},
        {"valueTwo": 2},
        {"valueThree": 3}
    ]
});

こんな感じになる
f:id:seiroise:20170508195512g:plain

曲線

Unity

public AnimationCurve wave;

PlayCanvas

Attribute.attributes.add("wave", {
    type: "curve"
});

こんな感じになる
f:id:seiroise:20170508195950g:plain

備考

Unityにはない使い方ができるものもあるので、 それはまた今度

【Unity】かっちょいいUIを作る!解説編【UI】

今朝に引き続きUIのお話です。

前回はこちら
seiroise.hatenablog.com

今回は前回紹介したこいつ↓について解説します。(主に忘れっぽい自分へ)

f:id:seiroise:20160919122549g:plain:w500

ソースコード

まず初めに全体のソースコードへのリンクをはっておきます。
説明なんていらん!という人はこっちを見てください。
github.com

円の描画

Unityで円を描画させるにはいくつかあるんですが、 今回は円形のMeshを作って描画するようにしました。 ソースコードでいうと

といった役割になっています。
円の形状定義と描画部分を分けてモジュールっぽさを上げてます。
描画するための形状のデータはEasyMeshとかいう、簡易的な頂点などの集合を介して行うようにしています。

メインの部分は円の形状を定義しているCircleFragment.csですかね。こいつに何度(start)から何度(end)まで、内径(inner)と外径(outer)といった円の形状を指定して、あとはSetIndicate()でどんな表示の仕方をするかを決めてCircleFragment.Update()を呼ぶことで形状をいじくってます。

コライダーへのポインタイベントの送信

今回の方法では既存のEventSystemは使えないので、こちらで都合の良いものを用意します。

ほぼほぼいうことはないと思いますがCollisionEventSystemはマウスの位置からRaycastを行って当たったものに対してICollisionEventHandlerが確認できたらそいつにイベントを送るといった感じです。

Radial Menu の制御

最後にRadialMenuの管理ですがその名の通りRadialMenu.cs君が全て行っています。
UniCoLib/RadialMenu.cs at master · seiroise/UniCoLib · GitHub

表示命令がきたらそのオブジェクトの子のUICircleFragmentを探しそれらに対して位置や範囲を指定して表示するようにしています。
親子関係はTreeではなくStackで擬似的に管理しています。
今回のRadialMenuでは別の子が同時に二つ以上表示されることがないのでStackでおけっていうことですね。

こいつの問題点

今更ですが、ここでこのRadialMenuの問題点を言っておこうと思います。 なんとなく察しているとは思いますが。こいつはワールド空間に配置されているので一緒にテキストを表示するのがちとめんどくさいです。

こいつに関しては今の所良い方法が思い浮かばないので保留です。
何か良い方法があったらコメントください・・・

おわり

かなり端折りましたが解説はこんな感じです。
ここわからんという方はコメント欄とかでよろしくです。

そんでは次の記事では使い方を説明していきたいと思います。
明日の朝には書けると良いなぁ・・・

ではではー

余談

すーぱー被ってるAssetを発見。
事前調査してなかった・・・
assetsale.hateblo.jp

https://www.assetstore.unity3d.com/jp/#!/content/15857

【Unity】かっちょいいUIを作った!みんなの憧れRadial Menu【UI】

お久しぶりです。せいろです。

近況報告みたいな感じです。

ゲームを作りたい欲求を我慢していたらなぜかUIを作ってしまいました。

だってこういうUIかっこいいじゃん・・・ f:id:seiroise:20160919121514j:plain

とりあえず作ったものの見た目はこんな感じです。 f:id:seiroise:20160919122549g:plain

親子関係を持ったRadial Menuを作ることができます。(見たまんま)
あとはボタンっぽい挙動のカスタマイズだったり、
表示方法とか結構いじくれます。

表示方法その1 内側から外側
f:id:seiroise:20160919123138g:plain

その2 端から端
f:id:seiroise:20160919123613g:plain

その3 外から中
f:id:seiroise:20160919123655g:plain

とりあえずこんな感じです。

UGUIを使うと当たり判定が矩形でしか行えないのでワールド座標に置いたメッシュをそれっぽく扱っています。

実際の詳しいことは次回で解説する予定です。今回は紹介のみです。
今日の夜ぐらいには書けたら良いなぁ・・・

ではっでは。

【Unity】ビルド後に使えるお手軽パラメータ調節機能【OnGUI】

最近,Unityのフロントのアルバイトをしているのですが,
ゲームデザイナの方がテスト用にビルドしたアプリのパラメータを調節したいとのことだったので,ビルド後もお手軽にパラメータを調節出来る何かを作りました。

お手軽にということで,C#のインタフェースとUnityのOnGUIを使って実装します。
OnGUIは小回りが利くのでお手軽にやるときには結構使える子だったりします。

今回は直線移動するプログラム(Move.cs)を調節できるようにします.

インタフェース(IParameterIndicator.cs)はこんな感じです.

このインタフェースをMove.csにインプリメンツして表示用のコードを書くとこんな感じです.
GUILayoutを使ってるのは下の方まで読めば分かるはずです.

そんで重要な関数を読んであげる人です

全てのインタフェースを取得するプログラムはここを参考にしてあります.
http://forum.unity3d.com/threads/how-to-get-all-components-on-an-object-that-implement-an-interface.101028/forum.unity3d.com

そんなこんなでそれぞれをUnity上に配置して実行するとこんな感じです.
f:id:seiroise:20160629124514g:plain

メリットとしては

  1. お手軽
  2. 表示側からスクリプトへの参照がないのでシンプル
  3. インタフェースなので後から付け加えるとか出来る

デメリットとしては

  1. あんまり複雑なのは表示に向いてない
  2. 表示するものが多すぎると重たい

ってことが挙げられますね.
やってることは単純なんですけどそこそこ便利だったりします.

ではでは~

【Unity】お手軽?なオブジェクトプール 【デザインパターン】

f:id:seiroise:20160606030443j:plain

備忘録第二弾ということで「オブジェクトプール」についてまとめておきます。

"お手軽?"とかは、ただの主観なのでもっと使いやすいオブジェクトプールがあったら教えてください。

オブジェクトプール?

まず「オブジェクトプールって何?」ってやつです。
オブジェクトプールは簡単に言えば「プログラムの実行中に作ると重たいから先にいっぱい作っておこう!」っていう仕組みです。

ゲーム以外のプログラムでも使われているものなので割といろんな人になじみ深いものなのかもしれないです。
聞いたところによると有名なHTTPサーバである「Apache」の内部でも使われているみたいです。

オブジェクトプールの効能

気になるオブジェクトプールの効能ですが、とりあえず下の図を見てください。

f:id:seiroise:20160606032602j:plain:w500

それぞれ1000個のGameObjectを生成した結果をProfilerで確認したものです。
1番上がおなじみのInstantiateで普通に生成した場合
2番目がオブジェクトプールで擬似的に生成した場合
3番目はおまけです

注目してほしいのは「Time ms」の項目で
普通にInstantiateした場合はInstantiateとActivate合わせて大体61.21ms
オブジェクトプールの方がActivateのみなので大体8.75ms
なんとオブジェクトプールを使うと速度が約7倍早くなってます!
驚異的ですねぇ~

しかしオブジェクトプールは速度と引き換えにバグを抱えやすくなる要因でもあるので、
きちんと管理していかなければなりません。

デザインパターンなの?

サブタイトルにデザインパターンとか書いてあるのは下の書籍でパターンとして紹介されていたからです。

この本はかなりおすすめです。一押しです。
多摩センターの丸善に一冊しか置かれてないのが不思議なぐらいです。

デザインパターンについて多少知識があった方がいいと思いますが、
作者の語り口やユーモアがいたるところに散りばめてあるので読むだけでもかなり面白いです。
あと裏表紙の著者とわんこが面白いです。

もう手遅れだけど、3年ぐらい前のプログラミング覚えたての時に欲しかったです。(いやほんとに…

前書きが長くなりましたが、次からオブジェクトプールを作っていきます。

作る前に仕様の確認

作ると言っていたのは嘘だったようです。
まずは仕様を確認しましょう。

  1. 管理はGameObjectのActiveのオンオフで
  2. 使わなくなったときはActiveをオフにするだけ
  3. 生成したオブジェクトは子にしてActiveがオフの状態にしてお
  4. 足りなくなったときは追加できるようにする
  5. ジェネリックで作っておいて用途に応じて継承する

とりあえずこんな感じです。
1と2はUnityで既にGameObjectという仕組みができてるのでそれをそのまんま使います。
とくに2は重要で元々の機能を使うことでオブジェクトプール用の基底クラスを作ったりしなくてよくなるのでお手軽さを上げてます。

ではではほんとに作っていきます。

ほんとに作る

まずはクラスの定義から

public class GenericObjectPool<T> : MonoBehaviour where T : Component {
  //クラスの中身
}

仕様の5番で基底はジェネリックにしとくようにしたのでこんな感じ。
一応whereで制限を掛けときます。

次にメンバたち

[Header("Option")]
[SerializeField]
private T obj;             //プールするオブジェクト
[SerializeField, Range(0, 1024)]
private int objNum = 128;    //プールするオブジェクトの数
[Header("Add Option")]
[SerializeField, Range(0, 1024)]
private int addObjNum = 128; //追加するオブジェクトの数

//内部パラメータ
private T[] pool;              //実際のプール
private bool awaked = false; //初期化フラグ

それぞれコメントに書いてある通りですね。

お次は生成(追加)部分

/// <summary>
/// プールにオブジェクトを追加
/// </summary>
private int AddPoolObj(T poolObj, int poolObjNum) {
    //新しいプールを作成
    T[] newPool;
    int count = 0;
    if(pool == null) {
        newPool = new T[poolObjNum];
    } else {
        newPool = new T[pool.Length + poolObjNum];
        //移し替え
        foreach (T elem in pool) {
            newPool[count] = elem;
            count++;
        }
    }
    //新しいのを追加
    int temp = count;  //countの値を一旦保持
    T obj;
    for (int i = 0; i < poolObjNum; i++, count++) {
        obj = Instantiate<T>(poolObj);
        obj.gameObject.SetActive(false);
        obj.name = poolObj.name;
        obj.transform.SetParent(transform);
        newPool[count] = obj;
    }
    //プール入れ替え
    pool = newPool;
    return temp;
}

一応追加と初期の生成も兼ねてるので、
プールがなかったら作って、プールがある場合は移し替えてます。

とあるところで配列の探索はforeachの方が高速やで!という記事(C#におけるforeach文とfor文による添え字アクセスのパフォーマンス検証 | ftvlog)を見かけたので、配列はなるべくforeachを使うようにしてます。

肝心の取り出し部分
単品

/// <summary>
/// オブジェクトの取り出し
/// </summary>
public T Pop() {
    //初期化確認
    if(!awaked) Awake();
    //使用可能オブジェクトの検索
    foreach(T elem in pool) {
        if(!elem.gameObject.activeInHierarchy) {
            elem.gameObject.SetActive(true);
            return elem;
        }
    }
    //足りない場合
    if(addObjNum > 0) {
        //追加して最初の要素を返す
        return pool[AddPoolObj(obj, addObjNum)];
    }
    return null;
}

複数

/// <summary>
/// 複数のオブジェクトの取り出し
/// </summary>
public T[] Pop(int popNum) {
    //初期化確認
    if (!awaked) Awake();
    //使用可能オブジェクトの検索
    T[] objs = new T[popNum];
    int popCount = 0;
    for(int i = 0; i < pool.Length; i++) {
        if(!pool[i].gameObject.activeInHierarchy) {
            pool[i].gameObject.SetActive(true);
            objs[popCount] = pool[i];
            popCount++;
            if(popCount >= popNum) break;
        }
        //足りない場合
        if((addObjNum > 0) && (i == pool.Length - 1)) {
            if(popCount < popNum) {
                //追加する
                AddPoolObj(obj, addObjNum);
            }
        }
    }
    return objs;
}

それぞれ毎回取得O(n)の計算量が掛かってしまっているのが若干痛いところですね。
O(1)にするためには「取り出し」と「しまい」があれば大丈夫なのですが今回オブジェクトプールが管理するのは「取り出し」だけなのでこんな感じになってます。
どちらとも管理しなくてもO(1)にする方法があったような気がするんだけど、忘れてしまったのでそれはまた思い出してからで。

とりあえず実装はこんな感じです。

次は継承先を作ります。恐らく一番使われるであろうTransform用のオブジェクトプールです。

/// <summary>
/// Transform用のオブジェクトプール
/// </summary>
public class TransformObjectPool : GenericObjectPool<Transform>{

    /// <summary>
    /// オブジェクトの取り出し
    /// </summary>
    public Transform Pop(Vector3 position) {
        Transform obj = Pop();
        if(obj == null) return null;
        obj.transform.position = position;
        return obj;
    }
    /// <summary>
    /// オブジェクトの取り出し
    /// </summary>
    public Transform Pop(Vector3 position, Quaternion rotation) {
        Transform obj = Pop();
        if (obj == null) return null;
        obj.transform.position = position;
        obj.transform.rotation = rotation;
        return obj;
    }
    /// <summary>
    /// 複数のオブジェクトの取り出し
    /// </summary>
    public Transform[] Pop(int popNum, Vector3 position) {
        Transform[] objs = Pop(popNum);
        foreach(Transform elem in objs) {
            if(elem == null) break;
            elem.position = position;
        }
        return objs;
    }
}

一旦継承させておくことでそのクラスにあった取り出したときの初期化が出来るようになります。

使うときはこんな感じで

//プールを取ってくる
TransformObjectPool pool = FindObjectOfType<TransformObjectPool>();
//(0, 0, 0)に生成
pool.Pop(Vector3.zero);

というわけでオブジェクトプールについての備忘録でしたー

【Unity】LineRendererの拡張 その2【メッシュ】【自動生成】

※その1の続きです。

seiroise.hatenablog.com

その1は実装部分についての備忘録だったので、
その2ではそれ以外のシェーダとかエディタ拡張とかの備忘録になると思います。
そして最後にプロジェクトファイルを上げておきます。

エディタ拡張

その1で要件の一つに「エディター上で確認できる」という項目を挙げたので、
エディタを拡張してなんとか要件を満たします。
エディタを拡張するといっても何のことはなく普段のインスペクターの描画に更新ボタンを追加するだけです。

シェーダ

Unity標準の頂点カラーシェーダだと透過処理を行ってくれなかったので
頂点カラーで透過処理を行うシェーダを書いていきます。
(正直ここが一番時間かかった!

やってることは頂点シェーダ、フラグメントシェーダを使って頂点カラーをそのまま描画してるだけです。
重要なのは最初のTagsとBlendなのですが、シェーダとかほとんど書いたことなかったので結構苦労しました。

一応サーフェイスシェーダ版も書いたので一応載せときます。

f:id:seiroise:20160530001446g:plain:w300

これはこれでありだけど陰影がついてしまうので、ちょっと変。
(あと描画順がおかしい?

使い方

使い方は割とLineRendererに近い感じです。

f:id:seiroise:20160530002621p:plain:w400

上から

  1. 「Edge Parameter」
    • エッジの大きさと何角形にするかが選べます
  2. 「Node Parameter」
    • ノードに使うメッシュとノードのデフォルトの大きさ、色が選択できます(スクリプトからいじる時用)。
  3. 「Nodes」
    • 表示するノードのリストです。
  4. 「Apply」ボタン
    • 更新用のボタンです。Nodesやパラメータの内容が反映されます。

プロジェクトファイル

最後にプロジェクトファイル
onedrive.live.com