アプリで、NGUIを使っていて、困った話です。以外に、悩んだのでメモも兼ねて投稿します。
当時の状況
アプリのUIには、NGUIを使っていました。
また、演出には、Spineが使われており、UnityのRuntimeで再生させています。
2Dの演出やエフェクトにはSpineが使われているところが多く、NGUIのインスタンスにAddComponentして再生している箇所もあります。
起きた問題
NGUIでViewを重ねた時に、後ろで再生されているSpineのエフェクトが、前面にあるViewに隠れず表示されてしまっていました。具体的には、UIScrollViewに、UIGridでアイテムを並べてあって、そのアイテムの子供にSpineのComponentがあり、再生された時にNGUIのクリップが効かず、スクロールで本来隠れて見えない範囲のSpineの演出が表示されてしまっている状況でした。
原因
NGUIは、子供のオブジェクトをクリップして、描画を行っていますが、SpineのRuntimeでは行っていません。その為、Spineにクリップする処理を足します。
行った対応
今回は、シェーダーにクリップ領域を渡して、フラグメントシェーダーでクリップしています。
実際に書いたコード
UIPanelには、localCorners と worldCorners というメンバを持っています。
下の画像は、dumpしてみたログになります。画像から、どんな情報が入っているかは、想像が付くかと思います。
今回は、Panelの頂点座標が、world座標系に変換されている worldCorners をそのまま使用する事ができました。
シェーダーに要素を渡しているコードです。
// MeshRendererは、MaterialにSetVectorする
meshRenderer = gameObject.GetComponent<MeshRenderer>();
// UIPanel は、GetComponentInParentしなくても、クリップする領域のpanelのインスタンスであれば、OK
worldCorners = gameObject.GetComponentInParent<UIPanel>().worldCorners;
mat = meshRenderer.material;
// _ClipArea
// x : UIPanel のxのworld座標系の左上のx座標
// y : UIPanel のyのworld座標系の左上のy座標
// z : UIPanel のxのworld座標系の右下のx座標
// w : UIPanel のyのworld座標系の右下のy座標
mat.SetVector("_ClipArea", new Vector4(worldCorners[0].x, worldCorners[0].y, worldCorners[2].x, worldCorners[2].y));
今回は、Skeleton-Fog.shaderを修正しています。
頂点シェーダーの内容です。
worldPosには、World座標に変換したvertexを入れています。2DのUV座標系になっている為、必要なのは、xとyの値になります。
Skeleton-Fog.shader
------------------------------------------------
struct v2f {
float4 vertex : SV_POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
float2 worldPos : TEXCOORD1;
};
v2f vert(appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.color = v.color;
o.texcoord = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
// クリップ領域をWorld座標に変換
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xy;
return o;
}
フラグメントシェーダーの内容です。
worldPosには、World座標に変換されたvertexが入っています。
また、_ClipAreaには、クリップ領域の情報が入っているはずなので、以下のような情報をセットしてありますので、xの座標の判定は、xとzでチェックし、yの座標の判定は、yとwでチェックしています。
Skeleton-Fog.shader
------------------------------------------------
fixed4 frag(v2f i) : COLOR
{
float inArea = (step(_ClipArea.x, i.worldPos.x) *
step(i.worldPos.x, _ClipArea.z) *
step(_ClipArea.y, i.worldPos.y) *
step(i.worldPos.y, _ClipArea.w));
// クリップ領域に入っていなければ、inAreaが 0.0 になる為、色が出ない。
return inArea * i.color * tex2D(_MainTex, i.texcoord);
}
以上となります。