04
12

썸넬

이 글에서 사용한 멀티플레이어 라이브러리는 Netcode For GameObjects다.

 

따라서 포톤같은 다른 라이브러리여도 원리만 비슷하게 적용해도 될 듯하다.

 

다른 싱글톤 패턴은 아래 글에서 확인 가능하다 ↓

https://kimyir.tistory.com/15

 

 


#1 클라이언트의 Player 싱글톤

 

로컬에서 유저가 조종하는 Player가 한 명이라면 즉 하나만 존재한다고 가정하자. 

 

그러면 로컬 싱글톤으로 작성해서 클라이언트 내에서 접근하는 싱글톤으로 만들어도 된다.

 

우선 클라이언트 내에서 로컬로 접근하는 인스턴스를 만들어 주자.

public class Player : NetworkBehaviour
{
	//로컬 인스턴스
    public static Player LocalInstance{get; set;}
}

 

그리고 이 로컬 인스턴스를 생성해줘야한다.

 

근데 흔히 아는 싱글톤처럼 Awake()에서 인스턴스를 생성하면 안된다.

 

네트워크 연결이 되어야 로컬인지 판단하기 때문에 OnNetworkSpawn()같은 네트워크 함수를 사용해주자.

public class Player : NetworkBehaviour
{
    //로컬 인스턴스
    public static Player LocalInstance{get; set;}
    
    public override void OnNetworkSpawn()
    {
        //소유자인지 판단(로컬)
        if(IsOwner)
        {
            LocalInstance = this;
        }
    }
}

이렇게 하면 로컬에서 접근하는 하나뿐인 Player 싱글톤을 만들 수 있다.

 


#2 외부에서 static Event 접근 활용

 

이제 싱글톤을 Event로 조금 더 활용해볼 차례다.

 

우선 Player이벤트를 구현해보자.

public class Player : NetworkBehaviour
{
    //event 구현
    public static event EventHandler OnAnyPlayerSpawned;
    //로컬 인스턴스
    public static Player LocalInstance{get; set;}
    
    public override void OnNetworkSpawn()
    {
        //소유자인지 판단(로컬)
        if(IsOwner)
        {
            LocalInstance = this;
        }
        //이벤트 호출
        OnAnyPlayerSpawned?.Invoke(this, EventArgs.Empty);
    }
}

이벤트는 이름처럼 Player가 생성될 때 실행해서 구독자들에게 알릴 것이다.

 

이제 구독자를 하나만 구현해보자.

public class SelectedCounterVisual : MonoBehaviour
{
    private void Start() {
        if(Player.LocalInstance != null)
        {
            Player.LocalInstance.OnSelectedCounterChanged += Player_OnSelectedCounterChanged;
        }else
        {
            Player.OnAnyPlayerSpawned += Player_OnAnyPlayerSpawned;
        }
    }

    private void Player_OnAnyPlayerSpawned(object sender, EventArgs e)
    {
        if(Player.LocalInstance != null)
        {
            Player.LocalInstance.OnSelectedCounterChanged -= Player_OnSelectedCounterChanged;
            Player.LocalInstance.OnSelectedCounterChanged += Player_OnSelectedCounterChanged;
        }
    }
    private void Player_OnSelectedCounterChanged(object sender, Player.OnSelectedCounterChangedArgs e)
    {
        //Player가 이벤트 실행
    }
}

 

위의 코드에서 활용할 부분만 한번 정리를 해보면

1. Player.Localnstance가 네트워크 접속되있다면 바로 Player_OnSelectedCounterChanged를 구독한다.

2. 만약 접속 되지 않았다면 일단 Player의 OnAnyPlayerSpawned를 구독해준다.

3. OnAnyPlayerSpawned는 네트워크 상관없이 로컬의 메모리에 올라간 static 이벤트다.

4. Player가 OnNetworkSpawn() 함수에서 네트워크 접속되었다면 자신의 OnAnyPlayerSpawned 이벤트를 실행한다. 

5. 이벤트 실행하면 구독해둔 Player_OnAnyPlayerSpawned가 실행된다.

6. 실행된 구독자 함수에서 Player.LocalInstance에 Player_OnSelectedCounterChanged를 구독한다.

7. Player가 접속과 동시에 실행되거나 이런저런 이유로 중복될 수 있으니 구독 해제-= 다시 구독+= 해준다.

 

조금 복잡하긴한데 결론은 Player.LocalInstance가 생성되있으면 이벤트에 구독자를 등록하고,

그렇지 않다면 구독자를 등록하는 과정을 실행할 함수를 Player의 이벤트에 구독해준뒤,

Player가 네트워크 접속해서 스스로 이벤트를 실행해서 함수 실행하여 구독자를 등록하면 된다.

 


#3 씬 전환시 static 중복

 

씬 전환시 static이 중복될 수 있기 때문에 static 데이터를 초기화해주자.

 

위에서 만들어 둔 Player 싱글톤에 초기화 함수를 만들어 보자.

public class ResetStaticDataManager : MonoBehaviour
{
    private void Awake() {
        Player.ResetStaticData();
    }
}

public class Player : MonoBehaviour
{
    public static event EventHandler OnAnyPlayerSpawned;
    //초기화
    public static void ResetStaticData()
    {
        OnAnyPlayerSpawned = null;
    }
    
    public static Player LocalInstance{get; set;}
}

위의 코드처럼 ResetStaticDataManager같은 매니저를 만들어서 static 데이터를 초기화하면 된다.

 

매니저는 다른 스크립트에 분리해서 관리 해도 된다.

 

그리고 Player에다가 ResetStaticData()함수를 static으로 만들어  매니저에서 접근가능하게 하면 된다.

COMMENT