Unity 강의/Unity Course(2) - 절대강좌! 유니티

[Unity Course 2] 15. 포톤 클라우드를 활용한 네트워크 게임 3

첨부엉. 2024. 7. 1. 21:51
반응형
위키북스 출판사 이재현 저자님 의 '절대강좌! 유니티' 책을 참고하여 필기한 내용입니다.

주인공 캐릭터의 네트워크 기능 구현

Player 오브젝트에 Photon View 컴포넌트 추가

 

PhotonView 컴포넌트

네트워크상에 접속한 플레이어 간의 데이터를 송수신하는 통신 모듈

동일한 룸에 입장한 다른 플레이어에게 자신의 위치와 회전 정보를 동기화시키고 특정 데이터를 송수신 하기위한 컴포넌트

 

Synchronization 은 동기화 방식을 의미 

속성 설명
None 동기화 처리를 하지 않는다. RPC 호출만을 위한 PhotonView에 적합
Reliable Delta Compress 마지막 데이터가 변경되지 않았을 대 데이터를 송신하지 않음
Unreliable 송신한 패킷의 수신 여부를 확인하지 않음
Unreliable On Change Unreliable과 동일하고 변경사항이 발생했을 때만 송

Observed Components 는 Photon View 컴포넌트가 관찰해 데이터를 송수신할 대상을 등록하는 속성

데이터를 송수신하기 위한 컴포넌트를 추가하는 것, 기본 설정은 Auto Find All으로 자동으로 검색하여 등록 쉬움

 

주인공은 같은 룸에 입장한 다른 네트워크 유저에게 자신의 위치와 회전값, 애니메이션에 대한 정보를 동기화 해야됨

  • Photon Transform View, Photon Animator View 컴포넌트를 사용하는 방식
  • OnPhotonSerializeView 콜백 함수를 사용하는 방식

첫 번째 방식은 가장 쉽게 네트워크를 동기화 가능하지만 세밀한 조정이 불가함, 네트워크 레이턴시가 발생했을 때 위치 및 회전값을 수동으로 보간할 수 없음

두번째 방식은 포톤에서 제공하는 컴포넌트를 사용하지 않고 OnPhotonSerializeView 콜백 함수를 통해 데이터 송수신을 수동으로 관리하는 방식(네트워크 레이턴시에 대응할 수 있는 코드를 작성해 좀 더 유연한 로직을 구현 가능)

Photon Transform View, Photon Animator View

Photon Transform View, Photon Animator View 컴포넌트를 추가한다.

이 두 컴포넌트는 어떤 데이터를 동기화할 것인지를 지정하고 동기화 속도를 설정하는 것,

실제로 데이터 통신을 하는 역할은 PhotonView 에서 처리함

 

Photon Transform View 컴포넌트의 Use Local 속성은 동기화하는 데이터가 로컬 기준인지를 결정

Photon Animator View 는 Animator 컴포넌트의 속성을 동기화 하는 컴포넌트로 추가할 때 Animator컴포넌트의 정보를 읽어서 Layer 와 Parameter 값을 자동으로 설정함

동기화할 Layer와 파라미터 값의 동기화 속도를 설정해야됨

네트워크 동기화의 정확성을 고려해 Discrete(이산)와 Continues(연속) 중 하나를 선택

모두 discrete로 설정

네트워크 환경에서 생성하기 위한 준비

하이러키 뷰의 Player 새로 생성한 Resources 폴더에 넣어서 프리팹을 새로 생성하고 Prefabs 폴더 내의 Player는 삭제한다.

 

포톤에서 네트워크로 동기화할 대상은 PhotonNetwork.Instantiate 함수를 사용하여 모두 Resources 폴더에 위치해야됨

 

PhotonManager 스크립트의 일부분을 아래와 같이 수정한다.

public override void OnJoinedRoom()
{
    Debug.Log($"PhotonNetwork.InRoom = {PhotonNetwork.InRoom}");
    Debug.Log($"Player Count = {PhotonNetwork.CurrentRoom.PlayerCount}");

    foreach(var player in PhotonNetwork.CurrentRoom.Players)
    {
        Debug.Log($"{player.Value.NickName},{player.Value.ActorNumber}");
    }

    // 출현 위치 정보를 배열에 저장
    Transform[] points = GameObject.Find("SpawnPointGroup").GetComponentsInChildren<Transform>();
    int idx = Random.Range(1, points.Length);

    // 네트워크상에 캐릭터 생성
    PhotonNetwork.Instantiate("Player", points[idx].position, points[idx].rotation, 0);
}

네트워크 객체를 생성하기 위해서는 반드시 PhotonNetwork.Instantiate 함수를 사용해야됨

 

실행했을 때 clone 이 제대로 생성됐는지 확인

런타임 시 동적으로 생성 된 Player이기 때문에 시네머신 카메라의 Follow 와 LookAt 속성이 모두 끊겨있어 따라가지 못함

 

Movement.cs 를 다음과 같이 수정

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using Cinemachine;

public class Movement : MonoBehaviour
{
    // 컴포넌트 캐시 처리를 위한 변수
    private CharacterController controller;
    private new Transform transform;
    private Animator animator;
    private new Camera camera;

    // 가상의 Plane에 레이캐스팅하기 위한 변수
    private Plane plane;
    private Ray ray;
    private Vector3 hitPoint;

    // PhotonView 컴포넌트 캐시 처리를 위한 변수
    private PhotonView pv;

    // PhotonView 컴포너느 캐시 처리를 위한 변수
    private CinemachineVirtualCamera virtualCamera;

    // 이동 속도
    public float moveSpeed = 10.0f;

    private void Start()
    {
        controller = GetComponent<CharacterController>();
        transform = GetComponent<Transform>();
        animator = GetComponent<Animator>();
        camera = Camera.main;

        pv = GetComponent<PhotonView>();
        virtualCamera = GameObject.FindObjectOfType<CinemachineVirtualCamera>();

        // PhotonView 가 자신일 것일 경우 시네머신 가상카메라를 연결
        if(pv.IsMine)
        {
            virtualCamera.Follow = transform;
            virtualCamera.LookAt = transform;
        }

        // 가상의 바닥을 주인공 위치를 기준으로 생성
        plane = new Plane(transform.up, transform.position);
    }

    private void Update()
    {
        // 자신이 생성한 네트워크 객체만 컨트롤
        if(pv.IsMine)
        {
            Move();
            Turn();
        }
    }

    // 키보드 입력값 연결
    float h => Input.GetAxis("Horizontal");
    float v => Input.GetAxis("Vertical");

    // 이동 처리하는 함수
    void Move()
    {
        Vector3 cameraForward = camera.transform.forward;
        Vector3 cameraRight = camera.transform.right;
        cameraForward.y = 0.0f;
        cameraRight.y = 0.0f;

        // 이동할 방향 벡터 계싼
        Vector3 moveDir = (cameraForward * v) + (cameraRight * h);
        moveDir.Set(moveDir.x, 0.0f, moveDir.z);

        // 주인공 캐릭터 이동 처리
        controller.SimpleMove(moveDir * moveSpeed);

        // 주인공 캐릭터의 애니메이션 처리
        float forward = Vector3.Dot(moveDir, transform.forward);
        float strafe = Vector3.Dot(moveDir, transform.right);

        animator.SetFloat("Forward", forward);
        animator.SetFloat("Strafe", strafe);
    }
    // 회전 처리하는 함수
    void Turn()
    {
        // 마우스의 2차원 좌푯값을 이용해 3차원 광선을 생성
        ray = camera.ScreenPointToRay(Input.mousePosition);

        float enter = 0.0f;

        // 가상의 바닥에 레이를 발사해 충돌한 지점의 거리를 enter 변수로 반환
        plane.Raycast(ray, out enter);
        // 가상의 바닥에 레이가 충돌한 좌푯값 추출
        hitPoint = ray.GetPoint(enter);

        // 회전해야 할 방향의 벡터를 계산
        Vector3 lookDir = hitPoint - transform.position;
        lookDir.y = 0;
        // 주인공 캐릭터의 회전값 지정
        transform.localRotation = Quaternion.LookRotation(lookDir);
    }
}

 

룸에 입장한 후 PhotonNetwork.Instanciate 로 생성한 네트워크 객체는 자신의 캐릭터와 네트워크를 통해 동일한 룸에 입장한 다른 네트워크 유저를 PhotonView.IsMine 속성으로 구별 가능

PhotonView .IsMine 속성이 true이면 로컬 유저를 의미

자신의 캐릭터이며 시네머신 가상카메라와 연결

 

8장에서 했던 SqawnPointGroup 똑같이 만들어주기

동시 접속을 위한 테스트 환경

edit - project setting 

Resolution and Presentation 의 Fullscreen Mode  속성을 Windowed로 변경한다.

File - Build Settings 선택하고 Build And Run 버튼을 클릭하고 저장하기 경로는 아래 사진과 같이

Builds 라는 폴더를 생성하고 해당 폴더에 저장

 

자동으로 실행된 게임내의 플레이어와 유니티 실행 화면의 플레이어 총 두명의 플레이어가 생성된 모습을 확인할 수 있다.

 

 프로젝트뷰에 있는 Player 를 선택하여 PhotonTransformView 컴포넌트를 제거하고 

Movement.cs를 아래와 같이 수정함

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using Cinemachine;

public class Movement : MonoBehaviourPunCallbacks, IPunObservable
{
    // 컴포넌트 캐시 처리를 위한 변수
    private CharacterController controller;
    private new Transform transform;
    private Animator animator;
    private new Camera camera;

    // 가상의 Plane에 레이캐스팅하기 위한 변수
    private Plane plane;
    private Ray ray;
    private Vector3 hitPoint;

    // PhotonView 컴포넌트 캐시 처리를 위한 변수
    private PhotonView pv;

    // PhotonView 컴포넌트 캐시 처리를 위한 변수
    private CinemachineVirtualCamera virtualCamera;

    // 이동 속도
    public float moveSpeed = 10.0f;

    // 수신된 위치와 회전값을 저장할 변수
    private Vector3 receivePos;
    private Quaternion receiveRot;
    // 수신된 좌표로의 이동 및 회전 속도의 민감도
    public float damping = 10.0f;

    private void Start()
    {
        controller = GetComponent<CharacterController>();
        transform = GetComponent<Transform>();
        animator = GetComponent<Animator>();
        camera = Camera.main;

        pv = GetComponent<PhotonView>();
        virtualCamera = GameObject.FindObjectOfType<CinemachineVirtualCamera>();

        // PhotonView 가 자신일 것일 경우 시네머신 가상카메라를 연결
        if(pv.IsMine)
        {
            virtualCamera.Follow = transform;
            virtualCamera.LookAt = transform;
        }

        // 가상의 바닥을 주인공 위치를 기준으로 생성
        plane = new Plane(transform.up, transform.position);
    }

    private void Update()
    {
        // 자신이 생성한 네트워크 객체만 컨트롤
        if(pv.IsMine)
        {
            Move();
            Turn();
        }
        else
        {
            // 수신된 좌표로 보간한 이동 거리
            transform.position = Vector3.Lerp(transform.position,
                                                receivePos,
                                                Time.deltaTime * damping);
            // 수신된 회전값으로 보간한 회전 처리
            transform.rotation = Quaternion.Slerp(transform.rotation,
                                                    receiveRot,
                                                    Time.deltaTime * damping);
        }
    }

    // 키보드 입력값 연결
    float h => Input.GetAxis("Horizontal");
    float v => Input.GetAxis("Vertical");

    // 이동 처리하는 함수
    void Move()
    {
        Vector3 cameraForward = camera.transform.forward;
        Vector3 cameraRight = camera.transform.right;
        cameraForward.y = 0.0f;
        cameraRight.y = 0.0f;

        // 이동할 방향 벡터 계싼
        Vector3 moveDir = (cameraForward * v) + (cameraRight * h);
        moveDir.Set(moveDir.x, 0.0f, moveDir.z);

        // 주인공 캐릭터 이동 처리
        controller.SimpleMove(moveDir * moveSpeed);

        // 주인공 캐릭터의 애니메이션 처리
        float forward = Vector3.Dot(moveDir, transform.forward);
        float strafe = Vector3.Dot(moveDir, transform.right);

        animator.SetFloat("Forward", forward);
        animator.SetFloat("Strafe", strafe);
    }
    // 회전 처리하는 함수
    void Turn()
    {
        // 마우스의 2차원 좌푯값을 이용해 3차원 광선을 생성
        ray = camera.ScreenPointToRay(Input.mousePosition);

        float enter = 0.0f;

        // 가상의 바닥에 레이를 발사해 충돌한 지점의 거리를 enter 변수로 반환
        plane.Raycast(ray, out enter);
        // 가상의 바닥에 레이가 충돌한 좌푯값 추출
        hitPoint = ray.GetPoint(enter);

        // 회전해야 할 방향의 벡터를 계산
        Vector3 lookDir = hitPoint - transform.position;
        lookDir.y = 0;
        // 주인공 캐릭터의 회전값 지정
        transform.localRotation = Quaternion.LookRotation(lookDir);
    }

    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        // 자신의 로컬 캐릭터인 경우 자신의 데이터를 다른 네트워크 유저에게 송신
        if(stream.IsWriting)
        {
            stream.SendNext(transform.position);
            stream.SendNext(transform.rotation);
        }
        else
        {
            receivePos = (Vector3)stream.ReceiveNext();
            receiveRot = (Quaternion)stream.ReceiveNext();
        }
    }
}

 

 

먼저 Movement 클래스에 MomoBehaviourPunCallbacks, IPunObservable  인터페이스를 추가함

OnPhotonSerializeView 콜백 함수의 인터페이스로 비주얼 스튜디오의 리펙토링 기능을 이용하여 편리하게 콜백 함수르 추가 가능 

 

OnPhotonSerializeView 콜백 함수의 첫번째 인자인 PhotonSteam.IsWriting 속성이 true 이면 데이터를 전송하는 것을 의미

 

PhotonView.IsMine 속성이 true 일 경우 해당 네트워크 객체는 자신의 캐릭터를 말함 

자신의 캐릭터 위치와 회전 정보는 같은 룸에 입장한 모든 네트워크 유저에게 전송되어야됨

PhotonStream.IsWriting이 false일 때는 반대로 다른 네트워크 유저의 캐릭터에 추가된 PhotonView 컴포넌트가 송신한 데이터를 수신함

 

public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
    // 자신의 로컬 캐릭터인 경우 자신의 데이터를 다른 네트워크 유저에게 송신
    if(stream.IsWriting)
    {
        stream.SendNext(transform.position);
        stream.SendNext(transform.rotation);
    }
    else
    {
        receivePos = (Vector3)stream.ReceiveNext();
        receiveRot = (Quaternion)stream.ReceiveNext();
    }
}

 

PhotonStream.SendNext 함수르 사용하여 데이터를 전송 

PhotonStream.ReceiveNext 함수를 사용하여 데이터를 수신

전송하는 데이터의 개수와 데이터 타입은 수신할 데이터의 개수와 타입이 일치해야됨

 

전달받은 데이터 receivePos, receiveRot 변수에 저장하고 Update 함수에서 사용함

Update 함수에서 PhotonView.IsMine 속성으로 자신의 로컬 캐릭터는 직접 컨트롤해서 이동 및 회전 처리하고 다른 네트워크 유저의 캐릭터는 수신받은 데이터를 이용해 이동

좌표와 회전 보간은 Vector3.Lerp 와 Quaternion.Slerp 함수를 사용함

private void Update()
{
    // 자신이 생성한 네트워크 객체만 컨트롤
    if(pv.IsMine)
    {
        Move();
        Turn();
    }
    else
    {
        // 수신된 좌표로 보간한 이동 거리
        transform.position = Vector3.Lerp(transform.position,
                                            receivePos,
                                            Time.deltaTime * damping);
        // 수신된 회전값으로 보간한 회전 처리
        transform.rotation = Quaternion.Slerp(transform.rotation,
                                                receiveRot,
                                                Time.deltaTime * damping);
    }
}

 

저장하고 Player 프리팹의 PhotonView 컴포넌트를 보면 Observable Search 속성이 Auto Find All 로 설정되어 있어서 

자동으로 Movement 스크립트가 Observed Component 속성에 추가된 것을 확인 가능 

Observalbe Search 속성을 Manual로 설정했다면 직접 드래그 해서 추가해야됨

다시 빌드하여 실행시키고 실행해보기

 

RPC를 활용한 총 발사 로직 

원격 네트워크 유저에게 총알을 발사하는 로직을 만들어보기

 

PhotonView 컴포넌트를 추가하여 생성하면 간단하게 구현할 수 있지만 초당 20회 데이터를 전송하기 때문에 효율적이지 못함

 

총알 발사와 같이 이벤트성 동작을 네트워크 유저와 공유할 때는 RPC 를 통해 구현하는 것이 일반적임

 

Bullet 프리팹을 하이러키뷰에 드래그 해서 보면 Capsule Collider와 Rigidbody 컴포넌트가 추가되어 있는 것을 확인가능

Tag는 BULLET 태그를 생성하여 지정하기

 

Bullet.cs 스크립트 생성 후 작성하기

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    public GameObject effect;

    private void Start()
    {
        GetComponent<Rigidbody>().AddRelativeForce(Vector3.forward * 1000.0f);
        // 일정 시간이 지난 후 총알을 삭제
        Destroy(this.gameObject, 3.0f);
    }

    private void OnCollisionEnter(Collision  coll)
    {
        // 충돌 지점 추출
        var contact = coll.GetContact(0);
        // 충돌 지점에 스카프 이펙트 생성
        var obj = Instantiate(effect,
                                contact.point,
                                Quaternion.LookRotation(-contact.normal));
        Destroy(obj, 2.0f);
        Destroy(this.gameObject);
    }
}

범선 벡터를 이용해 이페트의 방향을 설정

Effect 에 BulletImpact_Wall 을 연결하고 프리팹 저장함

실행해보고 총알이 발사되고 충돌을 일으키면 스파크가 발생하는지 확인하기, 이상이 없다면 하이러키 뷰의 Bullet은 삭제

 

Fire.cs 스크립트를 생성하고 아래와 같이 작성한다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;

public class Fire : MonoBehaviour
{
    public Transform firePos;
    public GameObject bulletPrefab;

    private ParticleSystem muzzleFlash;

    private PhotonView pv;
    // 왼쪽 마우스 버튼 클릭 이벤트 저장
    private bool isMouseClick => Input.GetMouseButtonDown(0);

    private void Start()
    {
        // 포톤뷰 컴포넌트 연결
        pv = GetComponent<PhotonView>();
        // FirePos 하위에 있는 총구 화염 효과 연결
        muzzleFlash = firePos.Find("MuzzleFlash").GetComponent<ParticleSystem>();
    }
    private void Update()
    {
        // 로컬 유저 여부와 마우스 왼쪽 버튼을 클릭했을 때 총알을 발사
        if(pv.IsMine && isMouseClick)
        {
            FireBullet();
            // RPC로 원격지에 있는 함수를 호출
            pv.RPC("FireBullet", RpcTarget.Others, null);
        }
    }
    [PunRPC]
    void FireBullet()
    {
        // 총구 화염 효과가 실행 중이 아닌 경우에 총구 화염 효과 실행
        if (!muzzleFlash.isPlaying) muzzleFlash.Play(true);

        GameObject bullet = Instantiate(bulletPrefab,
                                        firePos.position,
                                        firePos.rotation);
    }

}

스크립트는 Player 프리팹에 추가함 

fire 컴포넌트는 다음과 같이 연결한다.

 

RPC 호출 목적으로만 사용하려면 PhotonView 컴포넌트의 Synchronization 속성을 Off 로 설정해야됨

 

포톤 서버에서 일반적인 RPC 호출은 PhotonView.RPC(호출할 함수명, 호출 대상, 전달할 데이터) 함수를 사용함

원격으로 호출할 함수명 인자는 string 타입으로 전달하고 호출 대상은 특정 플레이어를 지정하거나 RpcTarget 옵션으로 전달 대상의 범위를 지정가능

 

  • void PhotonView.RPC(string methodName, RpcTargets target, params object[] parametoers)
  • void PhotonView.RPC(string methodName, Player targetPlayer, params object[] parameters)

RPCTarget 옵션

옵션 설명
All 모든 네트워크 유저에게 RPC를 전송하고 자신은 즉시 RPC를 실행함
Others 자신을 제외하고 모든 네트워크 유저에게 RPC를 전송
MasterClient Master Client 에게 RPC를 전송 
AllBuffered  모든 네트워크 유저에게 RPC를 전송하고 자신은 즉시 RPC를 실행
나중에 입장한 유저에게 버퍼에 저장돼 있던 RPC가 전달됨
OtherBuffered 자신을 제외하고 모든 네트워크 유저에게 RPC를 전송
나중에 입장한 유저에게 버퍼에 저장돼 있던 RPC가 전달됨
AllViaServer  모든 네트워크 유저에게 거의 동일한 시간에 RPC를 전송하기 위해 서버에서 모든 클라이언트에게 RPC를 동시에 전송
AllBufferedServer AllViaServer 와 동일
버퍼에 저장돼 있는 RPC를 나중에 입장한 유저에게 전송

 

 

RpcTarget.All 은 Rpc 함수를 룸에 입장한 모든 네트워크 유저에 대해 호출하고 로컬 유저는 해당 함수를 즉시 호출함

 

RPC로 호출할 함수는 반드시 [PunRPC] 어트리뷰트를 앞에 작성

 

여기서부터 책과 다르게 제 프로젝트에 문제가 있다고 생각하여 작성하는 글 입니다.

연사하면 가끔 총알이 날아가지 않고 생성되자마자 사라지는 모습을 볼 수 있었습니다.

Fire.cs 의GetMouseButtonDown 을 GetMouseButton으로 변경하고 확인해본 결과

 총알끼리 충돌을 일으켜 생성되자 마자 바로 사라지는 모습을 보이고 있었습니다.

 

저와 똑같은 현상이라면 아래의 글을 참고하여

[Unity Course 2] 06. 적 캐릭터 제작 5 — 첨부엉의 게임개발일지 (tistory.com)

 

[Unity Course 2] 06. 적 캐릭터 제작 5

위키북스 출판사 이재현 저자님의 '절대강좌! 유니티' 책을 참고하여 필기한 내용입니다.트특정 레어 간의 충돌 감지지금까지 동작들이 문제 업어보이지만 몬스터는 추적하는 동안에도 충돌 이

splash0wl.tistory.com

 

BULLET레이어를 하나 추가하고

ProjectSettings에서 

Collision 체크박스의 Bullet 끼리의 충돌시 OnCollision~ 계열의 함수를 호출하지 않도록 설정합니다.

 

다시 실행하면 정상 작동되는 모습을 확인할 수 있습니다.

 

여기까지 입니당

피격 및 리스폰

상대편 네트워크 유저가 발사한 총알에 데미지를 입고 사망하는 로직 구현

사망 후 일정한 시간이 지난 후 리스폰 되게함 피격 및 리스폰 로직은 별도의 스크립트에서 처리

 

Damage.cs 스크립트 생성후 Player 프리팹에 연결

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Damage : MonoBehaviour
{
    // 사망 후 투명 처리를 위한 MeshRenderer 컴포넌트의 배열
    private Renderer[] renderers;

    // 캐릭터의 초기 생명치
    private int initHp = 100;
    // 캐릭터의 현재 생명치
    public int currHp = 100;

    private Animator anim;
    private CharacterController controller;

    // 애니메이터 뷰에 생성한 파라미터의 해시값 추출
    private readonly int hashDie = Animator.StringToHash("Die");
    private readonly int hashRespawn = Animator.StringToHash("Respawn");

    private void Awake()
    {
        // 캐릭터 모델의 모든 Renderer 컴포넌트를 추출한 후 배열에 할당
        renderers = GetComponentsInChildren<Renderer>();
        anim = GetComponent<Animator>();
        controller = GetComponent<CharacterController>();

        // 현재 생명치를 초기 생명치로 초깃값 설정
        currHp = initHp;
    }

    private void OnCollisionEnter(Collision coll)
    {
        // 생명 수치가 0보다 크고 충돌체의 태그가 CULLET인 경우에 생명 수치를 차감
        if(currHp > 0 && coll.collider.CompareTag("BULLET"))
        {
            currHp -= 20;
            if(currHp<=0)
            {
                StartCoroutine(PlayerDie());
            }
        }
    }

    IEnumerator PlayerDie()
    {
        // CharacterController 컴포넌트 비활성화
        controller.enabled = false;
        // 리스폰 비활성화
        anim.SetBool(hashRespawn, false);
        // 캐릭터 사망 애니메이션 실행
        anim.SetTrigger(hashDie);

        yield return new WaitForSeconds(3.0f);

        // 리스폰 활성화
        anim.SetBool(hashRespawn, true);

        // 캐릭터 투명 처리
        SetPlayerVisible(false);

        yield return new WaitForSeconds(1.5f);

        // 생성 위치를 재조정
        Transform[] points = GameObject.Find("SpawnPointGroup").GetComponentsInChildren<Transform>();
        int idx = Random.Range(1, points.Length);
        transform.position = points[idx].position;

        // 리스폰 시 생명 초깃값 설정
        currHp = 100;

        // 캐릭터를 다시보이게 처리
        SetPlayerVisible(true);
        // CharacterController 컴포넌트 활성화
        controller.enabled = true;
    }
    // Renderer 컴포넌트를 활성 / 비활성화 하는 함수
    void SetPlayerVisible(bool isVisible)
    {
        for(int i =0; i<renderers.Length; i++)
        {
            renderers[i].enabled = isVisible;
        }
    }
}

 

 

 

반응형