이번 프로젝트에선 멀티플레이 기반의 협동게임을 구상하고 있으면서
포톤을 사용하지 않기에 멀티플레이에 대한 이해와 유니티 넷코드에 대한 정보에 접근하기전
먼저 네트워크에 대해 강의에서 나왔던 내용들을 복습하는 시간을 가졌다.
IP 주소 Internet Protocol
IP라는 주소를 통해서 우리가 인터넷을 사용하는 것은
대부분의 사람이 알고 있지만 무엇을 뜻하는지 잘 모르는 경우가 많다.
일단 약자에 포함된 프로토콜(Protocol) 이 무엇인지 부터 알아봐야 할것이다.
Protocol : 어떤 방식으로 패킷을 보낼 것인지 정의하는 것
여기서 패킷은 네트워크를 통해 전송되는 데이터의 작은 단위를 뜻한다.
큰 데이터를 보낼 때는 패킷이라는 단위로 나누어서 보내는 데
도착하였을 때는 이 패킷을 다시 조립하여 사용하는 방식이다.
즉 IP 는 패킷을 보내는 대표적인 방식 중 하나라고 생각하면 된다.
그런데 왜 주소라고 부를까?
IP 주소는 정말로 우리가 집이 어디있는지 알려주기 위해 사용하는 주소처럼
기기가 어디에 있는 지를 나타내는 것이다.
IP 주소는 한 필드당 0~255의 숫자로 표현하여 8비트의 용량을 가지고 있다.
그리고 주소를 이 필드 4개를 연결하여 사용하는 것이 보편적으로 사용하는 IPv4이다.
199.234.23.123 이런식으로 표현한 숫자의 나열을 한번쯤 본적이 있을 것이다. 이것이 그것에 해당한다.
그런데 이렇게 주소를 사용한다라고 해도 0~255의 제한을 두고 4개까지만 사용하였을 때
기기가 많아질수록 주소가 부족해지지 않을까 사람들은 생각하였다.
그래서 IP필드 6개를 연결한 IPv6라는 것을 만들었는데 이것은 지금 잘 사용되지 않는다.
왜냐면 NAT(Network Address Translation) 라는 기술 때문이다.
NAT는 무엇인가
NAT의 정의는 외부 IP와 내부 IP를 변환해주는 기술이라고 한다.
외부 IP : 인터넷을 사용하여야할 때 외부에서 기기를 식별하는 고유 주소 (예 : 공유기)
내부 IP : 외부 IP 에서 주소를 받았을 때 연결된 각각의 기기를 식별하는 주소 (예 : 컴퓨터 , 휴대폰 등)
기존의 IPv4만 사용하던 방식이 외부 IP를 사용하여 기기마다 고유의 주소를 매기는 것이었다면
지금은 집집마다 존재하는 공유기같은 존재를 외부IP로 지정하고 거기서 외부 IP를 공유받는 기기들을 나타내는 주소는 내부IP로 표현한다.
즉 어떤 IP주소에 대한 요청이 있다면 주소의 주소를 전달해주는 기술이라고 생각하면 될 것 같다.
근데 주소를 찾아갔는데 찾아가기만 해서는 우리가 원하는 정보를 얻을 수는 없을 것이다.
집을 찾아갔는데 집에 들어갈 수 없으면 그건 헛수고이지 않은가
그래서 포트라는 개념이 또 따로있다.
포트
IP주소가 정말로 집주소라면 포트는 집에 살고있는 구성원이다.
만약 서버 IP주소가 192.23.245.123 이런식으로 되어 있다고한다면
192.23.245.123 에서 256번 포트에 기능이 있다면
그것을 요청하기 위해 192.23.245.123 port:256 처럼 주소뿐아니라 포트를 통해 어떤 기능을 원하는지 전달을 하는 것이다.
Well-known Port (일반적으로 알려진 포트) 라고 이름 그대로 보편적으로 사용되는 포트는 1024번 까지 존재한다.
라우팅
라우팅이라는 개념은 주소를 찾아갈 때 걷는 길이라고 보면될 것 같다.
라우팅 : 기기간에 데이터 패킷이 이동하는 경로
라우터 : 패킷이 적절한 목적지로 이동하는 것을 안내한다.
주소를 찾아갈 때 건물과 길들을 거쳐가는 것을 라우팅이라하고
그것을 알려주는 것은 라우터가 네비게이션의 역할을 한다.
UDP와 TCP
TCP (Transmission Control Protocol) : 느리지만 안정적인 패킷 전달 방식
UDP (User Datagram Protocol) : 빠르지만 불안정한 패킷 전달 방식
여기도 프로토콜이라는 단어가 약자에 포함되어 있다.
TCP와 UDP는 패킷을 전달하는 방식이긴 하지만 IP와 다른 점은
IP는 주소를 매겨서 패킷을 전달할 곳을 정하는 거라면
TCP 와 UDP는 IP주소를 통해 데이터를 보내는 것을 말한다.
TCP 방식은 :
데이터를 보낼 때 먼저 받는 쪽에서 받을 수 있는 지 여부를 확인한다.
받는 쪽에서 다시 받을 수 있다고 응답이 오면 그 때 보내는 방식이다.
또한 이렇게 데이터를 보내게 된다면 데이터를 패킷으로 분할해서 보내는 데
패킷으로 분할할 때 일종의 순서를 매겨서 보내준다.
그래서 받는 쪽에서는 순서를 보고 다시 조립하면된다.
UDP 방식은 :
데이터를 일단 보내고 본다.
또한 데이터를 보낼 때 패킷에 따로 순서를 매기지 않고 보내게 되어
받는 쪽에서 또 분류하는 과정이 필요하다.
따라서 TCP는 일련의 과정들이 있기에 처리가 느리지만 데이터의 손실될 위험이 적지만
UDP는 바로바로 보내기에 처리는 빠르지만 데이터 손실의 위험이 있다.
그렇다고 UDP를 아예 안쓰는 게 아닌것이 FPS와 같이 0.1초가 체감이 큰 곳에는 UDP가 좋은 선택지일수도 있다.
그래서 UDP 방식으로 여러번 보내는 경우도 있다고한다.
멀티플레이어 설계 모델
강의에선 일단 두가지의 설계 모델을 보여주었다.
클라이언트 - 서버 모델
서버가 중앙에서 클라이언트의 요청을 처리하고 응답하는 역할을 하며
클라이언트(사용자의 장치)는 서버에 서비스를 요청한다.
Dedicated Server : 멀티 플레이어 게임에서 모든 게임 로직과 플레이어 데이터를
서버에서 관리하고 처리하는 방식
Host - Clitent : 한 플레이어가 게임 호스트와 클라이언트의 역할을 모두 수행하는 방식 (호스트가 클라이언트 역할도 수행하기에 호스트가 지연이 발생하면 참여자들도
처리를 받아야하는 입장이기에 지연이 발생한다)
피어 투 피어(P2P) 모델
중앙에서 조율하는 서버 없이 각 참여자가 서로 직접 통신하는 방식
네트워크 리소스를 효율적으로 사용할 수 있으나 악성 유저의 가능성을 피하기 힘들다.
(경쟁이나 랭킹이 없는 시스템에서나 고려할만한 방식이다)
멀티플레이를 설계할 때 유의할 사항
비동기로직
서버에서 처리되는 로직들은 비동기적으로 처리된다.
결과를 바로 활용하는 것이 아닌 콜백(다른 함수의 매개변수로 전달되어 실행될 때 사용되는 등)
등의 처리 결과를 받아서 이어 처리하는 방식으로 접근해야한다.
즉 바로바로 처리되는 코드가 아니라 주고받고해서 실행되는 코드를 작성해야한다.
틱 빈도
로직을 업데이트하는 빈도이며
프레임과 비슷하게 통신속도에 따라 처리결과가 달라지는 것을 경계하라는 의미이다.
업데이트하는 빈도를 제한하거나 하여 부하를 예방할 필요가있다.
연결해제
네트워크 불안정으로 인해 플레이어가 연결해제되는 부분을 고려해야한다.
재연결 시 어떻게 처리할 지 (재입장, 패배 등)
연결이 끊어졌다는 것을 판단할 기준도 생각해야한다.
정확한 IP 나 UDP TCP의 원리를 알려면
OSI 7계층 등의 추가 개념도 알아야할 것 같지만 지금은 대략적으로 어떤 역할을 한다는 맥락으로 복습을 한 것 같다.
다음은 C#에서 async 와 await에 대해 대략적으로 개념만 알고 있기에 더 파고들어봐야할 것 같다.
모의 면접을 대비해서 객체지향과 생명주기에 대해서 다시 공부하는 시간을 가졌다.
객체지향
준비를 하면서 절차지향에 대해서도 뭔가 많이 겪어봤다면
장단점에 대해 확실하게 알 수 있지 않았을까 생각되는 부분이다.
리팩토링도 그렇고 이런 개념들도 미리 겪은 배경지식들이 있다면 좀 더 잘 흡수하지 않았을까 아쉬운 부분..
지금은 이전에 텍스트 알피지 때 짰던 코드가 약간 절차지향과 비슷하지 않았을까 생각하면서 보았다.
객체지향프로그래밍은 무엇인가?
객체지향은
프로그램을 객체라는 여러 기능이나 형태같은 특성들을 묶은 요소들의 집합으로 보는 것을 말한다.
지금 활용하고 있는 형태는 공통된 요소들의 필드와 메서드를 클래스로 구성하여 사용하는 것을
예로 들수 있을 것 같다.
클래스는 무엇인가?
클래스는 설계도와 비슷한 개념으로 보고 있다.
클래스안의 필드와 메서드는 각각 설계도에 그려진 부품과 부품을 조립하는 과정이라 이해하는 중이다.
그리고 이 설계도를 기반 삼아 만드는 객체는 흔히 인스턴스라고 불린다.
클래스는 객체인가?
나는 클래스도 객체라고 생각한다. 그 이유는 클래스는 자기 자신과 동일한 형태의
새로운 객체를 만들 수 있는 하나의 객체라고 보기 때문이다.
객체지향의 특징 4가지를 아는가?
캡슐화 : 사용자에게 필요한 정보만을 제공하고 그외의 내용물을 숨길 수 있다.
접근제한자를 통해 요청한 클래스에 필수적인 기능만을 제공하는 것은
코드의 의존성을 최소화하고 독립성을 부여한다.
상속 : 부모가 자식에게 재산을 물려주듯이 상위 클래스가 하위 클래스에게
자신의 특징과 기능을 물려줄 수 있다.
상속을 통해 동일한 코드를 여러번 작성하는 것을 효과적으로 줄일 수 있다.
다형성 : 상속의 연장선으로 하위 클래스에서 상위 클래스의 특징을 물려받더라도
하위 클래스마다 다른 특징을 가질 수 있다는 것이다.
Virtual 키워드의 메서드를 Override와 Base.Awake(); 로 상속받는 등의 개념을 생각해볼 수 있다.
추상화 : 상속을 할 때 하위 클래스에서 공통적으로 가져야할 특징들만을 추려서 작성해야한다.
한 하위 클래스에서만 쓰이는 요소가 다른 하위 클래스에도 상속되는 경우는 피해야한다.
객체지향의 5가지 원칙 (SOLID) 에 대해 알고 있는가?
단일책임의 원칙
하나의 클래스에 여러가지 기능을 한번에 구현하는 것이 아닌
기능별로 클래스를 나누어 관리하여야한다는 원칙.
자전거를 구성할 땐 손잡이 바퀴를 안장을 따로 구분하듯 기능별로 역할을 나눈 스크립트를 만든다.
장점 :
가독성이 좋아진다 : 기능별로 스크립트를 나누었기에 보기 편하게 된다.
확장성이 좋아진다 : 하나의 기능으로만 이루었기에 해당 스크립트를 상속받아 확장할 여지가 생긴다.
재사용성이 좋아진다 : 하나의 기능으로 이루어졌기에 여러 부분에서 재사용이 가능해진다.
개방 폐쇄 원칙
확장에는 개방되어 있고 수정에는 닫혀있어야한다는 원칙
원본 코드를 수정하지 않고도 새로운 기능을 추가하는 것을 목표로 한다.
추상클래스와 상속을 적극적으로 사용하여 공용으로 쓰이는 코드를 작성하는 것이 좋다.
리스코프 치환 원칙
상속을 사용할 때 파생클래스가 기본 클래스를 대체할 수 있어야 한다는 원칙
상속을 통해 기능을 상속받더라도 분류에 맞게 범주를 잡아야한다.
따라서 상속을 사용하는 것 이외에도 인터페이스를 통해 다중상속으로 기능을 추가하는 것이
좋을 때도 있다.
인터페이스 분리 원칙
인터페이스를 사용할 땐 작은 단위로 묶는 것을 원칙으로 한다.
모양을 정의하는 인터페이스와 색을 정의하는 인터페이스를 따로만들 듯
일관성있는 기능들로 묶는 형태를 만들어야한다.
의존성 역전 원칙
고수준 모듈이 저수준 모듈에서 직접 가져오면 안된다는 원칙
스크립트에서 다른 스크립트의 기능을 사용할 땐 직접 사용하는 것이 아닌
메서드나 인터페이스를 통해 간접적으로 가져오는 것이 의존성을 줄이고 독립성을 유지하기 좋다.
유니티 생명주기
MonoBehaviour 를 상속받은 클래스에서 선언하였을 때 자동으로 호출되는 메서드를 지칭한다.
씬로드 직후 호출
Awake : 스크립트가 실행될 때 가장 처음 실행되는 메서드 (스크립트가 비활성화 상태라도 작동)
게임 상에서 인스턴스화된 단 한번만 호출된다.
따라서 초기화를 하는 데 좋지만
다른 스크립트의 Awake 문을 고려하여 연결이 필요하다
OnEnable : 스크립트가 활성화된 직후에 호출된다.
실행시 이미 활성화가 되어 있다면 Awake 다음에 호출된다.
실행된 이후 다시 비활성화하고 활성화 되더라도 할때마다 반복적으로 호출된다.
첫 프레임 업데이트 이전에 호출
Start : 첫번째 프레임 업데이트 전에 게임 상 단 한번 호출된다.
즉 Awake 와 OnEnable 이후에 호출되게 된다.
업데이트 관련 메서드 : 지속적인 작업 수행이 필요할 때 사용된다.
Fixed Update : 프레임이 아닌 고유의 주기로 호출이 된다. (설정에서 조작가능)
모든 물리 계산과 다른 업데이트는 이 Fixed Update 이후에 동작되므로
물리연산을 사용할 땐 (Rigidbody.AddForce 등) 이 메서드에 쓰는 것이 좋다.
Update : 프레임 당 한번 호출된다.
프레임은 기기의 성능에 따라 출력이 다르기 때문에
Time.DeltaTime을 통해 프레임이 조절하는 게 필요할 때가 있다.
LateUpdate : 업데이트가 끝난 이후에 프레임 당 한번 호출된다.
Update에서 수행된 계산은 LateUpdate 가 시작되기 이전에 완료되기에
카메라의 조작을 LateUpdate 에서 처리하는 것이 자연스럽게 보인다.
코루틴
코루틴은 주어진 조건(생명주기의 호출) 이 완료될 때까지 실행을 중단할 수 있는 함수다.
yield return null; 처럼 중단점을 만들고 조건에 따라 중단하였다가 다시 수행하는 것을 볼수 있다.
yield (기본적인 형태) : 중단점이 나온다음 Update 함수가 다음 프레임에 호출된 후 계속된다. (즉 1 프레임)
yield WaitForSeconds : 지정한 시간(초)이 지난 후에 모든 Update 함수가 프레임에 호출된 후 계속된다.
yield WaitForFixedUpdate : 모든 FixedUpdate가 모든 스크립트에 호출된 후 계속된다.
yield WWW WWW : 웹에서 다운로드가 완료된 후 계속된다.
yield StartCoroutine : 다른 코루틴을 호출하고 그 코루틴이 끝날 때까지 대기하였다가 계속된다.
충돌 관련 물리 상호작용
OnTriggerEnter : isTrigger 가 체크된 콜라이더와 다른 콜라이더가 겹칠 때 호출된다.
물리적 충돌을 실제로 발생시키진 않고 단순히 호출만 시킨다.
OnCollisionEnter : 콜라이더 끼리 부딪힐 때 호출된다.
물리적 충돌이 실제로 발생하며 충돌된 정보를 제공한다.
그리고 이 두 메서드는 물리연산을 다루기에 FixedUpdate에서 계산이 처리가 된다.
비활성화 시 혹은 파괴시 호출
OnDisable : 스크립트가 비활성화될 때 호출된다.
OnDestroy : 스크립트가 파괴되거나 해당 신에서 이동시에 호출된다.
'TIL' 카테고리의 다른 글
2024 12 06 TIL (0) | 2024.12.06 |
---|---|
2024 12 05 TIL (0) | 2024.12.05 |
2024 12 03 TIL (1) | 2024.12.03 |
2024 12 02 TIL (0) | 2024.12.02 |
2024 12 01 TIL (0) | 2024.12.01 |