VR、ゲーム制作、プログラミング。Unity とか Oculus Rift とか。

2013年12月21日土曜日

[Unity]DotFabで出力したモデルを取り込む

ドット絵を 3D にできる DotFab を Unity で使ってみたときのメモです。

最新の v0.6.1 は 3D モデル出力機能がないので、v0.4.1 を使います。

(注 : 3D の知識はほとんどないので、以下おかしなことを言っているかもしれません)

DotFab の使い方は readme を見ればわかるので割愛しますが、SAVE_3D_OBJECT で出力した .obj (と .mtl) を Unity にドラッグ & ドロップしてインポートすると、下記の画像のようになってしまいました。


どうも大半のポリゴンのマテリアルが正しく設定されていないようです。

試しに Blender でインポートしてみると正しく表示できたので、Unity で .obj のインポートに何かあるのかもしれないと思い、形式を変換してみることにしました。

最終的には .dae 形式でエクスポートすると Unity で期待通りインポートできました。


気になるのは、Blender 上での頂点数は 586 だったのが、Unity 上では 44,000 になっていることです。これはさすがに多すぎるんじゃないかなと。
(このモデルで元々 586 頂点というのも、各ポリンゴンのマテリアル設定で色をつけるのではなくテクスチャを貼ればもっと減らせそうですね)


Cube の頂点数が 8 でなく 24 になるので、Verts の表示は純粋な頂点数でない(もしくは標準の Cube は面ごとに 4 頂点 * 6 面?)のかもしれませんが、それにしても 75 倍は多いような…。


原因はまだわかりませんが、とりあえず表示は一応できたということでメモ。

2013年12月8日日曜日

[Unity]NGUIでUnityの2Dスプライトをアニメーションする

NGUI で Unity 4.3 の 2D スプライトを使う方法は [Unity]NGUIでUnityの2Dスプライトを使う に書きましたが、今回はこれをアニメーションさせる方法です。

NGUI のバージョンは 3.0.6f6 以降が必要です。

まずは上記記事などを参考に、Unity 4.3 の 2D スプライト表示とアニメーション、NGUI で Unity の 2D スプライト表示ができるところまで準備してください。

あとは SpriteRenderer と Animator をアタッチして、数行のコードを追加すれば完成です。(空のオブジェクトに下のコードだけアタッチすれば、必要なスクリプトは全てアタッチされます。)

コードは以下。( GitHub : unity-ngui/UI2DSpriteAnimation.cs )

using UnityEngine;
using System;

[RequireComponent(typeof(UI2DSprite))]
[RequireComponent(typeof(SpriteRenderer))]
[RequireComponent(typeof(Animator))]
public class UI2DSpriteAnimation : MonoBehaviour
{
    System.WeakReference spriteRenderer_ = new System.WeakReference(null);
    SpriteRenderer spriteRenderer {
        get { return (spriteRenderer_.Target ?? (spriteRenderer_.Target = GetComponent<SpriteRenderer>())) as SpriteRenderer; }
    }

    System.WeakReference ui2dSprite_ = new System.WeakReference(null);
    UI2DSprite ui2dSprite {
        get { return (ui2dSprite_.Target ?? (ui2dSprite_.Target = GetComponent<UI2DSprite>())) as UI2DSprite; }
    }

    void Reset () {
        if (spriteRenderer) { spriteRenderer.enabled  = false; }
    }

    void Update () {
        if (!spriteRenderer || !ui2dSprite) { return; }

        spriteRenderer.enabled = false;
        ui2dSprite.nextSprite = spriteRenderer.sprite; // NOT ui2dSprite_.sprite2D = spriteRenderer_.sprite;
    }
}

ポイントは UI2DSprite.nextSprite を使うことです。UI2DSprite.sprite を直接書き換えると点滅します。

nextSprite は直接アニメーションで指定できないので、非表示にした SpriteRenderer 経由でコピーする必要があります。(参考 : Animation Curve を設定できる型はこちら + Unity 4.3 で Boolean とスプライト関連が追加された模様)

スクリプトを追加したら、Animator Controller をインスペクタの Animator にドラッグ&ドロップしてください。


これで動きます。

現時点では公式ドキュメントに UI2DSprite.nextSprite の説明は "To be used with animations." としか書いていませんが、ソースコードを見た範囲ではたぶん間違っていないとおもいます。


2013/12/9 追記 : UI2DSpriteAnimation.cs をアタッチすれば他の必要なスクリプトもアタッチされることを追記。

[Unity]NGUIでUnityの2Dスプライトを使う

Unity 4.3 で 2D スプライト機能などが強化されましたが、GUI 向けではないということで、現状 GUI は NGUI などのアセットを使うのがよいようです。

Unity の 2D スプライトと NGUI を併用する方法は少なくとも 2 種類、それぞれ独立させる方法と NGUI に取り込む方法がありますが、今回は後者について紹介します。


前提として、Unity の 2D スプライトの表示方法は把握していることとします。([Unity]Unity4.3の2D機能入門まとめ もどうぞ。)

NGUI は 3.x 系 (アニメーションも考えると 3.0.6f6 以降) を使ってください。

NGUI で Unity の 2D スプライトを表示するには、UI2DSprite を使うだけと簡単です。

いつも通りメニューから NGUI -> Create -> 2D UI として UIRoot などを作成したあと、NGUI -> Create -> Unity 2D Sprite します。

あとはスプライトをインスペクタにドラッグ&ドロップするだけ(下図)。


これで NGUI と座標系やサイズを共有し、NGUI のイベント処理も使えるようになります。

Unity のスプライトアニメーションとも連携できます。方法はこちら( [Unity]NGUIでUnityの2Dスプライトをアニメーションする )。


NGUI の更新情報はこちら。



2013/12/9 追記 : [Unity]NGUIでUnityの2Dスプライトをアニメーションする を書きました。

[Unity]Unity4.3の2D機能入門まとめ

Unity4.3 で 2D 系の機能が強化され、スプライト表示やアニメーションなどが便利になりました。

これについては皆さんすでに書かれていますので、自分がとりあえず入門レベルになるまでにお世話になったサイトをまとめておきます。


Unity2DとNewGUIについて : Unity の中の人のスライド。Unity の 2D 機能の概要、噂の次期 GUI (uGUI) との住み分けの話など。uGUI が出るまでの GUI 作成は Unity の 2D スプライトではなく NGUI とか使っておくといいかも、といったことも書いてありました。


スプライトを表示して最低限のアニメーションをするところまでは以下のサイトを見ればできると思います。

Unity4.3 で追加された Sprite 機能を使ってみよう - その1 - Unityではじめるゲーム開発 - Mobile Touch

Unity 4.3 2D Spriteの使い方:TextrueとSpriteのパラメーターについて - タカシカンパニーブログ

テラシュールウェア [Unity]Unity2d機能、スプライトの使い方

テラシュールウェア [Unity]Unityのスプライトアニメーションの使い方(基本編)


標準のスプライト分割機能は一枚の画像に異なるサイズのスプライトを詰め込むと使いにくいので、ツール作ったほうがいいかな…と思っていたらすでに作られていました。まだ試してはいないのですが TexturePacker ユーザーには便利だと思います。

テラシュールウェア [Unity]TexturePackerで一つにまとめたテクスチャをUnity 2Dのスプライトで使う


もっと見る場合はこちらも。

UNITY - アップデート情報 : Unity4.3 リリースノート

Unity 4.3 - 2D Game Development Walkthrough - YouTube : 公式ビデオチュートリアル

2013年8月8日木曜日

[Unity]null合体演算子でコンポーネントへのアクセスをスマートにする

C# の null 合体演算子 "??" が地味に便利です。

C/C++ はじめ、他の言語ではあまり見かけない、というか C# くらいにしか無さそうな演算子なのでなじみが薄い方も多いのではないかと思いますが、 ?? の左が null でなければ左、null なら右を返す演算子です。

??演算子 (C# リファレンス)

(2013/12/8 追記 : 型によって期待通り動かないことがあるかもしれません。具体的には UnityEngine.Animator がダメでした(弱参照にした場合はもちろん大丈夫でした)。参照型だと思うのですが…理由はまだ分かりませんが取り急ぎ。 /Unity 4.3)

Unity だとこんな感じで GetComponent() や Find() などの結果を高速化のためにキャッシュしておいたり、
private Hoge hoge_ = null;
void Func(){
    Hoge hoge = hoge_ ?? (hoge_ = GetComponent<hoge>());
    hoge.DoSomething();
}

子オブジェクトのコンポーネントへの getter を提供したり、
System.WeakReference piyo_ = new System.WeakReference(null);
public Piyo piyo {
    get { return (piyo_.Target ?? (piyo_.Target = transform.GetComponentInChildren<Piyo>())) as Piyo; }
}

あるコンポーネントが付加されていなかったら追加しつつ取得する拡張メソッドを書いたりと、地味ながらよく使う処理をスマートに書けます。
public static class GameObjectExtentions
{
    public static Component ExAttachComponent(this GameObject gameObject, string type)
    {
        if(!gameObject){ return null; }
        return gameObject.GetComponent(type) ?? gameObject.AddComponent(type);
    }
    
    public static T ExAttachComponent<T>(this GameObject gameObject)
        where T : Component
    {
        if(!gameObject){ return null; }
        return gameObject.GetComponent<T>() ?? gameObject.AddComponent<T>();
    }
}

三項演算子でももちろん書けますが、冗長になりがちですね。
// GetComponent() が null でなくても 2 回呼ばれる(はず。最適化してくれるんだろうか?)
//return gameObject.GetComponent<Hoge>() ? gameObject.GetComponent<Hoge>() : gameObject.AddComponent<Hoge>();

Component component = gameObject.GetComponent<Hoge>();
return component ? component : gameObject.AddComponent<Hoge>();

以上、なくても困らないけどあると便利な null 合体演算子でした。


2013/12/8 追記 : UnityEngine.Animator で正しく動かないことを追記しました。

2013年6月4日火曜日

[Unity]Monodevelopの.NET Frameworkのバージョンを設定する

C# のデフォルト引数など、C# 4.0 からの機能を MonoDevelop でビルドできるようにするには、MonoDevelop のフレームワークを .NET (Mono) 4.0 に設定する必要があります。(デフォルトでは 3.5?)

設定は MonoDevelop 2.8.2 現在、Project > Assembly-CSharp Options > Build > General > Target framework にあります。


MonoDevelop でビルドできるかどうかと Unity でビルドできるかどうかは関係ないので設定は必須ではありませんが、MonoDevelop でエラーが出ていると気持ち悪いしちょっと不便ですね。

参考 :
MonoDevelopでデフォルトのTarget frameworkをMono.NET 4.0に変更する - Virtual Memory

2013年5月10日金曜日

[Unity]NGUIで当たり判定を付ける

NGUI でスプライトに当たり判定を付ける場合、普通に

  • スプライトに Collider を付ける
  • 必要に応じて RigidBody も付ける
  • OnCollisiionEnter などを書く

とした上で、

  • RigidBody の FreezePosision の z と FreezeRotation の x, y にチェック(負荷が減る、と思う)
  • 表示順の制御に z 座標を使っている場合、衝突し得るように Collider の Size.z などを調整
    • クリック等のイベントを受け取るスプライトの Collider は最も手前になるようにする
    • または、イベントを受け取るスプライトを含む UIPanel のレイヤを分け(スプライト個別にはレイヤを変更できないため UIPanel 単位になる)、UICamera の Event Receiver Mask をそのレイヤだけに、Camera の Culling Mask はそのレイヤも表示するようにする
あたりに気を付けるとよさそうです。

複数のスプライトが他のオブジェクトの子である場合などは、Collider は各スプライトに付け、イベントは親オブジェクトで処理(スプライト側で一旦受けて親に SendMessage)するのもよいかもしれません。

3D 用の衝突判定を 2D で使うのは富豪的な気もしますが…

2013年5月8日水曜日

[Unity]NGUIのOnDragイベントを使う

NGUI のスプライトをドラッグで動かせるようにしたときのメモです。

(2013/5/9 追記 : 動かすだけなら UIDragObject だけで済みます。こちらはコーディング不要です。)

スプライトに Collider をつけた上で、

public class Player : MonoBehaviour
{
    void OnDrag(Vector2 delta)
    {
        Vector2 mousePoint = UICamera.lastTouchPosition;
        Vector2 worldPoint = UICamera.currentCamera.ScreenToWorldPoint(mousePoint);
        
        transform.position = new Vector3(worldPoint.x, worldPoint.y, transform.position.z);
    }
}

または

public class Player : MonoBehaviour
{
    void OnDrag(Vector2 delta)
    {
        Vector2 mousePoint = UICamera.lastTouchPosition;
        Vector2 worldPoint = UICamera.currentCamera.ScreenToWorldPoint(mousePoint);
        
        // 普通は毎回 Find() しない
        transform.localPosition = GameObject.Find("UI Root (2D)").transform.InverseTransformPoint(new Vector3(worldPoint.x, worldPoint.y, 0));
    }
}

などですね。

引数の delta はスクリーン座標上での前回のマウス位置との差分(だと思う)なので、UIRoot の Scaling Style を Pixel Perfect にしていない限りはそのままでは使いにくそうですね。


2013/5/9 追記 : UIDragObject について追記しました。

2013年5月7日火曜日

[Unity]NGUIでスプライトを隙間なく並べる

NGUI で 2D ゲーム風にドット絵のマップチップを敷き詰めた時などに、スプライト間に 1px 程度の隙間が空いてしまう場合の確認内容です。

  • テクスチャの Filter Mode を Point にする


  • 元画像を周囲 1px 拡大しておく(TexturePacker を使っているなら、Layout の Extrude を 1 にするだけで簡単にできます)




2013年5月4日土曜日

[Unity]NGUIで画面サイズに合わせる(NGUI2.3.0対応版)

以前 [Unity]NGUIで画面サイズに合わせる(その2) で書いたスクリプトが、NGUI 2.3.0 の変更との兼ね合いで正しく動かなくなっていたので、今更ですが対応版です。

UIRoot.automatic が obsolete 扱いになり、代わりに scalingStyle というパラメータに変更になったようです。

  • PixelPerfect : 拡大縮小しない(automatic = true 相当)
  • FixedSize : 画面の高さに合わせる(automatic = false 相当)
  • FixedSizeOnMobile : iOS, Android のときは FixedSize、それ以外では PixedPerfect

横がはみ出ないように画面の幅に合わせる機能は現在でもないようですね。

というわけで、数行しか変わっていませんが 2.3.0 対応版です。

using UnityEngine;

[ExecuteInEditMode]
public class UIRootScale : MonoBehaviour
{
    public int manualWidth = 1280;
    public int manualHeight = 720;
    
    UIRoot uiRoot_;
    
    public float ratio {
        get {
            if(!uiRoot_){ return 1.0F; }
            return (float)Screen.height / uiRoot_.manualHeight;
        }
    }
    
    void Awake()
    {
        uiRoot_ = GetComponent<UIRoot>();
    }
    
    void Update()
    {
        if(!uiRoot_ || manualWidth <= 0 || manualHeight <= 0){ return; }
        
        int h = manualHeight;
        float r = (float)(Screen.height * manualWidth) / (Screen.width * manualHeight); // (Screen.height / manualHeight) / (Screen.width / manualWidth)
        if(r > 1.0F){ h = (int)(h * r); } // to pretend target height is more high, because screen width is too smaller to show all UI
        
        //if(uiRoot_.automatic){ uiRoot_.automatic = false; } // for before NGUI 2.3.0
        if(uiRoot_.scalingStyle != UIRoot.Scaling.FixedSize){ uiRoot_.scalingStyle = UIRoot.Scaling.FixedSize; } // for NGUI 2.3.0 or later
        if(uiRoot_.manualHeight != h){ uiRoot_.manualHeight = h; }
        if(uiRoot_.minimumHeight > 1){ uiRoot_.minimumHeight = 1; } // only for NGUI 2.2.2 to 2.2.4
        if(uiRoot_.maximumHeight < System.Int32.MaxValue){ uiRoot_.maximumHeight = System.Int32.MaxValue; } // only for NGUI 2.2.2 to 2.2.4
    }
}


2013/5/4 20:23 追記 : スクリプトに余計な行があったのを修正しました。

2013年1月21日月曜日

[Unity]インスタンス生成時に引数を渡したい

Unity で、インスタンス生成時にパラメータを設定したいとします。

たとえばプレハブから生成する場合、Object.Instantiate() は引数を取れないので、一般的には以下のように生成後にセットすることになります。

public class MyClass : MonoBehaviour
{
    public int param1 { get; set; }
    public int param2 { get; set; }
    
    void Start(){ Debug.Log("MyClass : param = " + param1 + ", " + param2); }
}

public class MyTest : MonoBehaviour
{
    public MyClass myClassPrefab;
    public int paramA;
    public int paramB;
    
    void Awake()
    {
        MyClass obj = Instantiate(myClassPrefab) as MyClass;
        obj.param1 = paramA;
        obj.param2 = paramB;
    }
}

これでも大きな問題があるわけではありませんが、MyClass の param1, 2 がいつでも外部から書き換え可能になっているので、MyClass 側では初期化以降もパラメータが変わることの考慮や、MyClass を使う側では param1, 2 を後から変えていいのかどうかの判断が必要になります。

コメントで「初期化直後以外は変えるな!」と書いて済ましてしまうのもひとつの手ではありますが、もうちょっと考えてみましょう。(以下、こういう細かい話が続きますよ!)

さて、とりあえず param1, 2 を外部から直接変更できなくして、いかにも初期化時しか呼んではいけなさそうな関数 Initialize() でのみセットできるようにしてみます。

public class MyClass : MonoBehaviour
{
    public int param1 { get; private set; }
    public int param2 { get; private set; }
    
    void Start(){ Debug.Log("MyClass : param = " + param1 + ", " + param2); }
    
    public void Initialize(int _param1, int _param2)
    {
        param1 = _param1;
        param2 = _param2;
    }
}

public class MyTest : MonoBehaviour
{
    public MyClass myClassPrefab;
    public int paramA;
    public int paramB;
    
    void Awake()
    {
        MyClass obj = Instantiate(myClassPrefab) as MyClass;
        obj.Initialize(paramA, paramB);
    }
}

これはこれでもう大体解決という感じではありますが、Initialize() を呼ぶまでの間に不完全なインスタンスが見えたり、毎回忘れずに 2 行書かないといけないことが気になる日もあるかもしれません。(以下、気になる日の人向けです!)

というわけで引数を取れる MyClass.Instantiate() を作ってみます。

public class MyClass : MonoBehaviour
{
    public int param1 { get; private set; }
    public int param2 { get; private set; }
    
    void Start(){ Debug.Log("MyClass : param = " + param1 + ", " + param2); }
        
    public static MyClass Instantiate(MyClass prefab, int _param1, int _param2)
    {
        MyClass obj = Instantiate(prefab) as MyClass;
        obj.param1 = _param1;
        obj.param2 = _param2;
        return obj;
    }
}

public class MyTest : MonoBehaviour
{
    public MyClass myClassPrefab;
    public int paramA;
    public int paramB;
    
    void Awake()
    {
        MyClass obj = MyClass.Instantiate(myClassPrefab, paramA, paramB);
    }
}

やっていることは前とそう変わりませんが、生成が 1 行になって、パラメータの設定忘れもなくなりそうですね。

もう一つ、略式の Builder パターンっぽくやってみます。

public class MyClass : MonoBehaviour
{
    public int param1 { get; private set; }
    public int param2 { get; private set; }
    
    void Start(){ Debug.Log("MyClass : param = " + param1 + ", " + param2); }
        
    public class Builder
    {
        public MyClass prefab { get; private set; }
        public int param1 { get; set; }
        public int param2 { get; set; }
        
        public Builder(MyClass _prefab)
        {
            prefab = _prefab;
        }
        
        public Builder SetParam1(int _param1)
        {
            param1 = _param1;
            return this;
        }
        
        public Builder SetParam2(int _param2)
        {
            param2 = _param2;
            return this;
        }
        
        public MyClass Build()
        {
            MyClass obj = Instantiate(prefab) as MyClass;
            obj.param1 = param1;
            obj.param2 = param2;
            return obj;
        }
    }
}

public class MyTest : MonoBehaviour
{
    public MyClass myClassPrefab;
    public int paramA;
    public int paramB;
    
    void Awake()
    {
        MyClass.Builder builder = new MyClass.Builder(myClassPrefab);
        MyClass obj = builder.SetParam1(paramA).SetParam2(paramB).Build();
    }
}

同じ設定で量産したり、あとでまた生成するために Builder を保持しておいたりするのには便利です。

パラメータ初期化込みで GameObject.AddComponent() するために、以下のようなものも用意しておくと役立つときもあるかもしれません。

public class Builder
{
    //...
    
    public MyClass AddComponentTo(GameObject gameObject)
    {
        MyClass obj = gameObject.AddComponent<MyClass>();
        obj.param1 = param1;
        obj.param2 = param2;
        return obj;
    }
}

…とはいえ、普通に Object.Instantiate() でパラメータ未設定なインスタンスが生成できてしまうのを禁止はできないので、これも気にするならデフォルト値のまま Start() が呼ばれたらエラーにするなどの対処が必要になると思います。

コンストラクタを private にしても new しか禁止できないからなあ。(private にしても Instantiate() できるのはどうなってるんだろう)

以上、使うシーンに合わせてお好みで。