【UEFN】 DiabloライクのTop-DownViewカメラを作る Part.2(Verseの学習④)

カメラ方向と操作方向が一致したTop-Down Viewカメラを作る

前回紹介したTop-Down View カメラはカメラ方向と操作方向が一致していない場合に、非常に操作が行いづらいという欠点がありました。

ringogames.hatenablog.com

そこで、前回のものを改修して、プレイヤーの向きにカメラが追従するように変更します。

イメージとしてはこのような形になります。

プレイヤーの向きに対応したTop-Down Viewカメラ

改修方針

  • 親となっているオブジェクト(図のNullProp)の位置をプレイヤーの位置と一致させるように動かします。(A)
  • カメラのオフセットは親からの相対座標として与えておきます。(B)
  • プレイヤーの視点移動に合わせて、親となっているオブジェクトのYaw角を動かします。(A)
  • ジャンプした際に、カメラ方向と操作方向を一致させるようにします。(常に一致させることも可能ですが、ゲーム性としてジャンプした際のみに一致させる方針で進めます)
  • 概念図

シーン準備

前回と同一のものを使用し、カメラオフセット(上記図のB)は変更しておきます。

Verse Code

改修したコードは以下のようになります。

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Fortnite.com/Characters }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Fortnite.com/Characters }

# A Verse-authored creative device that can be placed in a level
track_player_manager := class(creative_device):

    @editable
    _CameraRoot: creative_prop = creative_prop{}

    @editable
    _OtherRoot: creative_prop = creative_prop{}

    @editable
    _OffsetX : float = 0.0
    @editable
    _OffsetY : float = 0.0
    @editable
    _OffsetZ : float = 0.0

    var _CurrentRotation : rotation = rotation{}

    ### OnBegin
    OnBegin<override>()<suspends>:void=
        Offset:vector3 = vector3{X:=_OffsetX, Y:=_OffsetY, Z:=_OffsetZ}

        ## add jumped event 
        Players : []player = GetPlayspace().GetPlayers()
        if(Player : player = Players[0]):
            if(FortniteCharacter : fort_character = Player.GetFortCharacter[]):
                FortniteCharacter.JumpedEvent().Subscribe(OnPlayerJumped)

        UpdateRotationValue()

        ## start update loop
        spawn{UpdateTargetPosition(Offset)}

    ### process for jump
    OnPlayerJumped(the_player:fort_character):true= 
        UpdateRotationValue()

    ### update rotation value
    UpdateRotationValue():void=
        Players : []player = GetPlayspace().GetPlayers()
        if(Player : player = Players[0]):
            if(FortniteCharacter : fort_character = Player.GetFortCharacter[]):

                ### Not Good
                #var playerRotation : rotation = FortniteCharacter.GetTransform().Rotation
                
                ### Good
                var viewRotation : rotation = FortniteCharacter.GetViewRotation()

                var angles :[]float = viewRotation.GetYawPitchRollDegrees()
                if(angleYaw := angles[0]):
                    anglePitch := 0.0
                    angleRoll := 0.0
                    targetRotation := MakeRotationFromYawPitchRollDegrees(angleYaw, anglePitch, angleRoll)
                    set _CurrentRotation = _CurrentRotation.RotateBy(MakeShortestRotationBetween(_CurrentRotation, targetRotation))

    ### Loop Process
    UpdateTargetPosition(Offset:vector3)<suspends>:void=
        loop:
            ## get player and player position
            Players : []player = GetPlayspace().GetPlayers()
            if(Player : player = Players[0]):

                if(FortniteCharacter : fort_character = Player.GetFortCharacter[]):
                    PlayerPosition : vector3 = FortniteCharacter.GetTransform().Translation
                    ## add rotation
                    PlayerRotation : rotation = FortniteCharacter.GetTransform().Rotation

                    ## first rotation
                    TargetPositionX : float = PlayerPosition.X
                    TargetPositionY : float = PlayerPosition.Y
                    TargetPositionZ : float = PlayerPosition.Z

                    TargetPosition:vector3 = vector3{X:=TargetPositionX, Y:=TargetPositionY, Z:=TargetPositionZ} + Offset
                    
                    ## move target to player position
                    _CameraRoot.MoveTo(TargetPosition, _CurrentRotation, 0.5)
                    _OtherRoot.MoveTo(TargetPosition, IdentityRotation(), 0.5)
                    
                    # Not Good
                    # if(_CameraRoot.TeleportTo[TargetPosition, IdentityRotation()]):
                    #     void

            Sleep(0.5)

            # Not Good
            # Sleep(0.0)

コード解説します。 CameraRootは上記図で言うNullPropをセットする場所です。 OtherRootを今回追加しています。これは、回転を適用せずにプレイヤーに追従してほしいものを入れるために用意しました。 自分の作例では、ライトを入れています。(ライトは回転させたくないため)

    @editable
    _CameraRoot: creative_prop = creative_prop{}
    @editable
    _OtherRoot: creative_prop = creative_prop{}

_CurrentRotationに計算した回転量を保持していきます。

var _CurrentRotation : rotation = rotation{}

プレイヤーがジャンプした際に、回転量計算が発生するようにしています。

FortniteCharacter.JumpedEvent().Subscribe(OnPlayerJumped)
...
OnPlayerJumped(the_player:fort_character):true= 
        UpdateRotationValue()

UpdateRotationValueが回転量計算の部分になります。

今回の一番のポイントはここです。

                ### Not Good
                #var playerRotation : rotation = FortniteCharacter.GetTransform().Rotation
                
                ### Good
                var viewRotation : rotation = FortniteCharacter.GetViewRotation()

プレイヤーに追従させたいのでプレイヤーの回転をとってしまいそうなところですが、それではうまくいきません。実際の操作は視点の方向と一致する必要があるため、視点の回転情報を取得する必要があります。

Fortniteは3人称ゲームのため、キャラクターの方向と視点の方向が一致していません。

Yaw角だけを取り出して回転情報を作ります。現在の回転との差分を出し、時計回りと半時計回りで近い方向に回るようにしています。

                var angles :[]float = viewRotation.GetYawPitchRollDegrees()
                if(angleYaw := angles[0]):
                    anglePitch := 0.0
                    angleRoll := 0.0
                    targetRotation := MakeRotationFromYawPitchRollDegrees(angleYaw, anglePitch, angleRoll)
                    set _CurrentRotation = _CurrentRotation.RotateBy(MakeShortestRotationBetween(_CurrentRotation, targetRotation))

ここで出来た回転情報を使用してMoveTo関数を利用します。

_CameraRoot.MoveTo(TargetPosition, _CurrentRotation, 0.5)

_OtherRootは回転はせず位置だけ追従させるので前回と同様です。

バイス設定

バイス設定は以下のようになります。 _OtherRootはここではセットしていませんが、実際はライトのためのRootオブジェクトをセットしています。

まとめ

今回の結果はこのような形になります。

俯瞰カメラ ジャンプ時の回転対応

視点方向とカメラ方向が一致したため、感覚的に操作が行えるようになりました。

また、ジャンプで回転というゲーム性も出たため、なかなかいいように思います。

EpicのDevCommunityで今回のコードをSnippetとして投稿しておきました。

https://dev.epicgames.com/community/snippets/xve/fortnite-top-down-view-support-device

この情報が皆さんの参考になれば幸いです。

今後もさまざまな情報を発信していきますので、Twitterフォローしていただけると嬉しいです。 twitter.com

UEFN楽しい:)