강좌/Minecraft Mod 제작2015. 11. 9. 15:51

강의 환경 : forge-1.7.10-10.13.0.1160-1.7.10

강의 목표 :

1. 서버와 클라이언트의 개념을 이해할 있다.

2. 프록시를 이용하여 서버와 클라이언트 코드를 통일적으로 작성할 있다.

 

 

이 강좌에서는 예고했던 대로 프록시에 대해서 다뤄보도록 하겠습니다.

 

프록시를 간단히 설명하자면, 마인크래프트 서버 측과 클라이언트 측의 차이를 해결하기 위한 방법입니다.

 

헌데 모드에서의 서버와 클라이언트는 일반적으로 알고 있는 것과 다르므로 이 부분부터 짚고 넘어가도록 하겠습니다.

 

서버 (Server)

클라이언트(Client)

위치

- 마인크래프트 서버 (항상 활성)

- 마인크래프트 클라이언트

* 싱글플레이 중 활성

* LAN서버가 열려 있을 시에도 활성

마인크래프트 클라이언트

(항상 활성)

도식

역할

* 마인크래프트 월드 전체를 담당

(전지적 시점에서 월드를 컨트롤)

 

* ex) 월드 생성 및 저장,

엔티티(몹) 스폰 및 관리,

블록 상태 관리,

인벤토리 관리,

폭발, 불 등의 기작 제어 등

 

(엔티티에는 플레이어도 포함됩니다.)

* 마인크래프트 월드를 1인칭 시점에서 플레이어에게 보여주는 역할

(클라이언트에도 (관상용) 월드는 존재)

 

*서버의 행동을 그대로 따라하며

결과를 보여줌

 

* 플레이어의 행동은 대부분 패킷을 통해 서버로 전달, 서버와 클라이언트 양측에서 처리

 

* ex) 엔티티/블럭/아이템을 보여줌,

플레이어 자신의 행동 제어(일부)

스레드(Thread)

Server Thread

(* 서버 프로그램에서 Server Thread만 존재)

Client Thread

주요 클래스

관리자: MinecraftServer

월드: WorldServer

관리자: Minecraft

월드: WorldClient

 

한편 서버도 서버 프로그램에서 작동되느냐, 클라이언트와 같이 작동되느냐에 따라 달라지는데요,

 

각각을 Integrated Server, Dedicated Server라 합니다.

 

이들은 다음과 같은 차이점을 가집니다.

 

차이점

Integrated Server

Dedicated Server

위치

Minecraft.exe에서 월드가 열려 있을 때 활성

(싱글플레이, LAN 서버)

MinecraftServer.jar 와 같은 서버 프로그램

(실행시 항상 활성)

클라이언트와의 관계

동시에 같이 존재 (Thread를 달리함)

같이 존재하지 않음

세이브 데이터 위치

프로파일 위치(보통 .minecraft)

서버 프로그램 위치

(추가 예정)

  

 

그러니까 싱글플레이, LAN 서버, 멀티플레이 각각에서 다음과 같이 존재합니다.

 

서버측

클라이언트측

싱글플레이(SSP)

Integrated Server

O

LAN서버

Integrated Server

(LAN으로 플레이어 입장 가능)

O

멀티플레이(SMP)

Dedicated Server

(Minecraftserver.exe)

(Minecraft.exe)

 

(참고로, 한 프로그램 내에서 돌아가는 서버와 클라이언트조차도 서로 통신을 합니다. 일관성을 유지하기 위해서죠)

 

 

다시 모드 제작으로 돌아가봅시다.

 

만약 마인크래프트가 싱글 혹은 LAN 플레이만 지원하거나, 아니면 멀티플레이만 지원했다면,

 

모드를 제작할 때에도 서버와 클라이언트, 즉 Server Side와 Client Side의 구분만 잘 해주면 되었을 것입니다.

 

문제는 마크가 둘 다 지원한다는 데에 있습니다. 그에 따라 모드 제작에서도 Integrated Server와 Dedicated Server 환경이 모두 존재하게 됩니다.

 

그래도 모장과 포지 제작팀은 저 둘을 할 수 있는 만큼 비슷하게 만드려는 시도를 합니다.

 

하지만 클라이언트가 같이 돌아가는 경우와 그렇지 않은 경우는 차이가 날 수밖에 없었습니다.

 

결국 결론은, 모드 제작에서도 이를 고려해주어야 한다는 겁니다. 그리고 이 부분에서 항상 많은 실수와 오류가 발생하죠.

 

그나마 다행인 점은 고려해주어야 하는 경우가 그리 많지는 않다는 것입니다.

 

 

포지 팀도 이런 애로사항을 잘 알고 있었고, 그러한 경우들을 통합적으로 관리하기 위한 도구를 하나 창조하였습니다.

 

그 도구가 바로 '프록시(Proxy)'입니다!

 

주의: 여기서의 Proxy는 넷상의 프록시 서버와는 다른 개념입니다!

 

프록시는, 일반 마인크래프트 프로그램(Minecraft.exe)과 마인크래프트 서버 프로그램(주로 Minecraft_Server.exe) 를 구분해서,

 

두 경우에 서로 다른 코드를 동작하도록 해주는 도구입니다. (정확히는 각 경우에 대해 작동하는 클래스를 바꾸어 줍니다.)

 

이러한 프록시는 주로 다음의 문제를 해결하기 위해 쓰입니다.

 

# Dedicated Server에서도 클라이언트용 소스가 남아있는 문제

 

일반적인 모드는 클라이언트와 서버 양측 모두를 제어합니다. 그런데 이 경우, Dedicated Server에서 치명적인 문제가 생길 수밖에 없습니다.

 

모드 파일은 하나이지 둘이 아니다보니, 이것으로 Dedicated Server와 Integrated Server 모두를 돌려야 한다는 데에서 문제가 생깁니다.

 

Integrated Server 환경에서는 서버와 클라이언트가 모두 필요하니, 모드 파일에서도 둘 모두를 지원해야 하는 반면,

 

Dedicated Server에서는 마인크래프트 서버만 있고 클라이언트가 없기 때문에 사용되지 않는 클라이언트 부분은 무용지물이 됩니다.

 

그 뿐만 아니라, 클라이언트용 마인크래프트 코드가 없기 때문에 심지어 크래시를 터뜨리게 되죠.

 

바로 여기서 프록시가 해결사로 등장합니다.

 

프록시는 일반 마인크래프트 프로그램과 마인크래프트 서버 프로그램을 구분해준다고 했었죠?

 

여기서 일반 마인크래프트 프로그램은 Integrated Server 환경이고, 마인크래프트 서버 프로그램은 Dedicated Server 환경인 것을 생각하면, 답이 나옵니다.

 

즉 프록시는 각각의 환경에 대해 실행되는 코드를 다르게 해주는 역할을 하는 것입니다.

 

더 자세하게는, 클라이언트에서만 실행되어야 하는 코드 (ex. 렌더링 관련)의 경우에 이 위치에 놓이게 됩니다.

 

이제 코드를 통해 직접 그 사용법을 알아보도록 합시다.

 

 

갑자기 많은 코드가 추가되었죠? 하나 하나 설명을 해 드리겠습니다.

 

@Instance

모드 (기반) 클래스의 인스턴스를 지정해 주는 어노테이션입니다. 이렇게 지정된 전역변수는 그 모드 클래스의 인스턴스로서 기능하게 되죠.

항상 이 인스턴스를 이용하여 모드 클래스에 접근하면 됩니다. 이렇게 하지 않고 인스턴스를

 

@Instance(value=MODID)

public static (모드 클래스) instance;

 

이런 식으로 선언하시면 됩니다. 저의 경우 모드 아이디가 "abrtutorial" 이므로 이 값을 넣었습니다. 이런 식으로 넣어도 되지만

AbrTutorial.MODID 이런 식으로 전역변수로 선언했던 모드 아이디를 대입해도 됩니다.

 

 

@SidedProxy

이 글에서 계속 이야기했던, 바로 그 프록시를 제시해 주는 어노테이션입니다. 다음과 같이 선언합니다.

 

@SidedProxy(clientSide="(클라이언트 프록시 클래스의 경로)", serverSide="(서버 프록시 클래스의 경로)")

public static (프록시 클래스) proxy;

 

여기서, '클래스의 경로'에 들어가는 것은 클래스가 위치한 패키지까지 모두 표시한 클래스의 이름을 나타내는 문자열입니다.

영어로는 Fully Qualified Name 이라고도 합니다.

 

또한, 클라이언트 프록시와 서버 프록시는 사이드 자체와는 관련이 없음을 주의하셔야 합니다.

클라이언트 프록시는 일반 마인크래프트용, 즉 Integrated Server에서 작동하는 프록시입니다.

반면 서버 프록시는 마인크래프트 서버용입니다. Dedicated Server에서 작동하는 프록시이죠.

 

어쨌든, 이렇게 프록시는 기본적으로 클래스로 구현됩니다.

그리고 환경에 따라 프록시 인스턴스에 다른 프록시 클래스가 적용되는 것이죠.

 

원칙적으로는 프록시의 기반 클래스로 BaseProxy를 두고, 클라 프록시를 ClientProxy, 서버 프록시를 ServerProxy로 두어야 하지만,

일반적으로는 서버 프록시를 따로 둘 필요가 없기 때문에 이를 CommonProxy로 두고,

클라 프록시를 ClientProxy로 두어 CommonProxy를 상속받게 됩니다.

 

그래서 저도 마찬가지로 abr.tutorial 패키지에 CommonProxy와 ClientProxy의 두 프록시 클래스를 만들고, 저렇게 사용한 것입니다.

 

정말 특별한 모드를 제작하시는 것이 아니라면, 저처럼 그대로 따라하셔도 거의 문제가 없습니다.

 

 

그러면 이제 정말로 프록시를 쓰는 법을 알려드리도록 하겠습니다. 직접 써 보면 더 이해가 잘되겠죠?

 

먼저 어떻게 써야 하는지를 생각해 봅시다.

프록시의 가장 중요한 용도는, 클라이언트에서는 꼭 필요하지만 서버에서는 작동하지 않는 코드를 프록시에 위치시키는 것이라고 했었죠?

 

그 예를 살펴보자면, 우선적으로 렌더링이 있습니다.

 

렌더링이란, 프로그래밍에서의 '그리기' 에 해당하는 개념인데, 바로 우리가 보는 화면을 그려주는 것이죠.

 

우리도 우리가 만든 블럭이나 아이템, 몹 등을 직접 그려줘야 할 일이 있을지도 모릅니다.

 

그러기 위해서는 렌더링을 도맡아 해줄 '렌더러(Renderer)' 내지 '렌더링 핸들러(Rendering Handler)' 객체가 필요하게 되고,

이 객체들을 포지에 등록(Register) 해줄 필요도 생기죠.

 

문제는, 역시 이들이 클라이언트에서만 작동한다는 것입니다. 그러므로 등록(레지스터) 작업도 당연히 프록시에서 이루어져야 하죠.

 

그러면 이 부분을 개략적으로 프록시로 구현해보기로 합시다. 우선 프록시에 렌더러들을 등록할 메서드를 하나 새로 만들어보죠.

 

CommonProxy.java:

 

ClientProxy.java:

 

여기서 CommonProxy는 마크 서버용, ClientProxy는 일반 마크용이므로,

 

렌더러 등록(레지스터) 은 당연히 ClientProxy에서만 하게 될겁니다.

 

이제 다음과 같이 모드 클래스의 init 메서드로 하여금 이 메서드를 호출하게 하면 됩니다.

 

물론 이렇게 하면 단순히 init 메서드에서 등록해준 경우와 동일하겠지만, 서버에서 크래시를 내지는 않겠죠!

 

 

여기까지 사이드 개념과 Integrated/Dedicated 환경 개념을 알아보고, 프록시를 이용하는 법을 알아보았습니다.

 

다음 강좌에서는, 본격적으로 블럭을 만들어 보도록 하겠습니다!

Posted by Abastro