タイトル画面【絶対調味ロジック】

絶対調味ロジック

タイトル画面デザインをUnityに落とし込みました。
多重がどうなるか気になりましたが、3D空間にUI Canvasを呼び込んで想定通りの配置にすることができました。
円系のビジュアライザーは想定通りにできませんでしたが、とりあえず代替案で満足しています。

カメラ移動などの演出を入れたので、ボタン操作の遮断などをまた作らねばなりませんでした。
実装を残しているコントローラー対応など、操作系全般で遮断タイミングも必要になってくるので、
そこらへんは入力系をいじるときに一気にやろうと、後回しにしました。
とりあえずはタイトル画面だけの操作遮断システムを作っておきました。

デザインからUnityへ

ペラデザイン

各オブジェクトの形は単純だけれど、ソート順をうまく落とし込めるかがカギなデザインとなっています。

タイトルロゴも少し装飾してみました。
【ビンテージ風のテキスト効果】を取り入れてたのですが、文字自体の大きさがもう少し必要だったようです。
雰囲気だけ出ればよいかな…と強行。

Unity内配置

各パーツを書き出していきます。

下地を基に、3D空間に配置していきます。
カメラ移動時に立体感が出るように、各パーツの奥行の数値を何度もやり直しました。
2Dスプライトと3D板との間にUI Canvasも割り込ませるので、なかなか大変です。

Canvasの設定を「Render Mode」>「World Space」にすると、3D空間に引っ張てくることができます。

ボタンはメインでも設定した「UI_Button Visual」を使用して、
「Normal」「Hover」「Press」の各状態を設定していきます。

ホバー時に、説明文も出るようにしました。

配置完了

影も反映させて、ソート順もばっちりにできました。

ここで、ビルドしてテストしてみたところ、
キャラクターに適用していたBRP用をURP用に改善したマテリアルで不具合が生じてしまったので、
URPで新規作成したノーマルマテリアルを再適用することになりました。
他Sceneでのキャラクターも再適用しなくてはならなくなったので、ちと手間です…

走査線シェーダーも、他PCで確認したところ、画面ズレが生じていたので修正をしました。

ここら辺から、ちゃんと動作確認もしていかないといけない段階になってきましたね。

走査線(スキャンライン)シェーダーの改善

ShaderGraph 走査線(スキャンライン)動的生成【URP】

以前に設計したシェーダーを修正します。

■ 設計方針・設計ルール

  • 端末依存・初期化遅延の排除:
    アプリ起動時の解像度確定遅延やグラフィックドライバの挙動差による表示崩れを防ぐため、
    Screen ノード(画面解像度ピクセル数の動的取得)を一切使用しない 純粋なメッシュUV依存構造 を採用。
  • 低負荷・堅牢性の確保:
    テクスチャ参照を行わず、GPU内での算術計算(UV, Sine, Power等)のみで走査線を描画します。
    メモリ帯域を圧迫せず、モバイルやロースペックPC(統合GPU等)でも安定して動作します。
  • 非破壊的実装:
    既存の描画パイプラインやURP設定を損なわず、Full Screen Pass Feature単体で完結します。

Screen ノードを削除します。

■ 各パラメータの役割と調整ガイド

Shader GraphのBlackboardおよびMaterial Inspectorで設定可能な各プロパティの仕様。

ScanlineCount (Float / 推奨値: 300〜1000)
役割:画面内に描画する走査線の本数(密度)を制御します。
調整のコツ:値を大きくするほど線が細かく高密度になり、1本あたりの主張が抑えられて繊細な質感になります。

Thickness (Float / 推奨値: 1.0〜5.0)
役割:描画される黒い線の太さ(非透過部分の比率)を制御します。
調整のコツ:Power ノードの指数として機能するため、
値を大きくするほど線が細く(透明領域が広く)なり、値を小さくすると線が太く(黒い領域が広く)なります。

Opacity (Float / 推推奨値: 0.1〜0.3)
役割:走査線全体の不透明度(濃さ)を制御します。
調整のコツ:値を下げると背景のゲーム画面となじみ、レトロ感を残しつつ視認性を確保できます。

■ 運用上の注意点

解像度変更時の見え方:
本方式はUV空間(0〜1)に対して一定本数の線を引くため、アスペクト比が極端に変わらない限り安定して描画されます。
ディスプレイ解像度ごとに線の絶対ピクセル幅を固定したい場合は ScanlineCount をスクリプト等から解像度に応じて変更することも可能ですが、基本的には固定値で問題ありません。

エイリアシング(チラつき):
液晶モニターの解像度に対して ScanlineCount が高すぎると莫大な干渉(モアレ現象)が生じる場合があります。
実機画面で確認しながら、チラつきが目立たないライン(500〜800前後)に設定してください。

オブジェクト膨縮(Sprite連動型)ビジュアライザー

実機での挙動

メイン画面の方は、メモリを伸縮させる方式でしたが、
今回のは、任意のオブジェクト(2Dスプライト)を指定して、膨縮させるビジュアライザーです。
ミュートにした際は、動きが止まるようにもしました。

※ メイン画面内UIの実装【Unity6】 > 個別イコライザー

よくある円線を波打たせる形を構築しようとしましたが、難しくて諦めました…

概要・設計方針

目的:
タイトル画面の目玉焼きボタン外周にある同心円グラフィックを、
BGMの周波数(音域)に合わせて動的に拡大・縮小および明滅(透明度変化)させる。

基本仕様:
・低音・中音・高音に対応した同心円がそれぞれ跳ねるように動く
・ミュート時・音量ゼロ(Master/BGM)時:
画像が消えるのではなく、アニメーションのみがスムーズに「ポーズ(静止)」し、
基本サイズ(Base Scale)と基本透明度(Base Alpha)の状態で画面上に維持される

採用手法:
・複雑なシェーダー演算(URP Shader Graph等)を排し、
C#コードによる SpriteRenderer の Transform.localScale および Color.a(Alpha)のダイナミック制御 を採用
・Sys_AudioSettingsManager と連携し、Mixerの消音状態とは独立してリアルタイムにミュート/音量ゼロ状態を検知

選定理由:
1. ロースペック環境での描画負荷(DrawCall / Shader Pass)を最小限に抑えるため
2. ピクセルアート/UIデザインの世界観にフィットするパキッとした整ったアニメーション表現を実現するため
3. シェーダーのノイズ計算による歪みや描画崩れ等のバグリスクを完全に排除するため

Inspector 設定項目のリファレンス

・新規スクリプト「Sys_ConcentricSpriteEqualizer」

項目名型概要・調整のポイント
Bgm SourceAudioSource解析対象となるBGMの AudioSource を設定。
Lerp Speedfloat音量の変化に対する追従速度(アニメーションの滑らかさ)。
推奨値:20
数値を高くするとアタック感が強くなり、低くするとマイルドな揺れになる。
Base Alphafloatポーズ時(ミュート・音量ゼロ時)の基本透明度。
推奨値:0.3
音がある時はこの値をベースに明るくなり、ミュート時はこの値で静止保持される。
RingsArray制御対象となる各同心円の設定リスト(Element 0, 1, 2)。

■ Rings 各要素(Element)の内部設定

  • Target Transform:アニメーション(Scale変更)させたいオブジェクトの Transform
  • Sprite Renderer:透明度(Alpha)を変更させたいオブジェクトの SpriteRenderer
  • Frequency Band:反応させるサウンドの周波数帯域(0 〜 7)
  • 低音(バスドラム・ベース):0 または 1
  • 中音(メロディ・ボーカル):2 〜 4
  • 高音(ハイハット・シンバル):5 〜 7
  • Base Scale:無音時・通常時の基本拡大率
    ※ 0 だと円が消えてしまうため、必ず 1.0 前後を設定すること
  • Max Scale Add:音のピーク時に Base Scale から追加で膨らむ限界値。推奨値:0.1 〜 0.3
  • Sensitivity:音圧の感度。推奨値:10 〜 30

技術的注意点

● Unityの GetSpectrumData の仕様と対策(ミュート判定):
AudioMixer 側で音量を -80dB(消音)に下げても、AudioSource.isPlaying が true である限り、
GetSpectrumData() は波形データを返し続けてしまいます。
そのため、Mixerの消音に依存せず、スクリプト内の IsMutedOrSilent() で Sys_AudioSettingsManager から
「Master/BGMのミュートフラグ」 および 「Master/BGMの音量が0以下か」 を判定して強制的に PauseRings() へ分岐させています。

● Base Scale の初期設定:
スクリプトアサイン直後は Base Scale が 0 になっているため、実行時に円が消えて見えなくなります。
手動で 1.0 等を設定してください。

● 画像の中心位置揃え:
同心円画像を書き出す際は、3枚とも「同じキャンバスサイズ・中心揃え」で保存することで、
Unity上で Transform の Position を揃えるだけで完全に同心円として重なります。

発生していた不具合と原因

現象:
タイトルSceneから他Sceneへ遷移後、
再度タイトルSceneに戻ってくるとビジュアライザーがアニメーション(発火)しなくなる。

原因:
Sys_ConcentricSpriteEqualizer が Inspector 上で
Scene 内のローカルな AudioSource を直接手動参照(参照の固定化)していたため。
Scene 遷移に伴い参照先の AudioSource が破棄(Destroy)され、
タイトルSceneに復帰した際に参照が Missing となり、更新処理(Update)内のガード節で弾かれ続けていた。

■ 改善方針と設計

参照の動的バインド(自動接続)化:
Inspectorでの手動アサイン依存を廃止。
DontDestroyOnLoad で破棄されず存在し続ける Sys_BGMManager.Instance.AudioSource を、
スクリプト起動時(Awake)に自動取得する構造へ改修。

自己修復ロジックの追加:
何らかの理由で bgmSource が null または Missing になった場合でも、
Update 内で即座にマネージャーへ再接続(TryBindBgmSource)を試みるように変更。

■ 修正の要点

① 自動バインド用メソッドの定義
② ライフサイクルでの自動接続と Missing ガード

■ 運用上のメリット・変更点

セットアップ手順の簡略化:
Inspector で Bgm Source フィールドに AudioSource をドラッグ&ドロップで割り当てる作業が 不要 になりました。
プレハブをシーン上に配置するだけで自動接続されます。

Scene遷移・再ロード耐性の向上:
ボイス(Sys_AudioVisualizerTiled)やループSEと同様に、
ロードやScene再読み込みを行っても自前で参照を修復するため、Missing による機能停止が発生しません。

挫折作

● 円形シェーダー
シェーダーグラフで円線を表現して、波形させようとしました。
「円が波打って、また円に戻る」という挙動がうまくいきませんでした。
複雑化していく割に、どんどん変な方向に向かって行ったので、いったん諦め、ということに。

● カメラビジュアライザー
「酔うからやめた方がいい」というAi忠告も無視して、
カメラ自体もビジュアライザー化してみたらどうかと、カメライザーもテストしてみました。
しかし、ほんとに酔うのでやめました…
いつか何かの演出につかえるかもしれないので、覚えてはおきましょう…

※軽めにしていますが、画面酔い注意してください。
むしろ、軽めでもこんな感じに…

スプラッシュ画面からの移動

実機での挙動

インタラクティブ・タイトル画面になりました♪

カメラ移動での3D感を出したかったので、ソート順を頑張りました。
カメラ移動からのズレが、なかなか良い感じではないでしょうか。
ペラデザインから、思ったほど離れずに実装できたので、驚きと安堵と歓喜です。

当初から、タイトル画面にビジュアライザーをと考えていました。
とにかく何か動いていて欲しいなと。
想定のビジュアライザーではなかったにせよ、うまく取り込めたので満足しています。
タイトルロゴもノッているのがポイントです。

ちょっとした仕掛けをまだまだ追加したいのですが、そういうことは全て終わって余力があったらにします…笑

スプラッシュ画面

カメラ移動と読込み完了までの暗幕的な役割も兼ねたかったので、
Unity標準のスプラッシュ機能は使用せずに、直接配置しています。

ちなみにUnityロゴデータは、Unity公式ブランドガイドラインページからダウンロードできました。
すぐに行きつく公式ページには、ブラウザでしか開けない画像形式しかなかったので困りました。
ダウンロードリンク:Brandfolder(公式アセット配布先)

※初手にメールアドレスを求められますが、Grokによると安全ということでした。(ほんとかぁ?)
Brandfolderは、企業が公式のロゴやブランド素材を配布するために使う正式なデジタルアセット管理サービスです。
Unityも公式の商標ガイドラインページから直接このBrandfolderのリンクを案内しているので、Unityが公式に利用している正規の配布先です。

メールアドレスを求められる理由:
・ダウンロードの利用状況を把握するため(誰がダウンロードしたかを追跡する一般的な機能)
・スパムや不正利用を防ぐための簡易的なゲート

メールアドレスを入力しても、通常はロゴファイルのダウンロード以外に使われることはほとんどありません。
Brandfolder自体もSOC 2やISO 27001などのセキュリティ認証を取得している信頼性の高いサービスです。

ということらしいです。

UIロックシステム

スプラッシュ画面からのカメラ移動中に、各ボタン操作などを行えないように、
任意時間ロックし、その後自動解除するようなシステムを構築しました。

■ 概要

目的:
1つ以上のCanvasGroup配下のボタンを、一定時間完全に操作不能にする。
(クリック・キー・コントローラー・ホバー全て)

発動方法:
Sys_UILocker が付いたGameObjectをアクティブにするだけで自動ロック開始。

基本ルール:
・設定した Lock Duration 経過後に自動解除
・オブジェクトを非アクティブにする(強制解除)
・外部から Unlock() を呼ぶ

■ 設計の基本方針

項目採用した方法理由
操作ロックCanvasGroup.interactable = falseクリック・キー・コントローラーを一括無効化
(Unity標準・軽量)
クリック吸収CanvasGroup.blocksRaycasts = trueクリックが下のオブジェクトに貫通しない
ホバー停止UI_ButtonVisual.SetDisabled()独自のIPointer系イベントを止めるため
タイマーWaitForSecondsRealtimeTime.timeScale の影響を受けない
発動OnEnable任意のオブジェクトをアクティブにするだけで起動可能
複数対応List<CanvasGroup>複数キャンバスを同時にロック可能

使わなかった方法
・透明Imageを上に被せる方式 → キー・コントローラーが止まらない
・個別Button.interactableを回す方式 → 管理が煩雑で漏れやすい
・EventSystemを無効化 → 影響範囲が大きすぎる

■ 実装・設定手順

1. CanvasGroupの準備
・ロックしたいボタンの親(またはCanvas)に CanvasGroup を追加
・初期状態(解除時)は以下に設定しておく:
Interactable = オン
Blocks Raycasts = オン

2. Inspector設定

・Target Groups にロックしたいCanvasGroupを複数登録
・Lock Delay : ロック開始までの遅延(通常0)
・Lock Duration : ロックしている秒数(0以下で永久ロック)

3. 発動

・任意のタイミングで UI_LockTrigger.SetActive(true) するだけ

4. ルール・注意点

必須ルール
・UI_LockTrigger は Lock Duration が経過するまでアクティブのままにしておくこと
(途中で非アクティブにするとコルーチンが止まり、解除処理が走らない)
・解除後のCanvasGroup状態は必ずInteractable = true / Blocks Raycasts = true になるようにする

注意点
・UI_ButtonVisual が付いていないボタンは、ホバー反応の制御対象外(クリック等は止まる)
・Lock Duration = 0 以下にすると永久ロックになる
・Time.timeScale = 0 のポーズ中でも正確に動く(Realtime使用のため)
・同じ UI_LockTrigger を連続でアクティブにしても問題なく動作する(内部でコルーチンをリセットしている)

やってはいけないこと
・ロック中に対象のCanvasGroupを直接いじる
・blocksRaycasts を解除時に false にしてしまう(ホバーもクリックも効かなくなる)

初期化および演出遅延制御システム

開発環境以外のPCでビルドアプリを試したところ、初手起動時にカクツキがあったので、
スプラッシュ画面状態で、読み込みを完了させてから、動き出すようなシステムを構築しました。
ちょっとしたナウローディング的なものとなるでしょうか。

■ システムの全体設計方針とルール

目的:
ロースペックPC環境での動作を念頭に置いた設計。
初回起動時のアセットロード、GPUバッファ確保、オーディオデコード処理に伴う
「フレームスパイク(一瞬のカクつき・画面の引っかかり)」を、
暗幕(CanvasGroup.alpha = 1)の裏ですべて安全に消化・吸収してから演出を開始させます。

基本ルール:
・暗幕の裏で重い処理を完結させる:
BGMの事前ロード(デコード)やグラフィックのレンダリング初期化は、すべて暗幕を表示している間に行います。
・イベント駆動(数珠繋ぎ)の採用:
タイマーを個別に走らせず、Sys_SceneInitializer の完了イベント(On Initialization Complete ())を起点として
次のスクリプトを有効化(enabled = true)していきます。
これにより、PCスペックによってロード時間が伸縮しても演出のシーケンスが絶対に崩れません。
・起動タイミングのコンポーネント管理:
オブジェクト自体の非アクティブ化ではなく、
スクリプトコンポーネントの enabled(チェックボックス)の切り替えによって OnEnable() を安全に発火させます。

■ 実装完了後のヒエラルキー構造

[Scene Top]
 │
 ├─ Splash_Down (アクティブ)
 │   ├─ Sys_SceneInitializer [アタッチ] ※全体の初期化トリガー
 │   ├─ UI_LockTrigger (初期非アクティブ)
 │   ├─ VCam_Splash (初期VCam位置)
 │   └─ Delay_ (アクティブ)
 │       └─ Cmd_Activate_Object [初期OFF] ※暗幕明けにSys_SceneInitializerからONにされる
 │
 ├─ Splash_ (アクティブ)
 │   ├─ BG_Main_line_2D [CanvasGroup付与] ※暗幕フェード用
 │   ├─ Logo_unity
 │   └─ Logo_Cheap
 │
 ├─ TITLE_1st (アクティブ)
 │   ├─ BG_
 │   ├─ Canvas_Bt_
 │   ├─ Canvas_Extra
 │   ├─ TitleLog_Visualizer
 │   └─ Circle_Visualizer
 │
 └─ Title_Dwon (初期非アクティブ)
     ├─ VCam_Title_Down (初期アクティブ)
     ├─ Delay_ (アクティブ)
     │   └─ Cmd_Activate_Object [初期アクティブ] ※VCam移動完了後にBGM_PlayerをONにする
     └─ BGM_Player (初期非アクティブ)
         └─ Cmd_PlayPreparedBGM [アタッチ] ※待機させていたBGMのPauseを解除して再生

■ 各項目の詳しい説明と設定手順

① Sys_SceneInitializer(アタッチ先: Splash_Down)

暗幕の裏でバッファ消化とBGMの事前読み込みを行い、完了後に後続の演出を起動するメインコントローラー。

コード修正の要点:
暗幕明けてすぐにBGMが鳴り出すのを防ぐため、
InitRoutine 内の自動再生処理(PlayPreparedBGM())をコメントアウト(または削除)し、
一時停止(Pause)状態で待機させる形に調整しています。

Inspector 設定:

  • Fade Canvas Group:BG_Main_line_2D(暗幕用の CanvasGroup)を指定
  • Fade Duration:暗幕が明けるフェード時間(例:0.5)
  • Load Hold Time:ロード消化後の演出上の追加待機時間(0 〜 0.5 秒程度)
  • Bgm ID To Prepare:タイトルBGMのID文字列を指定(暗幕裏で事前ロード)
  • Ui Locker:空欄(None)または安全装置付きで設定

On Initialization Complete ():
・イベントスロットに Splash_Down 下の Delay_ をドラッグ&ドロップ
・ドロップダウン:Cmd_Activate_Object > bool enabled を選択
・チェックボックスにチェックを入れる(True)

② Cmd_Activate_Object(アタッチ先: Splash_Down / Delay_)

  • 役割:
    描画安定後に Sys_SceneInitializer から ON にされることで OnEnable() が発火し、タイマーカウントを開始。
  • 設定:
    • 初期状態:スクリプトのチェックボックスを外して OFF(非アクティブ) にしておく
    • activateTargets:Title_Dwon を登録(指定秒数後に Title_Dwon をアクティブ化)

③ Cmd_PlayPreparedBGM(アタッチ先: Title_Dwon / BGM_Player)

暗幕裏でロード済み・一時停止中になっているBGMを、
VCam移動完了後の任意のタイミングで再生(Pause解除)させるための専用軽量スクリプト。

役割:
Title_Dwon 内の Delay_ から BGM_Player がアクティブ化された瞬間に OnEnable() が走り、
デコード負荷ゼロで滑らかにBGMが再生されます。

■ 実行タイムライン(動作フロー)

  1. 0秒時点(シーン起動)
    1. Sys_SceneInitializer が起動し、暗幕(CanvasGroup.alpha = 1)を表示
    2. Sys_BGMManager を経由してBGMを事前にデコード読み込みし、先頭で一時停止(Pause)待機させる
  2. 2〜3フレーム後(描画安定時)
    1. GPUバッファやレイアウト構築が完了。暗幕がフェードアウトする
    2. OnInitializationComplete() が発火し、
      Splash_Down / Delay_ 内の Cmd_Activate_Object スクリプトが ON になる
  3. VCam移動開始
    1. Cmd_Activate_Object のタイマーが正確に動き出し、指定時間後に Title_Dwon をアクティブ化
    2. VCam_Title_Down へのカメラ移動が開始
  4. VCam移動完了時
    1. Title_Dwon 配下の Delay_ が BGM_Player をアクティブ化
    2. Cmd_PlayPreparedBGM の OnEnable() が発火し、事前に読み込まれていたBGMがカクつき無しで再生開始

■ 注意点

  • 非アクティブオブジェクトからの Coroutine 呼び出しエラー
    Sys_UILocker など、ヒエラルキー上で非アクティブになっているオブジェクトに対して StartCoroutine を呼ぶとUnityエラーが発生します。
    参照を外すか、呼び出し側で gameObject.activeInHierarchy のチェックを必須としてください。
  • Cmd_Activate_Object の初期状態
    Splash_Down / Delay_ についている Cmd_Activate_Object のコンポーネント自体のチェック(enabled)を最初外し忘れると、暗幕の裏でタイマーが勝手に始まってしまいます。
    必ず初期状態を OFF(チェックなし)にしてください。
  • 二重再生の防止
    BGM_Player 側で新規に Play() を実行する既存スクリプトが残っていると、曲の二重再生や巻き戻りが発生します。
    再生処理は必ず PlayPreparedBGM()(一時停止解除)に一本化してください。

コメント