PUN Classic (also called PUN1) is the original and first major version of PUN. It is now replaced by PUN2 which is refactored and enhanced. We highly recommend starting new projects with PUN2 and if possible migrating existing ones from PUN1 to PUN2 by following our "Migration Notes". PUN Classic will be maintained for the coming months. We will fix important bugs and support new Unity versions but new features will be added only to PUN2.

파트 6 - 플레이어 카메라 동작

이 섹션에서는 카메라가 플레이어를 따라 다니도록 해주는 CameraWork 스크립트 생성을 안내해줄 것입니다.

이 섹션은 네트워킹과는 아무런 관련이 없으므로 짧게 끝낼 것 입니다.

Contents

CameraWork 스크립트 생성하기

  1. /PunBasics_tutorial/Scripts/CameraWork 라는 새로운 C# 스크립트를 생성 합니다.
  2. CameraWork 를 다음 코드로 교체 합니다:
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 be offseted vertically from the target, for example giving more view of the sceneray and less ground.")]
        public Vector3 centerOffset = Vector3.zero;

        [Tooltip("Set this as false if a component of a prefab being instanciated 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 target is lost or 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 everytime 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
    }
}

플레이어를 따라가는 수학 연산인 실시간 3d, 벡터와 사원수(quaternion) 기반 수학을 처음 시작 하셨다면 매우 난해할 것 입니다. 따라서 이런 사항들을 이 튜토리얼에서 설명하도록 하겠습니다. 그럼에도 불구하고 잘 이해가 가지 않는 부분이 있다면 저희에게 연락을 해주세요. 최선을 다해서 설명해 드리도록 하겠습니다.

아무튼 이 스크립트에는 미쳐 버리는 수학만이 아니라 중요한 무엇인가도 설정하고 있습니다. 플레이어를 능동적으로 따라 다니는 기능 제어 그리고 플레이어를 따라갈 때 제어가 왜 필요한지에 대한 이해가 중요합니다.

일반적으로 항상 플레이어를 따라 간다면 어떤 일이 발생할지 생각 해 보죠. 플레이어가 가득 차 있는 룸에 접속할 때 다른 플레이어 인스턴스에 있는 각 CameraWork 스크립트는 각자의 플레이어를 보이게 하기 위해 메인카메라와 경쟁 할 것 입니다. 우리는 이런 것을 원하지 않으며 컴퓨터 앞에 있는 사용자를 대표하는 로컬 플레이어를 따라 다니기를 원합니다.

하나의 카메라만 있고 여러 플레이어 인스턴스가 있다는 문제를 정의 했으므로 이 문제를 해결할 여러 방법을 쉽게 찾을 수 있습니다.

  1. 로컬 플레이어에게 CameraWork 스크립트만을 붙입니다.
  2. 플레이어가 로컬 플레이어를 따라 갈지의 여부에 따라 CameraWork behaviour 를 off 와 on 하여 제어 합니다.
  3. 카메라에 CameraWork 스크립트를 붙여 신에 로컬 플레이어 인스턴스가 있을 때만 그 인스턴스를 따라가는 것에 주의 하세요.

이 3 가지 옵션이외에 더 많은 방법을 찾을 수 있지만, 위 3가지 방식 중 임의로 두 번째를 선택 할 것 입니다. 어떤것이 좋고 어떤것이 나쁘다고 말할 수는 없으나 선택한 방식이 가장 적은 코딩 규모와 가장 유연한 방식일 듯 합니다... "재미있군요..." 이렇게 말하실 것 같습니다 :)

  • public 프로퍼티 followOnStart를 노출 시켰으며 네트워크 환경이 아닌 곳에서 사용할 때 true 로 설정할 수 있습니다. 예를 들어, 테스트 신이나 완전히 다른 시나리오에서 말이죠.

  • 네트워크 기반 게임에서 실행 할 때, 플레이어가 로컬 플레이어라고 감지 되면 public 메소드인 OnStartFollowing() 를 호출 할 것 입니다. 이것은 플레이어 프리팹 네트워킹 챕터에서 설정 되고 생성 했던 PlayerManager 스크립트 안에서 수행됩니다.

이전 파트.

To Document Top