スクリプト制御
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で実装しています。興味のある方は参考にしてみてください。