この記事はUnity Advent Calendar 2022 その3 24日目 の記事です。
qiita.com
今回は、ダークソウルのようなカメラワーク をCinemachineで実装してみます。 記事内ではゲームパッド で操作することを前提 としています。
ちょっと不具合があったり、機能を省いていますがご了承ください。
ダークソウルのようなカメラワークとは
ダークソウルとは3人称視点のアクションRPG です。 一般的な3人称視点のゲームと同様、カメラはプレイヤーの移動に合わせて追従し、 プレイヤーを中心に回転 します。
ゲームパッド では左スティックで移動、右スティックで視点移動をします。
今回はダークソウル3 をベースとした移動とカメラワークをUnityで実装します。 具体的には
・左スティックでの移動 ・右スティックでの視点移動 ・ジャンプ機能(これはSEKIROから) ・R3(右スティック押し込み)でのロックオン
の4つを実装します。
開発環境
Unity 2021.3.12f1
Cinemachine ver2.8.9
Input System ver1.4.4
プロジェクトはURPですがBuilt-inでも問題ありません。 必須ではありませんが、ステージ用アセットとしてこちらも使用します。
プロジェクトの設定
パッケージのインストール
UPM(Unity Package Manager)からCinemachine とInput System をインストールします
Input Systemをインストールすると、このようなダイアログを表示されるのでYesを押し再起動します 。
再起動後、ProjectSettings/Player/OtherSettings/Configration/ActiveInputValidation がBoth 、またはInputSystemPackage(New) になっていることを確認します。
なっていない(Input Managerのまま)場合は変更してください。
シーンの作成
インポートしたステージアセット に含まれている、SpeedTutor Test Scene.scene をそのまま使います。 元のシーンを残しておきたい方は別名で保存するといいですね。
このシーン内に含まれているCameraは、TagがUntaggedになっているので、TagをMainCameraに変更 します。変更しないと後々エラーがでます。
また、ステージがそのままだとやや狭いので、DemoScene DecorationのScaleを全て2 にしておきます。
上記のステージアセットを使わない場合は、Create Sceneでシーンを作成します。 Playerが動けるようPlaneだけ置いておきます 。障害物などは必要ありません。
プレイヤーの作成
まずはプレイヤーを作成します。 Create/3D Object/Capsule からCapsule を作成 。名前をPlayer にし、Rigidbody をアタッチします。 RigidbodyはConstraintsのFreezeRotation にチェックしておきます。
追加でオブジェクトを作成
Playerの方向を取得するオブジェクト と、接地判定用オブジェクト を作成します。
Playerの方向を取得するため、Playerの子オブジェクトとして空のGameObjectを作成。 名前はOrientation にします。位置や角度はそのまま。
同じように設置判定をするGameObjectを作成。 名前はGroundCheck にします。
このGroundCheckはPlayerの足元に配置します。
移動制御Scriptの作成
以下の、PlayerMove.cs をPlayerにアタッチします。
PlayerTrn 、OrientationTrn 、GroundCheckTrn にそれぞれオブジェクトを紐づけ、WhatIsGround はDefaultを設定します
Input System で入力を受け取りたいのでその対応をします。 PlayerにPlayer Inputコンポーネント をアタッチ。
Player Input内のCreate Actionsボタン を押して、Input Action Asset を作成。
作成後、Input Action Assetの設定をしていきます。
このままでも基本的な移動や視点移動はできますが、ジャンプとロックオン機能を実装したいため、新しいActionを追加します。
PlayerマップのActionsにJump を追加、Action TypeはButton を設定。
BindingのPathはGamepad/Button South を設定、Use in control scheme のGamepadにチェック を入れます。
同じ手順でLockon Action を追加、BindingはGamepad/Right Stick Press を設定。
最後にウインドウ上部のSaveAssetで保存します。 保存したら、このウインドウは閉じても大丈夫です。
ここまでできたらゲームを実行してみましょう。ゲームパッド の入力で移動とジャンプができると思います。
左スティックで移動、下のボタン(PS配置なら×ボタン)でジャンプします。
カメラの作成
次にCameraを動かせるようにします。 シーン内にCinemachine/Virtual Camera *1 でCM vcam1 を作成
Inspectorビューから設定していきます。Follow にPlayer を紐づけます。
Bodyの設定
BodyはCameraの移動を制御する項目です。
BodyをFraming Transposerに設定
これはCameraがターゲットに対して(スクリーン空間上で)一定の距離を保ちながら追跡するアルゴリズム です。
パラメータはこのままにします。
Aimの設定
AimはCameraの回転を制御する項目です。
AimをPOVに設定
これはCameraをユーザーの入力に合わせて回転させるアルゴリズム です。
こちらのパラメータもそのままにします。
初期設定では旧Input Managerによる入力を使用してしまうので、Input Systemを使うよう変更 します
CinemachineVirtualCameraコンポーネント の上部にある、Add Input Provider ボタンを押します。
ボタンを押すと、CM vcam1にPlayer Inputコンポーネント が自動的にアタッチ されます。 これはCinemachineでInput Systemの入力を受け取り処理できるようにするコンポーネント です。
XY Axisに最初からActionが紐づけられています。 ここには先ほど作ったInput Action AssetのActionではなく、デフォルトで用意されているActionが使われていますが、機能的には同じなのでこのままでも問題ありません。
この状態でゲームを実行してみましょう。 右スティックの入力に合わせてカメラが回転します。
これでキャラク ターの移動とカメラの視点移動を実装することができました。 ここから先はおまけです。
Cinemachineの詳細設定
Cameraの更新設定(カクつきの対策)
Playerの移動をFixed Updateで行っている時限定
このまま動かすと、Playerが移動するたびCameraがガクガクします。 Playerの移動はFixed Updateで行っているのでCameraの更新タイミングを変更すると解決します。
Main CameraにアタッチされているCinemachineBrainのUpdate MethodをFixed Updateに変更 。
(CinemachineBrainはVirtualCameraをシーンに作成すると自動でアタッチされます)
Cameraが地形を貫通しないようにする
カメラが壁や地面を貫通しないようにします。CinemachineVirtualCameraコンポーネント のExtensions/Add Extension からCinemachineCollier を選択。
CinemachineColliderコンポーネント がアタッチされるので、StrategyをPullCameraForward に設定。 これはカメラがどうやって視線を確保するかを決める設定です。
これでカメラが地形を貫通しないようになりました
カメラが少しめり込んで裏世界が見えてしまう場合、CinemachineColliderのCameraRadiusを大きくすると改善します。
今回は省略してますが、敵やプレイヤーのColliderは無視してほしいので、レイヤーを分け壁や地面とだけ衝突するよう設定しておく のがいいですね。
よりダークソウルっぽいカメラ設定
カメラのパラメータはほとんど初期状態なので、よりダークソウルに近い設定をしてみます。
カメラの中心点
ダークソウルのプレイ動画を見ると、画面の中心はプレイヤーキャラク ターの顔~首周辺 です。 今の設定だとキャラク ターのお腹あたりが中心点になってしまうのでここを変えます。 Playerの子供に空のGameObjectを作成、名前をGazeにします。
Gazeはキャラク ターの頭当たりに配置します。
カメラの中心になるよう、CinemachineVirtualCameraコンポーネント のFollowに設定します。
距離と角度制限
カメラの距離は結構近めなためCameraDistanceは4 。 角度については、上方向に対し下方向はそこそこ制限があるのでこんな感じ。 (ちなみにPlayerMove.csのバグで、カメラが真上付近になるとプレイヤーの移動に支障がでちゃいます)
また、入力の反転はInvertで切り替えます。
これだけでダークソウルにちょっと近づきましたね。 追従速度など細かいところまでやりだすとキリがないのでこの辺りまで。
カメラのデーモン対策
カメラのデーモンとは
カメラのデーモンはダークソウルシリーズでよく用いられる単語で、 ・狭い場所や壁際でカメラが激しく動く ・障害物によりカメラの動きが制限される、または勝手に動く ・敵にカメラを遮られて何も見えなくなる など、カメラワークによってゲームプレイが阻害される問題全般を指します。
SEKIROでは井戸底の孤影衆が有名で、場所がかなり狭く壁際に押し込まれると何も見えなくなる こともあり、忍法クソカメラの術 と呼ばれたりします。
以下Youtube からの引用です(特に0:10~0:15あたり)
VIDEO youtu.be
大型の敵に関しては、見上げるような視点で地形の把握ができなくなったり、 敵のモーションが見えにくい問題が発生します。 そのため、ロックオンを使わず戦うのが推奨されることもあります。
VIDEO youtu.be
対策方法
対策方法として、モンスターハンター が参考になるので取り上げます。モンスターハンター もカメラの問題に悩まされてきましたが*2 、ワールドから大きく改善されました。
一部はSEKIRO以降のフロム作品でもたびたび見られます
オブジェクトを透過(ディザ抜き)する
カメラとプレイヤーの間に挟まれたオブジェクトを透過させる手法。
透過とは書きましたが、実際にはディザ抜きのケースが多い ようです。
プレイヤーが壁と密着しないようにする
プレイヤーの移動可能範囲と壁の間にカメラ用スペースを確保し、プレイヤーが壁と密着できない ようにする。
プラスして透過オブジェクトを間に挟む 手法が、モンスターハンター ワールドでは広く使われています。
図にするとこんな感じ↓
密着できる場所もあるにはありますが、ほとんど気にならないレベル。 まあダークソウルではわざと壁に接触 し、装備やコーデを眺めて楽しむといった遊び方があったりしますが。
モンスターと重なった時プレイヤーを透過する
アイスボーンから追加された機能で、プレイヤーがモンスターと重なった時にプレイヤーのシルエットを表示する 手法です。 ライズではアウトライン表示になりました。
今回はモンスターハンター から3つの手法を取り上げました。 これらを使うことで快適(当社比)なカメラワークを実現できると思います。
試しに、プレイヤーと壁の間にオブジェクトを挟んでみる とこんな感じ。 透過処理はシルエットで代用。
Playerと接触 するがCinemachineCollierは無視するレイヤーのColliderを配置すれば簡単に実装できます。 アウトラインはアセット を使いました。
ダークソウルでは、カメラが荒ぶる時ロックオンを強制的に解除するという措置が取られたりもします。 ゲームごとにさまざまな工夫が凝らされているので、いろいろ観察してみるのもいいですね。
ロックオン機能の実装(仮)
おまけで、ダークソウルシリーズで使われるロックオン機能を実装してみます。 手順としては、ロックオン時にVirtualCameraを切り替えて対応します。※プログラム的な問題点が多いです。ご了承ください。
ロックオン時のカメラを作成
ロックオン時にのみ使われるカメラを作成します。 新しくVirtual Cameraを作成。
Priorityは0 、FollowにはGaze 、BodyはFraming Transposer にします。 Bodyの値は最初に作ったCM vcam1と同じにしておいたほうがよさそう。
ダークソウルではターゲットとプレイヤーが重ならないように、画面中央よりやや上側にターゲットがくる ようになっています。
これを再現するためDamping、Screen Y、Soft Zoneの大きさをそれぞれ調整 します。
敵の作成
ロックオン対象となる敵を作成します。まずは判定用のEnemyレイヤーを定義。
Playerと同じ要領でCapsule を作成し名前をEnemyに変更、LayerはEnemyに設定。
場所は適当な所でOK。
※プログラムのバグで、ロックオン対象との位置取りによっては挙動がおかしくなるかも
ロックオンカーソルを作成
ロックオンしてることがわかるUIを作成します。Canvas とその子にImageを作成。
カーソル用に適当な画像を用意して使います。
最後に、このImageオブジェクトは必ず非アクティブにしてください 。
カメラを制御するScritpの作成
以下の、PlayerCamera.cs をPlayerにアタッチします。
FreeLookCameraには通常時のVirtualCameraを、LockonCameraには先ほど作ったロックオン用VirtualCameraを紐づけます。
ロックオンを行うScriptの作成
以下の、PlayerLockon.cs をPlayerにアタッチします。
PlayerCameraには先ほどのPlayerCameraコンポーネント を、OriginTrnにはPlayer を、TargetLayerNameにはEnemy を、LockonCursorには先ほど作ったカーソル をそれぞれ紐づけます。
VirtualCameraの切り替え速度
デフォルトだと切り替え速度が遅いので、MainCameraにあるCinemachineBrainコンポーネント のDefaultBlend の値を0.5 に変更します。
これでとりあえず完成。 カメラの正面方向にEnemyがいる状態で右スティックを押すとロックオン状態になり、再度押すとロックオンが解除されます。
正直あやしい挙動があったり、ダークソウルより動きが硬い気もしますがまあここまで。 ロックオン対象がいない場合はプレイヤーの向いている方向にカメラをリセットする機能が必要ですが今回はないです。
ロックオンカメラにCinemachineColliderをつける場合
割と致命的な問題として、ロックオン用カメラにCinemachineColliderがある状態で障害物をターゲットとの間に挟むとカメラが荒ぶる問題があります。
そのため、カメラとターゲットとの間にある障害物は、いい感じに衝突判定を無効化するなど工夫が必要です。 良い解決方法が思い浮かばなかったので対応していませんが、CinemachineColliderのIgnoreTagやTransparentLayersなどがヒントになりそう。
まとめ
今回はCinemachineを使ってダークソウルっぽいカメラワーク+移動処理を実装しました。 移動、視点移動、ジャンプ、ロックオン機能にCinemachineの調整でダークソウルっぽい見た目に仕上がったと思います。 3人称視点のゲームであればいろいろ応用できると思うのでぜひ遊び倒してください。
問題点
解決できていない問題点が複数あります
時間がなかったのでそのままです。ごめんなさい 。
・ジャンプ中に壁と接触 すると浮遊する ・カメラが真上付近にあるとプレイヤーが正常に回転できなくなる ・ロックオン中に右スティックでターゲットを切り替える機能がない ・ロックオン対象がいない時にカメラをプレイヤーの向いている方向にリセットする機能がない ・ロックオン中、ターゲットとの距離が離れた時に解除する機能がない ・ロックオンする敵との位置関係によってはプレイヤーが正常に移動できなくなる ・ロックオン用カメラにCinemachineColliderがあると障害物を挟んだ時にカメラが荒ぶる ・ロックオン時に前回のロックオン時の角度を引き継いで一瞬揺れることがある。
改善点などがありましたらコメントなど気軽にお願いします! まさかり大歓迎です!
参考資料
キャラク ターコントローラー関係
移動に関する処理は↓の動画を参考にしました
VIDEO youtu.be
Cinemachine関係
VIDEO
www.youtube.com
VIDEO www.youtube.com
nekojara.city
ロックオン機能関係
最初はTargetGroupでやろうとしたがロックオン系には不向きみたいなのでやめました
forum.unity.com
forum.unity.com
VIDEO www.youtube.com
VIDEO youtu.be
ロックオン機能のScriptは↓2つの記事を参考にしました ChatGPTの力も借りています
gomafrontier.com
qiita.com
その他
nekojara.city
raspberly.hateblo.jp
raspberly.hateblo.jp
raspberly.hateblo.jp
raspberly.hateblo.jp
以上です。