6-プレイヤーのカメラワーク
このセクションでは、プレイヤを追従するCameraWork
スクリプトを作成する方法を説明します。
このセクションはネットワーキングと関連性がないため、簡潔に説明します。
CameraWork Scriptを作成
CameraWork
という名前の新しいC#スクリプトを作成します。CameraWork
の内容を以下のように置換します:C#
using UnityEngine; using System.Collections; namespace Com.MyCompany.MyGame { /// <summary> /// Camera work. Follow a target /// </summary> public class CameraWork : MonoBehaviour { #region Public Properties [Tooltip("The distance in the local x-z plane to the target")] public float distance = 7.0f; [Tooltip("The height we want the camera to be above the target")] public float height = 3.0f; [Tooltip("The Smooth time lag for the height of the camera.")] public float heightSmoothLag = 0.3f; [Tooltip("Allow the camera to have a vertical from the target, for example giving more view of the scenery and less ground.")] public Vector3 centerOffset = Vector3.zero; [Tooltip("Set this as false if a component of a prefab being instantiated by Photon Network and manually call OnStartFollowing() when and if needed.")] public bool followOnStart = false; #endregion #region Private Properties // cached transform of the target Transform cameraTransform; // maintain a flag internally to reconnect if the target is lost or the camera is switched bool isFollowing; // Represents the current velocity, this value is modified by SmoothDamp() every time you call it. private float heightVelocity = 0.0f; // Represents the position we are trying to reach using SmoothDamp() private float targetHeight = 100000.0f; #endregion #region MonoBehaviour Messages /// <summary> /// MonoBehaviour method called on GameObject by Unity during initialization phase /// </summary> void Start() { // Start following the target if wanted. if (followOnStart) { OnStartFollowing(); } } /// <summary> /// MonoBehaviour method called after all Update functions have been called. This is useful to order script execution. For example a follow camera should always be implemented in LateUpdate because it tracks objects that might have moved inside Update. /// </summary> void LateUpdate() { // The transform target may not destroy on level load, // so we need to cover corner cases where the Main Camera is different every time we load a new scene and reconnect when that happens if (cameraTransform == null && isFollowing) { OnStartFollowing(); } // only follow is explicitly declared if (isFollowing) { Apply (); } } #endregion #region Public Methods /// <summary> /// Raises the start following event. /// Use this when you don't know at the time of editing what to follow, typically instances managed by the photon network. /// </summary> public void OnStartFollowing() { cameraTransform = Camera.main.transform; isFollowing = true; // we don't smooth anything, we go straight to the right camera shot Cut(); } #endregion #region Private Methods /// <summary> /// Follow the target smoothly /// </summary> void Apply() { Vector3 targetCenter = transform.position + centerOffset; // Calculate the current & target rotation angles float originalTargetAngle = transform.eulerAngles.y; float currentAngle = cameraTransform.eulerAngles.y; // Adjust real target angle when camera is locked float targetAngle = originalTargetAngle; currentAngle = targetAngle; targetHeight = targetCenter.y + height; // Damp the height float currentHeight = cameraTransform.position.y; currentHeight = Mathf.SmoothDamp( currentHeight, targetHeight, ref heightVelocity, heightSmoothLag ); // Convert the angle into a rotation, by which we then reposition the camera Quaternion currentRotation = Quaternion.Euler( 0, currentAngle, 0 ); // Set the position of the camera on the x-z plane to: // distance meters behind the target cameraTransform.position = targetCenter; cameraTransform.position += currentRotation * Vector3.back * distance; // Set the height of the camera cameraTransform.position = new Vector3( cameraTransform.position.x, currentHeight, cameraTransform.position.z ); // Always look at the target SetUpRotation(targetCenter); } /// <summary> /// Directly position the camera to a the specified Target and center. /// </summary> void Cut( ) { float oldHeightSmooth = heightSmoothLag; heightSmoothLag = 0.001f; Apply(); heightSmoothLag = oldHeightSmooth; } /// <summary> /// Sets up the rotation of the camera to always be behind the target /// </summary> /// <param name="centerPos">Center position.</param> void SetUpRotation( Vector3 centerPos ) { Vector3 cameraPos = cameraTransform.position; Vector3 offsetToCenter = centerPos - cameraPos; // Generate base rotation only around y-axis Quaternion yRotation = Quaternion.LookRotation( new Vector3( offsetToCenter.x, 0, offsetToCenter.z ) ); Vector3 relativeOffset = Vector3.forward * distance + Vector3.down * height; cameraTransform.rotation = yRotation * Quaternion.LookRotation( relativeOffset ); } #endregion } }
スクリプト
CameraWork
を保存します。
リアルタイム3DやVectors、ベクトル、クォータニオンを使い始めたばかりの場合には、プレイヤーを追従する数学理論は難しいかもしれません。
このチュートリアルでは説明しませんが、ご興味のある場合には説明しますので、お気軽にお問い合わせください。
このスクリプトは難しい数学に関するだけのものではありません。アクティブにプレイヤーを追従するという重要な機能も設定されます。
これを理解することが重要です。なぜプレイヤーを追従するタイミングを制御する必要があるのでしょうか。
プレイヤーを常に追従した場合に、一般的に何が起きるかを想像してください。
プレイヤーでいっぱいのルームに接続すると、相手のプレイヤーのインスタンスのCameraWork
スクリプトは、プレイヤーを映すためにそれぞれ「メインカメラ」を制御しようとぶつかり合います。
こういった状態を避けて、コンピュータの前にいるユーザーを表すローカルプレイヤーのみを追従するべきです。
1つのカメラに対して複数のプレイヤーインスタンスが存在する場合の問題を把握できたので、それを防ぐ方法を以下で説明します。
- ローカルプレイヤーにのみ
CameraWork
スクリプトを添付する。 - 追従するプレイヤーがローカルプレイヤーかどうかに応じてオンとオフを切り替え、
CameraWork
の動作を制御する。 CameraWork
をカメラに取り付け、シーン内にローカルプレイヤーインスタンスがある場合は注意をして、そのプレイヤーのみを追従するようにする。
他にも方法はありますが、これらの3つの方法の中から選ぶとしたら2つ目のオプションをお勧めします。
3つの方法には大差はありませんが、2つ目のオプションはコーディングが最も少なく、非常に高い柔軟性があります。
パブリックプロパティ
followOnStart
を公開しましたので、非ネットワーク化された環境でこれを使用する場合はtrueに設定してください。
例:テストシーンやまったく異なるシナリオネットワークベースのゲームで動作する際、プレイヤーがローカルプレイヤーであると検出した場合にはパブリックメソッド
OnStartFollowing()
を呼び出します。
これは、作成されたスクリプトPlayerManager
で実行されます。このスクリプトについては、プレイヤープレハブネットワーキングの章で説明します。