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

2012年10月24日水曜日

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

2013/5/4 追記 : スクリプトは [Unity]NGUIで画面サイズに合わせる(NGUI2.3.0対応版) が最新です。


以前 [Unity]NGUIで画面サイズに合わせる(その2) で書いたスクリプトが、NGUI のバージョンアップによる機能追加との兼ね合いで正しく動かなくなっていたので、その暫定対応版です。


NGUI 2.2.2 : http://www.tasharen.com/forum/index.php?topic=11.msg9404#msg9404 に
- NEW: You can now specify a minimum and maximum height on UIRoot.
とある通り、UIRoot に minimumHeight と maximumHeight が追加されました。ソースのコメントより
If the screen height goes below this value, it will be as if 'automatic' is turned off with the 'manualHeight' set to this value.
とのことで、画面サイズが minimunHeight を下回った場合に画面サイズに合わせて縮小するのを諦めてはみ出させたり、maximumHeight を上回った場合に拡大するのをやめて余白を作るための機能のようです。


この条件下で以前のスクリプトが正しく動かなくなっていたので、取り急ぎこの機能追加を実質無視するような方向での対応を加えたものが以下です。

using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
public class NGUIUtilScalableUIRoot : MonoBehaviour
{
    public int manualWidth = 1280;
    public int manualHeight = 720;
    
    UIRoot uiRoot_;
    
    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){ 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; }
        if(uiRoot_.manualHeight != h){ uiRoot_.manualHeight = h; }
        if(uiRoot_.minimumHeight > 1){ uiRoot_.minimumHeight = 1; }
        if(uiRoot_.maximumHeight < System.Int32.MaxValue){ uiRoot_.maximumHeight = System.Int32.MaxValue; }
    }
}

NGUI の機能追加を活かせるように対応するのがベストかとは思いましたが、本家 NGUI にも解像度調整機能が入るかも?(>ゲームは初心者にやさしく: NGUI 解像度調整機能が縦(Height)にしか対応してない)ということで上記のスクリプトは遠からず不要になるかもしれませんので(むしろなってほしい)、とりあえずはこれで。



2013/5/4 追記 : [Unity]NGUIで画面サイズに合わせる(NGUI2.3.0対応版) を書きました。

2012年8月24日金曜日

[Unity]NGUIで画面サイズに合わせる(その2)

2013/5/4 追記 : スクリプトは [Unity]NGUIで画面サイズに合わせる(NGUI2.3.0対応版) が最新です。


NGUI での複数解像度対応のための画面全体の拡大縮小は以前 [Unity]NGUIで画面サイズに合わせる に書いた方法もありますが、今回は別の、より NGUI の枠組みを活用したやり方でやってみます。

実は NGUI 自体にも画面サイズに合わせて拡大縮小する機能は一応あって、UIRoot の manualHeight がそれに当たります。manualHeight は、この高さが画面の高さにちょうど収まるようにするという意味があります。(少なくともそういう挙動をします。公式ドキュメントには特に説明はないのですが)


デフォルトでは Automatic=true になっているため、画面サイズの変更に合わせてこの manualHeight も自動で値が変わるようになっていますが、Automatic=false にすることで値を固定できます。ここで 240 にすると、下図のように 320x240 のスプライトがちょうど収まるようになります。


しかし、左右が余る場合はよいのですが、上下が余ってほしい場合でも下図のような表示になってしまいます。


というわけで、次のスクリプトを UIRoot に追加して、上下が余ってほしい場合に manualHeight を制御するようにしてみます。

using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
public class NGUIUtilScalableUIRoot : MonoBehaviour
{
 public int manualWidth = 320;
 public int manualHeight = 240;
 
 UIRoot uiRoot_;
 
 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){ 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; }
  if(uiRoot_.manualHeight != h){ uiRoot_.manualHeight = h; }
 }
}

これで、上下左右どちらが余る場合でも期待の表示を得ることができます。



この方法は、以前紹介した Anchor 等の Scale を操作する方法と比較して、Anchor が複数になった場合でも対応がいらないといった利点もあります。

ちなみに、余った部分はカメラの Clear Flags を Solid Color にして黒にしています。このあたりは他にもカメラがあるなら適宜調整ということで。


2012/10/10 追記 : UIRoot が小文字になっていたのを修正。
2012/10/24 追記 : [Unity]NGUIで画面サイズに合わせる(NGUI2.2.2対応版) を書きました。
2013/5/4 追記 : [Unity]NGUIで画面サイズに合わせる(NGUI2.3.0対応版) を書きました。

2012年7月29日日曜日

[Unity]NGUIの複数パネル時のDrawCallメモ

NGUI の複数パネル時の DrawCall について、個人的に勘違いしていたのでメモ。

UIPanel を増やすと同じ Atlas を使っていても DrawCall が増えると思っていたのですが、そうでもないようです。

図の Panel2(緑) と Panel3(青) は同じ Atlas です。以下の図では、Panel1(赤、別 Atlas)が 2 と 3 の間の Z 座標になっていて、合計の DrawCall は 3 になっています。


一方、以下の図は Panel1 の Z 座標は 2 と 3 よりも奥になっていて、合計の DrawCall は 2 になっています。ソースまでは確認していませんが、おそらく同一 Atlas かつ間に他の Atlas を描画する必要のない 2 と 3 をまとめて描画してくれていると考えられます。


(NGUI の複数 Atlas 時の表示順序については [Unity]NGUIのDepth設定その2(複数Atlas) を参照)

NGUI のバージョンは v2.0.9a です。

公式ドキュメント ( NGUI: UIPanel » Tasharen Entertainment ) にはパネルを増やすと DrawCall が増えるように読める記述が見られる気がしますが、まとめてくれるならありがたいですね。

2012年7月18日水曜日

[Unity]2Dゲーム用パーティクル

2D ゲームのエフェクトに Unity のパーティクルシステムを使おうと思い、試してみました。

以下のようなパーティクルを作っていきます。


パーティクルシステムは Shuriken (Unity 3.5) です。

カメラの作成

まずはパーティクル撮影用カメラを作ります。2D 撮影用カメラとは別にします。上の画像では、青い背景は 2D 部分です。


Hierarchy の Create > Camera で新しいカメラを作成し、Culling Mask を 3D パーティクル用レイヤだけを写すように設定します。ここでは "3D Particle" というレイヤを新しく作成して使用しています(Layer > Add Layer...)。

カメラからパーティクルの距離によってパーティクルのサイズが変わらないように、Projection を Orthographic にします。

パーティクルは 2D 部分(GUI やキャラクターなど)より手前に表示したいので、カメラの Depth は 2D 撮影用カメラよりも大きな値にします。

2012/7/31 追記 : Clear Flags を Depth only にします。

パーティクルの作成

まず、星の画像素材を作成します。星の部分の色はパーティクルシステム側でつけるので、画像では白にしておきます。星のまわりは透明にします。

画像ができたらプロジェクトビューにドラッグします。マテリアルを作成し(Project の Create > Material)、インポートした画像を設定、シェーダは Unlit/Trasparent Colored あたりにしておきます。

いよいよパーティクルです。Hierarchy の Create > Particle System で新しいパーティクルシステムを作成し、パーティクル撮影用カメラの子にします。レイヤはパーティクル用のものに忘れずに変えておきます。表示されない場合はカメラの撮影範囲に入っているか確認しましょう。

Renderer に先ほど用意したマテリアルを設定します。

Start Color を黄色に、Start Delay, Start Lifetime, Start Speed, Start Size, Duration を適当に調整します(値は画像参照)。

Color over Lifetime を調整して、赤から黄色に、かつフェードアウトするようにします。バーの下側が乗算する色、上側がアルファ値です。

Size over Lifetime を調整して、パーティクルが徐々に大きくなるようにします。


Shapeを Cone, Angle=90, Radius=0 にして、Rotation を 0,-180,-180 にすると中心から放射状に広がります(上図)。


Shapeを Cone, Angle=0, Radius=0 にして、Rotation を -90,-180,-180 にすると直線的になります(上図)。


Shape を Cone, Angle=0, Radius=90 に、Velocity over Lifetime で Y=-0.5 に、Force over Lifetime で Y=2 に、Rotation を 0,-180,-180 にすると放射状に広がりつつ放物線を描きます(上図)。

これで大体 2D エフェクトに使えそうになってきました。

ただ、複数解像度対応のためにパーティクルを拡大縮小する場合、パーティクルシステムはカメラ側でのスケール変更の影響を受けないため、拡大縮小にはちょっとした処理が必要です。ということで、次回はパーティクルのスケーリングについて書く予定です。


2012/7/18 追記 : 説明がどの画像を指しているかわかりにくかったのを修正。
2012/7/31 追記 : カメラの Clear Flags を Depth only にするのを書き忘れていたので追記。

2012年7月4日水曜日

[Unity]NGUIのDepth設定その2(複数Atlas)

NGUI で複数の Atlas 利用時には、前後関係は Depth ではなく Z 座標で決まるというのは以前 [Unity]NGUIのDepth設定 に書いたのですが、これが結構くせがありました。

単一の Widget 同士なら単純に Z 座標が小さいほうが手前になりますが、複数の場合「同一 Atlas の Widget の Z 座標の平均値」が小さいほうの Widget 全てが、もう一方の全てより手前になるようです。


上図で、SciFi Label 1, 2 と Fantasy Label 1, 2 はそれぞれ同じ Atlas を利用しています。Fantasy Label 1 は Z=1 と 4 つの中で最も Z 座標が大きいですが、Fantasy Label 2 が Z=-3 であるため平均は Z=-1、一方の SciFi Label 1 と 2 の平均は (0 + -1)/2 = -0.5 となり、Fantasy Label 1 も SciFi Label 1, 2 より手前になっています。

普通は「手前 : Fantasy 2 > SciFi 2 > SciFi 1 > Fantasy 1 : 奥」を期待すると思うんですけどね。ちなみに、上の状態で DrawCall=2 です。

では、期待通りにするにはどうするかと言うと、一つは Panel の Depth Pass にチェックを入れることです。…が、アルファブレンディングが犠牲になる上に DrawCall=4 になるため、これは正直使わないと思います(そもそも用途が違いそう)。


結局、Panel を増やすことで、下図のような表示を得ることができました。DrawCall=3 です。


なお、ここでは Panel だけ Z 座標を順序付けし、Widget の Z は全て 0 にしています。実際の表示順序は各 Panel 配下の Widget の絶対 Z 座標の平均で決まるため、上図で Fantasy 1 の Z を -3 にしたり、Panel 1 にもう一つ Widget を追加して Z=-5 にすると、Fantasy 1 が一番手前になります。
 
NGUI のバージョンは v2.0.9a でした。

おまけ

検証中に、あるオブジェクト配下の全ての Widget の Z 座標を -Depth にするスクリプトを書きましたが、上記の挙動のためあまり役に立ちそうもありませんでした。一応貼っておきます。

using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
public class OrderAdjuster : MonoBehaviour
{
    void Update () { AdjustZ(transform); }
    
    void AdjustZ (Transform tf) {
        foreach (Transform child in tf) {
            UIWidget widget = child.GetComponent<UIWidget>();
            if (widget) {
                child.localPosition = new Vector3(
                    child.localPosition.x,
                    child.localPosition.y,
                    -(float)widget.depth);
            } else {
                AdjustZ(child);
            }
        }
    }
}

2012年6月24日日曜日

[Unity]NGUIで画面サイズに合わせる

2012/8/24 追記 : 以下、おいそぎの方は [Unity]NGUIで画面サイズに合わせる(その2) の方がおすすめです。


NGUI で複数解像度対応のために、画面サイズに合わせて拡大縮小するスクリプト。

Panel に追加して、インスペクタで Base Object に画面サイズの基準となる GameObject (例えばデフォルト 1280*720 で作っているなら、そのサイズの GameObject) をドラッグ&ドロップすれば使えます。

using UnityEngine;
using System.Collections;

public class ScreenScaler : MonoBehaviour
{
    public GameObject baseObject;
    public bool aspectFit = true;
    
    void Update(){
        Vector3 ratio = new Vector3(
            Screen.width / baseObject.transform.localScale.x,
            Screen.height / baseObject.transform.localScale.y,
            1.0f
        );
        
        if(aspectFit){
            if(ratio.x > ratio.y){ ratio.x = ratio.y; }
            else if(ratio.y > ratio.x){ ratio.y = ratio.x; }
        }
        
        transform.localScale = ratio;
    }
}

起動中に画面サイズの変更がないなら、Update() でなく Start() でやってしまったほうがよいです。

Aspect Fit = true だと異なるアスペクト比のディスプレイで上下か左右が余ります。この部分の塗りつぶし色を変更したい場合は、Camera の Clear Flags を Solid Color にして Background で色を選択すれば OK です。

なお、localScale の setter の内部実装 (INTERNAL_set_localScale()) によっては、もしかすると 21 行目を

if(transform.localScale != ratio){ transform.localScale = ratio; }

と書いたほうが効率がよいかもしれませんが、小さなプロジェクトで試した範囲では違いは見られませんでした。

また、あくまで拡大縮小ですので、絵がつぶれたり粗くなったりしてしまいます。これはミップマップや HD 用画像を用意するなどして別途対処する必要があるかもしれません。


画面を引き伸ばさずに、UIAnchor + UIStretch で対応する場合の説明は NGUI 公式のドキュメント ( NGUI Example: UIAnchor » Tasharen Entertainment ) にあります。

参考:
Unity Script Reference - Transform


2012/8/24 追記 : [Unity]NGUIで画面サイズに合わせる(その2) を書きました。

2012年6月19日火曜日

[Unity]NGUIのDepth設定

Unity&NGUIはじめました

これまで、フルスクラッチでマルチプラットフォーム対応の2Dゲームエンジンを作ろう!と細々やってきたりしたのですが、多少は動くけどまだまだ時間かかるな、というところで 3D ゲームエンジン Unity とその 2D 用プラグインの一つ NGUI に出会ってしまったのでした。
  • マルチプラットフォーム対応
  • スクリプト言語利用可
  • ネイティブコードも利用可
  • 必要に応じて拡張可
  • 開発するゲームのジャンルにとらわれない
 と、自分で作ろうとしていたものがほぼカバーされてしまっているようだったので、名残惜しい気持もありつつも  Unity&NGUI に乗り換えてみることにしました。

NGUI の Depth 設定

さて、NGUI でスプライト同士の前後関係は通常 UIWidget の Depth で決まるのですが(大きいほど手前)、この順序関係が適用されるのは同一の Atlas を利用したスプライト間だけで、異なる Atlas を利用したスプライト同士では Depth 通りの順序になりません。

異なる Atlas を利用したスプライト同士の順序を決めるためには、Depth ではなく Z 座標を使います(小さいほど手前)。(2012/7/4 追記 : Z 座標の平均値依存という妙な挙動でした。詳細は [Unity]NGUIのDepth設定その2(複数Atlas) に書きました。)

…というのは公式の NGUI Tutorial: Step 2 に普通に書いてあるのですが、NGUI をいじってみる前に流し読みして分かったつもりになっていたので見逃してしまっていました。

ただ、Z 座標を操作すれば異なる Atlas を利用したスプライトを混在させられるものの、Atlas を増やすとその分だけ DrawCall が増える -> モバイルでの動作が厳しくなるようなので、なるべく一つにまとめたほうがよさそうです。

DrawCall を増やさずにリソースを分割したりできるか、逆に何も考えずにひたすら一つにまとめていいのか、などはまだ調査中です。

ちなみに、Atlas 用画像の生成にはテラシュールウェアさんで紹介されていた TexturePacker が便利でした。


2012/7/4 追記 : [Unity]NGUIのDepth設定その2(複数Atlas) を書きました。
2012/7/6 追記 : 体裁など微修正。

2012年6月12日火曜日

Hello, World!

Android の Blogger アプリから初投稿。

Blogger - GooglePlay Store

Android の標準ブラウザや Sleipnir ではブログ本文が入力できなかったので導入テスト中。