作成中のアクションゲームをテスト様にビルドしたけれど、重すぎてスマホでは起動できなくて、スタジオしまづさんのユニコンで、ジョニさんにテストプレイしてもった時のこと。
カメラがポリゴンを避ける様になっていないので、視点移動で当然のようにポリゴンの裏がしっかり見えてしまい、「これは見えてはいけない部分ですよね。」と指摘を頂いてしまった💦
その時にSinemachineで避けるカメラを導入すると決めていた。
操作説明の作成でロックオンの説明段階にきて、それを思い出したので、先に避けるカメラを作成する事にした。
忘れていたわけではなくて、簡単にはCinemachineを導入できない事情があったので、後回しにしていただけなんだけどね😅
作成中のアクションゲームは、流星咲斗さんの「【3Dアクションゲーム】作り方」をベースに作成している。
空Object、仮にCamera Objectと呼ぶけど、このCamera Objectの子にMainCamerを設定して、Camera Objectのpositionにプレイヤーのpositionを上書きさすることでカメラの追従。
Camera Objectに対するmain CameraのLocalpositionでプレイヤーとカメラの距離を設定している。
そして、移動、視点移動、ロックオンの各機能がCamera ObjectのTransformを元にしているため、単純にCinemachineでVirtualCameraを作成して、はい終わり。というわけにはいかないのである。
とはいえ避けるカメラは作りたいので、一つ一つの機能をCinemachineに対応するように修正していくしかない💦
Cinemachine自身の導入はあちこちのサイトで紹介されているので、割愛。
サクッとCinamachineをインストールしてVirtual Cameraを作成して設定する。
Virtual Cameraをスクリプトで使用するには、usingを追加して、VirtualCameraを変数に取り込む必要がある。
using Cinemachine;
[SerializeField] CinemachineVirtualCamera VirtualCamrera;
まずは、移動方法。
Camera Objectのtransform.eulerAnglesを元に、Playerの向きを設定しているので、これをVirtual Cameraを元にするように修正する。
void MoveSet() { speed.z=PlayerSpeed; transform.eulerAngles=new Vector3(0,Camera.transform.eulerAngles.y+rot.y,Camera.transform.eulerAngles.z); IsMoveing=true; isRun=true; }
これを、こう。
void MoveSet() { speed.z=PlayerSpeed; transform.eulerAngles=new Vector3(0,VirtualCamrera.transform.eulerAngles.y+rot.y,VirtualCamrera.transform.eulerAngles.z); IsMoveing=true; isRun=true; }
次に視点の移動で、これが結構難問だった。
先に説明したように元々の仕組みは、mainCameraをCamera Objectの子に設定して、Camera Objectの座標にプレイヤーの座標を上書きしている。
これをVirtual Cameraの座標にして回転させると、プレイヤーを中心としての回転(図の青線)ではなく、Virtual Cameraのoffsetの位置での回転(図の赤線)になってしまう。
これは思った動きではないので、修正してやる必要があるわけだけど、方法を思いつくまでに結構かかってしまった。
当初は、VirtualCameraが常にプレイヤーを見る様にしておいてから、プレイヤーを中心とした円周上に沿ってVirtualCameraが移動する様にしてみようと思った。
けれど、さすがCinemachine。さすがUnityというべきで、こういう事は想定済みとばかりに、ちゃんと解決できる設定があった🤣
Virtual CameraのインスペクターのBody→X Axisの値を変えると、Followに設定した座標を中心にして回転してくれた。
この時、Recenter To Target Headingのチェックを外しておかないと、徐々にこのX Axisが元に戻る挙動になってしまうので注意😃
このX Axisをスクリプトで変更するには、CinemachineOrbitalTransposerを変数に取り込んでおく必要がある。
using Cinemachine; [SerializeField] CinemachineVirtualCamera VirtualCamrera; CinemachineOrbitalTransposer _orbitalTransposer; void Start() { _orbitalTransposer = VirtualCamrera.GetCinemachineComponent<CinemachineOrbitalTransposer>(); }
これで視界の横移動を修正する準備が整ったので、早速スクリプトを修正する。
void Rotation() { Vector2 RightDirction=inputSystemSC.Player.Look.ReadValue<Vector2>(); if(RightDirction.x<-0.4f) { speed.y=-RotationSpeed*Time.deltaTime; } if(RightDirction.x>0.4f) { speed.y=RotationSpeed*Time.deltaTime; } }
これを、こう。
void Rotation() { Vector2 RightDirction=inputSystemSC.Player.Look.ReadValue<Vector2>(); var speed=Vector3.zero; if(RightDirction.x<-0.4f) { _orbitalTransposer.m_XAxis.Value-=RotationSpeed*Time.deltaTime; } if(RightDirction.x>0.4f) { _orbitalTransposer.m_XAxis.Value+=RotationSpeed*Time.deltaTime; } }
これでプレイヤーを中心とした視界の横移動ができる様になった。
次に視界の縦方向の移動と言いたいところだけど、もう一つ修正しなければならない箇所がある。
それは、元々の作りではカメラにLookatをさせておらず、あくまでCamere Objectの方向でCameraの向きを制御していたところ。
後々の避けるカメラを作成するには、どうしてもVirtual CameraのLook Atにプレイヤーを設定する必要がある。
ただし、単純にプレイヤーを設定すると、プレイヤーの座標の基準点が足元のため、Virtual Cameraは足元を見てしまい、ゲームの視界がどうしても地面向きになってしまう。
なので、Look PointというObjectを作成し、プレイヤーの子にして、プレイヤーの頭のあたりの座標に設定する。
このLook PointをVirtual CameraのLookAtに設定してやれば、足元に視点が向く事は防止できる。
こうなると、今度はVirtual CameraがLook Pointを凝視してしまって、Virtual Cameraのtranform.rotationを変えられなくなるため、視界の縦方向の移動にはVirtual Cameraのtranform.rotationとは異なる方法を考えてやる。
それにはVirtual CameraのFollow Offsetの変更が一番楽だと思った。
早速、スクリプトを修正。
void Rotation() { Vector2 RightDirction=inputSystemSC.Player.Look.ReadValue<Vector2>(); var speed=Vector3.zero; if(RightDirction.x<-0.4f) { _orbitalTransposer.m_XAxis.Value-=RotationSpeed*Time.deltaTime; } if(RightDirction.x>0.4f) { _orbitalTransposer.m_XAxis.Value+=RotationSpeed*Time.deltaTime; } if(RightDirction.y>0.4f) { if(Camrera.transform.eulerAngles.x<40) { speed.x=(RotationSpeed*0.5f)*Time.deltaTime; } } if(RightDirction.y<-0.4f) { if(VirtualCamrera.transform.eulerAngles.x>2) { speed.x=(RotationSpeed*0.5f)*Time.deltaTime; } } Camera.transform.eulerAngles +=speed; }
これを、こう。
void Rotation() { Vector2 RightDirction=inputSystemSC.Player.Look.ReadValue<Vector2>(); var yy=_orbitalTransposer.m_FollowOffset.y; var zz=_orbitalTransposer.m_FollowOffset.z; if(RightDirction.x<-0.4f) { _orbitalTransposer.m_XAxis.Value-=RotationSpeed*Time.deltaTime; } if(RightDirction.x>0.4f) { _orbitalTransposer.m_XAxis.Value+=RotationSpeed*Time.deltaTime; } if(RightDirction.y>0.4f) { if(yy<7) { _orbitalTransposer.m_FollowOffset=new Vector3(0,yy+(RotationSpeed*0.1f)*Time.deltaTime,zz+(RotationSpeed*0.05f)*Time.deltaTime); } } if(RightDirction.y<-0.4f) { if(yy>1) { _orbitalTransposer.m_FollowOffset=new Vector3(0,yy-(RotationSpeed*0.1f)*Time.deltaTime,zz-(RotationSpeed*0.05f)*Time.deltaTime); } } }
カメラの高さを上げるタイミングでカメラとプレイヤーの距離(Offsetのz座標を小さく)を近づけ、カメラの高さを下げるタイミングでカメラとプレイヤーの距離を遠ざけ(Offsetのz座標を大きく)ることで、できるだけプレイヤーとカメラの相対的な距離を変えないように工夫してみた。
これで、移動。視界の移動。と修正したので、最後の難関、ロックオン機能の修正だ。
元々のロックオン機能は、視界がロックオンした敵に固定され、前に移動すれば敵に近づけるというもの。
その方法は、Camera ObjectにLookatでロックオンしたい敵の座標を設定して、視界が敵のいる方向で固定するというもの。
これは、Camera Objectとプレイヤーの座標が同じだからこそ成り立っているので、単純にVirtual CameraのLookatにロックオンしたい敵を設定すれば解決するという問題ではない。
プレイヤーの移動はVirtual Cameraの向きが元になっているので、Virtual Cameraが敵をLookatすると、
VirtualCameraの位置からの敵の方向と、プレイヤーの位置からの敵の方向は違うため、ロックオンしているのに、まっすぐ敵に移動できないという、ロックオンの機能たり得なくなってしまう。
なので、ロックオン中は、Virtual Cameraには敵をLookatしてもらいつつ、プレイヤーの位置からロックオンした敵を向いてくれるObjectの向きを使ってプレイヤーが移動できる様にするのが望ましい。
ちょうど先ほど作ったLook Pointが、プレイヤーの頭上に位置しているので、これをロックオンした敵にLookatさせ、Look PointのTransform.rotatinを基準に移動させてやれば良い。
スクリプトの修正はこうなる。
void TargetLock() { if(inputSystemSC.Player.LockOn.triggered) { if(enemyListManager.EnemyList.Count==0) { return; } audioSource.PlayOneShot(changetargetSE); if(Target) { Target.GetComponent<LockOn>().TargetLockOff(); } if(enemyListManager.EnemyList.Count<=TargetCount) { Target=null; TargetCount=0; } else { Target=enemyListManager.EnemyList[TargetCount]; Target.GetComponent<LockOn>().TargetLockOn(); TargetCount++; } } if(Target) { var pos=Vector3.zero; pos=Target.GetComponent<Transform>().position; pos.y=Camera.transform.position.y; Camera.transform.LookAt(pos); } }
これを、こう。
void TargetLock() { if(inputSystemSC.Player.LockOn.triggered) { if(enemyListManager.EnemyList.Count==0) { return; } audioSource.PlayOneShot(changetargetSE); if(Target) { Target.GetComponent<LockOn>().TargetLockOff(); } if(enemyListManager.EnemyList.Count<=TargetCount) { Target=null; VirtualCamrera.LookAt=Lookpos; //ロックオンする敵がいない時はカメラのLookatを戻してやる。 TargetCount=0; } else { Target=enemyListManager.EnemyList[TargetCount]; Target.GetComponent<LockOn>().TargetLockOn(); TargetCount++; } } if(Target) { Lookpos.LookAt(Target.transform); //LookPointにロックオンした敵を向かせる。 VirtualCamrera.LookAt=Target.transform; } else { VirtualCamrera.LookAt=Lookpos; } }
次に移動のスクリプトもロックオン中とそうでない時に分ける必要がある。
void MoveSet() { speed.z=PlayerSpeed; if(Target) { //ロックオン中はLookpointの角度を基準に移動する。 transform.eulerAngles=new Vector3(0,Lookpos.transform.eulerAngles.y+rot.y,Lookpos.transform.eulerAngles.z); } else { //ロックオン中ではない時は、Virtual Cmaeraの角度を基準に移動する。 transform.eulerAngles=new Vector3(0,VirtualCamrera.transform.eulerAngles.y+rot.y,VirtualCamrera.transform.eulerAngles.z); } IsMoveing=true; isRun=true; }
ロックオンした敵がいなくなった時にVirtualCamreraのlookatをLookpointに戻すのを忘れない様に注意😃
これでようやく、すべての修正が終わったので、本題の避けるカメラの設定ができる様になった。
これは、今までの修正に比べるまでもないくらい簡単だ。
Virtual CameraのインスペクターのAdd Extensionから「Cinemachine Collider」を追加する。
そして、避けたい障害物のレイヤーをCollider Againstに設定し、退避行動でカメラがLookatの対象に最短どこまで近づけるかをMinimumDistanceFromTargetに設定する。
木の様に瞬間的にすれ違うだけの時にまで退避行動をされると、画面がバタバタしてしまうので、Miniimum Occlusion Timeに障害物を無視する時間を秒数で設定する。
最後にCamreの大きさをある程度大きくする。
こうしないと、斜めの地形などが微妙に見えてしまう場合があるみたいで、大きすぎても小さすぎても上手くいかない。
ここは自分の地形と相談しながら調整するしかないのだろう🧐
疲れた〜〜〜💦
最後に参考にさせて頂いた先駆者様のサイト紹介。
unitymaster2.com
nekojara.city
nekojara.city
qiita.com
note.com
偉大な先駆者様に感謝🙇