タグ別アーカイブ: Unity:UI

[Unity][uniSWF]newしたMovieClipが突然なくなってしまう現象でハマった話

Uniswfを使用しているとプログラム上で動的にMovieClipを生成したくなることがある。
これでちょっと複雑な構造にするだけで意図した動作がなされないことが良くある。

次のコードで例を示す。

    public Vector2 v2Out;
    MovieClip linkage;
    List<MovieClip> c = new List<MovieClip>();
    private void Test(){
        var stage = MovieClipOverlayCameraBehaviour.instance.stage; 
        var a = new MovieClip();
        var im = new MovieClip();
        a.addChild(im);

        var textureA = Resources.Load("images/iconA")  as Texture2D;
        var textureB = Resources.Load("images/iconB")  as Texture2D;
        a.graphics.drawRectUV( 
            texture,
            new Rect(0, 0, 1, 1),
            new Rect(0, 0, textureA.width, textureA.height)
        );
        im.graphics.drawRectUV( // この描画は失われる。
            texture,
            new Rect(0, 0, 2, 2),
            new Rect(0, 0, textureB.width, textureB.height)
        );

        this.linkage.addChild( a );
        c.Add(a);
        a.x = cor.v2Out.x;
        a.y = cor.v2Out.y;

        MovieClipOverlayCameraBehaviour.instance.stage.addChild(this.linkage);
    }
    private void TestMcStructOut(){
        var stage = MovieClipOverlayCameraBehaviour.instance.stage; 
        Debug.Log( "ListCount : " + this.c.Count + " : linkage has :" + this.linkage.numChildren + " : stage has :" + stage.numChildren + " : a's child : " + (this.linkage.getChildAt(0) as MovieClipUtil).numChildren );
    }
    void Update(){
        if(c.Count < 1) Test();
        else{
            //c.ForEach( d => this.linkage.addChild( d ) );
            //c.ForEach( d => d.visible = true );
            c[0].x = v2Out.x;
            c[0].y = v2Out.y;
        }
        TestMcStructOut();
    }

‘Vector2’ 型のメンバ v2Out は描画させたい MovieClip の位置を変更させるためのものとして用意。
Unityを再生中でもMovieClipをぐりぐり動かせる。

Test関数内部で作成しているMovieClipの構造を改めて示すと、

    stage -> linkage -> a -> im

これを実行すると最初のフレームだけ textureA, B の両方が描画され、次のフレームでは im に描画した textureB は消滅している。

状況を整理すると
– linkage はメンバなので次のフレームでは削除される
– a はメンバではないけれども list c で参照を保持している。
– im はメンバでもないし参照を保持していない。
– 消えているのはimだけでaに描画したテクスチャは表示され続けている。

推察1 MovieClip im がメンバじゃないから失われている

im は Test() の中で創られているので関数終了後はUnity上では参照を失っている。
なので自動でガベコレ的なものがuniSWF内で動作しているのであれば参照を保持しておけば大丈夫なはずだと考え、メンバに MovieClip 型の tmpim を追加した。

しかし結果は描画されず。

メンバ変数に格納すると参照をすぐに見いだせる。当たり前だけど参照そのもは破棄はされていないことが確認できた。

    if(tmpim != null) Debug.Log("alright!"); // ここを通る事は無い。

推察2 親子関係だけ外れてしまったのでは?

前項で調べた通り、参照先は失われていなかったので描画対象ではなくなったのではと考え、子の数を調べる関数でチェックを行った。

    private void TestMcStructOut(){
        var stage = MovieClipOverlayCameraBehaviour.instance.stage; 
        Debug.Log( "ListCount : " + this.c.Count + " : linkage has :" + this.linkage.numChildren + " : stage has :" + stage.numChildren );
    }

ここに記述されている ‘numChildren’ が子の数を保持しているプロパティ。
これを回すと最初の ‘Test()’ では linkage.numChildren が 1 だけど次のフレームで記述されたログには 0 になっている。

ここで外れてしまう親子関係だけどうにかするのも手としてはありなのだけどもう一歩踏み込んでみた。

親子関係は破壊されるけれども参照先として’MovieClip’が保持される事が分かったのでこれをもうちょっと掘り進めたら以下のようになる実装になっているということが分かった。

uniSWF_couldnotStoreMovieClip by Kunihiro OHGAKI, on Flickr

その場で ‘new’ した ‘MovieClip’ に ‘addChild’ した孫 ‘MovieClip’ は次のフレームでは親子関係が破棄されている。

まとめ

  • MovieClip をその場で生成する場合はメンバに格納すべき。
  • メンバに格納済の MovieClip でも、さらにその子の MovieClip に描画した場合は親子関係が破棄される。
  • その場で ‘new’ した MovieClip に直接 graphics を記述した場合は問題なく保持される。

対策

List形式とかで生成したやつは全部格納しておき、そこからアクセスするとか。

けっこう色んなパターンを調べてやっとこの結論に至ったのだけれども間違いがあったらご指摘いただきたい。

Unityによる2Dゲーム開発入門 ~プログラミング初心者がゲームを公開する最短コース

おまけ

uniSWFの問題点

  • uniSWFはswf化してからunityに取り込むので何かするにもFlaファイルを開かなければならないのでアプリケーション常に2つ立ち上げっぱなし。
  • さらにflaの素材編集となるとphotoshopも立ち上げなければならない。
  • さらにphotoshopのデータで曲線使ってたりするとillustratorも…
  • ちょっとしたレイアウト調整がGameObjectのInspectorでできない。やれるけど標準的ではない。
  • ソフトウェアレンダリングなので最初の1フレームはデバイスの画面によって拡大縮小された表示が見える。
  • 描画コストが高い。ドローコール減らせない。
  • unityのアップグレードにどこまでついて来れるのやら心配。
  • ドキュメントが完成してない。実は実装済みでドキュメントに記載が無いって事も多々ある。
  • swf作成のお約束が多い。
  • フォントが不自由。
  • フォントサイズも不自由。

uniSWFの使いどころ

  • アニメーション作業がしやすいのでガチャみたいな画面全域で表示しつつ演出過剰にしたい場所では作業を完全に切り分けられるのでオススメ。
  • monoDevelopでソースが見れるのでswfがどういう構造になってるのか勉強になるかも。

演出だけは突出した真価を発揮する。uniSWFをコレからも宜しくお願いします。間違ってもUI全部にuniSWFを使っては行けない!マジで!(涙目)

NGUI用にフォントテクスチャを作って表示するまで

[wp_ad_camp_1]

使ったツール

bmGlyph

作り方

流れ

  • bmGlyphをDL
  • ひらく
  • フォントを選ぶ
  • テクスチャ化したい該当文字を入力する
  • Publish!
  • Unityでフォントプレファブを作成する
  • UILabelで出してみる

使用するフォントの選択

日本語が使えるフォントと使えないフォントが入り交じっているが、表示用文字挿入カラム内に日本語を入力してフォントを選択することで、日本語フォントを内包するフォントを洗い出す事が出来る。
試用不可能な場合は画像の様に使用可能には見えなくなる。

bmGlyph

フォント一覧カラム内をアローキーで順繰りにしていけばプレビューに日本語が表示されるので、仕様可否が判別できる。
JapaneseFont
日本語が表示可能だとプレビューが変化する。

一つ注意をするとすれば、フォントを選ぶ際は最低限の文字列で行うべきだ。プレビュー作成にけっこうな時間を要してしまう。

必要な文字列の抽出

青空文庫のサイトで漢字の基準がまとまってUPされている。
青空文庫

ここから任意の基準の文字列をDLしてbmGlyphに記入する。

エラーが出る

suffixを記述しないとその旨のエラーが出る。
記述してもエラーが残る場合は、bmGlyphを再起動することで回避できる。
Error

NGUI Font Makerでフォントを作成する

bmGlyphで作成したテクスチャとテキストデータをドラッグしてフォントを作成する。
名前を新たにつけておく。
NGUI_FontMaker

Create the Fontをクリックしてしばらく待つとドラッグ元にプレファブが生成される。

UILabelに実際に文字を入れてみる

UILabelをアタッチしたオブジェクトに名前を入れる。

Test_UILabel

ちゃんと表示された。”晋弘”部分はフォントに無いので表示されない。

Test_UILabel2

このとき、UILabelのx,yのスケールはポイントを表すので画面サイズに対して何ポイントで表示させたいかでスケールを決める必要がある。
またUILabelには文字数の上限があって、1800文字程度入力すると表示がされなくなる。

よって、規約などの文字列がおおいものを一度に表示するのではなくて、複数のオブジェクトに分けるか、WebViewを出現させるスクリプトを仕込んで別アプリケーションで対応させるなどが現実的だ。

参考

【Unity】簡易AnimationViewer

簡易アニメーションビューワを作成してみたのでサンプルコードとその解説を残す。

MotionVIewer Screen Shot 0024-03-26 at 17.20.10

やること

  • 新しいシーンを作成
  • モーション付きのモデルをインスタンシング
  • CharacterControllerコンポーネントを追加
  • スクリプト追加

新しいシーンを作成

Command + Shift + N で新しいシーンを作成する。

シーンビューにはカメラしか写ってない。

MotionVIewer Screen Shot 0024-03-26 at 15.29.16

モーション付きのモデルをインスタンシング

アセットストア等からデータを入手できるので入手しておく。

これをプロジェクトビューからシーンビューかHierarchyにD&Dでインスタンシングする。

MotionVIewer Screen Shot 0024-03-26 at 17.33.01

インスタンシングされたオブジェクトを選択し、Inspectorに表示されたTransformの座標を全てゼロにしておく。

この時点でモノによってはカメラに納まっている。

CharacterControllerコンポーネントを追加

Component->Physics->Character Controllerを選択し、先にインスタンシングしたオブジェクトにコンポーネントを追加する。

Inspector の AnimationsにあるElementを見てアニメーションがちゃんと存在するを確認しておく。

MotionVIewer Screen Shot 0024-03-26 at 15.42.29

スクリプトの追加

下記のスクリプトをコピペして追加する。

#pragma strict
var roteSpeed:float = 3.5;

private var side : float = 1.0;
private var animationSpeed:float;
private var animationCount:uint;
private var animationList:Array;
function Start () {
     print("animationGetCount:" + animation.GetClipCount());
     print(animation.clip.name);
     animationCount = animation.GetClipCount();
     print(gameObject.animation);
     animationList = GetAnimationList();
}

function Update () {
     if( -0.9 < transform.rotation.y && transform.rotation.y < 0.9 ){
     }else{
          side *= -1;
     }
     transform.rotation.y += roteSpeed/180 * side;

}

function OnGUI (){
     var sw : int = Screen.width;
     var sh : int = Screen.height;
     var margin : int = 10;
     //ghool position
     var ghoolRoteYLabel:Rect = Rect(0,margin + 0,sw,sh/10);
     GUI.Label(ghoolRoteYLabel,"Rote Y:"+ transform.rotation.y);

     margin += 10;

     //slider
     var playSpeedRect:Rect = Rect(0,margin + 30,sw,sh/10);
     animationSpeed = GUI.HorizontalSlider(playSpeedRect,animationSpeed,0.0,5.0);
     for (var state : AnimationState in animation)
     {
          state.speed = animationSpeed;
     }
     GUI.Label(playSpeedRect,"playSpeed :"+ animationSpeed);

     margin += 100;

     //Buttons
     var buttonSpace:int = 40;
     var rectWidth:int = 100;
     var rectHeight:int = 40;
     var max:int = 10;
     var rects:Array = new Array();
     var i:int = 0;

     for (var name : String in animationList)
     {
          var rect:Rect = Rect(15,margin + 20*i + buttonSpace*i, rectWidth,rectHeight);
          if(GUI.Button(rect,animationList[i].ToString())){
               animation.CrossFade(animationList[i],0.01);
          }
          i++;
     }
}

private function GetAnimationList():Array
{
     var tmpArray = new Array();
     for (var state : AnimationState in gameObject.animation)
     {
          tmpArray.Add(state.name);
     }
     return tmpArray;
}

これを先のインスタンスしたオブジェクトにD&Dするとコンポーネントとして追加される。
再生ボタンで実行を行うと保持しているモーション一覧がボタン化されて列記される。
それらをクリックするとモーションが変更される。
ループ指定がOffだとモーションは一度再生された後に最終フレームで停止する。
スライダは再生速度を変動させる。ループ再生しているモーションを確認する際は便利。

MotionVIewer Screen Shot 0024-03-26 at 17.54.11

スクリプトの解説

Startメソッドで各種初期化。

var roteSpeed:float = 3.5;

private var side : float = 1.0;
private var animationSpeed:float;
private var animationCount:uint;
private var animationList:Array;
function Start () {
     print("animationGetCount:" + animation.GetClipCount());
     print(animation.clip.name);
     animationCount = animation.GetClipCount();
     print(gameObject.animation);
     animationList = GetAnimationList();
}

自身の保持するClip(アニメーション1個の単位)の数を取得。
同時に自作の関数でClipの名前を配列で取得。

UpdateメソッドではキャラのY軸上の回転を行わせている。

function Update () {
     if( -0.9 < transform.rotation.y && transform.rotation.y < 0.9 ){
     }else{
          side *= -1;
     }
     transform.rotation.y += roteSpeed/180 * side;
}

OnGUIメソッドではUI表示。

function OnGUI (){
     var sw : int = Screen.width;
     var sh : int = Screen.height;
     var margin : int = 10;
     //ghool position
     var ghoolRoteYLabel:Rect = Rect(0,margin + 0,sw,sh/10);
     GUI.Label(ghoolRoteYLabel,"Rote Y:"+ transform.rotation.y);

     margin += 10;

     //slider
     var playSpeedRect:Rect = Rect(0,margin + 30,sw,sh/10);
     animationSpeed = GUI.HorizontalSlider(playSpeedRect,animationSpeed,0.0,5.0);
     for (var state : AnimationState in animation)
     {
          state.speed = animationSpeed;
     }
     GUI.Label(playSpeedRect,"playSpeed :"+ animationSpeed);

     margin += 100;

     //Buttons
     var buttonSpace:int = 40;
     var rectWidth:int = 100;
     var rectHeight:int = 40;
     var max:int = 10;
     var rects:Array = new Array();
     var i:int = 0;

     for (var name : String in animationList)
     {
          var rect:Rect = Rect(15,margin + 20*i + buttonSpace*i, rectWidth,rectHeight);
          if(GUI.Button(rect,animationList[i].ToString())){
               animation.CrossFade(animationList[i],0.01);
          }
          i++;
     }
}

スライダはアニメーションの再生速度の変更を担う。
変更時に保持するアニメーション全ての再生速度を同時に変更する。
その後、自動でアニメーション数に応じてボタンを生成。
ボタン押下時の効果はクロスフェードさせてアニメーションをスイッチする、という内容。
アニメーションの名前が分からなくてもElement#で拾えてたと思ってサンプル探したけど出て来ず。
仕方なくAnswersで検索したら発見。コピペ。

GetAnimationListメソッド

private function GetAnimationList():Array
{
     var tmpArray = new Array();
     for (var state : AnimationState in gameObject.animation)
     {
          tmpArray.Add(state.name);
     }
     return tmpArray;
}

for inで、gameObjectが保持するAnimationState形式のデータを全て取得する。
取得したAnimationState各種のClipのnameプロパティ(任意で指定可能な文字列)を取得して配列に格納している。
ループが完了したら配列は返される。

参考