251103

lililllilillll·2025년 11월 3일

개발 일지

목록 보기
344/350

✅ 한 것들


  • R&D :: 멀티 게임
  • 게임 서버 프로그래밍 교과서


🛠️ R&D :: 멀티 게임


Player Input 컴포넌트가 씬에 2개 있으면 Multiplayer Play Mode의 추가 인스턴스에서 Player Input 입력 안 먹는 문제

  • Focus on Player : 그냥 view를 바꾸는 기능
  • 연구 결과 : 아무튼 씬에 Player Input을 2개 넣지 말자. PlayerInput은 InputUser 하나에 대응됨.
public class ManualPlayerSpawner : NetworkBehaviour
{
    bool isPlayerSpawned = false;
    [SerializeField] private NetworkObject playerPrefab;

    [ServerRpc(RequireOwnership = false)]
    public void SpawnPlayer()
    {
        print("SpawnPlayer()");
        if (isPlayerSpawned) { print("Player already spawned"); return; }

        foreach (NetworkConnection client in ServerManager.Clients.Values)
        {
            if (!client.IsAuthenticated) continue;

            NetworkObject obj = Instantiate(playerPrefab);
            Spawn(obj, client, gameObject.scene);
            isPlayerSpawned = true;
        }
    }
}

(작동 제대로 안 해줌)
(Cannot complete action because client is not active 문구만 반환)

수동 Spawner

  • NetworkBehaviour 달린 스크립트 있으면 NetworkObject를 넣어줘야 한다. 안 그러면 부모에 붙어버림.
  • ServerManager는 서버측만 사용 가능했음

만들어보려고 했으나, 어려움이 많았음. 유튜브에서 멀티 게임 튜토리얼 먼저 진행.

Multiplayer Tutorial

https://www.youtube.com/watch?v=Fj2DeO31oF4
(~30분)

What is Multiplayer game?
: 각자 컴퓨터에서 로컬 게임이 돌아가고, 네트워킹으로 서로의 변화를 mimicking해주는 것.
서로의 컴퓨터에 있는 오브젝트들은 네트워크 상의 id만 같은 것이지, 절대 같은게 아님.

ParrelSync

  • ParrelSync > Clones Manager > Create new clone > Open in New Editor
  • clone에서 변경 만들면 안되고, 원본 인스턴스에서만 변경

Network가 준비되기 전에 Network와 관련된 뭔가 하면 안된다.
Awake() 막 쓰면 안된다는 거.



📖 게임 서버 프로그래밍 교과서


1.11 멀티 스레드 게임 서버

멀티 스레드 서버

  • 서버 프로세스 많이 띄우기 곤란할 때 (MMO같이 게임 용량 크거나 연산 많음)
  • 코루틴이나 비동기 쓸 수 없고 디바이스 타임 발생
  • 서버 인스턴스를 서버 기기당 하나만 둠
  • 서로 다른 방이 같은 메모리 공간 액세스해야 함
  • 공통 데이터 뮤텍스, 방 별 뮤텍스 따로

1.12 스레드 풀링

  • 클라에 일일이 스레드 할당하지 않고 스레드 풀에서 꺼내쓴다
  • CPU 코어 수만큼 스레드 만들어도 스레드 쉬는 동안 CPU도 쉼.
    • 디바이스 타임 발생하면 스레드가 CPU보다 많아야 함

1.13 이벤트

이벤트 : 잠자는 스레드를 깨우는 도구

  • 상태 값 : Reset(이벤트 없음), Set(이벤트 있음)
Event event1;

void Thread1() { event1.Wait(); } // 이벤트 신호를 기다린다
void Thread2() { event1.SetEvent(); } // 이벤트에 신호한다

자동 이벤트 : 스레드 깨운 뒤에 상태 값 0
수동 이벤트 : 깨워도 1로 남음

1.14 세마포어

Semaphore sema1;

void Main() { sema1 = Semaphore(2); }

void Thread1()
{
	sema1.Wait();
	sema1.Release();
}
void Thread2()
{
	sema1.Wait();
	sema1.Release();
}
void Thread3()
{
	sema1.Wait();
	sema1.Release();
}

1.15 원자 조작

atmoic operation : 뮤텍스나 임계 영역 잠금 없이도 여러 스레드가 안전하게 접근 가능

  • 원자성을 가진 값 더하기
  • 원자성을 가진 값 맞바꾸기
  • 원자성을 가진 값 조건부 맞바꾸기

1.16 멀티스레드 프로그래밍의 흔한 실수들

멀티스레드 버그는 발생 빈도도 랜덤이고, 버그 원인과 발생지가 다를 때 많음.

  1. 읽기와 쓰기 모두에 잠금하지 않기
void func1()
{
	// lock(a_mutex) 누락
	print(a);
}
void func2()
{
	lock(a_mutex)
	a = a+10;
}

읽기에 락 안 걸면 가끔 비정상적 값 출력

  1. 잠금 순서 꼬임

프로그램 규모 커지면 잠금 순서 지키기 어려움.
최선은 규칙을 최대한 적게 유지하는 것.

  1. 너무 좁은 잠금 범위

잠금 범위 너무 넓으면

  • 처리 병렬성 떨어짐
  • 스레드 대기가 길어져서 컨텍스트 스위치 많아짐

잠금 범위 너무 좁으면

  • 컨텍스트 스위치 확률 떨어지지만,
  • 임계 영역 잠금은 단순 산술 연산보다는 처리 시간 많음
    • 임계 영역 잠금은 원자 조작. 원자 조작은 일반 메모리 접근보다 시간 더 걸림.
  • 보호 합칠 수 있으면 합치는게 좋다
  1. 디바이스 타임이 섞인 잠금

로그 출력이나 콘솔 함수 (printf나 cout) 는 디바이스 타임 발생
디버깅 목적으로 콘솔 출력 뿌리는데 메모리에 잠금하면 병목 생김

  1. 잠금의 전염성으로 발생한 실수
void func()
{
	lock(list_mutex);
	A* a = list.GetFirst();
	unlock(list_mutex);
	
	a->x++; // 문제가 되는 부분
}

잠금 전염성 : 잠금으로 보호된 리소스에서 얻은 값이나 포인터가 로컬 변수에 있어도 잠금 유지해야 함

합성 가능성의 부족 (lack of composability) : 여러 모듈이나 로직을 쉽게 조립할 수 없는 것.
사용자 A 변수 차감 → 사용자 B 변수 증가인 프로그램 있을 때,
사용자 A도 B도 뮤텍스로 잠가놨어도
송금 프로그램이 둘 이상 스레드 실행할 때, 오작동 가능. (A만 깎고 B 증가 못한다던지)

  1. 잠금 뮤텍스나 임계 영역 삭제

잠금 중인 뮤텍스 삭제하는 실수 종종 한다.
파괴자 함수에 "이미 잠금돼있으면 오류 발생" 넣으면 감지 가능

profile
너 정말 **핵심**을 찔렀어

0개의 댓글