【Unity】FadeCamera2のスクリプトを勉強する

前記事で、FadeCamera2のシーン開始時の処理を新しく作ったのですが、
3年前の処理では、上手く動かない事が発生していて、原因を探っていたのですが、
はっきりとした理由が見つけられてません。

ただ、原因を考察したので、
FadeCamera2のスクリプトを勉強しながら説明して行こうと思います。

まず、動作不安定の処理を軽く振り返ってみます。
シーンの開始時にフェード処理を入れたいので、
Fade.cs

using UnityEngine;
using System.Collections;
using UnityEngine.Assertions;

public class Fade : MonoBehaviour
{
IFade fade;

public bool startFade;

void Start ()
{
Init ();

if (startFade)
cutoutRange = 1f;

fade.Range = cutoutRange;
}

こんな感じでcutoutRangeを1にする処理をしていました。
FadeCamera2考察㉝.jpg
FadeCanvasのFadeコンポーネントにチェックを入れれば、
シーン開始時にフェード処理が行えると言う感じでっす。

ただ、このスクリプトでは、フェード処理が実行されないので、
FadeCanvasの複製を作る際、プレハブのCutoutRangeを1にしてから
アタッチする事で動くようになったので、やったー!となったんですが…

ここでミスをおかしております。
・startFadeをpublicにしている

publicな変数にしたのは、シーン開始時にフェード処理を入れない場合、
機能させたくなくてインスペクターでチェックできればいいなと思ったからです。

当時は、publicとprivateの変数をそれほど気にしてなかったのが、
一番の問題点なんですが(..*) オハズカシイ

また、プレハブのCutoutRangeを1にしてアタッチするのも
的外れな方法だったなと思います。

この辺りを考察するにあたって、
少しFadeCamera2のスクリプトを勉強してみたいと思います。

FadeCamera2で使われてる技術なんですが、

・Interface
・コルーチン
・デリゲートとSystem.Action
・エディタ拡張とoverride

このあたりは、覚えるのが必須と言ってもいいくらいの技術です。

正直、勉強しないとと思いながら放置していた部分なんで、
本当に今回は良い勉強をさせて頂いたな~、と実感してます。

まずは、コルーチンからと言いたい所ですが、
System.Actionと混在しているので、Interfaceから見て行きます。

・Interface
Interfaceは、スクリプト間のデータ共有などで使われる物で、
繋ぐものと考えてもらえばいいかと。

IFade.csを見てもらうと、

public interface IFade
{
float Range{get; set;}
}

これだけしか書かれてません。

最初は、なんでこれだけで機能するの?って感じでした。
Interfaceは、受け渡しするだけの機能なんで、
受け手になるクラスで定義する必要があります。

今回、cutoutRangeを受け渡しさせるので、
受け取り元のFadeImage.csに定義されています。
FadeImage.cs

public class FadeImage : UnityEngine.UI.Graphic , IFade
{
public float Range {
get {
return cutoutRange;
}
set {
cutoutRange = value;
UpdateMaskCutout (cutoutRange);
}
}
}

まず、クラスの継承にIFadeが書き込まれているので、
IFadeを継承したクラスって事になります。

Interfaceは、継承したクラスに定義を決めろと指示するので、
Rangeの変数宣言が行われています。

これでIFadeを経由して、他クラスからcutoutRangeの受け渡しができるようになります。

ケロの認識では、
FadeCamera2考察㉑.jpg
こんなイメージです。
厳密に言うと少し違うんですが…
(;^o^) \(ToT )てきとうやな!

Interfaceを使うと、複数のクラスから受け渡しができるので、
覚えておくと便利な処理になります。

ルールと言うか、Interfaceと分かるようにクラス名の頭にI(アルファベットのアイ)を
付けるようにするらしいです。
なのでIFadeと言うクラス名になってるわけですね。

・デリゲートとSystem.Action
続いて、デリゲートとSystem.Actionです。
デリゲートは、メソッドを代入できる変数です。

二つのクラスで、同じメソッドを使いたいって事はないでしょうか?
でも、いちいち二つのクラスに同じメソッドを書くって面倒です。

そんな時にメソッドを渡す事ができたら楽になるのにと考えてしまいます。

それを可能にするのがデリゲート変数です。
要は、メソッドも変数に格納して渡せば、
好きな所で使えるよねって考えです。

定義やコールバックなど、使うのに少し慣れが必要になります。

Unityでは、このデリゲートを簡単に使えるように、
SystemにActionとFuncが用意されています。

単純にメソッドだけを渡す場合は、Actionを使います。
詳しくは、Unity ActionとFuncで調べてもらうと、デリゲートも一緒に勉強できます。

この変数がどこで使われてるか見てみると、
Fade.cs

IEnumerator FadeoutCoroutine (float time, System.Action action)
{
}

IEnumerator FadeinCoroutine (float time, System.Action action)
{
}

public Coroutine FadeOut(float time, System.Action action)
{
}

public Coroutine FadeIn (float time, System.Action action)
{
}

コルーチンの第二引数に設定されています。

フェード処理のコマンドを設定する際、

fade.FadeIn(fadeTime,() =>
{
fade.FadeOut(fadeTime);
});

fade.FadeIn(秒数,メソッド)と設定しています。
書き方がラムダ式なので分かりにくいですが、
第一引数がfloat型、第二引数がAction型になっているので、
メソッドを割り当てる事ができます。

コルーチンの中を見てみると、

IEnumerator FadeinCoroutine (float time, System.Action action)
{
float endTime = Time.timeSinceLevelLoad + time * (1 - cutoutRange);

var endFrame = new WaitForEndOfFrame ();

while (Time.timeSinceLevelLoad <= endTime) {
cutoutRange = 1 - ((endTime - Time.timeSinceLevelLoad) / time);
fade.Range = cutoutRange;
yield return endFrame;
}
cutoutRange = 1;
fade.Range = cutoutRange;

if (action != null) {
action ();
}
}

最後に、if (action != null) があるので、
メソッドが代入されていれば実行させるとなってます。

なのでシーン遷移などが行えるわけですね。

このコルーチンを初めて見た時に、
FadeOutとFadeInが、二重に設定されていて何これ?って、
なったんですが、引数が第一しか設定されてなくても、
コルーチンが呼び出せるように処理されています。

第一引数しか設定してない場合は、第二引数をnullに設定して、
もう一度、呼び出してからStartコルーチンを掛けてる辺りは、
参考にさせてもらう点ですね。

こんな使い方ができるんだ!と思いました。

コルーチン内部は、説明は要らないと思いますが、
簡単に説明すると、cutoutRangeの加減算を行う処理になってます。
当然、計算値は都度Interfaceに流してる感じです。
FadeCamera2考察㉞.jpg
フレームが開始されると計算が行われ、yield returnがEndFrameなんで、
フレーム終了まで待機。次フレームに切り替わると冒頭で計算、フレーム終了まで待機。

フレーム毎に繰り返されて、設定秒が過ぎればコルーチンから抜け出る感じです。

・エディタ拡張とoverride
最後は、エディタ拡張を見て行きます。
エディタ拡張とは、Unityの開発環境を、もう少し楽にしたい。
そんな時に機能を追加できれば!って事で用意されてる物です。

自力でも作れるようですが、ハードルは高いです。

さて、エディタ拡張が盛り込まれてる部分ですが、
Fade.cs

void OnValidate ()
{
Init ();
fade.Range = cutoutRange;
}

このメソッドは、Unityのパラメータが変更された時に呼び出されます。
パラメータって何?って声はスルーして…
(;^o^) \(ToT )あんたほんとにそれでいいの

簡単に言うとインスペクターを変更した時、
PLAYボタンを押した時、
Unity上で何かを変更した時に呼び出されると思っていればOKかと。

メソッドの中身は、Startで呼び出される物と同じ内容なんですが、
実は、このメソッド連動しております。
FadeImage.cs

#if UNITY_EDITOR
protected override void OnValidate ()
{
base.OnValidate ();
UpdateMaskCutout (Range);
UpdateMaskTexture (maskTexture);
}
#endife

FadeImage.csの下の方に設定されています。

これは、OnValidateメソッドが呼び出された場合、
override、つまり上書きさせると言う処理になります。

中身は、CutoutRangeとMaskTextureの上書きです。

これの分かり易い状況は、
FadeImageのCutoutRangeを操作すると、ゲームViewでルール画像が再生される所です。
FadeImageは、初期状態で非表示に設定されています。

通常、FadeImageのチェックを入れないと表示状態になりません。
また、バーを動かしてもRangeが変化するだけで、透明度が変化する事はないんです。

overrideでUpdateMaskCutoutメソッドを呼び出してるので、
CutoutRangが0より大きくなった所で、FadeImageが自動的にONになります。

少しテストしてみます。
EditorTest.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class EditorTest : MonoBehaviour
{
[SerializeField,Range(0,1)]
private float cutoutRange;

[SerializeField]
private Image image;
}

こんなスクリプトを書いておいて、
Unity上にシーンを作成します。
FadeCamera2考察㉟.jpg
UIのImageを追加して、作ったスクリプトをアタッチしておきます。

この状態で、CutoutRangeを操作しても何も起きません。
そこでスクリプトに、
EditorTest.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class EditorTest : MonoBehaviour
{
[SerializeField,Range(0,1)]
private float cutoutRange;

[SerializeField]
private Image image;

private void OnValidate()
{
image.enabled = true;

var color = image.color;
color.a = cutoutRange;

image.color = color;

if (cutoutRange <= 0)
image.enabled = false;
}

}

OnValidate関数を加えて、Imageの表示・非表示と、
透明度の設定をすると。
FadeCamera2考察㊴.jpg
CutoutRangeを操作するだけで、ImageのON・OFFと透明度の調整ができます。
OnValidate関数は、こんな事ができます。

さて、連動してるoverrideの上を見てみると、
#if UNITY_EDITORと書かれています。

これは、Unity上だけ呼び出される設定になります。

Androidや他のプラットフォームにインストールした場合は、反応しない設定です。
あくまでUnity上で、開発中に自動化させるのが目的になります。


これで、FadeCamera2のスクリプトの勉強は、だいたい終わりです。
使える技術が盛りだくさんなんで、本当に勉強になります。ヽ(´▽`)/~♪


さて、ここからは、
以前の処理が不安定になった部分を考察して行こうと思います。

あくまで考察なんで確証ではないですが、
バグを発生させないようにする部分では、参考になるかと思うので、
お付き合い頂けるとうれしいです。

まず、Fade.csのStartでcutoutRangeを1に設定したのですが、
シーン開始でフェードが起きない状態を考えてみます。

フェードが起きない=cutoutRangeが0

しかし、Fade.cs内では、コルーチンの計算以外
cutoutRangeが0になる設定は無いんです。

となると、if(startFade) cutoutRange=1f;が機能してないって事になります。
この状態は、startFadeがFalseになるときだけです。

このことを踏まえて、OnValidate関数をもう少し見て行きたいと思います。

さっきも説明しましたが、OnValidate関数は、パラメータが変更された時に
呼び出される関数になります。

これは、ゲームPLAYしてなくても呼び出される関数です。
インスペクターを触った時、PLAYボタンを押した時、その他

どんな風に呼び出されるか調べてみます。
Fade.cs

void OnValidate ()
{
Debug.Log("呼び出された");
Init ();
fade.Range = cutoutRange;
}

OnValidate関数にDebugを仕込んで、
呼び出され方を見てみます。

とりあえずインスペクターのチェックを外してみます。
FadeCamera2考察㊲.jpg
呼び出されました。

続いて、PLAYボタンを押してみます。
FadeCamera2考察㊳.jpg
あれ?インスペクターのチェックを外した時は、1つだけなのに
PLAYを押すと2つの出力になりました。

なんで?( -᷄ω-᷅ )💭

OnValidate関数ってほかで使ってたかな…



プレハブのFade!
そうなんです、プレハブのFadeにもOnValidate関数があるので、
当然、呼び出される事になります。

少し調べてみたら、やはりプレハブ側からも呼び出されていました。

そうなるとプレハブにもstartFadeが存在します。
これは、触ってないのでFalseになっています。

そこで、考えたのですが、
OnValidate関数のパラメータって、どうやって取得してるかって事です。

例えば、変更するパラメータをピックアップして上書きして行くのって、
処理的にFindと同じくらい重くなるのでは?

Unity的には、処理を軽くしたいはず。
それならパラメータを取得しておいて、
必要に合わせて上書きしていくのでは?

そんな事を思いつきました。

そうなるとstartFadeも取得されていて、状況によって上書きされるって事に。

設定は、publicなんで、プレハブ側を取得して、PLAY時に複製側を上書きもありえます。
そうなると当然、Falseになるんで機能しないって事になります。

何かの状況、例えばヒエラルキーの位置なんかで取得順が変われば、
プレハブ側が先に取得されていても不思議ではないんですよね。

しかし、パラメータの取得方法は調べようがないんで、
仮説でしかないんですが…

結論としては、安易にpublicな変数を設定しない。
変数のスコープは、絶対に考えておかないといけない。

完全に初歩的なミスです。

なので、以前の記事を削除しようと思っています。
バグの温床なんで(T-T) グスッ

後、Androidでは機能しない点は、
OnValidate関数がそもそもAndroid上で呼び出されてるか分かりません。

プレハブは、複製の元になるので、
もしかするとビルド時に内包されるかもしれません。

OnValidate関数が生きていれば、この現象がAndroid上でも起きてるかも…

調べる事ができないので仮説になります。

上手く機能しない、Android上で機能しないなどあれば、
新記事で作った処理を試して見て下さい。

おそらく問題無く機能してくれると思います。

問題点に長い間、気づかずに放置してた事をお詫びします。m(_ _)m

また、FadeCamera2は、よくできたAssetなので、
使うも良し、勉強するも良し。おすすめの一品になります。

それでは長くなりましたが、皆さんよいUnityライフを送って下さい。
今回は以上です。(TωT)/~~~ BYE BYE


この記事へのコメント

この記事へのトラックバック