marker by rocamisakitohko

車輪を再発明しよう

Component Mod で作る!0マージ仮想タイヤ/ランディングギア

この記事はStormworks Advent Calendar 2025 25日目の記事です。

Component Mod の到来

2025年7月、Stormworksにまた新たなアップデートが到来し、プレイヤーが既存のパーツの枠に縛られない自作 Modded ブロックを Component Mod という形で追加できるようになりました。

コンポーネント Mod で何ができる? 仕組みから紐解く Stormworks Mod

Component Mod 及び Lua Component に関する詳細な説明は以上の記事に譲り、本稿ではその活用事例をひとつ紹介します。

痒いところに手が届かない

Component Lua API は既存のブロックでは実現できなかった挙動を実現させてくれます。 例えば、ビークルの physics body に直接力を加えたり、既存の Distance Sensor や Laser Distance Sensor の仕様に縛られることなく、自由自在に raycast (衝突判定)を行うことができるようになりました。

しかし残念なことに、既存のブロックの機能の殆どはおそらく Stormworks 内にハードコードされており、Component Lua API を介しても到底手が届かないところにあります。 そして、各種 Modding によりハードコードされた動作を呼び出したところで、全く新しい挙動を作り出すことはできないでしょう。

ただし、Component Mod が痒いところに手が届かない中途半端な存在だからといって、その価値を低く見積もりすぎてはいけません。

Component Mod 及び Component Lua API ではプレイヤーが追加したメッシュデータを自在に描画したり、既存のパーツの挙動をその一部でも再現できることは、十分有意義といえるでしょう。

特に、ビークルの physics body 数を厳しく制限されているマルチプレイヤーイベントなどでは、少しでも body 数を節約し、装飾や可動パーツなどをできる限りで Component Mod 製のパーツに置き換えるインセンティブが生まれ得ます。

そこで今回は、body 数節約の悩みのタネになりがちな航空機のランディングギアを置き換えることを想定し、既存のサスペンション付きタイヤパーツの挙動を Component Mod で再現することにしました。

残念なことに、Component Mod では RPS ノードこそ用意されているものの RPS を直接観測できないので、この仮想タイヤパーツは無動力となってしまいますが。

実装方針 1

サスペンション付きタイヤの細かい挙動は公開されていませんが、その物理的挙動は概ね2つの要素に分解することができます。

まず、接地面に対して垂直な向きには、自然長・剛性・減衰の要素を持つバネとして振る舞い、接地面とビークルとの間で力をやり取りします。 タイヤの基部に対して何らかの理由で接地面が近づけば、その分だけバネが伸びようとする力は大きくなり、バネが伸び切らない限り逆もまた同様になります。

これは Raycast による接地判定、サスペンションが生む反力の計算、そして physics API による力積の発揮によって十分模擬できるでしょう。

if ギアが下りている:
    レイキャストで地面までの距離を取得
    地面に接触しているか? = 地面までの距離 < (バネの最大長または自然長 + タイヤ半径)   -- バネの最大長=自然長とすることで、「引っ張る」ような挙動を無視
    if 地面に接触していれば:
        バネの縮み量 = (バネの最大長または自然長 + タイヤ半径) - 地面までの距離
        バネの縮み速度 = Δバネの縮み量 * タイムスケール

        バネが発揮する力 = (定数a * バネの縮み量 - 定数b * バネの縮み速度) * スケール定数

        component.physicsImpulse(0, バネが発揮する力,0) -- ギアに対して垂直な方向に力を発揮

このようにすれば、サスペンション付きタイヤのバネのような挙動を Component Lua API の機能だけで模擬できます。

問題があるとすれば、接地面に対して反作用を発揮しないこと、接地面が傾いていたときに「力が余る」ことが挙げられますね。 物理的に正確なものを目指しているわけではなく、Component Mod の制約の上でできる限りそれっぽい挙動をさせたいのですから、あまり問題にはならないでしょう。 既存のサス付きタイヤにもバグがありますし。

実装方針 2

そして、接地面に対して平行な2軸では、タイヤ末端の速度と地面の速度を常に比較し、その差とサスペンションの生む力から摩擦力を計算します。

得られた摩擦力は、タイヤに対しては角加速度として、ビークル本体に対しては推進力・制動力として寄与します。

if ギアが下りている:
    if 地面に接触していれば:
        ~~サスペンション力を計算~~

        接地点の速度ベクトル(ビークル基準) = **車輪の接地点での**機体のローカル速度(x,z)
        接地点の速度ベクトル(タイヤ基準) = ベクトルの回転(接地点の速度(x,z),ステアリング角度)
        車輪末端部の速度ベクトル = (0,車輪のRPS * 車輪半径)

        滑り速度ベクトル(x,z) = 接地点の速度ベクトル(タイヤ基準) - 車輪末端部の速度ベクトル

        摩擦力(x,z) = |滑り速度ベクトル| * (動摩擦係数 * バネが発揮する力 / 滑り速度ベクトル) -- 摩擦力は垂直荷重に比例

        車輪の角加速度 = 摩擦力のz成分 * 車輪の半径 / 車輪の慣性モーメント

        摩擦力 = ベクトルの回転(摩擦力,-ステアリング角度)

       
        component.physicsImpulse(-摩擦力のx成分, 0, -摩擦力のz成分) -- ギアに対して水平な面上で力を発揮

    車輪の回転角 = 車輪の回転角 + 車輪の回転速度 
    車輪の回転速度 = 車輪の回転速度 * clamp(1 - 300 * ブレーキ入力 / 車輪の慣性モーメント, 0, 1)  -- 非物理ですが、300という数字自体には根拠あり

    if パーキングブレーキがオン
        車輪の回転速度 = 0      -- これまた乱暴ですが、多分ストワのタイヤもこのくらい乱暴でしょう

このようにして、タイヤの回転とビークルの推進・制動力を摩擦力を介してやり取りできます。ちなみに摩擦力は実際にはより数値的に安定な形で正規化しており、微小な振動を抑制しています。

ただし、この実装には明確な欠点が幾つか挙げられます。

まず、接地面の速度=機体のローカル速度という暗黙の前提がありますが、実際には接地面自体が動いている場合があるはずです。しかし、Component Lua API では衝突判定先のオブジェクトの速度を取得することはできません。 結果として、接地面自体の動きを挙動に反映することができず、常にワールド速度に対して摩擦力を発揮するようになるわけです。

これは接地面の速度を Component Mod の外部から直接入力することで誤魔化すことができます。 具体的には、空母の甲板のような移動する面に対して適切に摩擦力を発生させたい場合、無線で甲板の速度を送ってやればいいわけです。

また、この手法では原理的に静止摩擦力を再現することができません。

実装方針 3

そして最後に、適切な位置にタイヤを描画します。

タイヤの位置は 4x4 martix として onTick() 内で計算しておき、実際の描画は onDraw() に行列を渡すことによって行います。 onDraw() での処理をできる限り減らすことが何より重要です。

得られたもの

ご覧頂いたほうが早いでしょう。

「直進すること」「旋回できること」のようなタイヤに求められる基本要素はもちろん、調整次第で悪路をものともしない走破性を獲得することができます。

そのようにサスペンション付きタイヤの挙動をほどよく模擬しながら、必要に応じて描画・衝突判定をオフにしたりもでき、まさに航空機のランディングギアにうってつけです。

衝撃吸収力・制動力ともに中々のもので、既存の可動ランディングギアよりも信頼がおけるとさえ言えることでしょう。

既にその場にある解決策を、多大な労力をもってゼロから作り直すことを俗に「車輪の再発明」などと言いますが、そうした試行錯誤の過程を侮ってはいけません。 ときに新たな発見がありますし、既存の「車輪」に対する洞察も深まることと思います。 実際今回の開発を通して、私(や大多数のストームワーカー)にとって未知の分野であった Component Mod に、ほんの少しだけ明るくなることができたように思います。

これからも胸を張って車輪を再発明していきたいものですね。

ちなみに、射撃しているのはこれまた Component Mod 製の 1x1 RAC です。

課題

ところで、先程の疑似コードには、何やら定数が含まれていました。 サスペンションの性能などがそうです。

しかし2025年現在、Component Mod には現状ワークベンチから設定できるようなプロパティを用意する方法がなく/発見されていません。

つまり残念なことに、サスペンションの自然長・剛性・減衰、想定重量などはすべてコンポジット信号などによって外部から与えてやるしかありませんでした。

また、2025年現在、Component Lua API の component.physicsImpulse() の挙動はリファレンスの記載と異なっており、ブロックのローカル系基準ではなく、physics body のローカル系を基準に……つまり重心位置を基準に力積を発揮します。

現状ではこれを補正するためにオフセット値を手入力する必要があります。 こちらも残念なことにコンポジット信号によって外部から与えるような実装となってしまっています。

今後は可能なかぎり開発フローを自動化し、このような手間を省けるようにしたいものです。

stormworksluacomponent-modlua-componentwheel