0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

New Input SystemのUIアクション未割当でタッチが全滅した話と、URPで飛行機シミュレーターの見た目を底上げした話

0
Last updated at Posted at 2026-06-14

New Input SystemのUIアクション未割当でタッチが全滅 → URPで飛行機シムの質感を底上げした話

はじめに

Unityで飛行機シミュレーターを開発中、実機で「画面のスロットルもヨークもラダーも一切反応しない」という事態に直面しました。EventSystemもGraphicRaycasterも確かにある、TouchFlightControllerの配線も済んでいる、なのに機体が1ミリも動かない。

同時並行で「素材ゼロ・コードと設定だけでビジュアルを底上げする」タスク(URP ポストプロセス・時間帯ライティング・灯火発光)も進めました。この記事ではその2本立ての実装記録と、それぞれのハマりポイントをまとめます。


やったこと

Part 1:画面操作が機体に届かない不具合の修正

原因の特定

InputSystemUIInputModuleAddComponent するだけでは、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拡張でランタイムに生成したケース)では actionsAssetnull のまま。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クリックで環境を揃えられるのが地味に便利です。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?