論争ゲームシステムと演出のアップデート

絶対調味ロジック

論争パート・ゲームシステムの対応と改善を行っていました。
演出面も加えて、想定通りの動きになってきたと思います。

論争ゲームシステム 対応版

改善概要

単一正解のみ対応だった旧システムから、単・複数正解対応の新システムに改善。
ラウンド自動判別アクティブ制御の安定化、プレイアビリティ改善まで含めた完全版。

スクリプト一覧

ファイル名対応主な変更内容
Sys_Debate_Judge.cs修正correctFlagNameを複数対応(List化)、
OnRebuttalSelectedの引数をList\<string>に変更
Sys_Debate_Memo.cs修正
(旧Sys_Debate_Slot)
GetActiveLoading()追加、
OnHover()・OnClick()・OnCancelButtonClicked()追加、
rebuttalLoading直接参照を廃止
Sys_Debate_Arrow.cs修正OnEnable追加、ラウンド切り替え時にlastScoreをリセット
Sys_Rebutta_Loading.cs新規受け皿UI管理、
選択積み上げ・重複チェック・上書き・確定ボタン制御

Hierarchy構成(確定版)

Debate_CanvasHP_Arrow(← Sys_Debate_Arrow)
├ Rebutta_Memo(センターレフトメモ・共通 ← Sys_MemoManager)
│  ├ Rebutta_Egg_A(← Sys_Debate_Memo)
│  ├ Rebutta_Egg_B(← Sys_Debate_Memo)
│  └ ...
└ Rebutta_Slots(bottomメモ・共通)
    ├ Egg_AEgg_B
    └ ...
Round_1b(単一正解ラウンド)
 │
 ├── Rebutta_Round_Canvas
 │     └ R1_Confirm_Loading(← 単一用Sys_Rebutta_Loading・初期非アクティブ)
 ├── Round1_Judge(← Sys_Debate_Judge)
 ├── ITO_Rebuttal_R1_win
 ├── ITO_Rebuttal_R1_lose
Round_3b(複数正解ラウンド)
 │
 ├── Rebutta_Round_Canvas
 │     └ R3_Confirm_Loading(← 複数用Sys_Rebutta_Loading・初期非アクティブ)
 ├── Round3_Judge(← Sys_Debate_Judge)
 ├── ITO_Rebuttal_R3_win
 ├── ITO_Rebuttal_R3_lose

システムの流れ

1. ラウンド開始
→ ラウンドオブジェクトがアクティブ
→ Rebutta_Confirm_Rnは初期非アクティブ
→ Debate_scoreを-1(ラウンド開始時・京子用スコア)

2. ボトムメモのホバー or クリック
→ OnHover() or OnClick()
→ GetActiveLoading()で親がアクティブなものを検索
→ 該当ラウンドのSys_Rebutta_Loadingをアクティブ化
→ センターレフトメモ表示

3. 「コレを選ぶ」押下
→ flagName + rebuttalTextを受け皿スロットに積む
→ 重複選択は弾く
→ 満杯時は一番古いものを上書き
→ 全スロット埋まる →「コレで反論だ!」有効化

4. 非表示ボタン押下
→ センターレフトメモ・Sys_Rebutta_Loadingを非アクティブ化
→ 選択状態は維持(リセットしない)
→ 再ホバー/クリックで選択状態が復元される

5. 「コレで反論だ!」押下
→ Judge判定 → win/lose発火
→ Sys_Flag_ControllerがDebate_scoreをwin+2

6. 「やりなおす」押下
→ ResetSlots()で選択状態を完全リセット
→ Sys_Rebutta_Loadingを非アクティブ化
→ センターレフトメモを非アクティブ化
→ ボトムメモ状態に戻る

設計の核心:GetActiveLoading()

親オブジェクト(ラウンド)がアクティブかどうかで判別する方式。
非アクティブのものも含めて全検索しつつ、アクティブなラウンドのSys_Rebutta_Loadingだけを正確に返す
OnHover・OnClick・OnRebuttalButtonClicked・OnCancelButtonClickedの4つで共通利用。

Inspector設定のポイント

■ Sys_Debate_Memo(センターレフトメモ)
Rebutta_Egg_A等のレフトメモにSys_Debate_Memoをアタッチ。

フィールド設定内容
FlagName“Egg_A”など
RebuttalText表示テキスト
TextPanelテキスト表示UI
LabelTMPラベル
RebuttalButton反論素材決定ボタン(コレを選ぶ)

RebuttalTextの改行は\nで指定可能

■ Sys_Rebutta_Loading(Rebutta_Confirm_Rn)

フィールド設定内容
RequiredCountこのラウンドの必要選択数
SlotObjectsSlot_1(〜Slot_n)をリストに登録(そのラウンド分のスロットのみ登録)
SlotLabels各SlotのTextMeshProをリストに登録(そのラウンド分のスロットのみ登録)
ConfirmButtonButton_Confirmを登録
Judge該当ラウンドのSys_Debate_Judgeを登録

受け皿UI(Sys_Rebutta_Loading)を本実装

ボタンのOnClick設定

  • Button_Confirm(コレで反論だ!)
    OnClick → Sys_Rebutta_Loading.OnConfirmClicked()
  • Button_Reset(やりなおし)
    OnClick → Sys_Rebutta_Loading.OnResetClicked()
    OnClick → Rebutta_Memo.SetActive(false)
  • 各レフトメモの「コレを選ぶ」ボタン
    OnClick → Sys_Debate_Slot.OnRebuttalButtonClicked()

■ Sys_Debate_Judge(Round_Judge)

フィールド設定内容
CorrectFlagNames正解フラグ名をList形式で登録(複数可)
RequiredCount正解フラグの必要数(2以上の場合はCorrectFlagNamesも同じ数にすること)
WinObject/LoseObject判定結果オブジェクトを登録

運用ルール

  • Rebutta_Confirm_Rnは必ず該当ラウンドオブジェクトの子に配置する
  • Rebutta_Confirm_Rnの初期状態は非アクティブ
  • Rebutta_Confirm_RnはラウンドごとにHierarchyに個別作成(プレハブ化しない)
  • bottomメモ・Sys_Debate_Memoは全ラウンド共通オブジェクトを使用
  • 複数ラウンドが同時にアクティブにならないことが前提
  • debate_scoreは論争シーン開始時にSys_Debate_Initで必ず0初期化される
  • やりなおしボタンが唯一の完全リセット手段

スコア変動時のHPバー移動修正

1つずつ区切って設定していた旧スクリプト(スコア自身に反応するタイプ)

■ 発生していた問題と原因
現象:スコア変動時に、バー(矢印)が想定と異なる位置へ移動していた。
(例: スコア2の時に本来300に動くべきが、150へ移動)
原因:旧スクリプトが「現在のスコア」と「絶対座標」を手動で紐付けるマップ(配列)方式だった。
インスペクター上の設定で、スコア 1〜4 の目標座標がすべて誤って 150(コピペミス)と入力されていたため、
スクリプトが設定通りに機能してしまっていた。

■ 採用した解決アプローチ(等間隔計算方式)
手動マッピング方式を廃止し、
「現在のスコア × 1マスの幅(150px)」から常に目標の絶対座標を算出する方式へ変更しました。

「差分」ではなく「絶対座標」で管理する理由:
「2増えたから+300する」という相対的な計算(加算)を行うと、
処理の重複やアニメーションの割り込みによって座標のズレ(バグ)が蓄積するリスクがあります。
常に「スコアに基づいた最終目的地」を計算してそこへTween(移動)させることで、
結果的に「2増えたら300ピクセル移動する」という理想の挙動を安全に実現しました。

Sys_Messenger 複数対応修正

論争パートで、複数の Sys_Messenger同一Location内に同時表示する必要が生じました。
既存の単発動作を維持しつつ対応。

■ 変更ファイルと要点

Sys_Messenger.cs:

keepActiveOnFinish(bool、デフォルトfalse)を追加
 ・true のとき、会話終了後も自身と messageObject を SetActive(false) しない
 ・非アクティブ化は外部(Cmd_Activate_Object 等)に委ねる
 ・false のままなら既存動作と完全に同じ

hasFinished(privateフラグ)を追加
 ・keepActiveOnFinish=true のMessengerが終了したときに立てる
 ・非アクティブ化は外部(Cmd_Activate_Object 等)に委ねる
 ・false のままなら既存動作と完全に同じ

ShowNextMessage() のルビClearをガード
 ・keepActiveOnFinish=true のときは rubyRenderer.Clear() を呼ばない
 ・これが「次のMessengerが起動したとき前のMessengerのルビが消える」の根本対処

activateOnFinishedObjects をディレイ付きに変更
 ・List<GameObject> → List<FinishActivateEntry>(target + delay)
 ・delay=0 のときは即時発火で従来通り
 ・既存アサインはリセットされるため再設定が必要

ホイールダウン進行の修正
 ・ScrollValue < -0.5f(ホイールダウン)は IsPointerOverGameObject() チェックを外した
 ・メッセージウィンドウ上でもホイールで進行できるようになった
 ・クリック・キー操作は従来通りヒットテストでガード

Sys_RubyRenderer.cs:

Initialize() の RubyRoot 名をインスタンスID付きに変更
 ・”RubyRoot” → “RubyRoot_{instanceID}”
 ・同一親を持つ複数Messengerが RubyRoot を奪い合うことを防ぐ予防的修正

■ 運用方法

論争パート用Messengerのインスペクター:
 ・Keep Active On Finish にチェック
 ・非アクティブ化は Cmd_Activate_Object の deactivateObjects に登録して管理

■ 既存機能への影響

  • keepActiveOnFinish はデフォルト false のため、既存のすべてのMessengerは動作変更なし
  • activateOnFinishedObjects の再アサインのみ作業が必要。

カメラ揺れ(シェイク)システム

■ 最終確定したスクリプト

スクリプトはただ引き金を引くだけ、演出はインスペクターに完全委ねる」という
ピュアで最も堅牢な構造にリファクタリングしました。
連打された場合も、Cinemachine側がブレンドやリトリガーを自動で安全に処理します。

■ 各インスペクター設定の役割と調整ガイド

Cinemachine Impulse Listener:

使用しているVcamオブジェクトに「Cinemachine Impulse Listener」コンポーネントを追加しておきます。

以下は空オブジェクトにコンポーネント追加とスクリプトアタッチです。

Cinemachine Impulse Source (Component)

Impulse Channe
衝撃を発信する「チャンネル(電波の周波数のようなもの)」
基本はそのままでOKですが、
例えば「地鳴りの揺れ(チャンネル0)」と「キャラの被ダメージ(チャンネル1)」を分け、
カメラ側で「地鳴りだけを無視する」といった高度なフィルタリングができます。

Impulse Type(揺れの伝わり方)
衝撃の空間的な広がり方です。
2D的なADV演出や画面全体のシェイクには、この Uniform が最も適しています。
カメラとの距離を気にする必要がありません。

項目名揺れ方のイメージADVでの主な用途
Uniform
(均一)
ADVで最も推奨
オブジェクトの位置やカメラとの距離を完全に無視し、
画面全体を一様・均一に揺らします。
2D、UI、ノベルゲームの演出、
または「どこで発生しても一律で画面を揺らしたいダメージ演出」
Dissipating
(消散・減衰)
衝撃の発生源(オブジェクトの座標)から、カメラが離れるほど揺れが小さくなるモード。3D空間で距離感を表現したいバトル演出など。
Propagating
(伝播・時間差)
衝撃が波のように空間を伝わっていくモード。
発生源から離れたカメラは「少し遅れて」揺れ始める。
遠くの巨大爆発が遅れてドスンと届くようなリアルな表現用。
Legacy
(旧仕様互換)
旧バージョンスタイルの挙動です。基本は Uniform と同等
Unity 6の新システムではあえて選ぶ必要はありません。

Impulse Shape揺れの基本波形)
揺れの「波形(プロフィール)」とその「持続時間」。(演出の「質感」を決める最も重要な項目)
右側の s(秒数)で揺れが収まるまでの時間を変更します。
ここを 0.5 にすると長く余韻のある揺れになり、0.1 にすると一瞬の鋭い衝撃になります。

項目名揺れ方のイメージADVでの主な用途
Custom自分でカーブ(アニメーションカーブ)を自由に作れる上級者向けモード心臓の鼓動(ドクン、ドクン)など、特殊なリズムを作りたいときに。
Recoil
(反動)
発生した瞬間に一方向に「グッ」と強く押し出され、そのあと逆方向に少し揺れ戻りながら収まる。重い扉を蹴り開ける、銃の発射、強いビンタなど、
一方向への強い反動を表現したいときに。
Bump
(ドンッ)
発生した瞬間が最大で、そこから滑らかに減衰する最も素直な1回きりの衝撃。通常の打撃、小ダメージ、決定ボタンの強調など。
迷ったらコレ。
Explosion
(爆発)
ドガガガッと激しいランダムな振動が短時間に集中して発生し、急激に収まる波形。爆発、魔法の着弾、クリティカルヒットなど、派手で情報量の多い衝撃に最適です。
Rumble
(地鳴り)
比較的低周波の細かい振動が、
一定時間「ゴゴゴゴ…」と持続する波形。
地震、巨大な敵の足音、イベントシーンでの空間の崩落など、持続的な恐怖感や重量感を出したいときに。

Default Velocity揺れの方向)
衝撃が最初に加わる「基本の方向と強さ」のベクトルです。
調整例:
・X: 0, Y: 1, Z: 0 → 縦揺れ(叩きつけ、落石など)
・X: 1, Y: 0, Z: 0 → 横揺れ(ビンタ、横からの衝突など)
・X: 1, Y: 1, Z: 0 → 斜め揺れ(激しい衝撃など)

Cmd_CameraShake_Impulse (Script):

Start Delay
オブジェクトがACTIVE(またはPlay呼び出し)になってから、実際にカメラが揺れ始めるまでのタイムラグ(秒)です。
画面にフラッシュエフェクトが出てから、0.1秒遅れて画面をドスンと揺らしたい」というような、時間差のタイムライン演出をノンプログラミングで組めます。

Velocity Velocity Scale
揺れの「全体の強さ」の倍率です。
スクリプトを共通化しておき、雑魚敵の攻撃なら 0.5(弱め)、ボスの一撃なら 3(激しく)といった調整をインスペクターだけで容易に行えます。

Default Velocity
スクリプトがインパルスを発信する際に指定する、「揺れの方向(ベクトル)」です。
現在は X:1, Y:1 なので、斜め右上(右上と左下を行き来するようなランダムな揺れ)になります。
・横揺れ(ビンタなど)にしたい場合 → X: 1, Y: 0, Z: 0
・縦揺れ(落石、叩きつけなど)にしたい場合 → X: 0, Y: 1, Z: 0

回転演出における新規コンポーネント

■ 背景と新規実装の理由

3回転(1080度)や5回転(1800度)といった多回転演出を連打進行した際、
従来のDOTween(Cmd_Rotate_Loop)では、Unity内部の角度丸め込み仕様(Quaternion ⇄ EulerAngles の変換限界)と衝突し、最終的な着地角度が数度ずつズレるバグが発生していました。

これを根本解決し、連打耐性と多回転を両立させるため、DOTweenに依存しない新しい回転制御コンポーネント Cmd_Rotate_Update を新規に実装しました。

■ 新規コンポーネント:Cmd_Rotate_Update の要点

実装のポイント変更内容もたらす効果(なぜ直るのか)
Transform.Rotate の採用目標座標(絶対値)への補間をやめ、毎フレームの確定した回転差分を直接加算する仕組み(Update駆動)へ移行。5回転(1800度)以上の巨大な数値でも、Unityの内部丸め込み計算を完全にスルーし、寸分の狂いもなく正確に元の向き(-180度など)に着地します。
発火時の「物理ベタ打ちリセット」Play() が呼ばれた瞬間、スクリプト内の全制御フラグとカウンター、およびターゲットの初期角度(_initialEulerAngles)を強制的に上書きリセット。DOTweenの内部メモリやシーケンスを介さないため、どれだけ超高速で連打されても、100%確実に同じ初期位置からリスタートします。

■連打時に発生する演出効果について(※仕様)

連打進行した際に「ブツ切り感がなく、回転の勢いが増して気持ちよく加速する(ブースト感が生まれる)」という極めて手触りの良い演出効果(ジュースアップ)が実現されています。

メカニズム:
連打時に内部のトランスフォーム計算やフラグは完全にクリーンアップ(リセット)されていますが、
超高速な切り替えにより「人間の目の残像」が追いつかず、前の回転の慣性と新しい回転のエネルギーが脳内で合算されることで発生しています。

汎用モーションスクリプト(統合型・Update駆動版)

「上下限定・片側往復」の個別設計から、
「全軸対応・基準位置モード変更・Update駆動による完全自己完結型」の統合スクリプトへとリプレイスを行いました。
これにより、パラメータ調整の柔軟性と、他のTweenアニメーション等との親和性が極限まで高まりました。

■ 共通の設計思想(なぜ「親子構造」と「統合型」への移行か)

当初の「移動用Tween」と「揺らし用Update」を単一オブジェクトに同居させる力技から、
「Hierarchyの親子構造による役割分離」と「軸・基準を選べる統合コンポーネント」へ移行した理由は以下の3点です。

  • 競合の完全排除(親子構造の採用):
    DOTween等の外部システムによる座標移動と、Update駆動による微振動を同じオブジェクトに対して行うと、座標の書き換えが衝突(競合)して激しいブレやバグを引き起こします。
    「親がTweenで大まかなベース位置を動き、子がローカル座標で健気に揺れる」という親子関係に分けることで、バグを100%排除しました。
  • 中央基準(CenterOnBase)モードの追加:
    従来の「初期位置 $\rightarrow$ プラス方向 $\rightarrow$ 初期位置」という片側往復だけでなく、「初期位置を中心に、マイナスとプラスの両側に均等に揺れる」モードを搭載しました。
    これにより、デザイン配置時の座標を崩すことなく、直感的に「その場で揺らす」演出が可能になりました。
  • コンポーネントの乱立防止と共通化:
    上下・左右・前後・斜めの移動ロジックと、イージング計算をひとつのコンポーネントに集約しました。
    管理コストが激減し、UIと3Dオブジェクトで全く同じ感覚で演出を調整できます。

■ 3Dオブジェクト用:Sys_Motion_Transform 仕様解説

3Dオブジェクトや2D Sprite(Transform)の Position を操作するスクリプトです。

インスペクタ項目の詳細

項目名(変数名)説明
Space
(空間定義)
移動基準を Local(親基準)か World(世界座標基準)か選択。
親子構造で揺らす場合は「必ず Local」にしてください。
Axis
(方向設定)
移動する軸を Horizontal(X)、Vertical(Y)、Forward(Z)、
Custom(任意ベクトル) から選択します。
PivotMode
(基準位置モード)
StartFromBase(初期位置から片道往復)、
CenterOnBase(初期位置を中心に両側に揺れる)を選択します。
運動設定
(ループ等)
_amplitude(総移動距離・メートル単位)、_duration(片道時間)、
_startDelay(開始遅延)、_interval(端点での静止時間)で構成。
EaseType
(演出設定)
加減速のタイプ。
Linear、InOutSine、InOutQuad から選択可能です。

【重要:SpaceType.World の罠】
本スクリプトを SpaceType.World に設定すると、親オブジェクトがどれだけ移動しても、子はワールド全体の絶対座標(世界原点付近)に取り残されて往復運動してしまいます。
他のスクリプト(DOTweenなど)で親を動かし、その移動先で追従して揺らしたい場合は、
100% Local モードで運用してください。

■ UI用:Sys_Motion_UI 仕様解説

UI(RectTransform)の anchoredPosition を操作するスクリプトです。
Canvas Scaler(解像度変更)の影響を受けません。

矢印HPバーや通知バッジでの運用

HPバーの変動(スコアによる位置移動)は、親オブジェクトにアタッチした Sys_Debate_Arrow が担います。
この Sys_Motion_UI は子オブジェクト(矢印画像自体)にアタッチされ、親が移動した先(ローカル座標 0)を基準に、
干渉することなく安全に揺れ続けます。

SetActive(false) による画面の開閉時にも座標が破綻しないよう、Awake 時に一番最初の初期位置を死守(記録)し、OnEnable 時に安全に初期フェーズからリスタートする機構を内蔵しています。

インスペクタ項目の詳細(UI特有部分)

方向設定 (_axis):UIでは基本的に Horizontal(左右) または Vertical(上下) を使用します。
移動幅 (_amplitude):ピクセル単位で指定します。
演出設定 (_easeType):UI用に InOutQuad を選択可能。
サイン波よりも少しメリハリのある(加減速が急な)心地よい動きになり、ボタンや通知などのシステムUIに馴染みやすくなります。

■ 運用の要点

動的な再生コントロール
自動制御:オブジェクトの SetActive(true / false) に完全に連動します。
画面やポップアップを閉じれば自動で止まり、開けば安全に最初(またはディレイ)から再生されます。
手動制御:外部のスクリプトから強制的に最初の状態(ディレイ待機など)に戻して再スタートさせたい場合は、パブリックメソッド ResetMotion() を呼び出してください。

タイムスケール(一時停止)への追従
本スクリプトは Time.deltaTime を使用しているため、ゲーム全体のポーズ処理(Time.timeScale = 0)が行われた場合、自動的にオブジェクト・UIの揺れ運動も完全に一時停止します。
DOTweenのように「ポーズ画面を開いたのに裏でUIが動き続けてしまう」といった不具合が構造上発生しません。

選択肢ルーレット

UIの Image(10個のマス)を順番にハイライト(スプライト変更)させ、
ストップボタンが押されたマスで停止・点滅し、連動するオブジェクトをアクティブにする仕様になっています。

※現状「一度実行したら2回目が遊べない」「オブジェクトのアクティブ化が重複する」などの設計上の弱点があり。

■ UI(マスとボタン)の準備

  1. Hierarchyで右クリック > UI > Canvas を作成します(すでに存在する場合は不要)。
  2. Canvasの子要素として、ルーレットのマスとなる Image を10個作成します。
    右クリック > UI > Image を10回行うか、1つ作って Ctrl + D で複製します。
    分かりやすいように名前を Cell_0 ~ Cell_9 にしておくと便利です。
  3. 同様に、Canvasの子要素として Button を1作成します。
    右クリック > UI > Button (TextMeshPro) などを選択し、名前を StopButton にします。

■ 当たり演出用オブジェクトの準備

  1. Hierarchy上で、ルーレットが止まった時に出現させたいオブジェクト(3Dモデル、UIパネル、エフェクトなど)を 10個 作成します。
  2. 重要: これら10個のオブジェクトは、最初から画面に見えていると困るため、インスペクターの左上にあるチェックボックスをオフにして、あらかじめ非アクティブ(消えた状態) にしておきます。

■ インスペクター(Inspector)での紐付け

RouletteManager を選択し、インスペクターの Sys Roulette Script コンポーネントに各要素を割り当てます。

項目名設定方法
CellsCells の左の矢印を開き、Size に「10」と入力。
展開された Element 0 ~ 9 に、Image(Cell_0~9) を順番にアサインします。
Highlight Spriteルーレットが現在位置を示すための
「光る枠」や「色違いの画像」などのスプライト(Sprite)をアサインします。
Stop ButtonStopButton をアサインします。
Spin Sound音声を鳴らすために、
RouletteManager 自体に AudioSource コンポーネントを追加(Add Component)し、
そこに鳴らしたいSE(AudioClip)をセットした上で、
この項目にその AudioSource をアサインします。
Activation ObjectsCellsと同様にSizeを「10」にします。
Element 09 に、 非アクティブな10個のオブジェクト を、
セルの番号と対応する順番でアサインします。

■ blinkCount と blinkInterval の役割

これらの変数は、
ルーレットが停止した後に「どのくらいの激しさ(回数と速さ)で、当たったマスを点滅させてプレイヤーにアピールするか」を制御する演出用のパラメーターです。
人間の視覚的なフィードバックとして、「おめでとう!ここが当たりだよ!」と強調するために使われます。

blinkCount(点滅回数)とは?

  • データ型: int(整数)
  • 初期値: 5
  • 意味: 当たったマスを何回パチパチと点滅させるかの合計回数です。

blinkInterval(点滅の間隔)とは?

  • データ型: float(小数)
  • 初期値: 0.5f(0.5秒)
  • 意味: 点滅する際の「光っている時間」と「消えている時間」の長さ(秒数)です。

■ 実用上の弱点

リプレイ性がない(使い捨て状態)
このスクリプトは Start() で自動的に回り始め、ボタンを押して停止(isSpinning = false)したら、二度と再始動する関数がありません。
ゲーム内で何度もルーレットを回す場合、シーンを再読み込みするか、手動でコルーチンを再始動するコードを追記する必要があります。

ボタンを押した「瞬間」に止まる(出来レース・狙い撃ちができない)
コードを見ると、ストップボタンを押した瞬間の currentIndex がそのまま当選確率100%で選ばれます。
プレイヤー視点: 目押し(タイミング合わせ)が完璧な人なら、欲しい景品を100%狙えてしまいます。
開発者視点: 逆に「最初から確率で中身を決めておき、そこに向けて減速しながら止まる」という、よくあるルーレット演出(出来レース)がこのコードでは不可能です。

ActivationObjects の管理
現状、当たったマスのオブジェクトを SetActive(true) にするだけなので、2回目(仮に再始動できたとして)に別のマスが当たった場合、前回のオブジェクトが出っぱなしになります。

オブジェクト要素の自動配置

UnityのUI(UGUI)で、10個の要素を「横に5つ、縦に2列」できれいに自動配置したい場合、
Horizontal Layout Group ではなく、Grid Layout Group を使うのが最も簡単でスマートです。
Horizontal や Vertical を組み合わせる方法もありますが、階層が複雑になります。

■ ヒエラルキーの準備

  1. 配置したい10個のオブジェクトの親となるゲームオブジェクト(空のImageなど)を作成します
    (名前を GridContainer とします)。
  2. GridContainer の下に、並べたい10個のUI要素(ImageやButtonなど)を配置します。

■ コンポーネントの追加と設定

GridContainer を選択し、インスペクターで Add Component から Grid Layout Group を追加します。

  • Cell Size: 配置するUI要素1個あたりの「幅(X)」と「高さ(Y)」を指定します。
  • Spacing: 要素と要素の間の「隙間」を指定します(Xが横の隙間、Yが縦の隙間)。
  • Start Corner: Upper Left(左上から並べ始める)
  • Start Axis: Horizontal(横方向に並べていく)
  • Child Alignment: Upper Left(または中央揃えにしたい場合は Middle Center
  • Constraint(ここが重要):
  • Fixed Column Count に変更します。
  • Constraint Count5 に設定します。

Constraint を「Fixed Column Count(列数を固定)」にして「5」に設定することで、
6個目の要素が来たら自動的に次の列(2列目)へ折り返して配置されるようになります。

自動配置とランダム並び替え

■ 横並び画像グループの中央寄せ

Imageオブジェクトを1〜8枚まで動的に横に並べ、常に全体が画面中央に寄るようにします。

Horizontal Layout Group子オブジェクトを横方向に自動整列させる核心コンポーネント。

  • Padding(内側余白)Left / Right / Top / Bottom を必要に応じて設定(例: 10〜20)
  • Spacing(子要素間の間隔)画像同士の隙間(例: 15〜30)
  • Child Alignment(子要素全体の寄せ位置)Middle Center最も重要
    これにより子要素全体が親の中央に配置される。
  • Control Child Size(子のサイズを制御):Width:チェック(推奨)Height:チェック(推奨)
    → 子要素のサイズをレイアウトグループが尊重・制御する
  • Child Force Expand(子を強制的に伸ばす):Widthオフ(チェックを外す)Heightオフ(チェックを外す)
    → 子が親の幅いっぱいに広がらないようにする
  • Use Child Scale:通常はオフ

Content Size Fitter親オブジェクトのサイズを「子要素の内容」に合わせて自動調整する。

  • Horizontal Fit:Preferred Size(推奨)
    → 子要素のPreferredサイズの合計 + Spacing + Padding に合わせて横幅を自動調整
  • Vertical Fit:Preferred Size(推奨) または Unconstrained

■ UIランダム並び替え機能(Sys_MemoManager)

UI表示順を、ゲーム実行時に毎回ランダムに並び替える機能の実装ログです。
文字データの差し替えではなく、「オブジェクト(塊)ごとの安全なシャッフル」を実現しています。

1. 採用したアプローチ

コンポーネント(Sibling Index)制御方式
Unityの Horizontal Layout Group が「ヒエラルキー上の順序(Sibling Index)」に従ってUIを左から右へ並べる特性を利用します。
ゲーム起動時に子オブジェクトのヒエラルキー順をプログラム側でシャッフルすることで、見た目の並び順をランダム化しています。

2. メリット・技術的要点

  • 堅牢性(データの一致):
    Sys_Debate_Memo が持つテキスト(rebuttalText)とジャッジ用識別ラベル(flagName)、およびボタンイベントが一体となった「塊(GameObject)」のまま移動するため、見た目と内部判定のズレが根本的に発生しません。
  • 軽量・低負荷(GC Allocゼロ):
    実行時の Instantiate や Destroy を一切行わず、既存のオブジェクトを並び替えるだけなので、メモリ消費やCPU負荷がほぼゼロです。
  • メンテナンスフリー:
    表示するメモの内容や数をインスペクター側で変更しても、マネージャー側のコードを修正する必要がありません(自動追従)。

3. システム構成・設定手順

  1. 親オブジェクト(Horizontal Layout Group がある場所)
  2. 新規スクリプト・Sys_MemoManager をアタッチ。
  3. 子オブジェクト(「卵①」「醤油①」などの各メモ)
  4. 従来通り Sys_Debate_Memo のみをアタッチ(独自の flagName やテキストを設定)。

SAV_変数初期化問題(Win/Lose変数)

発生していた問題

Unity6(URP)環境におけるビルド後テストにおいて、直前のテストプレイで取得した勝敗フラグがリセットされず
次回プレイ時に「Winフラグを取得していない(Falseのはずの)状態でも、Winの演出やUIが暴発してしまう」
という不具合。
(エディタ上では発生しづらく、連続ビルドテスト時に顕著に発生していた)

原因の分析(2つの死角)

【原因①:コンポーネント同士の競合】
当初、SAV_Win_Ob_R1(勝ち)と SAV_Lose_Ob_R1(負け)のチェックを2つの異なるコンポーネントで同時に評価していたため、Unityの実行順序(Execution Order)のブレによって命令の上書き(殴り合い)が発生していた。

【原因②:データ生存期間と管理マネージャーの仕様(本質)】
勝敗フラグを管理する Sys_Flag_Manager(シングルトン)は、内部でデータを Dictionary(辞書)で管理している。
ゲームをリトライしたりタイトルに戻ったりしても、一度登録された「キー(変数名)」自体は辞書内に残り続ける。
判定スクリプト側が「キーの存在有無(HasKey等)」だけでフラグの取得を判定していた場合、
初期化として値を false や 0 に書き換える(Setする)だけではキー自体が消えないため、システムが「フラグ取得済み」と誤認し続けていた。

解決策(実施内容)

Sys_Flag_Manager自体のソースコードを書き換えるリスク(セーブデータ破損などの危険)を避けるため、既存の安全な関数を利用した「初期化スクリプトの配置」を採用。

  • 対応手順:
    タイトル画面(またはゲーム開始時の暗転中)に、以下の役割を持つ専用オブジェクトまたは中継スクリプト(SAV_ResetTrigger)を配置する。
  • 実行する命令:
    Sys_Flag_Manager.Instance.ClearSavVariables(); を実行。

不具合の本質:
フラグ管理(Sys_Flag_Manager)が値の変更ではなく「キー(変数名)の存在」をベースに判定していたため、値を false にするだけでは初期化にならなかった。
解決策の決定:
タイトル画面(別シーン)が読み込まれたタイミングで、マネージャーの ClearSavVariables() を実行し、メモリ上の辞書から SAV_ 変数の形跡を根こそぎ削除する。
なぜ同じシーンでの初期化はダメなのか:
同じシーンに置くと、「フラグの獲得(書き込み)」と「初期化(全削除)」が同じフレーム内で競争(レースコンディション)を起こし、せっかく獲得したフラグを直後に消し去ってしまうリスクがあるため。

実機での挙動

今回の論争パートも、以前に構築したゲームシステムの改善をしました。
ゲームの流れとしては、
探索パートのLocationモードをそのままRoundにして、Location移動をRound移動にみたてています。

探索パートで再構築したシステムを、論争パート用に対応させました。
まずはゲ-ムループを目指して組み上げたので、セーブ対応は後回しにしています。

演出面(オブジェクトの動きなど)にも時間を取られたパートになりました。
当初の予定では、もう少しあっさりした挙動を想定していましたが、
分かりやすさ」を念頭に置いて動きを付けていくと、どうしても装飾的なものが多くなってしまいます。
そして細かいところが気になってしまうと、修正をせざるを得ません。

いままでの作業はプログラム的なことをAiに任せての構築が主でしたが、
久々に、ノベルゲームエンジンなどで演出の組み立てをしていた頃を味わっているところです。

ここでの残り作業は、問題が多々ありそうなセーブ対応と、分岐ロケーションです。(細かい演出も有)
これが終われば、ノベルパートを2つとタイトル画面を作れば、一応の完成です。

おっと、エクストラも忘れてはいけません。
そう考えると、まだけっこうありますね…(´・ω・`)
しかし山場は越えてきているので、もうひと踏ん張りです。

アクティブ非アクティブスクリプトの改修

演出上、個別実行ディレイが必要になってきたので、
オブジェクトをアクティブ非アクティブさせるコマンドスクリプトを改修します。

これまでスクリプト全体で共通だったディレイ設定を、
「対象オブジェクトと個別のディレイ時間」を1つのセット(ペア)としてリスト管理できるようにします

改修の要点

  • データ構造の整理(構造体化)
    GameObject と float delay を内包した DelayTarget という構造体(struct)を定義し、それを配列化しました。
    これにより、インスペクター上でオブジェクトごとに異なるディレイを直感的に設定可能になりました。
  • シンプルかつ堅牢なコルーチン設計(要素数「数個」に最適化)
    リストの要素数が最大でも3〜4個程度という前提に基づき、
    各オブジェクトに対して独立したコルーチンを走らせる方式を採用しました。
    無駄なフレーム監視ループを排除したため、コードが短く、バグが混入しにくい最もスタンダードな設計です。
  • 安全性の担保
    ディレイ待機中にオブジェクト自体が破棄(Destroy)される可能性を考慮し、
    有効化・無効化の直前に必ず null チェックを挟む堅牢な実装にしています。

Element問題

インスペクターでの「Element」表示問題も、ついでに改善しておきました。
Unity標準の仕様では、
構造体やクラスのリストをインスペクターで見ると Element 0、Element 1 と表示されて中身が判別できないため、
閉じている状態でも「アタッチされているオブジェクト名」が直接表示されるようにカスタマイズしました

  • ゲームロジックと描画の完全分離(エディタ拡張の採用)
    製品版ゲーム(ビルド)に不要なコードが混入するのを防ぐため、インスペクターの見た目を変えるプログラムはすべて Assets/Editor フォルダ内 の専用スクリプトに分離しました。
  • 既存データの安全性を100%維持(Sys_Messengerへの応用)
    メインスクリプト(Sys_Messenger など)のデータ構造や変数名を一切書き換えず、「エディタ側の見た目の描画だけを上書きする」アプローチをとりました。
    これにより、すでにステージ上に配置・設定されていた大量のコンポーネントデータが未設定(Missing)に巻き戻るリスクを完全にゼロにしています。
  • 汎用的なPropertyDrawerの配置
    以下の2つのエディタスクリプトを Editor フォルダに配置することで、対象のリストが自動的にオブジェクト名表示へと切り替わります。
    1. DelayTargetDrawer.cs(Cmd_Activate_Object 用)
    2. FinishActivateEntryDrawer.cs(Sys_Messenger 用)

シーン遷移スクリプトの改善

修正の目的

既存の「ボタンクリックによる手動遷移(デモ機能)」を維持したまま、
オブジェクトがアクティブ(有効化)になった瞬間に「自動でシーン遷移する機能」を、安全かつ堅牢に共存させる。

主な変更点

  • 切り替えスイッチ(チェック)の追加:
    インスペクターからON/OFF可能な loadOnEnable(bool型)を新設。
  • ライフサイクルイベントの組み込み:
    Unityの OnEnable() 内で上記スイッチを判定し、ONの場合のみ LoadScene() を自動実行するロジックを追加。
  • 多重ロード防止の維持:
    既存の isLoading フラグによるガード(2重ロード防止)が、自動ロード時にもそのまま機能するように設計。

コンポーネントの設定とヒエラルキーの運用方法

UseAsynce(非同期ロード)

Unityにおける非同期ロード(SceneManager.LoadSceneAsync)の本質は、
メインスレッドを止めずに、裏(バックグラウンド)でシーンを読み込む機能」です。

  • 同期ロード(false)との違い:
    同期ロードは、アセットの展開が終わるまでゲーム全体の処理(描画や入力)が完全にフリーズします。
    一方、非同期ロードは処理を別スレッドに逃がすため、ロード中もゲームが止まりません。
  • 最大のメリット
    ロード中に画面が固まらないため、
    「ローディングアニメーションの再生」「進捗バー(%)の更新」など、ユーザーを不安にさせない演出を維持できます。
  • 制御の注意点
    メソッドを抜けた後も「裏で数フレーム〜数秒間ロードが続く」という時間差が発生するため、完了するまでの状態管理が必須となります。

Load On Enable
用途に応じて、インスペクターの設定とヒエラルキーの配置を以下のように使い分けます。

用途Load On Enable(スイッチ)ヒエラルキーの配置・運用方法
ボタンクリック
(手動)
OFF(チェックなし)UIのButtonオブジェクト、または管理用オブジェクトにアタッチ。
Buttonの OnClick() から LoadScene() を呼び出す。
即時遷移
(自動)
ON(チェックあり)空のGameObject(Trigger_SceneLoad 等)にアタッチ。
初期状態からアクティブにしておく。
任意のタイミング
(自動)
ON(チェックあり)空のGameObjectにアタッチ。
初期状態は非アクティブにしておき、
遷移させたいタイミングで他スクリプトから
.SetActive(true) する。

コメント