【Timeline】AnimationTrackに早送りやスキップがうまく適応されない時に見直したい事

起きた事象

前提として、TimelineのAnimationTrackを使うことで、TimelineからAnimationClipのアニメーションを呼びだせる。

コードからTimelineを操作してスキップ(特定の時間へ即移動)したり、倍速再生したときに

 playableDirector.time = 5f; //5秒目へスキップ

AnimationTrackで作ったアニメーションだけにスキップ処理や倍速処理が適応されなかった…(他のTrackは問題なし)

本来は、TimelineからAnimationClipを呼んだ場合もスキップや早送りが適応されます。
(Timelineで作ったアニメーションじゃなくてもこれらの恩恵はちゃんと得られる)

対処法


AnimationTrackに設定しているAnimatorのContollerにAnimatorController がついていたことが原因。Timelineで使う場合はNoneになってる事を確認しよう。
(そもそもAnimatorControllerの代わりにTimeLine作ってるのでいらない)
Timelineの操作がAnimatorControllerで打ち消されてしまっていた模様。
(自分でアニメーションを作っている人なら問題でなそうだが、デザイナが作ったデータをエンジニアが操作しているときとか発生しそう)

ちなみにシグナルから呼ばれたスクリプト処理にはスキップや早送りは適応されません
シグナルから呼ばれるスクリプトでサウンド再生処理とか作った上で早送りとかしても、サウンド自体は早送りされないって意味。

Addressableのことはじめ的な記事。

最近はResourcesをやめてAddressableを使う現場が増えてきたと自分の身の回りでも感じられるようになってきました。(遅いかな?^^;)
というわけでこの記事ではAddressableの基本をまとめてます。(全く使ったことない人が見るレベルね)

環境:Unity2021.3.3f1(win10)

Addressable何がいいの?

Addressableの登場前は、
パッケージに組み込む(ローカル)アセットはResources等の内部フォルダに置いていて、
パッケージ外(リモート)に置いてDLして使うアセットはアセットバンドルを使う。
というケースが大半だったと思います。

Resourcesはそんなに迷わずお手軽に使っちゃってました*1が、アセットバンドルは仕組みを実装するのが非常に複雑でした。
そんな時に出てきたのがAddressable!

Addressableを使うと、
・リモートでもローカルでもコードを変える必要がない(設定の切り替えだけで済む)
・リモートアセットもアセットバンドルで実装するよりも非常にわかりやすく実装できる。
という恩恵をすぐに得ることができます:)

PackageManagerからインストール

(環境によるかもしれないけど)現在はデフォルトで入っていないので、PackageManagerからインストールする必要があります!

インストールが終わるとすべてのアセットにAddressableを設定する欄↓ができます。

アドレスを登録

Addressableの入力欄にチェックを入れて、任意のアドレスを入力します。

Selectを押すか Window -> Asset Management -> Addressable -> Groups で出てくる「Addressable Group」ウィンドウで
設定されているアドレスとリソースの一覧が表示されます。
上で入力したアセットがあるか確認してみましょう:)
何もしていなければ 「Default Local Group」に入ります。

非同期ロードする

以下のようにしてアドレスを指定してからリソースを取得できます。(以下は画像を取得する例)

using UnityEngine;
using UnityEngine.AddressableAssets; //←これが必要
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.UI;

public class TestAddressable : MonoBehaviour
{
    AsyncOperationHandle<Sprite> handle;

    async void Start()
    {
        handle = Addressables.LoadAssetAsync<Sprite>("bg/town_1"); //アドレスを指定してSpriteを取得
        await handle.Task;
        GetComponent<Image>().sprite = handle.Result;
    }

    private void OnDestroy()
    {
        Addressables.Release(handle); //使い終わったら解放してあげる必要あり!
    }
}

注意なのは、Addressableは基本、非同期ロードになることです。
今までResources.LoadAsync を使っていた箇所では、
Addressables.LoadAssetAsyncに単純に置き換えれられると思います。

同期ロードする

上述で基本Addressableは基本、非同期ロードになると言いましたが、同期ロードの仕組みも後から実装されたようです。
AsyncOperationHandle#WaitForCompletion でロードされるまで待機し、戻り値で結果を返してくれます。

using UnityEngine;
using UnityEngine.AddressableAssets; //←これが必要
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.UI;

public class TestAddressable : MonoBehaviour
{
    AsyncOperationHandle<Sprite> handle;
    void Start()
    {
        handle = Addressables.LoadAssetAsync<Sprite>("bg/town_1"); //アドレスを指定してSpriteを取得
        GetComponent<Image>().sprite = handle.WaitForCompletion(); //同期ロード
    }

    private void OnDestroy()
    {
        Addressables.Release(handle); //使い終わったら解放してあげる必要あり!
    }
}

実機ビルドする時はアセットもビルドする必要があるけど、自動でやってくれるよ

Androidやios、Window版等パッケージでリソースをロードするには、アセットをビルドする必要があります。
※エディタでもPlay Mode Script を Use Existing Build にしている場合はビルドされたアセットを使うのでアセットのビルドをする必要があるよ!

手動で行う時は「Addressable Group」ウィンドウBuild -> New Build -> Default Build Scriptでアセットのビルドをしてから、
パッケージ(ゲーム本体)のビルドを行います。

(このような記事が多いですが↑)
最新のバージョンでは、自動で行う仕組みが既に実装されていて手動で行うケースはあまりなさそうです。
AddressableAssetSettingsというファイルの Build->Build Addressables on Player Build の項目が、
「Build Addressables content on Player Build」になっていればパッケージのビルドの際に、一緒にアセットバンドルもビルドされます。

ちなみに、「Use grobal Settings (stored in preferences)」になっている場合は、

Prefarences にある Addressables の項目で指定した値が使われます。

*1:正直Unity公式から非推奨という情報もかなり前から出ていたが…

【解像度対応】SafeAreaを跨ぐUIの配置

今回はセーフエリアを跨ぐUIをどう配置すれば、どの端末でもある程度キレイに見えるのかについて考えます。
セーフエリアを跨ぐUIって何?→具体的な例を見ていきましょう↓↓↓ :)

こういったレイアウトで、ノッチのない端末であれば単純にセーフエリア内で左上寄せにすれば問題ないのですが、

ノッチありの端末の場合はどうでしょう。
上で問題なかった状態のままノッチあり端末で見てみると、このように切れてしまうことがあると思います。

ノッチありの端末での期待値はこういう状態です。

引き延ばせる画像ならSliceで対応できる

対象の画像のSpriteEditorでスライスの設定をします。

この画像は横150pxの内の10px分を引き延ばす領域にしたので、width=140pxでぴったり左寄せ(x=0)にするとこのようになります。

どんなにセーフエリア外が広い端末でも問題ないように今度は、width=1000pxにして、xの値を1000-140px=860にすればこのようになります。

この状態でノッチなし端末で見てみても問題なさそうです。

引き延ばせない画像はSliceで対応できない→画像側で対応してもらおう

スライスで出来ない場合は、引き延ばせない画像の場合です。 今度はちょっと柄が入ってしまっているボタン画像を用意しました。

スライスでおなじ設定にしてみるとノッチあり端末で、このように汚くなってしまいます…

というわけで引き延ばせない画像の場合はセーフエリア外の領域分、画像に足します。
どの端末でもある程度カバーできるだけの余裕をもったサイズ用意しましょう。

ImageTypeをSimpleにして、widthは画像のサイズに。左寄せならxは画像サイズ-セーフエリア内に見せたいサイズ にすればOKです。

まとめ

まとめると、SafeAreaを跨ぐUIの解像度対応は、引き延ばせるかどうかで対応方法が変わる。
引き延ばせる画像の場合:Sliceで対応できる。画像側はとくに変更しなくてもいける場合が多い。
引き延ばせない画像の場合:画像側の対応も必須。

【PlayableAPI】アニメーションPlayableの使い方

PlayableAPIを使ってモーション(アニメーション)の再生してみます。
Unity側で用意されているモーションを再生する仕組みには、SimpleAnimationやTimelineやAnimationControllerを使う方法がありますが、
そういった高機能な仕組みでは実装しづらい、またはできない場合に使えそうです。

環境:Unity2021.3.3f1(win10)

モーションを再生する(基本)

モーションを再生するのにはAnimationClipPlayableAnimationPlayableOutputを使います。

using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Animations;

[RequireComponent(typeof(Animator))]
public class TestAnimationPlayableExample : MonoBehaviour
{
    [SerializeField] private AnimationClip animeClip; //予めアタッチしておくこと

    private void Start()
    {
        PlayableGraph _playableGraph = PlayableGraph.Create("Example Playable");

        // モーションを設定
        AnimationClipPlayable clip1Playable = AnimationClipPlayable.Create(_playableGraph, animeClip);
        AnimationPlayableOutput playableOutput 
                = AnimationPlayableOutput.Create(_playableGraph, "Animation", GetComponent<Animator>());
        playableOutput.SetSourcePlayable(clip1Playable);

        _playableGraph.Play();
    }

    private void OnDisable()
    {
        _playableGraph.Destroy();
    }

このスクリプトをAnimatorをアタッチされたゲームオブジェクトにアタッチすればモーションが再生されます。

AnimationPlayableUtilities で簡単に書くこともできる

単純に1つのモーションを再生するなら、AnimationPlayableUtilitiesを使って簡単に書くこともできるようになってた。お手軽!

using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Animations;

[RequireComponent(typeof(Animator))]
public class TestAnimationPlayableExample : MonoBehaviour
{
    private PlayableGraph _playableGraph;
    [SerializeField] private AnimationClip animeClip;

    private void Start()
    {
        Animator animator = GetComponent<Animator>();
        AnimationClipPlayable clip1Playable = AnimationPlayableUtilities.PlayClip(animator, animeClip, out _playableGraph);
    }

    private void OnDisable()
    {
        _playableGraph.Destroy();
    }
}

モーションをブレンドする

ブレンド?

モーションの切り替わりによく使われる仕組みで、
複数のモーションを同時に再生し、その割合(ウェイトと呼ばれることの方が多い)を決めることができる仕組み。
例えば、歩行モーション→走るモーションに切り替わる際に、すぐに切り替えてしまうと不自然な場合が多い。
そこで走るモーションの割合を少しずつ増やしていって、徐々にモーションを切り替えることで自然に見せることができる。


ブレンドするにはAnimationMixerPlayableを使って複数のモーションを登録します。

以下のソースの例は、インスペクタのRangeで2つのモーションの割合を切り替える例です。
AnimationMixerPlayable#SetInputWeight でウェイト設定できます。

AnimationMixerPlayable#ConnectInputの1つ目の引数にいれている数字は、
AnimationMixerPlayable#SetInputWeight の1つ目の引数の数字と対応していて、対象のモーションに対してウェイトを設定します。

using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Animations;

[RequireComponent(typeof(Animator))]
public class TestAnimationPlayableExample : MonoBehaviour
{
    private PlayableGraph _playableGraph;

    [SerializeField] [Range(0, 1)] private float weight;

    AnimationMixerPlayable mixer;

    [SerializeField] private AnimationClip animeClip;
    [SerializeField] private AnimationClip animeClip2;

    private void Start()
    {
        _playableGraph = PlayableGraph.Create("Example Playable");

        AnimationClipPlayable clip1Playable = AnimationClipPlayable.Create(_playableGraph, animeClip);
        AnimationClipPlayable clip2Playable = AnimationClipPlayable.Create(_playableGraph, animeClip2);

        //ブレンド
        mixer = AnimationMixerPlayable.Create(_playableGraph, 2);
        mixer.ConnectInput(0, clip1Playable, 0);
        mixer.ConnectInput(1, clip2Playable, 0);

        AnimationPlayableOutput playableOutput
                 = AnimationPlayableOutput.Create(_playableGraph, "Animation", GetComponent<Animator>());

        //ブレンドしたmixerを設定する
        playableOutput.SetSourcePlayable(mixer);

        _playableGraph.Play();
    }

    private void Update()
    {
        //2つのモーションのブレンド割合を切り替える
        mixer.SetInputWeight(0, weight);
        mixer.SetInputWeight(1, 1 - weight);
    }

    private void OnDisable()
    {
        _playableGraph.Destroy();
    }
}


▼Weightを1にすると静かめの待機モーションで、0にすると激しめ(笑)のモーションに徐々に切り替わる。

▼PlayableGraph Visualizerでみると複数のAnimationClipをMixerしてるのがわかりやすい

レイヤーごとにモーションを設定する

レイヤー?

ブレンドの他にも複数のモーションを同時に再生する仕組みとしてレイヤーがあります。
上半身と下半身でモーションを分けてる場合だったり…
AnimationControllerを使っている人は一発でわかりますが、これ↓のことです。


というわけで、上半身用と下半身用のモーションを用意してみました。


レイヤーの設定するにはAnimationLayerMixerPlayableを使います。ソースはほとんどブレンドと変わらないのでわかりやすいと思います。

using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Animations;

[RequireComponent(typeof(Animator))]
public class TestAnimationPlayableExample : MonoBehaviour
{
    private PlayableGraph _playableGraph;

    [SerializeField] private AnimationClip topAnimeClip;
    [SerializeField] private AnimationClip bottomAnimeClip;

    private void Start()
    {
        _playableGraph = PlayableGraph.Create("Example Playable");

        //レイヤー
        AnimationClipPlayable topClipPlayable = AnimationClipPlayable.Create(_playableGraph, topAnimeClip);
        AnimationClipPlayable bottomClipPlayable = AnimationClipPlayable.Create(_playableGraph, bottomAnimeClip);
        AnimationLayerMixerPlayable layerMixer = AnimationLayerMixerPlayable.Create(_playableGraph, 2);
        
        layerMixer.ConnectInput(0, topClipPlayable, 0, 1);//最後の引数はウェイト
        layerMixer.ConnectInput(1, bottomClipPlayable, 0, 1);//最後の引数はウェイト
        AnimationPlayableOutput playableOutput 
                = AnimationPlayableOutput.Create(_playableGraph, "Animation", GetComponent<Animator>());
        playableOutput.SetSourcePlayable(layerMixer);

        _playableGraph.Play();
    }

    private void OnDisable()
    {
        _playableGraph.Destroy();
    }
}


▼結果:こんな風に両方のモーションが再生されます

▼PlayableGraph Visualizerでみると複数のAnimationClipをLayerMixerしてるのがわかる

関連記事

www.snoopopo.com

【PlayableAPI】カスタム(User)Playableの作り方

2023年現在、Unity側が用意しているPlayable APIには
アニメーションとAudioを再生するAPI(クラス群)が用意されており、これに加えて、
自分でPlayable APIの機構を使って何かを再生したい場合にカスタムするためのAPIが用意されている。 *1

この記事では、このカスタム(User)Playableの作り方をまとめました。

環境:Unity2021.3.3f1(win10)

PlayableBehaviourを継承したクラスを作成する

まずカスタムPlayableを作成するためにはPlayableBehaviourを継承したクラスを作ります。
このクラスには実際にPlayableで行いたい処理を書きます。

必要に応じて各メソッドをoverrideして行いたい処理を実装しよう。
よく使われれるのは以下の3つのメソッドです。

public class TestPlayableBehaviour : PlayableBehaviour
{
    public override void OnBehaviourPlay(Playable playable, FrameData info)
    {
        //開始時に一度だけ呼ばれる
    }

    public override void ProcessFrame(Playable playable, FrameData info, object playerData)
    {
        //このPlayableを実行している間、毎フレーム呼ばれる
    }

    public override void OnBehaviourPause(Playable playable, FrameData info)
    {
        //このPlayableが終了した時に1度だけ呼ばれる
    }
}

Playableには処理を実行している時間*2を設定でき(やり方は後術)、
各メソッドのパラメータで渡ってくるPlayableクラスのGetDurationでこの値を取得することができます。

またこのPlayableを開始してからの時間*3Playable#GetTimeで取得できます。


これを基本にここではImageの透過を調整してフェードインするPlayableを作ってみます。

public class FadePlayableBehaviour : PlayableBehaviour
{
    public Image fadeLayer;

    public override void OnBehaviourPlay(Playable playable, FrameData info)
    {
        this.fadeLayer.color = new Color(0, 0, 0, 1); //初期化:はじめは真っ暗に
    }

    public override void ProcessFrame(Playable playable, FrameData info, object playerData)
    {
        //全体時間÷実行時間で徐々に透過
        double alpha = playable.GetTime() / playable.GetDuration();
        this.fadeLayer.color = new Color(0, 0, 0, (float)1f - alpha);
    }

    public override void OnBehaviourPause(Playable playable, FrameData info)
    {
        //ProcessFrameで必ずアルファ値が0になることは保証されないので最後に必ず完全に透過
        this.fadeLayer.color = new Color(0, 0, 0, 0); 
    }
}

注意したいのは、OnBehaviourPauseで必ず終了処理を実装するということです。
ProcessFrameは毎フレーム呼ばれると書きましたが、仮に処理落ちした場合は
アルファ値が半端な状態でPlayableが終わってしまいますし、1度も呼ばれない可能性もあります。

作成したPlayableを実行する

作成したPlayableを実行するには
ScriptPlayableScriptPlayableOutputを使って、PlayableGraphに登録します。

   protected override void DoExecute()
    {
        //PlayableGraph を生成
        PlayableGraph playableGraph = PlayableGraph.Create("Example Playable");

        //ScriptPlayableを使って、自作したカスタムPlayableを生成
        ScriptPlayable<FadePlayableBehaviour > fadePlayable = 
                ScriptPlayable<FadePlayableBehaviour >.Create(playableGraph , 0);

        //ScriptPlayableOutputへ登録
        ScriptPlayableOutput output = ScriptPlayableOutput.Create(_playableGraph, "fader");
        output.SetSourcePlayable(fadePlayable);

        //カスタムPlayableに渡したい情報がある場合はこんな風に書けるよ
        fadePlayable.GetBehaviour().fadeLayer = fadeLayer; //この例ではフェード用のImage

        //実行時間を設定
        fadePlayable.SetDuration(2f); //この例では2秒かけてフェードする

        //実行!
        playableGraph.Play();
    }

ポイントは、ScriptPlayable<カスタムPlayable>#Createで作成すること
ScriptPlayable.GetBehaviourでカスタムPlayableが取得できるので外部から渡す情報があれば渡せることです。

またカスタムPlayableに限った話ではありませんが、
ScriptPlayable.SetDurationで実行時間を設定することができます。

*1:docs.unity3d.com

*2:アニメーションならアニメーションしている時間

*3:deltaTimeでもゲームを開始してからの時間でもないので注意

Androidビルドで「Android SDK is missing required platform API」エラー出た時の対処法

起きた事象

普段使っているPCではないPCで、
androidでビルドしようとしたら以下のエラ―出てビルドできなかったのでメモ。

一番左の「Update Android SDK」を押す!これで解決する人はおしまい。


対処法

「Update Android SDK」おしたら、以下のエラーがUnityのコンソールに吐き出された。
AndroidSDKのUpdateもうまく行かなかったらしい。
その理由が「JAVA_HOMEがなく、javaのPathが通ってないよ 」とエラーが出てる。

Exception: Unable to install additional SDK platform. Please run the SDK Manager manually to make sure you have the latest set of tools and the required platforms installed. 
C:\Program Files\Unity\Hub\Editor\2020.3.35f1\Editor\Data\PlaybackEngines\AndroidPlayer\SDK\tools\bin\sdkmanager.bat "platforms;android-33", exit code 1
Powershell non elevated output:

Arguments are "C:\Program Files\Unity\Hub\Editor\2020.3.35f1\Editor\Data\PlaybackEngines\AndroidPlayer\SDK\tools\bin\sdkmanager.bat" "platforms;android-33" 
�x��: WARNING: Administrative privileges required
Command finished with exit code: 1

Powershell elevated output:
<psElevated-null>
Batch output:


ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

①JDKのインストールをしているか確認

Unity Hub を起動します。
Installs から 使っているUnityの歯車をクリックして「Add modules」を開きます。

「Android Build Support」の「Android SDK & NDK Tool」、「OpenJDK」がインストールされているかを確認します。
されてなかったらしよう。

②Unity で SDK の設定がされているか確認

Edit -> External Tools の Android の設定項目の各パスが設定されていることを確認。

されていない場合は、設定します。(普通は自動でされている)
既にJDKがインストールされているので、windowsなら以下あたりにインストールされているはず。

【Unit Hub のインストールパス】\Unity\Hub\Editor\【使っているUnityのバージョン】\Editor\Data\PlaybackEngines\AndroidPlayer\

③JAVA_HOME,Path の設定をする

環境変数の設定をします。

win10なら以下を参照。 qiita.com

JAVA_HOME・・・上述した②で設定した「JDK Installs with Unity」に設定したパスを設定
・Path・・・%JAVA_HOME%\bin を追加

コマンドプロンプト等で、set JAVA_HOME で設定したパスが表示されれば、正常に設定されてます。


ここまでやったらandroidビルドできるようになると思われる。
ちなみにAndroid Studio を別途インストールしたりする必要は今現在はないです。
(一時そういう時代があった気がする)