UNITY/크레이지아케이드 모작

멀티플레이 구현

2zreal 2024. 7. 23. 21:43

멀티플레이 구현이 어느 정도 완성되었다.

 

 

우선 방을 들어오면서

 public override void OnJoinedRoom()
 {
     PhotonNetwork.Instantiate("Player", new Vector3(0, 4, 0), Quaternion.identity);
 }

플레이어 객체를 만든다.

 

만약 자기가 플레이어 객체의 주인이면 

if(PV.IsMine)
{
    gameObject.transform.GetChild(0).gameObject.SetActive(true);
}
else
{
    gameObject.transform.GetChild(0).gameObject.SetActive(false);
}

화살표 오브젝트를 활성화한다.

 

플레이어 객체의 움직임은 Photon Transform View와 Photon Rigidbody 2D를 통해 동기화된다. 각 플레이어는 IsMine 속성이 true일 때만 컨트롤할 수 있도록 설정되어 있다.

애니메이션 동기화는 

private void Update()
{
    if(PV.IsMine && live)
    {
        horizontal = Input.GetAxisRaw("Horizontal");
        vertical = Input.GetAxisRaw("Vertical");

        if (Mathf.Abs(horizontal) > Mathf.Abs(vertical))// 대각선 이동 방지
        {
            vertical = 0;
        }
        else
        {
            horizontal = 0;
        }

        if (Input.GetKeyDown(KeyCode.Space))// 스페이스바로 물풍선 생성
        {
            if (bubblecur < bubbleCount)
            {
                PV.RPC("PlaceBalloon", RpcTarget.MasterClient, transform.position);
            }
                
        }
    }
}

private void FixedUpdate()
{
    if (PV.IsMine && live)
    {
        Vector2 movement = new Vector2(horizontal, vertical).normalized * speed * Time.fixedDeltaTime;
        rb.MovePosition(rb.position + movement);

        animator.speed = ((speed - 1) * 0.2f) + 1; // 속도가 빨라지면 애니메이션 속도도 증가!

        if (movement != Vector2.zero)// 움직임이 있다면
        {
            lastMovement = movement; // 멈추는 로직을 구현하기 위해 사용

            if (horizontal != 0)
            {
                if (horizontal > 0)
                {
                    nowAnime = moveRightAnime;
                }
                else
                {
                    nowAnime = moveLeftAnime;
                }
            }
            else if (vertical != 0)
            {
                if (vertical > 0)
                {
                    nowAnime = moveUpAnime;
                }
                else
                {
                    nowAnime = moveDownAnime;
                }
            }
        }
        else // 움직임이 없다면
        {
            if (Mathf.Abs(lastMovement.x) > Mathf.Abs(lastMovement.y))// 이전 움직임을 보고 판단
            {
                if (lastMovement.x > 0)
                {
                    nowAnime = idleRightAnime;
                }
                else
                {
                    nowAnime = idleLeftAnime;
                }
            }
            else
            {
                if (lastMovement.y > 0)
                {
                    nowAnime = idleUpAnime;
                }
                else
                {
                    nowAnime = idleDownAnime;
                }
            }
        }

        if (nowAnime != oldAnime)// 애니메이션이 바뀌면 Play
        {
            PV.RPC("MoveRPC", RpcTarget.AllBuffered, nowAnime);
            oldAnime = nowAnime;
        }
        
    }
    
}

 

만약 애니메이션이 변경되는 부분이 있다면 그때 RPC를 해줘서 동기화를 시켜주었다.

if (nowAnime != oldAnime)// 애니메이션이 바뀌면 Play
{
    PV.RPC("MoveRPC", RpcTarget.AllBuffered, nowAnime);
    oldAnime = nowAnime;
}

 애니메이션 동기화는 Photon Animation View를 통해 처리할 수 있지만, RPC를 통해 코드로 직접 구현했다.

 

물풍선 설치 로직은 IsMine인 객체가 스페이스바를 누를 때, 마스터 클라이언트에게 해당 입력을 전송한다. 마스터 클라이언트는 물풍선 생성 가능 여부를 판단한 후, 조건이 충족되면 물풍선 생성 요청을 모든 클라이언트에 전파하는 RPC를 호출한다. 

 [PunRPC]
 void PlaceBalloon(Vector3 position)
 {
     if(PhotonNetwork.IsMasterClient)
     {
         Vector3Int cellPosition = tilemap.WorldToCell(position); // 플레이어의 현재 위치를 셀 좌표로 변환
         Vector3 cellCenterPosition = tilemap.GetCellCenterWorld(cellPosition); // 현재 셀 좌표에 해당하는 타일의 중심점을 계산
         Collider2D []hitCollider = Physics2D.OverlapPointAll(cellCenterPosition);

         bool sig = true;
         for(int i=0; i< hitCollider.Length; i++)
         {
             if (hitCollider[i].CompareTag("Balloon"))
             {
                 sig = false;
             }
         }
         if(sig)
         {
             PV.RPC("PlaceBalloonRPC", RpcTarget.AllBuffered, cellCenterPosition);
         }
         
     }
 }

이전에 작성한 물풍선 설치 로직에서 문제가 발생하여 수정하였다. hitCollider가 가장 바깥쪽의 콜라이더만 인식하여 물풍선을 설치한 후 약간만 움직이면 태그가 Balloon에서 Player로 변경되면서 물풍선이 재설치되는 문제가 발생했다. 이를 해결하기 위해 OverlapPointAll을 사용하여 모든 콜라이더를 검사하고, Balloon 태그가 하나라도 존재할 경우 물풍선 설치를 방지하도록 로직을 개선하였다.

 

아이템 생성과 획득 로직은 마스터 클라이언트를 통해 처리되도록 구현하였다. 현재 물줄기를 통해 타일을 파괴하는 로직은 클라이언트 차원에서 처리되고 있다. 물풍선의 동기화가 이미 이루어지고 있기 때문에 타일 파괴까지 동기화할 필요는 없다고 판단하였다. 그러나 향후 문제가 발생할 경우, 이 부분도 적절히 수정할 예정이다.

 

물줄기에 플레이어가 맞아 Bubble 상태가 되는 로직은 플레이어 측에서 자연스럽게 처리된다. 플레이어 화면에서 물줄기에 맞았다고 판단되면, 해당 정보를 다른 클라이언트들에게 RPC를 통해 전달하여 일관된 게임 상태를 유지한다. 이 부분도 문제가 발생한다면 수정할 예정이다.

(클라이언트에서 충돌 감지->서버에 알림-> 서버가 충돌 여부를 검증 -> 충돌 처리 RPC) 이 순서대로 로직을 구성하면 잘 돌아갈까??

 

 

 public void WaitBubble()
 {
     if (!live && PV.IsMine)
     {
         PV.RPC("MoveRPC", RpcTarget.AllBuffered, DieAnime);
     }

 }

public void WaitDie()
{
    Destroy(gameObject);
}