ここからみれます。
これからUnity3Dを学ぶ人のための最初に知っておくべきこと
使ったAsset
- NGUI
作業時間
- 6時間ほど
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に描画したテクスチャは表示され続けている。
im は Test() の中で創られているので関数終了後はUnity上では参照を失っている。
なので自動でガベコレ的なものがuniSWF内で動作しているのであれば参照を保持しておけば大丈夫なはずだと考え、メンバに MovieClip 型の tmpim を追加した。
しかし結果は描画されず。
メンバ変数に格納すると参照をすぐに見いだせる。当たり前だけど参照そのもは破棄はされていないことが確認できた。
if(tmpim != null) Debug.Log("alright!"); // ここを通る事は無い。
前項で調べた通り、参照先は失われていなかったので描画対象ではなくなったのではと考え、子の数を調べる関数でチェックを行った。
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’が保持される事が分かったのでこれをもうちょっと掘り進めたら以下のようになる実装になっているということが分かった。
その場で ‘new’ した ‘MovieClip’ に ‘addChild’ した孫 ‘MovieClip’ は次のフレームでは親子関係が破棄されている。
List形式とかで生成したやつは全部格納しておき、そこからアクセスするとか。
けっこう色んなパターンを調べてやっとこの結論に至ったのだけれども間違いがあったらご指摘いただきたい。
演出だけは突出した真価を発揮する。uniSWFをコレからも宜しくお願いします。間違ってもUI全部にuniSWFを使っては行けない!マジで!(涙目)
[wp_ad_camp_1]
日本語が使えるフォントと使えないフォントが入り交じっているが、表示用文字挿入カラム内に日本語を入力してフォントを選択することで、日本語フォントを内包するフォントを洗い出す事が出来る。
試用不可能な場合は画像の様に使用可能には見えなくなる。
フォント一覧カラム内をアローキーで順繰りにしていけばプレビューに日本語が表示されるので、仕様可否が判別できる。
日本語が表示可能だとプレビューが変化する。
一つ注意をするとすれば、フォントを選ぶ際は最低限の文字列で行うべきだ。プレビュー作成にけっこうな時間を要してしまう。
青空文庫のサイトで漢字の基準がまとまってUPされている。
青空文庫
ここから任意の基準の文字列をDLしてbmGlyphに記入する。
suffixを記述しないとその旨のエラーが出る。
記述してもエラーが残る場合は、bmGlyphを再起動することで回避できる。
bmGlyphで作成したテクスチャとテキストデータをドラッグしてフォントを作成する。
名前を新たにつけておく。
Create the Fontをクリックしてしばらく待つとドラッグ元にプレファブが生成される。
UILabelをアタッチしたオブジェクトに名前を入れる。
ちゃんと表示された。”晋弘”部分はフォントに無いので表示されない。
このとき、UILabelのx,yのスケールはポイントを表すので画面サイズに対して何ポイントで表示させたいかでスケールを決める必要がある。
またUILabelには文字数の上限があって、1800文字程度入力すると表示がされなくなる。
よって、規約などの文字列がおおいものを一度に表示するのではなくて、複数のオブジェクトに分けるか、WebViewを出現させるスクリプトを仕込んで別アプリケーションで対応させるなどが現実的だ。
簡易アニメーションビューワを作成してみたのでサンプルコードとその解説を残す。
Command + Shift + N で新しいシーンを作成する。
シーンビューにはカメラしか写ってない。
アセットストア等からデータを入手できるので入手しておく。
これをプロジェクトビューからシーンビューかHierarchyにD&Dでインスタンシングする。
インスタンシングされたオブジェクトを選択し、Inspectorに表示されたTransformの座標を全てゼロにしておく。
この時点でモノによってはカメラに納まっている。
Component->Physics->Character Controllerを選択し、先にインスタンシングしたオブジェクトにコンポーネントを追加する。
Inspector の AnimationsにあるElementを見てアニメーションがちゃんと存在するを確認しておく。
下記のスクリプトをコピペして追加する。
#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だとモーションは一度再生された後に最終フレームで停止する。
スライダは再生速度を変動させる。ループ再生しているモーションを確認する際は便利。
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プロパティ(任意で指定可能な文字列)を取得して配列に格納している。
ループが完了したら配列は返される。