【Unity】アニメーションするボタンとウィンドウを作る
UIがアニメーションするとかっこいいので、記事を参考に導入してみましたが、スクロールウィンドウが思ったより手こずってしまいました。
1.ボタン
下の動画のような押すとアニメーションするボタンを作りました。
コードは以下のようになりますが、これはAnimancer Proというアセットを使用しています。
[Header("Animancer")][SerializeField]AnimancerComponent animancer;
/////////////////中略///////////////
private void AnimePlay() {
if (animancer == null) return;
var state = animancer.Play(pressed, 0.1f);
state.Events.OnEnd = () => animancer.Play(notPressed, 0.1f);
}
Animancer Proを使用しない場合は、デフォルトのボタンコンポーネントのボタンアニメーションを使用することになると思います(こちらのほうが簡単かつ手軽)。
そのやり方は以下の記事を参考にしてください。
ねこじゃらシティさんの記事が特にわかりやすいです。
2.ウィンドウ(ダイアログ)
(1)非スクロールウィンドウ
以下のようなアニメーションして表示されるウィンドウを作成しました。
以下のねこじゃらシティさんの記事を参考に、UniTaskとAnimancer Proを使用したスクリプトを作成しました。
このスクリプトはウィンドウではなく、ボタンのほうにアタッチしてください(ウィンドウは始め、非アクティブだと思いますので)。
また、アニメーションクリップは「ループする」のチェックを必ず外してください。
using System.Threading;
using UnityEngine;
using UnityEngine.UI;
using Animancer;
using Cysharp.Threading.Tasks;
public class AnimatedDialog : MonoBehaviour {
[SerializeField] private AnimancerComponent _animancer;
[SerializeField] private AnimationClip _openClip;
[SerializeField] private AnimationClip _closeClip;
[SerializeField] private Button [] _openButton;
[SerializeField] private Button [] _closeButton;
[SerializeField] private GameObject _dialog;
private CancellationTokenSource _cts;
// アニメーション中かどうか
private bool IsTransition;
// ダイアログは開いているかどうか
public bool IsOpen => _dialog.activeSelf;
private void Awake() {
// キャンセルトークンを生成
_cts = new CancellationTokenSource();
// ボタンにリスナーを追加
foreach (var button in _openButton)
button.onClick.AddListener(() => OpenAsync().Forget());
foreach (var button in _closeButton)
button.onClick.AddListener(() => CloseAsync().Forget());
}
private void OnDestroy() {
_cts.Cancel();// キャンセルトークンをキャンセル
_cts.Dispose();// キャンセルトークンを破棄
// ボタンのリスナーを削除
foreach (var button in _openButton)
button.onClick.RemoveAllListeners();
foreach (var button in _closeButton)
button.onClick.RemoveAllListeners();
}
public async UniTask OpenAsync() {
if (IsOpen || IsTransition) return;
IsTransition = true;
_dialog.SetActive(true);
//ボタンを押せなくする
foreach (var button in _openButton)
button.enabled = false;
if (_animancer == null) return;
// 開くアニメーションの再生、アニメーションのループは外す
_animancer.Play(_openClip);
// 開くアニメーションが終了したらIdleに戻る
IsTransition = false;
//ボタンを押せるようにする
foreach (var button in _openButton)
button.enabled = true;
}
public async UniTask CloseAsync() {
if (!IsOpen || IsTransition) return;
IsTransition = true;
//ボタンを押せなくする
foreach (var button in _closeButton)
button.enabled = false;
if (_animancer == null) return;
_animancer.Play(_closeClip);
_dialog.SetActive(false); // アニメーションが終了した後にダイアログを非表示
IsTransition = false;
//ボタンを押せるようにする
foreach (var button in _closeButton)
button.enabled = true;
}
}
(2)スクロールビュー
スクロールビューも同じように、アニメーションさせて表示させようと思ったのですが…できませんでした。
理由はScrollResetterと絶望的に相性が悪かったからです。
↓ScrollResetterの内容についてはこちら
スクロールビューのScrollRectを一番上に戻すスクリプトです。(1)と同じ方法で、スクロールビューを表示させると、ScrollRectが中途半端な位置で止まってしまいました。
どうやらスクロールビュー全体のスケールの値をいじって、表示させているのに原因がありそうでしたが、上手く解消できませんでした。
ただ、表示時にアニメーションはさせたかったので、TimeLineを使用し、上から移動してくるようにしました。
クレジットボタンをクリックしたときにScrollResetterとConveniButtonのDelayPlayTimelineを実行しています。
↓ConveniButtonについてはこちら
2028.8.26追記
以下のScrollResetterを以下のように改良し、アニメーションするダイアログでも問題なく、スクロールビューが一番上に戻るようになりました。
using UnityEngine;
using UnityEngine.UI;
using Cysharp.Threading.Tasks;
public class ScrollResetter : MonoBehaviour{
[Header("位置をリセットするツマミを登録")] public ScrollRect scrollRect;
//クリックするとスクロールビューが開くボタン等にセット。
//スクロールビューが開くと同時にSetActiveAndResetScrollを実行。
private Button button;
//このスクリプトが有効になったときにボタンを取得
private void OnEnable() {
button = GetComponent<Button>();
// ボタンがあればイベントリスナーを追加
if (button != null){
button.onClick.AddListener(SetActiveAndResetScroll);
}
}
private async void ResetScrollToTop() {
Canvas.ForceUpdateCanvases(); // すべてのキャンバスの更新を強制
await UniTask.Yield(); // 1フレーム待機してレイアウト更新を待つ
// ScrollRectのcontentの位置をリセット
if (scrollRect != null) {
scrollRect.verticalNormalizedPosition = 1.0f;
}
}
public async void SetActiveAndResetScroll() {
// 親オブジェクトのスケールを変更するアニメーションを開始
var parentTransform = scrollRect.transform.parent;
if (parentTransform != null) {
await ScaleParentObjectAsync(parentTransform);
}
// スケール変更後にスクロール位置をリセット
ResetScrollToTop();
}
private async UniTask ScaleParentObjectAsync(Transform parentTransform) {
Vector3 originalScale = parentTransform.localScale;
Vector3 targetScale = new Vector3(1.0f, 1.0f, 1.0f); // 目標のスケールを設定
float duration = 0.1f; // アニメーションの期間
float elapsedTime = 0f;
while (elapsedTime < duration) {
elapsedTime += Time.deltaTime;
float t = Mathf.Clamp01(elapsedTime / duration);
parentTransform.localScale = Vector3.Lerp(originalScale, targetScale, t);
await UniTask.Yield(); // フレームをまたいで処理
}
parentTransform.localScale = targetScale; // 最終的なスケールを設定
}
}