This document is about: PUN 1
SWITCH TO

PUN Classic (v1), PUN 2, Bolt는 휴업 모드입니다. Unity2022에 대해서는 PUN 2에서 서포트하지만, 신기능의 추가는 없습니다. 현재 이용중인 고객님의 PUN 및 Bolt 프로젝트는 중단되지 않고, 퍼포먼스나 성능이 떨어지는 일도 없습니다. 앞으로의 새로운 프로젝트에는 Photon Fusion 또는 Quantum을 사용해 주십시오.

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

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

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

CameraWork 스크립트 생성하기

  1. /PunBasics_tutorial/Scripts/CameraWork 라는 새로운 C# 스크립트를 생성 합니다.
  2. 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 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 스크립트 안에서 수행됩니다.

이전 파트.

Back to top