はじめに
SkyrimSE.exeが1.6.1170の環境です。
Skyrim’s Paragliderを導入してNemesisを走らせます。
Skyrim’s Paragliderはプラグイン(espファイル)、SKSEプラグイン(DLLファイル)、その他のアセット(メッシュ、テクスチャ、サウンド、アニメーション、スクリプト)で構成されています。このうちDLLがAEに対応していないので、いずれかのパッチをあわせて導入します。
- Skyrim’s Paraglider Anniversary Edition Update
- Paraglider Commonlib-NG DLL update
前者を使いました。単純にAEに対応させているだけのようで、問題なく動作しました。
後者は色々な機能が追加されているようなのですが、動作しませんでした。原因は不明です。
上昇しやすくする
無限に上昇できるようにするためにBetter Jumping SEを導入します。Skyrim’s Paraglider側の設定で済めばいいのですが、どうやらBetter Jumping SEを入れるしかないようです。
上昇のパワーを使うと7秒間上昇するということになっていますが、問題がたくさんあります。
- エフェクトが邪魔
- いちいち装備するのが面倒
- 少しだけ上昇したくてもできない
- 長時間上昇するのが面倒
ということでParaglider Auto-Equip Tarhiel’s Galeを導入しました。このModは飛んでいる間だけパワーを装備する、というものですが、これを改造することで使い勝手を改善してみました。ほとんど原型をとどめていませんが。
上昇の仕組みですが、プレイヤーにパワーのNotRevalisGaleMGEFというMagic Effectが発動していれば上昇するようでした。
そこで、常時発動型のアビリティを用意して、ジャンプキーを押したらプレイヤーにアビリティを付与、ジャンプキーを離したら除去するようにしました。これでジャンプキーを押している間だけ上昇するようにできました。
True Directional Movementの機能だったと思うのですが、水中でジャンプキーを押している間は上昇、スニークキーを押すと潜水という操作になりますが、これと似た感覚で操作できるので使いやすいです。
飛行速度をかえる
これは簡単で、Paragliding.espに含まれるMovement Typeで決まっているようなので、調整するだけです。
せっかくなのでWalkはデフォルトの700のまま、Runを1400にしてみました。
飛行中の判定
使い勝手の改善というよりは、Paraglider Auto-Equip Tarhiel’s Galeの改造に関する話となります。後で解説するフォロワー対応に関係します。
以下のイベントをRegisterして受け取るようにします。
イベント名 | 内容 | 備考 |
---|---|---|
JumpLandSoft | 飛行開始 | Skyrim’s Paragliderオリジナル |
JumpLandFailSafe | 飛行終了 | Skyrim’s Paragliderオリジナル |
FootLeft | 左足の足音発生 | |
FootRight | 右足の足音発生 | |
JumpLand | ジャンプの終わり |
JumpLandSoftは常にRegisterしておきます。JumpLandSoftを受け取ったら他のイベントをRegisterすることで飛行終了を判定します。飛行終了時にJumpLandSoft以外をUnregisterします。これで無駄なイベントを受け取らなくて済みます。
ところで、Skyrim’s Paragliderはどういうわけか、たまに勝手にパラグライダーを閉じてしまい、飛行が終了して落下してしまいます。そのままだと地面に激突して即死するので、急いで再度パラグライダーを開くのですが、この一連の流れを飛行終了とみなさないように無視する必要があります。
まず、FootLeft、FootRight、JumpLandは飛行中には絶対に発火しないようなので、これらの発火を確認したら飛行終了とみなしてよさそうです。
JumpLandFailSafeイベントが発火したら、着陸するかもしれないので、着地の判定を開始します。残念ながら完璧な方法を思いつかなかったので、RegisterForSingleUpdateを使って座標の変化を見ることにしました。飛行中に座標が変化しない状況はほぼないと思われるので、座標が変化しなくなったら地面に着地してじっとしている、つまり飛行終了したと見なせます。
それから、IsSwimming関数がtrueなら着水したことになるので、飛行終了とみなします。
飛行中の判定ができるようになったら、ParaglidingというFactionを用意して、飛行中はプレイヤーに属させるようにしました。
Paraglider Riding Form
飛行のモーションを変更するModです。
RidingFormというFactionを用意します。Paraglider Auto-Equip Tarhiel’s Galeに、飛行中にスニークを押したらRidingFormに属させるようにしました。もう一度押したら離脱させてトグル式にします。
モーションはOpen Animation Replacer(OAR)に対応させます。条件式を「RidingFormというFactionに属している」とします。
これで飛行中にスニークキーで飛行モーションを切り替えられます。
フォロワーに対応させる
自前のフォロワー拡張に実装しました。
飛行の開始
プレイヤーがParaglidingというFactionに属していたら飛行中ということになるので、フォロワーの飛行開始のトリガーとします。着地の判定をあれこれ工夫したのはこのためです。
フォロワーが飛行中の間もParaglidingというFactionに属するようにします。それから飛行中は納刀させてその場でじっとしているPackageを適用するようにします。
OARにフォロワー用飛行モーションを追加します。フォロワーのIdleモーションをすべてParaGlideIdle.hkxで上書きするようにします。ここでOARのAnimation Log機能が役に立ちます。
以下のアニメーションを差し替えました。
mt_idle.hkx
MT_IdleLookingA.hkx
MT_IdleLookingB.hkx
MT_IdleLookingC.hkx
MT_IdleLookingD.hkx
MT_idle_A_shoulders.hkx
MT_idle_A_sigh_var1.hkx
MT_idle_A_sigh_var2.hkx
MT_idle_A_soft_right.hkx
MT_idle_A_sway_fast.hkx
MT_Jump.hkx
MT_JumpFall.hkx
MT_JumpLand.hkx
Shd_BlockIdle.hkx
パラグライダーが表示されないので、これはスペルを用意してエフェクトとして表示させるようにしました。
ここまでで、プレイヤーが飛行中はフォロワーも飛行の姿勢になります。
空中の移動
TranslateTo関数を使って移動させます。
TranslateToが目標地点を座標で指定、TranslateToRefが目標地点をリファレンスで指定です。どちらがいいかは難しいですが、TranslateToRefを使うことにしました。
TranslateTo関数は、移動が完了するとOnTranslationCompleteイベントが発火して移動完了を検知できます。それからStopTranslation関数で中断が可能です。
ところで、移動中は空中であろうが岩の中であろうが強制的に移動するため、落下することはありませんが、移動完了と同時に足場判定が復活するため落下します。そこで、どうにかして落下を防ぐ必要があります。
まず、移動先に床を置いて落ちなくする方法です。この場合はTranslateToRef関数を使うことになると思います。床はShapeのAlphaを0にするかShapeそのものを削除して見えなくしますが、コリジョンは残します。これて立つことができるわけですが、プレイヤーも立ててしまいます。これがデメリットです。あとは床に立っている間は空中で静止しており座標が変化しないので違和感があります。
そこでTranslateTo関数を連続して使い続けることで、強引に飛び続けるようにしました。この場合は座標指定でいいので床が不要になります。どうやらStopTranslation関数は要らないようです。コツは目標地点を遠目に設定してあえて時間がかかるようにすることです。これでスクリプトの遅延による落下を防げます。間隔は1秒としました。
着地はプレイヤーとの距離があるならプレイヤーの座標を元に目標地点を指定してTranslateTo関数を実行、OnTranslationCompleteイベントを受け取って飛行終了とします。プレイヤーとの距離が十分近いならStopTranslation関数を実行して直ちに落下させて飛行終了とします。
いずれにせよ、フォロワーは落下ダメージを0にしておいた方がいいでしょう。
これでとりあえずはフォロワーを飛ばせることができます。
飛行をチューニングする
とりあえず飛んでほしいならプレイヤーの後ろをついてこさせればいいのですが、姿が見えないのは飛んでいて面白くありません。そこでプレイヤーの状況に応じて位置を変化させるようにしてみました。
プレイヤーの飛行速度はアニメーション変数のSpeedを見ることでわかります。
Float fPlayerSpeed = PlayerRef.GetAnimationVariableFloat("Speed")
前後左右の移動速度になります。上昇や落下は含まれないので注意します。
その場で上昇した場合はSpeedで1000相当、落下は200相当のようでした。
プレイヤーにNotRevalisGaleMGEFが発動していたら上昇中ということがわかります。
あとは三角関数を駆使して頑張って目標地点を設定するだけです。
TranslateTo関数はSpeedをユニット単位で指定しますが、このユニットはGetDistanceで得られる距離と同じです。つまり、1000ユニット先をSpeed 500でTranslateさせると、到達は2秒後になります。
処理が複雑になってくると到達時間が1秒未満になってしまい、たまに落下してしまいます。移動先までの距離を求めるのが大変だったので、目標地点に見えないオブジェクトを置き、GetDistanceで求めてSpeedを調整するようにしました。
ループの実装
TranslateTo関数を連続して実行する方法ですが、一般的にはRegisterForSingleUpdateを使ってループさせることになりますが、良くも悪くもゲームエンジンの負荷次第では遅延するため、今回のように遅延が困るケースではよろしくありません。OnUpdateイベントの発火もその都度スクリプトのインスタンスが生成されるはずなので、ボトルネックになるのではないかと思っています。
そこでwhileループでwait関数を使って待機する方式にしました。私のフォロワー拡張は元からこの実装で、回復や命令を迅速に処理するようにしてあったので、新たに作ったわけではないのですが。