This document is about: PUN 2
SWITCH TO

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

5 - 플레이어 만들기

이 섹션에서는 이 튜토리얼에서 사용할 플레이어 [프리팹]을 처음부터 생성하는 단계별 생성 절차를 통해 안내해 드릴 것 입니다.

PUN 연결 없이 작동할 수 있는 플레이어 [프리팹]을 생성하여 시도하는 것이 좋은 방식으로 테스트, 디버그가 쉬우며 네트워킹 기능 없이 모든 기능이 올바르게 작동하는지 확인 할 수 있습니다. 그리고 천천히 네트워크 기능을 추가하면서 구축해 나갈 수 있습니다. 전형적으로 다른 플레이어들의 컴퓨터가 아닌 플레이어가 소유한 인스턴스에서만 사용자 입력이 활성화 되게 됩니다. 아래에서 이에 대해 상세히 다루겠습니다.

프리팹 기본

PUN 에서 가장 먼저 알아야 하고 중요한 것은 네트워크 상에서 Prefab이 인스턴스화 되는 것으로 프리팹은 Resources 폴더내에 있어야만 합니다.

Resources 폴더 안에 Prefabs 를 갖고 있는 것의 두 번째로 중요한 사이드 이펙트는 프리팹의 이름을 감시해야 한다는 것 입니다.

에셋의 Resources 경로에 동일한 이름을 가진 2개의 Prefab이 있어서는 안됩니다. 왜냐하면 유니티는 첫 번째로 찾은 것을 선택하기 때문에 항상 프로젝트 에셋내에서 Resources 폴더 경로에 동일한 이름을 가진 Prefabs 이 없다는 것을 항상 확인 하시기 바랍니다.

이제 유니티가 무료 에셋으로 제공 하고 있는 Kyle Robot 을 사용할 것입니다. Kyle 은 Fbx 파일로 제공되며 3ds Max, Maya , cinema4d 와 같은 3D 소프트웨어에서 (만들어 지는)(https://docs.unity3d.com/Manual/HOWTO-importObject.html) 것 입니다. 이러한 소프트웨에의 메시 또는 애니메이션을 다루는 것은 이 튜토리얼의 범위를 벗어납니다. Robot Kyle.fbx 파일은 \Assets\Photon Unity Networking\Demos\Shared Assets/ 에 위치하고 있습니다.

kyle robot fbx asset
Kyle Robot Fbx 에셋
다음은 플레이어로 `Kyle Robot.fbx` 를 사용하기 위한 방법 입니다:
  1. "Project Browser" 에서 컨텐츠를 관리 해주는 "Resources" 와 동일한 이름의 폴더를 생성합니다. 'DemoAnimator_tutorial/Resources/' 이런 식으로 말이죠.
  2. 새로운 빈 씬을 생성하고 'DemoAnimator_tutorial/Resources/' 에 Kyle Test 로 저장 합니다. "Kyle Test" 씬의 목적은 프리팹 생성과 설정을 하기 위한 것 입니다. 이 것이 완료되었으면 씬을 삭제할 수 있습니다.
  3. "씬 Hierarchy" 에서 Robot Kyle 을 드래그앤 드롭 합니다.
  4. Hierarchy 에서 방금 생성한 게임 오브젝트를 My Robot Kyle로 이름을 변경 합니다.
  5. My Robot Kyle\PunBasics_tutorial\Resources/ 로 드래그앤 드롭 합니다.

이제 Kyle Robot 에셋을 기반으로 한 [프리팹]을 생성 했고 Kyle Test 씬의 Hierarchy 에는 이 프리팹 인스턴스가 있습니다. 이제 이것으로 작업을 진행할 수 있습니다.

CharacterController

  1. 이제 Hierarchy 에 있는 My Kyle Robot 인스턴스에게 CharacterController Component 를 추가 하겠습니다. [프리팹] 자체에 직접 수행 할 수 도 있지만 이렇게 하는 것이 좀 더 빠른 방식입니다.

    이 컴포넌트는 유니티가 제공하는 아주 편리한 표준 에셋으로 애니메이터를 사용하고 있는 캐릭터를 더 빠르게 만들 수 있도록 하기 때문에, 이러한 유니티의 기능을 이용 하도록 하겠습니다.
  2. My Kyle Robot 을 더블 클릭하여 Scene View 에서 크게 보이도록 합니다. 발 끝 중심에 "Capsule Collider" 가 있는 것을 주목합니다; "Capsule Collider" 가 캐릭터에 맞게 위치해 있어야 합니다.

  3. CharacterController 컴포넌트에서 Center.y 프로퍼티를 1로 변경 합니다( Height 프로퍼티의 반).

    kyle robot capsule collider
    Kyle Robot Capsule Collider
  4. "Apply" 를 눌러 변경사항을 적용 하세요.

매우 중요한 설정 단계로 My Kyle Robot 프리팹의 인스턴스를 편집 했고 하나의 인스턴스가 아닌 모든 인스턴스에 변경사항이 적용 되어야 하기 때문에, "Apply" 를 누르는 것 입니다.

애니메이터 설정

애니메이터 컨트롤로 지정하기

Kyle Robot Fbx 에셋은 Animator Graph 로 제어 되어야 합니다. 이 튜토리얼에서는 이 그래프에 대한 생성 방법을 다루지는 않습니다. 그리고 이에 대한 컨트롤러는 프로젝트 에셋내의 \Photon Unity Networking\Demos\PunBasics\Animator/ 에서 Kyle Robot 이라고 하는 컨트롤러를 제공하고 있습니다.

animatorcontroller
AnimatorController

[프리팹]에 Kyle Robot 컨트롤러를 할당 해 주기 위해서는 애니메이터 컴포넌트의 Controller 프로퍼티를 Kyle Robot 으로 설정 합니다.

assigning animatorcontroller
AnimatorController 지정하기

My Kyle Robot의 인스턴스에서 진행 했다면 "Apply" 버튼을 눌러 변경사항이 [프리팹]에 적용 될 수 있도록 해야 한다는 것을 잊지 마세요.

컨트롤러 파라미터 작업하기

Animator Controller에서 이해해야 하는 중요한 특징은 Animation Parameters 입니다. 우리는 이것을 애니메이션을 제어하기 위해 사용하고 있습니다. 우리의 경우에서는 Speed, Direction, Jump, Hi와 같은 파라미터들이 있습니다.

Animator Component 의 훌륭한 기능들 중의 하나는 애니메이션에 기반하여 캐릭터를 실제로 움직 일 수 있다는 것입니다. 이 기능을 Root Motion 이라고 부르며 Animator Component 에 디폴트 값이 true 인 Apply Root Motion 프로퍼티가 있습니다.

이제 사실상 걷고 있는 캐릭터가 있기 때문에 Speed Animation Parameter를 양수 값으로 설정하여 앞으로 걸어가게 할 필요가 있습니다. 시작 해 봅시다!

Animator Manager 스크립트

사용자 입력값에 따라 캐릭터를 제어 할 수 있는 새로운 스크립트를 생성 하도록 하겠습니다.

  1. PlayerAnimatorManager 라고 하는 이름의 새로운 C# 스크립트를 생성 합니다.

  2. 이 스크립트를 My Robot Kyle Prefab에 붙입니다.

  3. 아래와 같이 Com.MyCompany.MyGame 네임스페이스로 클래스를 둘러 쌉니다.

  4. 명확하게 하기 위해 MonoBehaviour CallBacks region 으로 Start()Update() 를 둘러 쌉니다.

    C#

    using UnityEngine;
    using System.Collections;
    
    namespace Com.MyCompany.MyGame
    {
        public class PlayerAnimatorManager : MonoBehaviour
        {
            #region MonoBehaviour Callbacks
    
            // Use this for initialization
            void Start()
            {
            }
    
            // Update is called once per frame
            void Update()
            {
            }
    
            #endregion
        }
    }
    
  5. PlayerAnimatorManager 스크립트를 저장합니다.

Animator Manager: 속도 제어

우선 첫 번째로 코딩해야 할 것은 Animator Component 를 얻어 제어 할 수 있도록 하는 것 입니다.

  1. PlayerAnimatorManager 스크립트를 편집하고 있는지 확인 해 주세요.

  2. Animator 타입의 private 변수 animator 를 생성 합니다.

  3. Start() 메소드에서 Animator 컴포넌트를 이 변수에 저장 합니다.

    C#

        private Animator animator;
        // Use this for initialization
        void Start() 
        {
            animator = GetComponent<Animator>();
            if (!animator)
            {
                Debug.LogError("PlayerAnimatorManager is Missing Animator Component", this);
            }
        }
    

    우리는 Animator Component 가 필요하기 때문에 만약 이 컴포넌트를 얻지 못했다면 개발자가 바로 알 수 있도록 에러를 로그한다는 것을 주목 하세요. 다른 사람이 사용 할 수도 있기 때문에 항상 이런 방식으로 코딩 합니다 :) 지루하긴 하지만 장기적인 관점에서 그만큼 가치가 있습니다.

  4. 이제 [사용자 입력]을 기다리고 Speed Animation Parameter를 제어 합니다. 그리고 PlayerAnimatorManager 스크립트를 저장 합니다.

    C#

    // Update is called once per frame
    void Update() 
    {
        if (!animator)
        {
            return;
        }
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        if (v < 0)
        {
            v = 0;
        }
        animator.SetFloat("Speed", h * h + v * v);
    }
    
  5. PlayerAnimatorManager 스크립트를 저장합니다.

스크립트가 어떤 기능을 하는 지 알아 보도록 하겠습니다:

게임에서는 뒤로 가는 것을 허용하지 않기 때문에 v 가 0 보다 작은 값인지 확인 하세요. 사용자가 '아래 화살표'' 키 또는 's' 키 (Vertical 축에 대한 기본 설정) 를 입력하면 허용하지 않고 값을 0 으로 설정합니다.

두 입력값을 제곱하고 있다는 것을 알아 챘을 것 입니다. 왜 그럴까요? 항상 양의 절대 값이고 easing을 추가하기 때문 입니다. 절묘한 트릭입니다. Mathf.Abs() 도 역시 사용할 수 있으며 잘 동작 합니다.

Speed 를 제어하기 위한 두 개의 입력을 추가하여 우측 입력의 왼쪽을 누를 때 속도를 얻게 될 것 입니다.

물론 이러한 모든 것은 우리 캐릭터 디자인 특성이고 게임 로직에 의존 하고 있으며 캐릭터가 제자리에서 회전 하기를 원할 수 도 있고 또는 뒤로 갈 수 있는 능력을 부여 하길 원할 수 도 있습니다. 즉, [Animation 파라미터] 들을 제어하는 것은 게임마다 다르게 됩니다.

테스트, 테스트, 1 2 3...

지금까지 우리가 진행해 왔던 것을 검증 해 보도록 하겠습니다. Kyle Test 씬이 열려져 있는지 확인 해 주세요. 현재 이 씬에서 하나의 카메라와 Kyle Robot 인스턴스가 있으며 로봇이 서 있을 땅이 없습니다. 지금 실행 시키면 Kyle 로봇은 아래로 떨어질 것 입니다. 또한 우리는 씬에 빛과 같은 것을 고려하지 않을 것이며, 캐릭터와 스크립트가 잘 동작 하는지 테스트 하고 검증만 할 것 입니다.

  1. 씬에 "큐브"를 추가 합니다. 큐브는 디폴트로 "Box Collider" 가 있으므로 바닥으로 사용하기 좋습니다.
  2. 큐브의 높이가 1이기 때문에 Position 을 0,-0.5,0 으로 설정 합니다. 큐브의 윗면을 바닥으로 사용할 것 입니다.
  3. 큐브의 Scale 을 30,1,30 으로 설정 합니다. 이제 우리는 실험 장소를 만들었습니다.
  4. 카메라를 선택 하여 보기 좋게 위치를 움직여 줍니다. 편리한 트릭은 씬 뷰를 보기 좋게 표시한 후 카메라를 선택하고 메뉴에서 "GameObject/Align With View" 를 선택하면 카메라가 신 뷰에 맞춰 지게 됩니다.
  5. 마지막 단계에서 My Robot Kyle 을 y 축으로 0.1 증가 시켜 줍니다. 이렇게 하지 않으면 충돌이 감지 되지 않아 캐릭터가 바닥 아래로 떨어지게 됩니다. 따라서 접촉을 만들어 시뮬레이션 하기 위하여 두 collider 간에 물리적인 공간이 두어야 합니다.
  6. 씬을 플레이하고 '위 화살표'' 또는 'a' 키를 누르면 캐릭터가 걷게 됩니다! 검증을 위해 모든 키를 확인 해 볼 수 있습니다.

좋습니다. 하지만 앞으로 해야 할 일이 많습니다. 카메라가 따라가야 하고, 그리고 아직 회전 하지도 못했습니다...

지금 카메라에 대해서 계속 작업 하길 원하면 전용 섹션으로 가서 이 페이지의 남은 부분에서는 Animator 제어와 회전의 구현을 끝낼 것 입니다.

Animator Manager 스크립트: 방향 제어

회전을 제어하는 것은 좀 더 복잡합니다. 왼쪽 또는 오른쪽 키를 누를 때 캐릭터가 회전을 갑자기 하는 것 보다는 완만하고 부드럽게 회전 하는 것이 더 좋습니다. 다행히도 Animation Parameter를 감쇠를 위해서 사용 될 수 있습니다.

  1. PlayerAnimatorManager 스크립트를 편집하고 있는지 확인 하세요.

  2. public float 변수 directionDampTime 를 "Private Fields" 영역 안에 생성 하세요.

    C#

    #region Private Fields
    
    [SerializeField]
    private float directionDampTime = 0.25f;
    
    #endregion
    
  3. Update() 함수의 마지막에 다음을 추가 합니다:

    C#

    animator.SetFloat("Direction", h, directionDampTime, Time.deltaTime);
    

    우리는 직관적으로 animator.SetFloat() 에 다른 특징이 있다는 것을 알아 챘습니다. Speed 를 제어하는 데 사용한 것은 직관적 이었으나 여기에서는 두 개의 파라미터를 더 받고 있습니다. 하나는 damping time 이고 하나는 deltaTime 입니다. Damping time 은 일리가 있습니다: 이것은 원하는 값까지 도달하는데 걸리는 시간입니다. 하지만 deltaTime 은 어떨까요? Update() 가 프레임율에 의존적이기 떄문에 프레임율 독립적으로 코드를 작성할 수 있도록 해 주는 것 입니다. 우리는 deltaTime 을 카운터로 사용할 필요가 있습니다. 이 주제에 대해서 가능한 많이 읽어 보고 웹 검색에서도 많이 찾을 수 있을 것 입니다. 개념에 대해서 이해 한 후에 수 많은 유니티 기능을 작성할 수 있을 것이고 애니메이션과 일관성 있는 제어를 시간에 구애 받지 않고 할 수 있을 것 입니다.

  4. PlayerAnimatorManager 스크립트를 저장 합니다.

  5. 씬을 플레이하고 화살표키를 사용하여 캐릭터가 걷고 회전을 잘 하는지 살펴 봅니다.

  6. directionDampTime 효과를 테스트 해 보세요: 1을 넣어 보고 확인 해보고 5 를 넣어서 확인하여 최대 회전까지 얼마나 걸리는지 학인해봅니다. 회전 반경이 DirectionDampTime 에 대해서 증가되는 것을 볼 수 있을 것 입니다.

Animator Manager 스크립트: 점프하기

두 개의 팩터 때문에 점프에 대해서 좀 더 많은 작업을 해 주어야 합니다. 첫 번째는 달리고 있지 않으면 점프를 하면 안되며, 두 번째는 반복하여 점프를 해서는 안됩니다.

  1. PlayerAnimatorManager 스크립트를 편집하고 있는지 확인 해 주세요.

  2. Update() 메소드에서 사용자 입력을 얻는 부분 앞에 삽입 합니다.

    C#

    // deal with Jumping
    AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);          
    // only allow jumping if we are running.
    if (stateInfo.IsName("Base Layer.Run"))
    {
        // When using trigger parameter
        if (Input.GetButtonDown("Fire2")) 
        {
            animator.SetTrigger("Jump");
        }
    }
    
  3. PlayerAnimatorManager 스크립트를 저장 합니다.

  4. 테스트 합니다. 달리기를 하고 'alt' 키를 누르면 Kyle 은 점프를 해야 합니다.

좋습니다. 우선 첫 번째로 이해해야 하는 것은 애니메이터가 달리고 있는지에 대한 것을 어떻게 판단하는지에 대한 것입니다. stateInfo.IsName("Base Layer.Run") 을 이용해 판단 할 것 입니다. 애니메이터의 현재 활성 상태가 Run 인지에 대하여 문의 합니다. Base Layer 안에 Run 상태가 있으므로 Base Layer 를 추가 해야 합니다.

Run 상태에 있다면 Fire2 Input 을 감지하며 필요시에 Jump 를 트리거 발생시킵니다.

지금까지 했던 PlayerAnimatorManager 의 전체 소스 입니다:

C#

using UnityEngine;
using System.Collections;

namespace Com.MyCompany.MyGame
{
    public class PlayerAnimatorManager : MonoBehaviour 
    {
        #region Private Fields
        
        [SerializeField]
        private float directionDampTime = .25f;
        private Animator animator;
        
        #endregion
        
        #region MonoBehaviour CallBacks
        
        // Use this for initialization
        void Start() 
        {
            animator = GetComponent<Animator>();
            if (!animator)
            {
                Debug.LogError("PlayerAnimatorManager is Missing Animator Component", this);
            }

        }
        
        // Update is called once per frame
        void Update() 
        {
            if (!animator)
            {
                return;
            }
            // deal with Jumping
            AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);          
            // only allow jumping if we are running.
            if (stateInfo.IsName("Base Layer.Run"))
            {
                // When using trigger parameter
                if (Input.GetButtonDown("Fire2")) 
                {
                    animator.SetTrigger("Jump");
                }
            }
            float h = Input.GetAxis("Horizontal");
            float v = Input.GetAxis("Vertical");
            if (v < 0)
            {
                v = 0;
            }
            animator.SetFloat("Speed", h * h + v * v);
            animator.SetFloat("Direction", h, directionDampTime, Time.deltaTime);
        }
        
        #endregion
    }
}

씬에 비하면 그리 많은 코드는 아닙니다. 이제 카메라가 따라 다니도록 하여 세계가 우리 주위에 있는 것처럼 하기 위하여 이제 카메라에 대한 처리를 하도록 하겠습니다.

Camera 설정

이 장에서는 CameraWork 스크립트를 사용할 것 입니다. CameraWork 를 처음부터 작성하고 싶으면 다음 파트로 가서 학습을 완료 하신 후 다시 와 주시기 바랍니다.

  1. My Kyle Robot 프리팹에 CameraWork 컴포넌트를 추가 합니다.
  2. Follow on Start 프로퍼티를 켜 주어 처음부터 카메라가 따라다니도록 만들어 줍니다. 네트워크 구현을 시작 할 때는 off 로 꺼두도록 할 것 입니다.
  3. Center Offset 프로퍼티를 0,4,0 로 설정하여 카메라를 좀 더 높이 두게 되어 좀 더 나은 뷰를 제공 해 줍니다. 카메라가 플레이어를 직접 바라볼 때 땅만 보이는 것 보다 더 나은 관점을 볼 수 있을 것 입니다.
  4. Kyle Test 씬을 실행 하고 캐릭터를 움직여 카메라가 잘 따라가고 있는지 확인 해 보세요.

빔 설정

로봇 캐릭터는 아직 무기가 없습니다. 눈에서 발사되는 레이저 빔을 생성 하도록 하겠습니다.

빔 모델 추가하기

단순하게 진행하기 위해서 큐브 하나를 이용하여 매우 가늘고 길게 만들 것 입니다. 이 작업을 빠르게 할 수 있는 트릭이 있습니다: 머리의 자식으로 큐브를 추가 하지말고 자신이 생성하고 이동하고 확대하여 머리에 붙입니다. 이렇게 하면 눈에 맞추어 광선의 적절한 회전 값을 추측하지 않아도 됩니다.

또 다른 중요한 트릭은 두 빔에 대해 하나의 Collider 만을 사용하는 것 입니다. 물리엔진이 잘 동작 하게 하기 위함 입니다. 가느다란 collider 는 신뢰할 수 없기 때문에 좋은 생각은 아닙니다. 따라서 목표물을 맟추었는지 신뢰할 수 있게 큰 박스 collider 를 만들 것 입니다.

  1. Kyle test 씬을 오픈 합니다.
  2. 씬에 큐브를 추가하고 Beam Left로 이름을 부여 합니다.
  3. 긴 광선 처럼 보이오록 변경 하고 왼쪽 눈과 맟추어 위치가 적절한지 위치를 조정합니다.
  4. Hierarchy 에서 My Kyle Robot 인스턴스를 선택 하세요.
  5. Head child 로 위치 시킵니다.
kyle robot head hierarchy
Kyle Robot Head Hierarchy
  1. 자식(child)으로 빈 게임오브젝트를 생성 하여 Head 게임 오브젝트의 자식으로 이름은 Beams로 이름을 부여 합니다.
  2. Beam LeftBeams 안으로 드래그앤 드롭 합니다.
  3. Beams Left 복사하여 Beams Right 로 이름을 부여 합니다.
  4. 오른쪽 눈에 맞춰 질 수 있도록 위치를 이동 시켜 주시기 바랍니다.
  5. Beams Right 에서 Box Collider 컴포넌트를 제거 합니다.
  6. Beams Left 의 Box Collider 를 중앙으로 조정하고 두 빔을 감싸 질 수 있도록 크기를 조정 합니다.
  7. 빔이 충돌이 아닌 플레이어에 부딫히는 것만 인지할 수 있도록 하기 위해 Beams Left 의 "Box Collider" IsTrigger 프로퍼티를 True로 켜줍니다.
  8. 새로운 Material 을 생성하고 Red Beam으로 이름을 부여 합니다.'DemoAnimator_tutorial/Scenes/' 안에 저장 합니다.
  9. 두 개의 빔에 Red Beam 을 할당 합니다.
  10. 프리팹 변경사항을 적용 합니다.

노트: 레이저 빔은 캐릭터의 플레이어 체력을 손상시키지 않기 위해 콜라이더의 외부에 있어야 합니다.

이제 다음과 같이 보여야 할 것 입니다.

kyle robot beams
Kyle Robot 헤드 빔
kyle robot updated head hierarchy
Kyle Robot Updated Head Hierarchy

사용자 입력으로 빔 제어하기

좋습니다. 이제 우리는 빔을 갖게 되었습니다. Fire1 입력을 받아 발사 할 수 있도록 해보겠습니다.

PlayerManager라고 하는 가진 새로운 C# 스크립트를 생성 합니다. 빔이 작동하기 위한 초기 버전의 전체 소스 코드 입니다.

C#

using UnityEngine;
using UnityEngine.EventSystems;

using System.Collections;

namespace Com.MyCompany.MyGame
{
    /// <summary>
    /// Player manager. 
    /// Handles fire Input and Beams.
    /// </summary>
    public class PlayerManager : MonoBehaviour 
    {
        #region Private Fields
        
        [Tooltip("The Beams GameObject to control")]
        [SerializeField]
        private GameObject beams;
        //True, when the user is firing
        bool IsFiring;
        #endregion
        
        #region MonoBehaviour CallBacks
        
        /// <summary>
        /// MonoBehaviour method called on GameObject by Unity during early initialization phase.
        /// </summary>
        void Awake()
        {
            if (beams == null)
            {
                Debug.LogError("<Color=Red><a>Missing</a></Color> Beams Reference.", this);
            }
            else
            {
                beams.SetActive(false);
            }
        }
        
        /// <summary>
        /// MonoBehaviour method called on GameObject by Unity on every frame.
        /// </summary>
        void Update()
        {
            ProcessInputs ();
            // trigger Beams active state 
            if (beams != null && IsFiring != beams.activeInHierarchy) 
            {
                beams.SetActive(IsFiring);
            }
        }
        
        #endregion
        
        #region Custom
        
        /// <summary>
        /// Processes the inputs. Maintain a flag representing when the user is pressing Fire.
        /// </summary>
        void ProcessInputs()
        {
            if (Input.GetButtonDown("Fire1")) 
            {
                if (!IsFiring)
                {
                    IsFiring = true;
                }
            }
            if (Input.GetButtonUp("Fire1")) 
            {
                if (IsFiring)
                {
                    IsFiring = false;
                }
            }
        }
        
        #endregion
    }
}

이 단계 스크립트에서 중요한 점은 빔을 활성화거나 비활성화 하는 것 입니다. 활성화 되었을 때 빔은 다른 모델과 충돌을 효과적으로 트리거 해 줄 것 입니다. 그리고 이러한 트리거를 각 캐릭터의 체력에 영향을 주도록 할 것 입니다.

My Kyle Robot 프리팹의 Hierarchy 내부의 게임오브젝트를 추출하여 참조할 public 프로퍼티 Beams 를 노출 시켜 놓았습니다. Beams를 연결시키기 위해 필요한 작업에 대해서 살펴 보도록 하겠습니다. 프리팹이 에셋 브라우저 안에 있어 애매모호 하기 때문에 프리팹은 첫 번째 child 만 노출 합니다. 그리고 우리 Beams 는 프리팹 Hierarchy 내부 안에 깊숙히 감추어져 있기 때문에 신의 인스턴스로 부터 가져와야 합니다. 그리고 프리팹 자체로 되돌아와 적용합니다.

  1. Kyle test 씬을 오픈 합니다.
  2. 씬 Hierarchy 에서 My Kyle Robot을 선택 합니다.
  3. My Kyle RobotPlayerManager 컴포넌트를 추가 합니다.
  4. 인스펙터에서 PlayerManager Beams 프로퍼티에 My Kyle Robot/Root/Ribs/Neck/Head/Beams 를 드래그앤 드롭 합니다.
  5. 프리팹으로 다시 돌아와서 변경사항을 적용 시켜 줍니다.

플레이 하여 Fire1 Input(디폴트로 왼쪽 마우스클릭 또는 왼쪽 컨트롤 키 입니다.)를 누르면 빔,이 나타날 것 이고 떼면 즉시 사라지게 됩니다.

체력 설정

이제 플레이어가 빔에 맞았을 때 체력이 감소하는 아주 간단한 체력 시스템을 구현하도록 하겠습니다. 총알이 아니고 에너지가 일정한 흐름이므로 체력 소진에 대해 두 가지의 방식이 있습니다. 광선에 맞았을 때와 광선을 맞는 전체 시간 입니다.

  1. Player Manager 스크립트를 오픈 합니다.

  2. PhotonView 컴포넌트로 노출시키기 위해 PlayerManagerMonoBehaviourPunCallbacks 로 만들어 줍니다.

    C#

    using Photon.Pun;
    public class PlayerManager : MonoBehaviourPunCallbacks 
    {
    
  3. Public Fields 에서 public Health 프로퍼티를 추가합니다.

    C#

    [Tooltip("The current Health of our player")]
    public float Health = 1f;
    
  4. MonoBehaviour CallBacks 영역에 다음의 두 개의 메소드를 추가 합니다. 그리고 PlayerManager 스크립트를 저장 합니다.

    C#

    /// <summary>
    /// MonoBehaviour method called when the Collider 'other' enters the trigger.
    /// Affect Health of the Player if the collider is a beam
    /// Note: when jumping and firing at the same, you'll find that the player's own beam intersects with itself
    /// One could move the collider further away to prevent this or check if the beam belongs to the player.
    /// </summary>
    void OnTriggerEnter(Collider other) 
    {
        if (!photonView.IsMine)
        {
            return;
        }
        // We are only interested in Beamers
        // we should be using tags but for the sake of distribution, let's simply check by name.
        if (!other.name.Contains("Beam"))
        {
            return;
        }
        Health -= 0.1f;
    }
    /// <summary>
    /// MonoBehaviour method called once per frame for every Collider 'other' that is touching the trigger.
    /// We're going to affect health while the beams are touching the player
    /// </summary>
    /// <param name="other">Other.</param>
    void OnTriggerStay(Collider other) 
    {
        // we dont' do anything if we are not the local player.
        if (! photonView.IsMine) 
        {
            return;
        }
        // We are only interested in Beamers
        // we should be using tags but for the sake of distribution, let's simply check by name.
        if (!other.name.Contains("Beam"))
        {
            return;
        }
        // we slowly affect health when beam is constantly hitting us, so player has to move to prevent death.
        Health -= 0.1f*Time.deltaTime; 
    }
    
  5. PlayerManager 스크립트를 저장합니다.

우선, 두 개의 메소드는 거의 동일 하며, 차이점이 있다면 TriggerStay 동안 감소 속도를 프레임율에 의존하지 않고 DeltaTime을 사용하여 체력을 감소 시킨다는 것 입니다. 이것은 아주 중요한 개념이며 일반적으로 애니메이션에 적용됩니다. 여기에서도 모든 단말에 대해서 예측할 수 있는 체력감소를 원하기 때문에 필요 합니다. 빠른 컴퓨터에서 체력이 빠르게 감소된다면 것은 공평하지 않겠지요 :)

Time.deltaTime 을 통하여 일관성을 보장 해 줍니다. 만약 Time.deltaTime 에 대해서 의문이 있다면 유니티 커뮤니티에서 찾아 보시고 완전히 이해하신 다음에 여기로 다시 오시기 바랍니다. 아주 기본적인 사항입니다.

두 번째로 중요한 개념은 이미 이해하고 있으셔야 하는 것으로 로컬 플레이어의 체력에만 영향을 주게 되는 것으로 PhotonView 가 Mine 이 아니면 메소드 초반부에서 빠져나왔기 때문입니다.

마지막으로 객체가 우리를 친 것이 빔일 때만 체력에 영향 받기를 원하기 때문에 빔을 사용하여 체크할 것 입니다.

쉬운 디버깅을 위해 UI 가 구축 되기전에 Health를 public float 로 만들어 쉽게 값을 체크할 수 있도록 했습니다.

좋습니다. 모든 것이 잘되었나요? 음... 체력 시스템은 체력이 0 이 되어 플레이어의 게임 오버 상태 고려 없이는 완전한 것이 아닙니다. 이제 해 보죠.

게임종료를 위한 체력 체크

단순함을 유지 하기 위해서 플레이어 체력이 0 으로 간단하게 룸을 떠나고 GameManager 스크립트에 룸을 떠나는 메소드를 이미 생성 해 놓았다는 것을 기억 하실 수 있을 것 입니다. 동일한 기능을 두번 코딩 할 필요 없이 이 메소드를 재 사용할 수 있으면 정말 좋을 것입니다. 동일한 결과를 내는 중복 코드는 정말 피해야 하는 사항입니다. 여기에서 매우 편리한 프로그래밍 개념인 "싱글톤(Singleton)" 에 대한 소개를 할 적절한 시기 입니다. 이 주제만으로도 강의를 진행 할 수 있겠지만 "Singleton" 에 대한 최소한의 구현만 할 예정 입니다. 싱글톤을 이해 한다는 것은 유니티 컨택스트에서의 변형과 강력한 기능을 구축하는데 매우 중요하며 많은 어려움을 극복할 수 있게 해줄것입니다. 따라서 싱글톤을 학습하는데 이 튜토리얼과는 별도로 하는 것을 주저 하지 마시기 바랍니다.

  1. GameManager 스크립트를 오픈 합니다.

  2. 이 변수를 public 필드 영역에 추가합니다.

    C#

    public static GameManager Instance;
    
  3. 아래와 같이 Start() 메소드를 추가합니다.

    C#

    void Start()
    {
        Instance = this;
    }
    
  4. GameManager 스크립트를 저장합니다

인스턴스 변수를 [static] 키워드로 했다는 것을 주목 하세요. 이 변수는 GameManager 의 인스턴스 없이 사용 할 수 있도록 합니다. 따라서 코드의 어떤 곳에서도 GameManager.Instance.xxx() 를 사용 할 수 있게 됩니다. 정말 실용적입니다! 로직에서 어떻게 게임 오버에 대한 것을 처리 했는지 살펴 보겠습니다.

  1. PlayerManager 스크립트를 오픈 합니다.

  2. Update() 메소드 안에서 ProcessInputs 이후에 다음을 추가하고 PlayerManager 스크립트를 저장 합니다.

    C#

    if (photonView.IsMine)
    {
        ProcessInputs();
        if (Health <= 0f)
        {
            GameManager.Instance.LeaveRoom();
        }
    }
    
  3. PlayerManager 스크립트를 저장합니다.

  • 노트: 레이저 빔의 강도가 다르기 때문에 손상 정도가 달라 체력이 음수로 갈 수 있다는 것을 고려했다는 것을 주목 하세요.
  • 노트: 컴포넌트등 아무것도 없이 GameManager 인스턴스의 LeaveRoom() public 메소드에 간다는 것을 주목 하세요. GameManager 컴포넌트가 현재 씬의 게임오브젝트에 있다는 사실에 의존하고 있습니다.

좋아요. 이제 우리는 네트워킹에 빠져 보도록 하겠습니다!

Next Part.
Previous Part.

Back to top