New Input SystemのUIアクション未割当でタッチが全滅 → URPで飛行機シムの質感を底上げした話
はじめに
Unityで飛行機シミュレーターを開発中、実機で「画面のスロットルもヨークもラダーも一切反応しない」という事態に直面しました。EventSystemもGraphicRaycasterも確かにある、TouchFlightControllerの配線も済んでいる、なのに機体が1ミリも動かない。
同時並行で「素材ゼロ・コードと設定だけでビジュアルを底上げする」タスク(URP ポストプロセス・時間帯ライティング・灯火発光)も進めました。この記事ではその2本立ての実装記録と、それぞれのハマりポイントをまとめます。
やったこと
Part 1:画面操作が機体に届かない不具合の修正
原因の特定
InputSystemUIInputModule を AddComponent するだけでは、New Input System 環境で UIアクション(point / click 等)が空のまま になります。アクションが割り当たっていないと、タッチ/ポインタイベントが uGUI へ一切ディスパッチされません。
結果として:
タッチ → InputSystemUIInputModule(アクション空) → uGUIに届かない
→ VirtualYoke(IDragHandler)が受け取れない
→ TouchFlightControllerがControls未更新
→ 機体が無反応
問題のあったEditor生成コードの骨子:
// ❌ Before: AddComponentするだけ
var es = new GameObject("EventSystem");
es.AddComponent<EventSystem>();
es.AddComponent<InputSystemUIInputModule>(); // アクション未割当!
修正方針:.unity を触らずランタイムで自己修復
プロジェクト規約で .unity / .prefab の直接編集が禁止されているため、[RuntimeInitializeOnLoadMethod] で起動時に自己修復するブートストラップを採用しました。
// UiInputBootstrap.cs(新規)
using UnityEngine;
using UnityEngine.InputSystem.UI;
public static class UiInputBootstrap
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
static void Setup()
{
var module = Object.FindFirstObjectByType<InputSystemUIInputModule>();
if (module == null) return;
// UIアクションが未割当なら既定値をアサイン(冪等)
if (module.actionsAsset == null)
{
module.AssignDefaultActions();
Debug.Log("[UiInputBootstrap] UIアクションを割り当てました");
}
// EventSystem の多重生成を防ぐ
var systems = Object.FindObjectsByType<EventSystem>(FindObjectsSortMode.None);
for (int i = 1; i < systems.Length; i++)
Object.Destroy(systems[i].gameObject);
// Canvas に GraphicRaycaster が無ければ追加
foreach (var canvas in Object.FindObjectsByType<Canvas>(FindObjectsSortMode.None))
{
if (canvas.renderMode != RenderMode.ScreenSpaceOverlay) continue;
if (!canvas.TryGetComponent<GraphicRaycaster>(out _))
canvas.gameObject.AddComponent<GraphicRaycaster>();
}
}
}
Editor生成側も合わせて修正:
// ✅ After: SkyPilotUiSetup.cs
var module = es.AddComponent<InputSystemUIInputModule>();
// Assets/InputSystem_Actions.inputactions の "UI" マップを割り当て
var inputActions = AssetDatabase.LoadAssetAtPath<InputActionAsset>(
"Assets/InputSystem_Actions.inputactions");
if (inputActions != null)
module.actionsAsset = inputActions;
else
module.AssignDefaultActions(); // fallback
デバッグHUD
「入力がRunnerに届いているかどうか」を実機で目視確認するため、DebugHudを実装しました。
// DebugHud.cs(抜粋)
void OnGUI()
{
if (!_visible) return;
var c = _runner.Controls;
GUILayout.Label($"Throttle : {c.Throttle:F2}");
GUILayout.Label($"Elevator : {c.Elevator:F2}");
GUILayout.Label($"Aileron : {c.Aileron:F2}");
GUILayout.Label($"IAS(kt) : {_runner.State.IAS_kt:F1}");
GUILayout.Label($"AGL(m) : {_runner.State.AGL_m:F1}");
GUILayout.Label($"onGround : {_runner.State.IsOnGround}");
// 操作権限(手動 vs AutoTaxi)
bool overriding = _groundOps != null && _groundOps.AutoTaxiOverriding;
GUI.color = overriding ? Color.yellow : Color.green;
GUILayout.Label($"操作権限 : {(overriding ? "AUTO-TAXI(上書き中)" : "手動")}");
GUI.color = Color.white;
}
F3キーまたは左上ボタンでトグル可能。
操作権限の競合回避
AutoTaxiとの競合は ExecutionOrder で解決しました:
[DefaultExecutionOrder(-100)] // 手動入力を先に書き込む
public class TouchFlightController : MonoBehaviour { ... }
[DefaultExecutionOrder(100)] // 手動の後に判定し、手動中は上書きしない
public class GroundOpsController : MonoBehaviour
{
void LateUpdate()
{
if (!AutoTaxiEnabled) return;
if (Manual != null && Manual.ManualActive) return; // 手動優先
// AutoTaxiによる上書き処理
AutoTaxiOverriding = true;
// ...
}
}
Part 2:URP ポストプロセス+時間帯ライティングで質感向上
実装方針
素材(テクスチャ等)は一切使わず、コードと設定だけで実現。Editorスクリプトでアセットを冪等生成し、.unity を直接編集しない規約に沿いました。
ポストプロセス Volume を冪等生成
// SkyPilotVisualSetup.cs(Editor拡張・抜粋)
static void SetupPostFX(Camera cam)
{
// Volume Profile を生成または再利用
const string path = "Assets/Settings/SkyPilotPostFX.asset";
var profile = AssetDatabase.LoadAssetAtPath<VolumeProfile>(path)
?? CreateProfile(path);
EnsureComponent<Tonemapping>(profile, out var tm);
tm.mode.Override(TonemappingMode.ACES);
EnsureComponent<Bloom>(profile, out var bloom);
bloom.intensity.Override(0.6f);
bloom.threshold.Override(1.1f);
bloom.highQualityFiltering.Override(false); // モバイル配慮
EnsureComponent<ColorAdjustments>(profile, out var ca);
ca.postExposure.Override(0.1f);
ca.contrast.Override(8f);
ca.saturation.Override(6f);
EnsureComponent<Vignette>(profile, out var vig);
vig.intensity.Override(0.22f);
// カメラに PostProcess を有効化
var camData = cam.GetUniversalAdditionalCameraData();
camData.renderPostProcessing = true;
camData.antialiasing = AntialiasingMode.FastApproximateAntialiasing;
}
時間帯ライティングコントローラー
// SkyLightingController.cs(ランタイム・抜粋)
void Update()
{
float hours = _weather.TimeOfDayHours; // 0–24
float t = Mathf.InverseLerp(6f, 18f, hours); // 日の出〜日没を0–1に
// 太陽の色温度:朝夕=暖色、正午=白
_sun.color = Color.Lerp(HorizonColor, NoonColor,
Mathf.Sin(t * Mathf.PI));
// 強度:夜0 → 正午1.25
_sun.intensity = Mathf.Clamp01(Mathf.Sin(t * Mathf.PI)) * 1.25f;
// 環境光をTrilight(ベイク不要で軽量)
RenderSettings.ambientMode = AmbientMode.Trilight;
RenderSettings.ambientSkyColor = Color.Lerp(_nightSky, _daySky, t);
// Procedural Skybox パラメーター更新
if (_skyMat != null)
{
_skyMat.SetFloat("_Exposure", Mathf.Lerp(0.3f, 1.3f, t));
_skyMat.SetFloat("_AtmosphereThickness", Mathf.Lerp(0.5f, 1.0f, t));
}
// 熱負荷が高い時はBloomをOFF(実機パフォーマンス配慮)
if (_bloom != null)
_bloom.active = ThermalState < ThermalStatus.Serious;
}
ハマったポイント
1. AddComponentするだけでは動かない(一番の罠)
InputSystemUIInputModule のドキュメントには「コンポーネントを追加すれば動く」と読めますが、New Input System の Preset が自動適用されない環境(特にEditor拡張でランタイムに生成したケース)では actionsAsset が null のまま。AssignDefaultActions() か明示的なアセット代入が必須です。
2. スクリプトの実行順で競合が起きる
手動入力(TouchFlightController)とAutoTaxi(GroundOpsController)が同一フレームで Runner.Controls を書き込むと、どちらが「最終値」になるかは実行順次第。[DefaultExecutionOrder] で順序を明示し、「手動が先書き・AutoTaxiが後判定・ただし手動中は上書き禁止」というルールを実装しました。
3. Procedural Skyboxのパラメーター名をコードで確認
_SkyTint / _AtmosphereThickness / _Exposure などUnity組み込みスカイボックスシェーダーのプロパティ名は、パッケージのソースを直接見て確認しました。ドキュメントに載っていないものもあるので注意。
4. Cesium地表とサーフェスマテリアルの関係
Cesium 3D Tilesが地表を覆う構成では、「滑走路サーフェスメッシュ」が実際には描画されないケースがあります。今回は接地面マテリアルをアセット化・配線まで済ませましたが、可視の改善は中心線・灯火(発光)・ポストFX側に効きました。
学び
-
New Input Systemを使うなら
AssignDefaultActions()か明示アクション割当を必ず行う。AddComponentだけは罠。 -
[RuntimeInitializeOnLoadMethod]による「起動時自己修復」は、.unityを編集できない制約のある環境(チームのScene管理ルール等)で非常に有効なパターン。 - デバッグHUDは「入力がどこで詰まっているか」を切り分けるのに絶大な効果があります。Controls値・IAS・操作権限をリアルタイム表示するだけで、原因究明が劇的に速くなりました。(個人の感想ですが、本当に助かりました)
- URPのポストプロセスは**軽め設定(Bloom intensity 0.6、highQualityFiltering=OFF)**にして、熱負荷時はコードで動的OFFにするとモバイル・実機でも安定します。
- エディタ拡張を冪等に書いておくと、何度実行しても副作用なく安全に使えて、チーム全員がメニュー1クリックで環境を揃えられるのが地味に便利です。