ビデオUIの実装★【Unity6】

絶対調味ロジック

UIの方向性が決まったので、デザイン・実装を行いました。
ビデオUIなので、各種の設定リンクできるかが見栄えのカギになりそうです。

ビデオUI

ボタンとスライダーは「ノーマル」「ホバー」「プレス」を用意しました。

  • ノーマル:通常表示のボタン画像
  • ホバー:ポインタが乗った場合や選択された場合のボタン画像
  • プレス:ボタンが押された状態のボタン画像(今回はここにアニメーションを採用)

UIボタンのマウスオーバーでは、Unity由来のボタン操作やイベントトリガーを使用した方法でした。

各自のボタン役割アニメーションを連携させるとなると、
独立独自のスクリプトで制御した方がシンプルに無理なくできそうでした。

しかし、実装していく途中で、いろいろと気づきがあったので、2~3回仕様を変えることになりました。

トグル式アニメーションボタン

実装の目的

メニューやバックログといった「開閉状態」を持つUIに対し、
「開いている間はパラパラ漫画(アニメーション)をループさせ、閉じている間は静止画(+ホバー演出)」を、
外部との整合性を保ちながら実現します。

コア・ロジック:自己完結型同期

従来の「ボタンを押した時だけ処理する」方式ではなく、「状態監視型」を採用します。

状態の二重管理を回避
Update 内で UI_PanelBase.IsOpen(実際の画面の表示状態)を常に監視。
 ・ 右クリックによるクローズ
 ・ 他のパネルが開いたことによる強制クローズ(CloseAll)
 ・ キーボードショートカット
これら外部要因で状態が変わっても、ボタンの見た目が自動で同期します。

  • メニューボタン:メニュー画面が開いているときはプレスアニメ状態キープ
  • ログボタン:バックログ画面が開いているときはプレスアニメ状態キープ
  • 電源ボタン:ダイアログが開いているときはプレス状態キープ
  • スキップボタン:スキップ状態時はプレス状態キープ
  • オートボタン:オート状態時はプレス状態キープ・スキップ状態時はグレーアウト
  • ロックボタン:ロック状態時はプレス状態キープ、各種ボタンはグレーアウト

優先順位の定義(表示ロジック)

1つの Image コンポーネントを制御するため、以下の優先順位で表示を切り替えています。

状態優先順位表示内容
トグルON1 (最優先)animeSpritesOn リストによるループ再生
マウスホバー2hoverSprite (OFF時のみ有効)
通常時3normalSprite

コンポーネント構成のルール

各ボタンオブジェクトの設定は以下の通り。

  • 必須コンポーネント: Image, UI_MenuButton (または UI_LogButton)
  • 不要なもの
    • Event Trigger:スクリプト内のインターフェース(IPointerHandler等)で完結しているため不要
    • Button.onClick:OnPointerDown で即時反応させているため、
      OnClickにメソッドを登録する必要はありません(二重実行防止のため空を推奨)

Unityボタン仕様に合わせて改善

Unity仕様

ボタンやスライダーの反応範囲は、指定Spriteのサイズに固定されます。
ボタン自体と付随する文字などを1つのボタンスプライトにしてしまうと、文字や空き空間などでもボタンが反応してしまう挙動になります。

それを回避するために、
ボタンスプライト用とそれに付随する文字などスプライト用に分ける必要がありました。

ボタン」としてではなく、
変化させたい「Image」を指定し、それぞれに変化画像やアニメーションを設定できるようにしました。
増減できる仕組みなので、他にリンクさせたいものができた場合にも対応できます。

今回は、プレス時のみにアニメーション設定可能にしています。
すべてに対応していた方が演出表現的には良いですが、欲張らないようにしておきました。

スライダーを実装していた時に、反応範囲に気がつき、新規格として統一した形になります。

共通設計思想:SyncTarget方式

すべてのUIコンポーネント(メニュー、ログ、スキップ、オート、電源、スライダー)を
以下の構造で統一しました。

  • 多点同期:1つの判定(Collider/Raycast)に対し、複数の Image(アイコン、背景、装飾など)を連動可能。
  • 状態の分離:normal, hover, press, disabled の各スプライトを個別に設定可能。
  • フォールバック機能:スプライトが未設定の場合、colorOn / colorOff による色補完が行われる。
  • アニメーション対応:animeOnPress リストにコマ割り画像を入れるだけで、
    ON/プレス状態のループアニメーションを実装可能。

実装上の改善ポイント

アニメーションの継続性(リセット防止)

  • 変更前:UpdateVisuals() が呼ばれるたびにアニメが 1 コマ目から再生し直されていた。
  • 変更後
    状態が変わらない限り(例:プレス中にマウスが判定内を動くだけなど)、
    アニメーションのコルーチンを維持。
    これにより、ドラッグ中やオート中に演出が「チカチカ」する現象を排除。

排他制御と状態優先順位

  • プレス優先
    プレス(ドラッグ)中、またはオートON中などの「アクティブ状態」では、
    マウスのホバー(Enter/Exit)による見た目の変化を無視するようにガードを強化。
  • 外部連動
    スキップ実行中にオートボタンを無効化(Disabled)するなど、
    マネージャー系クラス(Sys_SkipManager 等)の状態監視に基づく表示切り替えを標準化。

堅牢なガード処理

  • 右クリックガード:すべてのボタン・スライダーで「左クリック以外」を明示的に無視。
  • Nullガード
    animeOnPress が空、あるいは Image が未アサインの状態でもエラーを吐かないようチェックを徹底。

今後の運用・拡張ガイド

  • 演出の追加:「ボタンを押している間だけ周囲の粒子を動かしたい」といった場合は、SyncTarget に新しい Image を追加するだけで対応可能です。
  • 新ボタン作成時:UI_PowerButton か UI_AutoButton をテンプレートとしてコピーすれば、今回の「アニメ維持・ホバー保護」の恩恵をそのまま受けられます。
  • 注意:コード内の注釈タグ(“ 等)はプログラムとして解釈されるとエラーになるため、今後の実装でも「純粋なコメント」以外が紛れ込まないよう留意してください。

スキップ・オートボタンの視覚演出改修

表示ロジックの優先順位

スキップとオートでは、ボタンが「無効」になる状態があるため、以下の優先順位で表示を切り替えています。

優先度状態表示内容
1(最優先)無効 (Disabled)disabledSprite を表示(画像がない場合は colorOff を適用)
2実行中 (ON)animeSpritesOn によるループアニメーション(カラーは White 固定)
3ホバー (Mouse Over)hoverSprite を表示
4待機 (OFF)spriteOff を表示

スキップボタン (UI_SkipButton) の特徴

  • ホールド操作への対応:IPointerDown で開始、IPointerUp で停止する既存のホールド挙動を維持。
  • アニメーションの連動
    手動クリックだけでなく、Ctrlキー等の外部操作でスキップが開始された場合も、
    Update による監視により自動的にボタンがアニメーションを開始する。

オートボタン (UI_AutoButton) の特徴

  • 競合回避ロジック
    スキップ実行中はオートボタンを強制的に interactable = false にし、
    専用の disabledSprite へ切り替えることで、操作不能であることを視覚的に提示。
  • カラーフォールバックの分離
    • アニメ画像リストがある場合:スプライト本来の色を出すため Color.white で再生。
    • アニメ画像がない場合:設定された colorOn / colorOff で色を制御。

スライダー:クイック設定

概要

メイン画面に常時表示されるUIとして、明るさとマスター音量をその場で調整できるスライダーを実装。
既存の Sys_BrightnessManager と Sys_AudioSettingsManager を直接呼び出す構成のため、
設定パネル側との値は常に同期される。

設計方針

  • 既存のManagerを直接呼び出すだけで完結。新たな保存ロジックは持たない
  • 設定パネル → クイック設定の同期は Update() の差分チェックで実現
  • 毎フレームDictionaryルックアップ(O(1))と int 比較のみのため、
    ロースペックPCへの負荷は無視できるレベル
  • onValueChanged の二重発火防止のため、
    Manager側から値を書き戻す際はリスナーを一時解除して再登録する

新規作成スクリプト

UI_QuickSettings.cs

・明るさとマスター音量のスライダーをメイン画面常時表示UIとして管理
・System_Btn_Canvas 配下に配置
・Inspectorアサイン:
 brightnessSlider / brightnessValueText
 masterVolumeSlider / masterVolumeValueText
 (TextMeshProUGUIは任意。未アサインでも動作する)
・Start()でManagerから現在値を読み込みスライダーに反映
・Update()でManagerの現在値と前回値を比較し、
 差分があればスライダーを更新(パネル→クイック設定の同期)
・スライダー操作時はManagerのSetXxxImmediate()を呼び即時反映(クイック設定→パネルの同期)

主要な処理

処理タイミング内容
SetupBrightnessSlider()Start初期値反映・リスナー登録
SetupMasterVolumeSlider()Start初期値反映・リスナー登録
SyncFromManager()Update(毎フレーム)Manager値との差分チェック・スライダー更新
OnBrightnessChanged()スライダー操作時BrightnessManagerへ即時反映
OnMasterVolumeChanged()スライダー操作時AudioSettingsManagerへ即時反映

Hierarchy 構成

System_Btn_Canvas
  ├── MenuButton(既存)
  ├── SkipButton(既存)
  ├── AutoButton(既存)
  └── QuickSettings(新規・空のGameObject)← UI_QuickSettings.cs アタッチ
        ├── Brightness_Slider(UI Slider)
        │      └── Brightness_Value(TextMeshPro・任意)
        └── MasterVolume_Slider(UI Slider)
              └── MasterVolume_Value(TextMeshPro・任意)

Lockボタン機能

概要

ゲーム画面上の特定のUI群(ボタン・スライダー)を一括で操作不能にする機能。
「物理的な入力遮断」と「視覚的なロック状態の提示」を両立させつつ、
アドベンチャーゲーム本編の進行(クリック等)は妨げない設計を採用している。

構成要素と役割

コンポーネント役割備考
Sys_LockManager真実のソース(司令塔)ロック状態 IsLocked を管理。
板のON/OFFと群のAlphaを制御。
UI_LockButton入力・表示(スイッチ)マネージャーを監視。
ON時はループアニメ、OFF時はホバー演出を行う。
OperationGroup制御対象の器CanvasGroup を持ち、
ロック時に一括で半透明化(Alpha 0.6)される。
LockCover物理的な盾透明なImage。
ロック中のみActiveになり、背後へのクリックを遮断する。

実装上の工夫(論理的整合性)

  • 右クリックガード:他のUIと同様、右クリックによる意図しないロック/解除を防止。
  • 物理ブロック方式
    自作の IPointerHandler を使用したボタンでも、透明な板(LockCover)を前面に出すことで、
    個別のスクリプト修正なしに確実なクリック遮断を実現。
  • アニメーション同期
    電源ボタンやメニューボタンと作法を合わせ、
    ロック中(ON状態)はアニメーションをループ再生することで、現在のシステム状態をユーザーに明示。

スキップ中:アクティブ/非アクティブ 追加

スキップ中・スキップ後に、
アクティブ化するオブジェクト指定と非アクティブ化するオブジェクト指定をできるように機能を追加しました。

実際にやりたかったのは、スキップ中に早送りエフェクトのアニメーションを流したかったというものです。
拡張性として、スキップ中に非アクティブにしたいオブジェクトも指定できるようにしました。
「REC」などの表示を消そうと目論んでいます。

どちらもオブジェクトを増減できるので、演出的には十分な機能だと思います。

改善★0515 イベント駆動型の一括管理

スキップ中のアクティブ非アクティブオブジェクト指定を「SkipManager」に持たせていると
ロード後にMissingになってしまう問題がありました。

新規スクリプト「Sys_SkipViewElement」を作成し、
シーン側のコントローラー」がマネージャーの状態を監視する形に逆転させました。

  • Sys_SkipManager (常駐):「今スキップ中か?」という状態を保持し、変化した瞬間に「号令(イベント)」を出すだけで、シーン内の個別のUIについては関知しません。
  • Sys_SkipViewElement (シーン側):その号令を聞き取り、インスペクターで登録されたオブジェクトをまとめて表示・非表示にします。

導入と運用の手順

  1. 管理アンカーの設置:
    各シーンに、最初から最後まで常にアクティブな空のGameObjectを1つ用意します。
  1. スクリプトのアタッチ:
    そのオブジェクトに Sys_SkipViewElement を貼り付けます。
  1. リストの登録:
    インスペクター上で、そのシーンにおいて「スキップ中に表示したいもの(SkipIconなど)」と「非表示にしたいもの(通常メッセージ枠など)」をそれぞれのリストへドラッグ&ドロップします。

技術的メリットと解決された課題

  • Missingエラーの根絶
    参照リストがシーンごとに破棄・生成されるため、DDOL側に「以前のシーンの残骸」が残ることが物理的にあり得なくなりました。
  • 初期化順の安定化
    Start メソッドでの購読と初期反映により、シーンロード時にオブジェクトが非アクティブな状態から始まっても、マネージャーの状態に合わせて正しく叩き起こすことができます。
  • 超軽量動作
    毎フレームの監視(Update)や、シーン全体の検索(Find)を一切行いません。
    スキップボタンが押された瞬間に一度だけリストを回すため、パフォーマンスへの影響はほぼゼロです。

開発時の注意点

  • 「アンカー」は常にアクティブに:
    Sys_SkipViewElement を貼ったオブジェクト自身が非アクティブだと、マネージャーからの号令を受け取れません。必ず、シーン内で常に起きている(Activeな)オブジェクトに持たせてください。
  • 階層構造の意識:
    リストに入れた子オブジェクトの「親」が非アクティブな場合、スクリプトが SetActive(true) を呼んでも画面には映りません。UIの階層構造には注意を払ってください。

最終構成ファイル

  • Sys_SkipManager.cs:Action<bool> イベントを搭載した常駐スクリプト。
  • Sys_SkipViewElement.cs:Start で購読し、OnDestroy で解除する一括リスト制御スクリプト。

実機での挙動

ボタン系はテストすればするほど、しないでほしい挙動してほしい挙動がでてきますね。

・右クリックで反応しないように
・ホバーでアニメーションがスタートからにならないように
・ボタン押で進行しないように

と多々ありましたが、
連打対応UI画面時に触れないようにするなど、まだまだ細かい対応が残っていると思われます。

とはいえ、懸念していたスライダーなどの連携もすんなりできたので安心しております(*´ω`*)

次はメニュー画面含む、各設定画面にとりかかります。
何度か言っていますが、これさえ終わればシステム的なことが終わりそうです…
想定時間の5倍はかかっているので、恐ろしいです。

コメント