2025유니티수업

0711 Photon(Pun, Realtime)

버스창문 2025. 7. 11. 12:58

Photon Realtime : 핵심 엔진이자 기반

 

Pun : Photon Unity Networking

그 Realtime을 유니티에 맞춰서 최적화하고 쓰기쉽게 만든 패키지

 

 

Photon View : 게임 오브젝트에 추가해서 나의 움직임(로컬)을 다른쪽(리모트)에게 전달하기 위한 것, 통신

 

________________________________________________________

 

private void OnGUI()
{
    GUILayout.Label(PhotonNetwork.NetworkClientState.ToString());
}

왼쪽위에 접속 상태를 표시하는게 뜬다

 

GUILayout.Label : 왼쪽위에 글씨 띄우기

PhotonNetwork.NetworkClientState : 접속 상태에 대한 내용 . ToString()

 

꼭 저렇게 왼쪽 위가 아니라, 그냥 UI에 안에 내용만 저걸로 넣으면 UI로써 표시할수도 있다

 

 

________________________________________________________________

 

Photon View

 

 

게임 오브젝트에 부착(프리팹에도 붙일수 있음)

 

Synchronization : 동기화 방식

Reliable Delta Compressed :  TCP/IP : 가다가 잃어버리는 등, 잘 안가면, 다시 보낸다 -> 데이터가 중요한 내용들에 대해선 이렇게 처리함

Unreliable : UDP : 가다가 잃어버리든 말든 그냥 순서대로 다 보낸다 -> 렉걸리면 겟앰프드 등의 게임에서 상대가 순간이동을 하는것

Unreliable On Change : 필요에 따라서 양쪽을 다 하는데, 무료버전에선 안바뀐다

 

 

 

 

 

Manual로 변경(사실 auto Find All로 해도 된다)

 

Transform 끌어다 붙이기

 

 

Position과 Rotation은 리모트쪽에 전달되고, Scale은 전달되지 않는다

void CreateTank()
{
    float pos = Random.Range(-80f, 80f);
    PhotonNetwork.Instantiate(
        "Tank", 
        new Vector3(pos, 3f, pos), 
        Quaternion.identity, 
        0 , 
        null);
}

PhotonNetwork.Instantiate

-> string : Resources 에서 저 이름인 애를 가져옴

Vector : 생성될 좌표

Quaternion : 회전값

 

그리고 그룹, 전달할 데이터 순

탱크가 생성되지만, 카메라가 탱크를 잡지않음

public class TankMove : MonoBehaviourPun
{
    public TankInput input;
    public Rigidbody rb;
    
    IEnumerator Start()
    {
        this.input = GetComponent<TankInput>();
        this.rb = GetComponent<Rigidbody>();
        this.rb.centerOfMass = new Vector3(0,-0.5f,0); // 물리 엔진에서 탱크의 중심을 조정
        yield return null;

        if (photonView.IsMine) // 포톤 네트워크 상 포톤 뷰가 나의 것이라면(로컬 이라면)
        {
            CinemachineVirtualCamera vCam = FindObjectOfType<CinemachineVirtualCamera>();
            if (vCam != null)
            {
                vCam.Follow = this.transform; // 가상 카메라가 탱크를 따라가도록 설정
                vCam.LookAt = this.transform; // 가상 카메라가 탱크를 바라보도록 설정
            }
        }
    }

 

MonoBehaviourPun : MonoBehaviour의 기능을 그대로 쓰면서, 통신까지함

바로 카메라가 탱크를 바라보는 모습

 

 

PunCallbacks : 이벤트 들이 있어서 통신관련을 관리할수있음

Pun : photonView를 관리할 수 있음, 간단히 그냥 photonView만 컨트롤 할거면 Pun만 달면 됨

 

 

 

빌드와 에디터를 동시에 키면 탱크가 2개인걸 확인가능

하지만 탱크 2개가 동시에 움직이는 문제가 있다

 

 

public class TankMove : MonoBehaviourPun , IPunObservable   
   
   
   
   
   Vector3 curPos = Vector3.zero; // 다른 네트워크의 이동값
    Quaternion curRot = Quaternion.identity; // 다른 네트워크의 회전값

    IEnumerator Start()
    {
        this.input = GetComponent<TankInput>();
        this.rb = GetComponent<Rigidbody>();
        this.rb.centerOfMass = new Vector3(0,-0.5f,0); // 물리 엔진에서 탱크의 중심을 조정

        this.curPos = this.transform.position; // 현재 위치를 저장
        this.curRot = this.transform.rotation; // 현재 회전을 저장
        
        
        
        
        
            //로컬의 이동 회전을 네트워크상의 타인인 리모트에 송신하고,
    //반대로 리모트의 이동 회전을 수신받아서 네트워크 상 움직임이 서로 보여야 하기 때문에
    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    //IPunObservable 인터페이스의 구현 내용
    //stream : 데이터 통로, 네트워크에서 정보가 전달되는 통로
    {
                if (stream.IsWriting) //송신중이라면
        {
            stream.SendNext(this.transform.position);
            stream.SendNext(this.transform.rotation);
        }
        else if (stream.IsReading) //수신중이라면
                                   //사실 그냥 송신중이 아니라면 이란 뜻으로 else만 해도 된다
        {
            this.curPos = (Vector3)stream.ReceiveNext(); // 송신된 위치값을 받는다
            this.curRot = (Quaternion)stream.ReceiveNext(); // 송신된 회전값을 받는다
        }
    }
    
    
    
    
        void FixedUpdate()
    {
        if (photonView.IsMine) // 로컬이라면
        {
            #region 탱크 회전
            this.transform.Rotate(0, input.h * 2f, 0, Space.Self);
            #endregion

            #region 탱크 이동
            this.transform.Translate(Vector3.forward * this.input.v * Time.deltaTime * 100);
            #endregion
        }
        else // 리모트라면
        {
            this.transform.position = Vector3.Lerp(this.transform.position, this.curPos, Time.deltaTime * 3f); // 다른 네트워크의 위치값으로 보간
            this.transform.rotation = Quaternion.Lerp(this.transform.rotation, this.curRot, Time.deltaTime * 3f); // 다른 네트워크의 회전값으로 보간
        }

    }

 

 

 

______________________________________________________

 

 

    public override void OnJoinedLobby()
    {
        base.OnJoinedLobby();
        Debug.Log("로비 접속");
        PhotonNetwork.JoinRandomRoom(); //랜덤 방 접속 시도
        userID.text = GetUserID(); //유저 아이디 가져오기
    }
    string GetUserID()
    {
        string userID = PlayerPrefs.GetString("User_ID");

        if (string.IsNullOrEmpty(userID))
        {
            userID = $"User_{Random.Range(0, 999)}";
        }
        
        return userID;
    }



///////////////////////////////////////////////////////////////


public override void OnJoinedRoom()
{
    base.OnJoinedRoom();
    Debug.Log("방 접속 성공");
    StartCoroutine(LoadBattleFieldScene()); // 씬을 이동하는 코루틴
}
IEnumerator LoadBattleFieldScene()
{
        PhotonNetwork.IsMessageQueueRunning = false; // 씬을 이동하는 동안 클라우드 서버로부터
                                                     // 네트워크 메시지 수신 중단

        AsyncOperation ao = SceneManager.LoadSceneAsync(1); // 씬 비동기적으로 로딩

        yield return ao;
}



    public void OnClickJoinRandomRoom()
    {
        PhotonNetwork.NickName = this.userID.text;
        PlayerPrefs.SetString("User_ID", this.userID.text);
        PhotonNetwork.JoinRandomRoom(); //랜덤 방 접속 시도

    }

 

 

public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }
    public bool isGameOver = false;
    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
        }
        else
        {
            Destroy(gameObject);
        }

        PhotonNetwork.IsMessageQueueRunning = true; //다시 네트워크로부터 메세지 받게 설정
        CreateTank();
    }
    void CreateTank()
    {
        float pos = Random.Range(-80f, 80f);
        PhotonNetwork.Instantiate(
            "Tank",
            new Vector3(pos, 3f, pos),
            Quaternion.identity,
            0,
            null);
    }
}

PhotonNetwork.IsMessageQueueRunning : 아까 씬 로딩할때 메세지 안받게 false로 바꿨으니, 다시 true로 바꿔야함

 

 

내거(editor)

다른애(빌드파일)

 

 

 

____________________________________________________________

 

RPC : Remote Procedure Call

원격 함수 호출

멀리 떨어진곳에서 함수를 호출시키는것

 

내 화면에서 총을 쏘면 다른 사람의 화면에서도 총이 쏴지는것

 

 

 

 

All : 전부 다

Others : 나 빼고 다

 

이 외에도 많은 옵션이 있음

    void Update()
    {
        if (this.input.isFire)
        {
            if (photonView.IsMine) // 로컬이라면
            {
                Fire(); //그냥 내 Fire호출
                photonView.RPC("Fire",RpcTarget.Others); //내 컴퓨터 말고 다른 컴퓨터에 있는
                                                         //내 Remote들에 원격 호출
            }

            
        }
    }
    
    
        [PunRPC] //Fire함수에 어트리뷰트 붙여놔야한다
    void Fire()
    {

 

            else //리모트라면
            {
                photonView.RPC("Fire",RpcTarget.Others); //내 컴퓨터 말고 다른 컴퓨터에 있는
                                                         //내 Remote들에 원격 호출
            }
            
            
            
            이렇게 쓰면 뭔가 꼬인다

 

 

___________________________________________________________________________

 

 

탱크 체력

3대 맞으면 사라질건데, destroy하면 안되고 오브젝트 풀링을 쓰기 복잡하니,

그냥 mesh를 껐다가, 5초뒤 다시 켜서 부활시키는 식으로 구현

public class TankDamage : MonoBehaviourPun
{
    //탱크가 3대쯤 맞으면 사망
    //메쉬를 전부 껐다가 , 5초뒤 다시 켜서 부활
    public Image hpBar; // 탱크의 체력바 UI

    [SerializeField]
    private MeshRenderer[] meshRenderers; // 탱크의 메쉬 렌더러들
    private readonly string tankTag = "Player"; // 탱크 태그
    private readonly string apacheTag = "Apache"; // 아파치 태그
    private GameObject expEffect; // 폭발 이펙트

    private readonly int maxHp = 100; // 최대 체력
    private int curHp; // 현재 체력

    void Start()
    {
        this.expEffect = Resources.Load<GameObject>("Effects/BigExplosionEffect");
        this.curHp = this.maxHp; // 현재 체력을 최대 체력으로 초기화
    }
    public void OnDamage(string tag)
    {

            photonView.RPC("OnDamageRPC", RpcTarget.All, tag); // 모든 클라이언트에 데미지 전달
            Debug.Log("OnDamage called with tag: " + tag);
        
    }
    [PunRPC]
    void OnDamageRPC(string tag) 
    {
        if (this.curHp > 0 && tag == this.tankTag) 
        {
            //데미지 전달
            HpBarInit(tag); // 체력바 UI 업데이트
            if(this.curHp <= 0)
            {
                //파괴 연출(이펙트)
            }
        }
    }
    void HpBarInit(string tag)
    {
        if(tag == tankTag)
        {
            this.curHp -= 40;
        }
        this.hpBar.fillAmount = (float)this.curHp / (float)this.maxHp; // 체력바 UI 업데이트
    }
}

 

 

_______________________________________________________________

 

방 만들기 & 스크롤바

 

 

스크롤 바

panel 생성후 scroll rect부착

빈 오브젝트 추가

스크롤바 추가 후 top to bottom

 

 

 

 

scroll Rect쪽에 content에 ScrollContents부착

vertical Scrollbar에 scrollbar 부착

 

 

scroll Contents 밑에 Image생성

image 밑에 text2개 생성

 

RoomList에 Mask부착

'2025유니티수업' 카테고리의 다른 글

0715 (포톤)  (0) 2025.07.15
0714(포톤 채팅)  (1) 2025.07.14
0710 유니티(LineRenderer, 사운드매니저) , Pun2  (3) 2025.07.10
0709 유니티(헬기, )  (0) 2025.07.09
0708 유니티(좀비 서바이벌 완성 , scope zoom)  (0) 2025.07.08