フォロワーを並走させる

Modを作ろう

KeepOffsetFromActorを使ってフォロワーを任意の位置に持ってきます。

スクリプト制御

Skyrimにはフォロワーを並走させる都合のいい機能がないと思います。そこで、Papyrus ScriptにあるKeepOffsetFromActor関数を使って並走を実現します。

KeepOffsetFromActorは対象のActorを指定した位置に配置する命令です。例えばプレイヤーの右側に来させるとか、1人目のフォロワーの前方に2人目を配置するという形で使います。

動き方としては、シューティングゲームで自機の周りに付いてくるオプションのような動きになります。ですのでややぎこちない動き方をします。

基本的に配置された位置から離れられないので戦闘ができません。配置を取り消すにはClearKeepOffsetFromActorを使います。

ここにこの関数の使いにくさがあります。いつKeepOffsetFromActorを使い、いつ解除するか、これを自然に行うにはフォロワー拡張を1から作らなければなりません。

もうひとつの問題は移動速度です。afCatchUpRadiusを調整することで歩きと走りの範囲を指定できます。遠いときは走って配置箇所に向かい、十分に近くまできたら歩きにかわるという動きになります。

速さは2段階なので微調整はありません。プレイヤーが馬に乗っていると自動的に加速するらしく、フォロワーは歩くことができなくなります。逆にプレイヤーが遅すぎるとフォロワーの速度調節がうまく合わず、前後に行ったり着たりしてしまいます。そこでActorのSpeedMultを変更することで微調整を行います。

そして、ある程度はコリジョン判定をしてくれるので岩にめり込むとか宙に浮くなんてことはないのですが、橋などの足場の悪いところで使うと並走しようとして落っこちてしまいます。

Fallout 4にはRangeというプロシージャが追加されています。これがズバリ並走のためにあり、上で説明している面倒なことを全部やってくれます。橋から落ちるのは相変わらずですが。

サンプルソースコード

自作のフォロワー拡張からの抜粋です。いかに面倒かということがわかるかと思います。

並走の例 (papyrus)

Function RefreshKeepOffsetForFollow()
    Float fAngle = PlayerRef.GetHeadingAngle(FollowerRef)
    Float fDistance = FollowerRef.GetDistance(PlayerRef)
    Bool bIsInPosition = fDistance < 200.0
    Bool bKeepOffset = false

    ;Log("distance=" + fDistance + " angle=" + fAngle + " IsInPosition=" + bIsInPosition)

    if IsMoving ; 隊形発動中
        if fDistance > 500.0 ; 遠すぎるなら隊形解除(follow packageで走って追い付く)
            IsMoving = false
            FollowerRef.ClearKeepOffsetFromActor()
            ;Log("ClearKeepOffsetFromActor, reason=too far, distance=" + fDistance)
        else
            Float fSpeed = PlayerRef.GetAnimationVariableFloat("Speed")

            ;Log( "speed=" + fSpeed + " distance=" + FollowerRef.GetDistance(PlayerRef) )

            if bIsInPosition ; 正しい位置についている
                ; 向きがおかしいなら補正
                if Math.abs( FollowerRef.GetAngleZ() - PlayerRef.GetAngleZ() ) > 45.0
                    FollowerRef.SetAngle( 0.0, 0.0, PlayerRef.GetAngleZ() )
                endif

                if fSpeed < 5.0 ; プレイヤーが立ち止まったらフォロワーも止まる(隊形解除)
                    IsMoving = false
                    FollowerRef.ClearKeepOffsetFromActor()
                    ;Log("ClearKeepOffsetFromActor, reason=player is too slow, speed=" + fSpeed)
                elseif fSpeed < 190.0 ; プレイヤーが歩いているならフォロワーも歩く
                    if IsKeepOffsetRun
                        IsKeepOffsetRun = false
                        bKeepOffset = true
                        ;Log("stop running, reason=player is slow, speed=" + fSpeed)
                    endif
                else ; プレイヤーが走っているならフォロワーも走る
                    if IsKeepOffsetRun
                        Float fSpeedP = PlayerRef.GetActorValue("SpeedMult")
                        Float fSpeedF = FollowerRef.GetActorValue("SpeedMult")

                        if fSpeedP > 100.0 && fSpeedP <= 150.0 && fSpeedF - fSpeedP > 0.0 && !PlayerRef.IsOnMount()
                            bKeepOffset = true
                            DamageSpeedMult = fSpeedF - fSpeedP + 15.0
                            ;Log("slow down, reason=player is fast, speed mult=" + fSpeedP + "/" + fSpeedF)
                        endif
                    else
                        IsKeepOffsetRun = true
                        bKeepOffset = true
                        ;Log("start to run, reason=player is fast, speed=" + fSpeed)
                    endif
                endif
            else ; 正しい位置にまだつけていない
                fAngle = FollowerRef.GetHeadingAngle(PlayerRef)

                ; 向きがおかしいなら補正
                if Math.abs(fAngle) > 30.0
                    FollowerRef.SetAngle(0.0, 0.0, FollowerRef.GetAngleZ() + fAngle)
                endif

                ; 走る
                if !IsKeepOffsetRun
                    IsKeepOffsetRun = true
                    bKeepOffset = true
                    ;Log("run, reason=in not in position, distance=" + fDistance)
                endif
            endif
        endif
    else ; まだ隊形を発動していない
        ; 十分に近くて正しい位置についていないなら隊形を発動
        if !bIsInPosition && fDistance < 500.0
            IsMoving = true
            IsKeepOffsetRun = true
            bKeepOffset = true
            ;Log("move, reason=in not in position, distance=" + fDistance)
        endif
    endif

    ; 隊形発動処理
    if bKeepOffset
        Float fOffsetX = 80.0
        Float fOffsetY = 80.0
        Bool bMount = PlayerRef.IsOnMount()

        if bMount
            fOffsetX += 20.0
            fOffsetY += 50.0
        endif

        if KeepOffsetPosition % 2 == 0
            fOffsetX = -fOffsetX
        endif

        if KeepOffsetPosition >= 3
            fOffsetY -= 100.0
        endif

        if IsKeepOffsetRun
            if IsKeepOffsetForceWalk
                IsKeepOffsetForceWalk = false
                DamageSpeedMult = 0.0
                FollowerRef.RestoreActorValue("SpeedMult", 999.0)
                ;Log("resume speed, reason=must run")
            else
                if DamageSpeedMult
                    FollowerRef.RestoreActorValue("SpeedMult", 999.0)
                    FollowerRef.DamageActorValue("SpeedMult", DamageSpeedMult)
                endif
            endif
        else
            Float fSpeed = FollowerRef.GetActorValue("SpeedMult")

            if bMount
                IsKeepOffsetForceWalk = true
                DamageSpeedMult = fSpeed - 35.0
                FollowerRef.RestoreActorValue("SpeedMult", 999.0)

                if DamageSpeedMult > 0.0
                    FollowerRef.DamageActorValue("SpeedMult", DamageSpeedMult)
                endif

                ;Log("slow down, reason=player is mount, speed damage=" + DamageSpeedMult)
            else
                DamageSpeedMult = fSpeed - 100.0

                if DamageSpeedMult > 0.0
                    IsKeepOffsetForceWalk = true
                    FollowerRef.RestoreActorValue("SpeedMult", 999.0)
                    FollowerRef.DamageActorValue("SpeedMult", DamageSpeedMult)
                    ;Log("slow down, reason=follower is too fast, speed damage=" + DamageSpeedMult)
                else
                    if IsKeepOffsetForceWalk
                        IsKeepOffsetForceWalk = false
                        DamageSpeedMult = 0.0
                        FollowerRef.RestoreActorValue("SpeedMult", 999.0)
                        ;Log("resume speed, reason=do not need to walk anymore")
                    endif
                endif
            endif
        endif

        FollowerRef.ModActorValue("CarryWeight", 1.0)
        FollowerRef.ModActorValue("CarryWeight", -1.0)

        Float fCatchUpRadius = 0.0

        if !IsKeepOffsetRun
            fCatchUpRadius = 150.0
        endif

        FollowerRef.KeepOffsetFromActor(PlayerRef, fOffsetX, fOffsetY, 0.0, afCatchUpRadius = fCatchUpRadius, afFollowRadius = 0.0)
        ;Log("KeepOffsetFromActor, run=" + IsKeepOffsetRun + " walk=" + IsKeepOffsetForceWalk + " mount=" + bMount)
    endif
EndFunction

応用例

KeepOffsetFromActorは並走に限らず、敵をプレイヤーの背後に誘導するとか、フォロワーをプレイヤーの前方に配置して壁役とするということにも使えます。

敵をプレイヤーの背後に持ってくるのはOreno Combat AIで実装しています。興味のある方は参考にしてみてください。

タイトルとURLをコピーしました