마인크래프트/모드/개발

마인크래프트 주요 문서
게임 진행
관련 문서
명령어마법부여
아이템회로업적
바이옴구조물차원
진행 외
주요 문서
모드리소스팩
멀티플레이플러그인서버 구동
출시된
에디션
포켓에디션콘솔에디션
스토리 모드에듀케이션 에디션
관련 문서튜토리얼여담
커뮤니티닉네임 스킨
개발 관련기초모드
플러그인ModPE
업데이트PC 업데이트 내역PE 업데이트 내역

1 개요

마인크래프트 플레이의 끝장판 더 이상은 단순 플레이가 아니다. 게임의 뿌리를 살펴보고 수정하는 일이다. 당연히 프로그래밍 실력은 물론 마인크래프트라는 게임에 관한 이해도 또한 요구된다.[1]

자바로 짜인 마인크래프트는 포지의 표준 API를 바탕으로 제작할 수 있으며 마인크래프트 표준 개발에서 현재 가장 안정된 버전인 1.7 이상과 모드의 가장 표준적 API인 forge를 기반으로 작성한다. 이후 non-forge 스타일의 모드 개발은 추가 예정. 또한 현재 작성되지 않은 다른 기법들 또한 있으면 추가 바람.

적어도 제네릭과 자바 언어를 자유롭게 다룰 정도의 지식이 필요하다. 만약 자바 문법에 관해 중수 이하의 실력으로 부족할 거 같다는 생각이 든다면 자동 개발 툴을 이용해 보는 것도 방법이다.

문서 작성은 개발 커뮤니티에서 주로 사용하는 튜토리얼 형식으로 작성하여 이용자들의 이해도를 높인다.

2 개발 도구 설치

서로 엇갈리는 설명을 방지하기 위해 문서에서 다루는 툴은 eclipse로 통합한다. non-eclipse 개발도 있는데?

2.1 자바 표준 개발환경 구축

jdk
eclipse

먼저 해당 링크에서 jdk를 설치한다. 만약 윈도우 환경에서 개발할 경우 환경 변수를 추가해야한다. 이는 다음 절차를 참조.

제어판 → 시스템 밎 보안 → 시스템 → 고급 시스템 설정 → 환경 변수 → 사용자 변수에 추가 → jdk가 설치된 폴더의 bin을 참조시킨다.

그 다음 명령 프롬프트에서 명령어 java -version과 javac를 입력해 작동 여부를 확인한다.

마지막으로 이클립스를 설치한다. 이클립스는 설치 형식이 아니라 압축 파일에서 압축을 풀어서 사용하는 툴이니 원하는 폴더에 압축을 풀어 바로 사용하면 된다.

2.2 forge API

forge API

포지 API의 설치 방식도 1.6에서 1.7로 넘어오면서 큰 변동이 일어났다. 여기서는 1.7 이상을 다룬다.

먼저 해당 버전의 src를 다운받는다. 다운받은 파일 역시 이클립스처럼 원하는 폴더에 압축을 풀면 된다. 1.6과 다르게 1.7부터는 gradlew라는 설치기를 바탕으로 툴이 변경돼서 모든 관리를 명령어로 해야 한다.

기본적으로 필요한 명령어의 목록이다. 유닉스 계열(맥OS, 우분투 등)들은 앞에 ./을 삽입해 명령어를 실행한다.

setupDecompWorkspace : 디컴파일된 개발환경 구축
eclipse : 구축된 개발환경을 이클립스의 개발방식으로 정돈
build : 모드를 컴파일한다
runClient : 마인크래프트에 모드를 적용해 실행시킨다

이들 중에서 setupDecompWorkspace 명령어로 개발환경 구축을 한다. 이때 인터넷이 느리거나 컴퓨터 CPU, 하드디스크 성능이 받쳐주지 못할 경우 설치시간이 영 좋지 않다(...) 컴퓨터도 열심히 설치를 하는데 인내심을 갖고 기다려주자.

gradle을 이용한 워크스페이스인 만큼 만약 내장된 tasks(명령어들)들이 궁금하다면 gradlew tasks를 이용해서 확인 해볼 수 있다.

2.3 개발 도구 통합과 마인크래프트 설치

개발 환경을 기반으로 정리 정돈을 해야 한다. 보통 이클립스를 사용하나 정 원한다면 src 폴더에서 직접 하드코딩을 한 후 gradlew의 명령어로 디버깅, 실행, 빌드 또한 가능하며 gradle 플러긴을 적용한다면 넷빈즈 또한 사용 가능하다. 사실상 gradlew와 호환된다면 거의 대부분의 IDE와 호환된다.

일단 모드를 개발할 워크스페이스(폴더)를 만들어야 한다.
단, 폴더의 경로에 한글이 포함되어서는 안된다. 경로에 한글이 있으면 영 좋지 않다(...)

그 후 해당 폴더에서 shift+우클릭을 이용해 "여기서 명령 창 열기(w)"를 이용한 후 gradlew 관련 명령어를 실행하여 개발 환경을 설치하자.
보통 간단하게 gradlew에서 eclipse 명령어를 사용한다.

그 다음 이클립스를 켠다. 만약 이클립스를 처음 사용한다면 개발 공간을 할당하라는 창이 뜰 것이다. 만약 이미 공간을 할당했을 경우에는 File -> Switch Workspace로 변경할 수 있다. 방금 forge API를 설치한 폴더에 eclipse를 할당한다.

최종적으로 이클립스에 초록색 벌레모양의 버튼(디버그)를 클릭한다. 마인크래프트가 정상적으로 로딩되며 포지의 설치가 확인될 경우 최종적으로 개발환경 구축이 끝난다. 디버그와 실행버튼의 차이는 디버그는 코드를 수정하면 즉시 반영된다. [2]

최종적으로 일반적으로 사용하는 마인크래프트를 설치한다. 당연히 필수는 아니며 설치를 권장할 뿐이다. 용도는 최종적인 모드 테스트와 버그를 찾는 용도. 직접 모드를 플레이하며 버그를 찾아다니면 분명히 예상하지 못한 버그들을 찾을 수 있다.

3 본격적인 모드 개발

준비는 완전히 끝났다. 이제부터 필요한 것은 오직 자신이 해낼 수 있다는 의지 랑 믿음뿐.

4 패키지

일단 개발을 위해 패키지 선언은 필수적이다. 자신의 모드의 자존심이라고 봐도 과언이 아닌 것이 바로 패키지이니 좋아하는 이름을 넣어 선언한다. 보통 자신의 홈페이지 도메인을 첫 번째로 사용하고 두 번째로 자신의 이름 또는 닉네임을 넣는 방식으로 추가한다.

예제

<syntaxhighlight lang="java" line="1">
wiki.bluesky.main
</syntaxhighlight>

하지만 원한다면 기존 워크스페이스에서 만들어진 패키지를 벗어나서 작성할 수 있다. src폴더를 나가지 않으면 어떠한 패키지도 사용할 수 있다.

또한 패키지는 각종 모드 개발에 요구되는 파일들을 정리해 관리하는 역할도 담당한다.

다음 패키지는 꼭 생성해야한다.

assets : 모든 필요한 리소스들이 여기에 들어간다. 모드의 소스폴더와는 다른 resources라는 폴더에 생성한다.
assets.<모드 ID> : 이곳에 각종 폴더를 생성하여 마인크래프트 내부에 파일들을 전달한다. 여기서부터 오타가 나기 쉬우니 주의해야한다.
assets.<모드 ID>.textures : 아이템, 블록 등의 자료들이 들어간다.
assets.<모드 ID>.models : 웨이브 프론트를 이용해 3D 모델을 삽입할 때 사용할 obj파일들이 들어간다.
assets.<모드 ID>.lang : lang파일들이 들어간다.
assets.<모드 ID>.shaders : minecraft super secret settings에 들어갈 쉐이더 파일들이 들어간다. 확장자는 추가바람
assets.<모드 ID>.textures : 각종 텍스쳐들이 들어간다.
assets.<모드 ID>.sounds : 각종 효과음들이 들어간다.

textures에 들어가는 하위 폴더들은 각각 문단에서 서술한다. 지금은 textures까지만 만들어둬도 무방하다.

페키지엔 가능하면 대문자를 사용하지 않도록 한다.

5 Lang 파일

원래 과거 포지에서는 LanguageRegistry 클래스를 이용해 각종 요소들의 게임상 이름을 등록했다. 그러나 유연한 언어적응 능력은 물론이고 프로그래밍에 관한 지식이 없으면 이름을 수정할 수도 없었다. 또한 거대한 메인클래스를 이름 변경이 요구될 때마다 다시 컴파일 해야했었다.

그래서 최근엔 이쪽 라이브러리에 대부분 Deprecated 어노테이션(annotation)이 붙어버리고[3] 그 대신 Lang 파일을 사용하여 이름을 정의하는 방식이 가장 많이 사용된다.

먼저 Lang 파일을 이용해 이름을 추가하기 위해서는 assets.<모드 ID> 다음에 lang 파일을 생성한다. 파일 이름은 국가별로 정해지는데 다음은 그 목록이다링크. 그 후에 UTF-8로 인코딩한 텍스트 파일로 저장한다. 그리고 다음과 같이 내용을 만들고 저장한다.

<표기되는 게임상 이름[4]>=<추가할 이름>

이 때 절대로 '=' 앞/뒤에 공백을 만들면 안된다. 툭하면 공백을 넣는 게 습관인 프로그래머들 입장에서는 넣었다 다시 지우는 경우가 많다.

만약 이름이 추가되지 않을 경우에는 한번 엔터를 눌러 줄을 내려주는 것도 통할 수 있다.

6 메인 클래스

포지 기반의 모드가 작동하는 양식을 보면 다음과 같다.

lwjGL을 바탕으로 모든 개발이 시작되며 마인크래프트는 이 API의 표준 양식을 따른다. 이렇게 엔진이 마련되고 각 엔진에 클래스를 만들어 추상화한다. 이렇게 추상화된 클래스를 상속하고 또 역으로 등록하여 엔진에 클래스를 추가한다. 즉, 상속받은 클래스 하나가 게임 속의 요소를 정의하는 하나의 객체가 된다. 객체지향 언어에서는 흔하게 볼 수 있는 모양새다.

마인크래프트 모드 개발에서도 당연히 똑같은 현상이 발생한다. 어노테이션을 이용해 외부에 있는 모드 파일이 마인크래프트 속으로 삽입되도록 유도한다. 이때 모든 파일과 클래스를 통합하여 관리하기 위해 만들어지는 클래스가 있는데 이것이 바로 메인 클래스다.

메인 클래스는 다음과 같이 선언된다.

모든 메인클래스는 포지와 연결되기 위해 mod 어노테이션을 사용한다. 이 어노테이션을 추가하면 그 아래에 해당된 문장들과 클래스들은 전부 마인크래프트 모드에 해당된다는 말이다. 이곳을 시작으로 모드가 로딩되기 시작한다. 만약 다른 모드를 디컴파일해 분석하면 이 부분을 찾아서 들어가보자. 또한 이를 위해서 반드시 cpw.mods.fml.common.Mod를 임포트 해야한다.

<syntaxhighlight lang="java" line="1">
import cpw.mods.fml.common.Mod;

@Mod(modid=<모드 아이디>, name=<모드 이름>, version=<모드 버전>)
</syntaxhighlight>
이 때 modid를 빼고 나머지 부분들은 필요할 경우에 추가한다. 하지만 이름과 버전등은 관리와 배포에 중요한 역할을 하기 때문에 삽입할 것을 권장한다. 어노테이션에 들어갈 변수들은 정적 상수로 메인클래스에 선언해 넣는다.

이렇게 어노테이션을 삽입하고 다음으로 클래스를 선언한다. 보통 클래스명은 자신의 모드 이름 또는 그 뒤에 Main이라는 단어를 삽입해 생성한다.

그리고 Instance 어노테이션을 이용해 해당 모드의 메인 클래스를 가리키는 필드를 관리한다.

<syntaxhighlight lang="java" line="1">
@Instance(value = <모드 아이디>)
</syntaxhighlight>

마지막으로 이벤트 EventHandler 어노테이션을 이용해 모든 이벤트 발생과 처리를 관리한다.

<syntaxhighlight lang="java" line="1">
@EventHandler
</syntaxhighlight>
이 때 모든 이벤트 핸들러 어노테이션에는 함수가 필요하다. 모든 함수는 파라미터로 FMLPreInitializationEvent, FMLInitializationEvent, FMLPostInitializationEvent을 갖고 있는 상태로 선언한다. 리턴값은 없다.

이 또한 cpw.mods.fml.common.Mod.EventHandler를 상속받아야한다. 또한 각 파라미터의 객체가 되는 클래스 또한 상속받아 사용한다.

예제

<syntaxhighlight lang="java" line="1">
import cpw.mods.fml.common.Mod.EventHandler;
import cpw.mods.fml.common.event.*;

//클래스 내부

@EventHandler
public void perInit(FMLPreInitializationEvent paramPerEvent) {}

@EventHandler
public void init(FMLInitializationEvent paramEvent) {}

@EventHandler
public void postInit(FMLPostInitializationEvent paramPostEvent) {}
</syntaxhighlight>

이렇게 최종적으로 메인클래스 선언이 완료됐다. 마인크래프트를 당장 실행시켜보면 추가된 모드중에 자신의 모드가 보일 것이다.

예제

<syntaxhighlight lang="java" line="1">
package wiki.bluesky.main;

import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.Mod.EventHandler;
import cpw.mods.fml.common.Mod.Instance;
import cpw.mods.fml.common.event.*;

@Mod(modid=NamuMain.MODID, name=NamuMain.NAME, version=NamuMain.VERSION)
public class NamuMain {
public static final String MODID = "modNamu";
public static final String NAME = "project Namu";
public static final String VERSION = "testing";

@Instance(value=NamuMain.MODID)
public static NamuMain namuobj;

@EventHandler
public void perInit(FMLPreInitializationEvent paramPerEvent) {}

@EventHandler
public void init(FMLInitializationEvent paramEvent) {}

@EventHandler
public void postInit(FMLPostInitializationEvent paramPostEvent) {}
}
</syntaxhighlight>

마지막으로 클라이언트와 서버 프록시를 추가하여 보이는 부분, 작동하는 부분을 따로 작성할 수 있도록 클래스를 선언하면 된다. 프록시에 관한 부분은 후술하도록 한다.

7 클라이언트와 서버 사이드

마인크래프트는 까다로운 클라이언트/서버 시스템을 가지고 있는데, 클라이언트(Client)와 서버(Server)가 각자의 사이드를 나눠 서로 다른 코드를 수행하고 있다는 것이다.

만약 서버가 렉으로 멈추어도 유저들은 서버에 반영은 되지 않겠지만 움직이거나 상자를 여는 등의 특정 동작을 수행할 수 있고, 반대의 경우인 클라이언트인 마인크래프트 런처가 렉으로 인해 플레이어 입장에선 클라이언트가 멈춰보여도 몬스터가 플레이어를 때린다는 일련의 동작은 그대로 수행되어 데미지가 전달돼 죽는 것이 바로 위와 같은 특성때문에 생겨나는 현상의 예이다.

이 개념은 우리가 일반적으로 아는 클라이언트와 서버와 거의 비슷한데, 클라이언트 사이드는 말 그대로 마인크래프트란 게임의 클라이언트인 런처 자체를 뜻한다. 즉, 클라이언트가 가지는 사이드에서 수행되는 코드는 단순히 플레이어의 화면에 아이템 아이콘을 띄운다거나, 어떠한 GUI의 동작 과정을 띄우는 코드나 키 인식을 반영하여 플레이어의 위치를 움직이는 코드 등과 같이 플레이어가 현재 플레이하고 있는 클라이언트 내에서만 수행되는 코드를 뜻한다.

서버 사이드에서 수행되는 코드란 말그대로 이 코드를 수행하는 주도권이 플레이어의 클라이언트가 아닌 서버에게 있다는 것을 의미한다.[5] 화로같이 특정 동작을 수행하는 타일 엔티티의 동작 과정이나 몬스터, 동물과 같은 엔티티의 움직임과 같은 동작 코드는 보통 이 쪽에서 많이 수행된다.

물론 서버 쪽에서 수행되는 코드는 적고 대부분의 경우가 클라이언트와 코드를 공유하는 경우가 많기에 클라이언트/서버가 같이 공동으로 수행할 코드는 공통으로 묶은 뒤, 특정 사이드에서만 수행될 코드를 따로 편성해 공통적으로 묶은 코드들에 상속하는 경우가 대다수이며 이를 커몬 사이드(Common Side)라고 부른다.

각 사이드의 코드에 대한 특성을 살펴보면 클라이언트 사이드에서 수행되는 코드들은 대부분 스피커나 키보드와 같이 플레이어의 컴퓨터 하드웨어를 이용하는 부분이 많고, 서버(커몬) 사이드에서 수행되는 코드는 아이템이 구워진다던가, 곡괭이로 광석을 캐내면 아이템이 나온다는 등의 실제 과정을 수행하는 부분이 많다.

7.1 @SideOnly(Side.?)

<syntaxhighlight lang="java" line="1">
public void registerBlockIcons(IIconRegister p_149651_1_)
{
this.blockIcon = p_149651_1_.registerIcon(this.getTextureName());// 마인크래프트 내 Block 클래스에서 실제 블럭의 텍스쳐를 등록하는 메소드이며, 이 메소드는 클라이언트 사이드 쪽에서만 수행할 수 있다.
}
</syntaxhighlight>
간혹가다 위와 같이 @SideOnly(Side.?) 형태의 어노테이션을 붙여준 코드가 있는데, 이는 위에서 설명한 사이드 별로 수행되는 코드가 다르다는 것의 한 예이다.
이 어노테이션이 붙었다는 의미는 컴파일 시에 해당 사이드가 아니라면 컴파일하지 말라는 의미를 지니기에, 만약 이 어노테이션이 붙었다면 해당 사이드가 아닌 곳에선 코드를 찾아내지 못한다. 따라서 사이드 별로 수행될 코드를 분리하는 역할을 할 수 있는 것이다.

하지만 해당 어노테이션은 모더들이 직접 사용하는걸 권장하지 않는데, 그 이유는 모더가 이 어노테이션을 잘못 사용하면 해당 사이드가 아닌 곳에서 컴파일 때 무시된 코드로 접근할 수 있어 오류가 나기 십상이고, 코드가 난잡하게 꼬일 수 있다는 문제가 생기기 때문이다. 따라서 위 어노테이션을 직접 사용하기보단 worldObj 클래스의 isRemote 메소드나 FMLCommonHandler의 인스턴스를 받아와 getEffectiveSide 메소드를 호출하는 등과 같은 사이드를 가려주는 메소드를 사용하거나 밑과 같이 프록시를 이용하고, 위 어노테이션은 조상 클래스에서 오버라이딩 하고싶은 메소드에 어노테이션이 붙어있을 때만 사용하는 것을 권장한다.

7.2 프록시

위와 같은 마인크래프트의 서버/클라이언트 시스템을 획기적으로 조작할 수 있는 기법이다.

거의 대부분의 모드 개발자들은 습관처럼 프록시를 나눠 사용하는데, 첫번째 이유는 이는 모드 개발자가 모델링 오브젝트 파일을 등록하여 자신만의 엔티티 모델을 렌더하거나, GUI, 파티클을 띄우는 등의 렌더링 작업을 담당하는 상당수의 코드가 클라이언트와 서버 사이드의 코드를 분류하여 사용하기 때문이다. 특히 위와 같은 @SideOnly 어노테이션이 붙은 코드같은 경우에는 자기 사이드가 아니면 컴파일 시 코드를 전부 무시해버리기 때문에, 자칫 제대로 분류하지 않으면 서버 사이드 측에서 컴파일 시에 무시된 클라이언트 사이드 측 코드가 불러와지는 등의 오류가 잦아질 수 있다. 이러한 현상을 예방하기 위해 프록시를 이용하여 미리 사이드 별로 불러올 함수를 나눠 모딩을 하는 것이 첫번째 이유이다.

두번째 이유는 바로 메인 클래스의 번잡성을 줄이기 위해서 프록시를 사용하는데, 만약 아이템이나 블럭을 추가하기 위해 아이템이나 블럭의 등록을 담당하는 GameRegistry 클래스를 불러와서 메소드를 친다고 한다면 아이템이나 블럭의 수가 많지 않다면 그리 문제가 되지 않을 것이다. 하지만 모드에서 추가하려는 아이템이나 블럭이 많아질 수록 메인 코드의 줄 수는 계속 늘어날 것이고 이는 코드가 번잡해져 유지보수에도 매우 안 좋을 뿐만 아니라 다른 함수를 불러오는데도 지장이 생길 수 있다.

각 사이드마다의 다른 부분들처럼 사용 비중이 압도적으로 많은 부분은 아니지만 모드의 규모가 커질 수록 사용 확률이 점점 높아지므로 설령 쓸 일이 없더라도 개발하기 이전에 꼭 준비 해두도록 하자.

프록시에 대한 실질적인 설명을 하자면 프록시를 통해 호출한 함수는 만약 호출한게 서버라면 커몬(Common) 프록시 클래스의 함수가 호출되고 만약 클라이언트라면 클라이언트(Client) 프록시 클래스의 함수가 자동으로 호출된다.
만약 Minecraft 코드 등에 @SideOnly 어노테이션이 있어서 서버와 클라이언트 둘다 이용하는 클래스 접근에 문제가 생긴다면 사용하는 것이 좋다.
단, 클라이언트(Client) 프록시 클래스에서 커몬(Common) 프록시 클래스의 함수를 오버라이딩하지 않고 프록시를 호출할 경우 당연히 부모 클래스인 커몬(Common) 프록시 클래스의 함수가 호출된다.

먼저 클래스 2개를 하나는 Common이라는 키워드를, 하나는 Client라는 키워드를 포함하여 선언한다. 이름은 사실상 중요하지 않으나 구별을 위하여 두 키워드를 사용한다.

그런 다음 Client가 Common을 상속하도록 한다.

이제 메인 클래스에 @SidedProxy() 어노테이션을 호출하여 다음과 같은 프로토타입으로 Common의 객체를 만든다.

<syntaxhighlight lang="java" line="1">
@SidedProxy(clientSide="<Client의 위치>", serverSide="<Common의 위치>" )
public static CommonProxyClass objCommonProxy;
</syntaxhighlight>
이것으로 프록시를 준비하는 일은 끝났다. 텅 빈 클래스 2개를 어디다 써먹는가 의문을 품을 수도 있지만 프록시는 사용될 확률이 매우 높은 기술이니 사용되는 일이 있을 경우 후술하도록 한다.

예제

<syntaxhighlight lang="java" line="1">
//메인 클래스 내부
@SidedProxy(clientSide="wiki.bluesky.main.ClientProxyClass", serverSide="wiki.bluesky.main.CommonProxyClass")
public static CommonProxyClass objCommonProxy;
</syntaxhighlight>
클라이언트(Client) 프록시

<syntaxhighlight lang="java" line="1">
package wiki.bluesky.main;
public class ClientProxyClass extends CommonProxyClass {}
</syntaxhighlight>
커몬(Common) 프록시

<syntaxhighlight lang="java" line="1">
package wiki.bluesky.main;
public class CommonProxyClass {}
</syntaxhighlight>

8 블록 만들기

대부분의 모더들이 처음으로 밟고 지나가는 절차이며 가장 마인크래프트의 기본적인 부분을 다루며 가장 눈으로 확인하기 쉬운 부분이기도 하다. 블록을 추가하는 일 또한 앞에서 서술했던 마인크래프트의 양식을 따라가야한다. 자바 문법에 벗어나지 않는 한에서 원하는 문법을 전부 적용 가능하다. 어나니머스 이너 클래스나 로컬 클래스, 이너클래스 등등 실험용 블록들은 이렇게 만들어 놓고 필요 없어지면 버리기도 한다. 데이터 관리를 위해서 정식으로 게임에 영향을 주게될 블록은 반드시 일반 클래스로 만들도록 하자.

모든 블록은 Block클래스를 상속받아 그 안의 함수를 호출 혹은 재정의하여 마인크래프트에 추가된다. OOP의 매우좋은 예시 중 하나다. 상속된 자식은 부모의 위치에 들어가도 문제가 없어야 하며 부모의 함수를 똑같이 선언하면 그것을 오버라이딩이라 한다.

먼저 새로운 블록이 될 클래스를 생성/선언하여 Block을 상속받도록 한다.

부모 클래스가 되는 Block은 모든 블록의 프로토타입이며 게임에 직접 집어넣을 수 없다. 대신에 Block이라는 자료형을 담당하고 해당 자료형에 클래스들이 반드시 꼭 갖고 있어야하는 값들을 관리하는 용도의 클래스다. 예를 들어서 블록이 내는 빛, 블록의 딱딱함은 이러한 값들로 관리되며 자식 클래스에서 불편하게 새로 선언할 필요가 없다.

상속을 했으면 당연히 생성자를 만들어 내라고 오류를 뿜는다. Block의 생성자는 매개변수로 Material을 받는다. 이걸 받아서 부모의 객체에 넣는다

그리고나서 꼭 필요한 값들을 관리한다. 이 방법엔 2가지 방법이 있는데 Block의 생성자(또는 다른 함수)에서 관리하거나 메인 클래스에서 관리하는 방법이 있다.

이렇게 클래스가 완성되면 메인 클래스에서 객체를 만들어준다.


<syntaxhighlight lang="java" line="1">
//블록을 만들고 해당 블록을 어떠한 크리에이티브 탭에 추가할지 정하기.

//메인 클래스 안에서 값을 관리하기

//생성한 Block의 객체에 자식 객체를 넣었다. 어떻게 이런 구문이 정상작동 하는지는 후술.
public Block namuBlock = new BlockNamu(Material.glass).setCreativeTab(CreativeTabs.tabBlock);

//새로운 블록 클래스 안에서 관리하기
public BlockNamu(Material paramMatreial) {
super(paramMatreial);
setCreativeTab(CreativeTabs.tabBlock); //블록 클래스 안에서 부모의 setCreativeTabs를 호출한다.
}
</syntaxhighlight>

마지막으로 블록을 등록한다. 블록을 등록하는 일은 이벤트 핸들러(EventHandler)에서 처리한다. 앞에서 미리 만들어둔 메인 클래스의 함수 안에서 블록을 등록하는 일을 하면 된다.

메인 클래스의 이벤트 처리 함수 중에서 FMLInitializationEvent라는 자료형을 파라미터로 선언한 함수에 다음처럼 문장을 삽입한다.


<syntaxhighlight lang="java" line="1">
import cpw.mods.fml.common.registry.GameRegistry;

//클래스 내부

GameRegistry.registerBlock(<새 블록 클래스의 변수>, "<이름>");
</syntaxhighlight>

GameRegistry 또한 클래스이기 때문에 import가 필요하다. 이 클래스에서 만들어진 함수의 대부분은 마인크래프트의 각종 요소를 추가하는 데 사용된다. 이 클래스에 관해서는 후술.


<syntaxhighlight lang="java" line="1">
//만들어낸 블록 클래스의 객체를 등록하기
@EventHandlerpublic void init(FMLInitializationEvent event) {GameRegistry.registerBlock(NamuBlock , "Block_1");
}
</syntaxhighlight>

이 상태에서 마인크래프트를 실행하면 그냥 그대로 블록이 추가된다.
파일:2016-01-15 18.31.42.png

이 텍스쳐는 마인크래프트 엔진이 텍스쳐를 찾지 못 했을 때 임시로 렌더링을 하기 위해 만들어놓은 텍스쳐이며 텍스쳐를 추가하면 저절로 대치된다.

텍스쳐를 추가하기 위해선 먼저 패키지를 선언해야 한다. textures 패키지 안에 blocks이라는 이름의 패키지를 넣어주고 그 안에 png 형식의 파일을 넣어주면 된다. 여기서 주의할 점이 몇가지 있는데 바로 절대 패키지 이름이 대문자라면 작동하지 않는다. 이거 하나 실수하면 하루종일 고생한다. 또한 이미지 크기는 반드시 16의 배수만을 사용한다. 즉, 16, 32, 64까지만 크기를 지원한다. 크기의 기준은 반드시 픽셀로 한다. 텍스쳐 또한 변수로 관리되며 set함수가 있다. 이 함수에 관해선 후술.

나머지 블록 함수들과 변수들은 직접 레퍼런스를 읽으며 찾아 봐야 한다[6]. 대부분 mcp에 포함된 자바독이나 forge src에 포함된 자바독에도 블록 클래스에 관한 대부분의 설명은 나와있다. 그리고 죄다 영어다

자바에서 언제나 그래왔듯 마인크래프트도 오버라이딩을 위한 함수와 값을 받아내는 함수 그리고 오버라이딩을 위한 함수들로 구성되어있다. 함수에 정해진 용도란 의미가 무색하지만 사용되는 정석은 있다. 모든 함수와 필드들을 알 수는 없지만 알아두면 매우 편리한 몇가지 요소들을 나열한다.

다음은 블록 클래스 내부에서 사용 가능한 함수들의 일부분.

호출하여 값을 변경하는 set함수들. 리턴값이 this이기 때문에 만약 설정해야할 부분이 하나 이상일 경우에도 '.'연산자를 통해 연속으로 설정이 가능하다.

당연하지만 용도에 구애받지 않고 거의 대부분의 함수를 마음대로 오버라이딩 하여 자신만의 함수로 다시 재정의 해버릴 수 있다.

set함수들

Block setBlockName(String) : 블록의 지역화되지 않은 이름을 설정한다.
Block setCreativeTab(CreativeTabs) : 크리에이티브 모드의 탭에 해당 블록을 추가한다.
Block setBlockTextureName(String) : 텍스쳐 이름을 지정.
Block setBlockHardness(float) : 블록의 강도를 설정.
Block setResistance(float) : 블록의 폭발 저항력을 설정.
Block setStepSound(Block.SoundType) : 블록을 밟았을 때 나는 소리를 설정.
Block setHarvestLevel(String, int) : 문자열로 해당 도구의 클래스를, 정수로 체취 가능한 레벨을 설정한다.

오버라이딩하여 사용하는 함수들.

오버라이딩의 좋은 예제가 바로 마인크래프트에 있다. 멀리갈 것 없이 방금 만든 블록에 이것들을 오버라이딩 시켜 println() 파라미터들을 찍어보자.

void onBlockDestroyedByPlayer(World, int, int, int, int) : 플래이어에 의해서 블록이 파괴될 경우에 호출된다.
void onBlockActivated(World, int, int, int, EntityPlayer, int, float, float, float) : 플래이어의 우클릭에 반응하여 호출한다.
void onEntityWalking(World, int, int, int, Entity) : 엔티티가 해당 블록 위에서 걸어다니고 있을 경우 호출된다.
void onBlockPlaced(World, int, int, int, int, float, float, float, int) : 월드에 블록이 추가되었을 경우 호출된다.
void onBlockClicked(World, int, int, int, EntityPlayer) : 플래이어가 좌클릭으로 때릴 경우에 호출된다.
void onBlockExploded(World, int, int, int, Explosion)

값을 반환하는 함수들. 논리값이 리턴형인 함수들은 is, can이라는 접두사로 사용되니 주의할것. 일부 함수들은 접근 제한자로 호출이 자식 클래스에서만 가능한 경우가 있다.

추가 중.

블록의 렌더러를 재정의 하여 자신이 원하는 형태를 자유롭게 만들어내는 커스텀 렌더러(타일 엔티티 스페셜 렌더러)는 타일엔티티 부분에서 후술.

8.1 유체 블록 만들기

마인크래프트 모드 내에서의 유체는 실제로 월드 내에서 블록 형태로 구현되지 않은 부분인 Fluid 클래스를 상속받아 만들어진 객체가 유체의 특성(밀도, 온도 등)을 관한 데이터를 저장하고, Block 클래스를 상속받은 유체 블록 클래스가 실제 월드 내에서 블록 형태로 구현할 때 유체에 관한 정보를 담고있는 Fluid 객체를 인자로 받아 블럭 인스턴스를 생성하는 형태로 되어있다.

즉, 예를 들면 '나무즙'이라는 이름의 액체를 Fluid 클래스를 상속받아 FluidNamuJuice란 클래스를 만들어 생성시킨 다음, 유체 블럭 클래스를 통해 나무즙의 유체 블럭을 구현시키지 않으면 나무즙은 그저 탱크나 양동이와 같이 액체를 담는 용기에서 존재할 수 밖에 없는 데이터 쪼가리로밖에 존재하지 않는다. [7]

따라서 자신이 만든 유체를 월드에 놓고싶다면, 아래의 예제와 같이 Fluid 클래스를 상속받아 유체의 개념과 특성을 정의한 객체를 만든 다음, 그 객체를 유체 블럭 클래스의 생성자 안에 담아 블럭 형태로 구현시켜야한다.

<syntaxhighlight lang="java" line="1">
package wiki.bluesky.common.fluids;

/**

  • 이 클래스는 Fluid 클래스를 상속받아 만들어졌으며, 유체의 개념과 특성을 정의할 수 있습니다.
  • 이 클래스의 변수들은 기본적으로 마인크래프트 물의 값을 Default로 갖습니다.
  • @Author Estiv
  • /

import net.minecraftforge.fluids.Fluid; // 나무즙 클래스의 부모 클래스가 될 Fluid 클래스

public class FluidNamuJuice extends Fluid {

FluidNamuJuice() {super("NamuJuice"); // Fluid 클래스의 생성자의 인자는 'String fluidName' 이며, 액체의 이름(UnlocalizedName)을 결정짓는다.
}

int density = 1000; // 액체의 밀도를 정의하는 변수, 마인크래프트 물의 밀도는 1000의 값을 갖는다.int temperature = 295; // 유체의 온도를 정의하는 변수, 기본 295의 값을 갖는다.int luminosity = 0; // 유체가 얼마나 빛을 발산하는가를 정의하는 변수, 기본 0의 값을 갖는다.int viscosity = 1000; // 유체가 얼마나 빠르게 확산되는지를 정의하는 변수, 기본 1000의 값을 갖는다.

boolean isGaseous = false; // 유체가 액체인지, 기체인지를 결정짓는 변수, 이 변수가 true일 경우 유체는 위로 솟는 기체의 특성을 갖게되며, 기본 false의 값을 갖는다.

}

</syntaxhighlight>

<syntaxhighlight lang="java" line="1">
package wiki.bluesky.main;

import cpw.mods.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidRegistry;

public class CommonProxyClass {

public void init(FMLInitializationEvent event) {Fluid namuJuice = new FluidNamuJuice();FluidRegistry.registerFluid(namuJuice); // 자신이 만든 새로운 Fluid를 등록시키는 메소드이다.
}
}

</syntaxhighlight>[8]
다음과 같이 자신만의 유체 클래스를 만들어서 자신이 만들 유체의 특성을 정해놓을 수 있다. Fluid 클래스를 상속받은지라 Fluid 클래스의 메소드를 사용할 수 있는데, 주로 쓰이는 메소드는 다음과 같다. 이 외의 메소드는 위키러들이 모딩을 하다 필요한 상황이 생기면 직접 찾아보자.

메소드역할
setDensity(int density)유체의 밀도의 값을 변환시킨 후, 변환된 값을 리턴한다.
setTemperature(int temperature)유체의 온도의 값을 변환시킨 후, 변환된 값을 리턴한다.
setLuminosity(int luminosity)유체의 발광도의 값을 변환시킨 후, 변환된 값을 리턴한다.
setViscosity(int viscosity)유체의 점성의 값을 변환시킨 후, 변환된 값을 리턴한다.
setGaseous(boolean b)유체가 액체인지 기체인지를 정하고, 유체의 상태를 리턴한다.
setIcons(IIcon still, IIcon flowing)유체가 멈춰있을 때와 흐를 때의 텍스쳐를 지정한다.

다음은 정의한 유체에 대응하는 블럭을 만들어야 위키러들이 만든 유체를 월드 내에 설치할 수 있다고 위에서 언급하였으니, 새로운 블럭을 정의해보자.
액체 블럭을 정의할 때 상속받는 클래스는 보통 두 가지로 나눌 수 있는데, BlockFluidBase 클래스와 BlockFluidClassic 클래스로 나눌 수 있다. 전자는 유체의 기본적 특성은 따오지만 자신만의 새로운 특성을 추가하거나 재정의하고 싶을 때 많이 상속되고, 후자는 바닐라 마인크래프트 유체의 특성을 따르는 일반적인 액체 블럭을 정의할 때 많이 상속된다. 이 문서 내에선 후자의 경우를 다룬다.

<syntaxhighlight lang="java" line="1">
package wiki.bluesky.common.blocks;

/**

  • 이 블록은 현재 지정된 Texture가 없습니다.
  • @Author Estiv
  • /

import net.minecraftforge.fluids.BlockFluidClassic; // 유체 블럭 클래스의 부모가 되는 클래스.
import net.minecraftforge.fluids.Fluid;
import net.minecraft.block.material.Material;

public class BlockFluidNamuJuice extends BlockFluidClassic {
BlockFluidNamuJuice(Fluid fluid) {super(fluid, Material.water); // 유체 블럭의 생성자. 요구 인자는 Fluid, Material 인스턴스이며, Material은 블럭의 기본 재질을 결정한다.
}
}
</syntaxhighlight>

<syntaxhighlight lang="java" line="1">
package wiki.bluesky.main;

import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.registry.GameRegistry;

import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidRegistry; // 유체는 별도의 레지스트리를 가지므로 import로 따로 호출해준다.
import net.minecraft.block.Block;

import wiki.BlueSky.common.blocks.BlockFluidNamuJuice;

public class CommonProxyClass {

public void init(FMLInitializationEvent event) {Fluid namuJuice = new FluidNamuJuice();FluidRegistry.registerFluid(namuJuice); // 나무즙 유체 추가


Block blockNamuJuice = new BlockFluidNamuJuice(namuJuice);GameRegistry.registerBlock(blockNamuJuice, "blockNamuJuice"); // 나무즙 유체 *블록* 추가 }
}

</syntaxhighlight>Texture 지정 방법은 후에 추가할 예정, 혹시 모더 위키러가 있다면 추가 바람.

위 문단을 참고하여 블록 클래스를 만들고 그것을 프록시 클래스에 등록하였다. 이 때, 이 클래스에선 유체 블럭을 다루는 클래스기에 생성자에 인자가 더 늘어났는데 바로 Fluid 인스턴스이다. 상속한 클래스가 생성하는 인스턴스는 유체 블럭의 개념을 상정하고 만들어진터라 반드시 대응되는 유체가 존재해야 하기에 인자에 Fluid 인스턴스가 있어야 하는 것을 유념해야한다.

8.1.1 유체 컨테이너 만들기

8.2 타일 엔티티 추가

9 크리에이티브 탭 만들기

말 그대로 크리에이티브 모드에서 사용할 크리에이티브 탭이다. 일명 아이템 그룹이라고도 읽는다.

블록과 마찬가지로 크리에이티브 탭을 관리하는 클래스를 상속해 등록해 버리면 끝이다. 하지만 방식이 블록보다 단순해 클래스를 만드는 행위는 코딩의 낭비를 야기시킬 수 있다. 그렇기 때문에 다른 특별한 것을 넣기는 게 아니라면 새로운 클래스를 작성하는 방법보다 그냥 익명 클래스로 만들어버리면 끝이다. 심지어 등록할 필요도 없다.

CreativeTabs 클래스의 객체를 만들어 버리고 getTabIconItem 함수를 재정의해 아이콘만 만들어주면 끝이다.

예제


<syntaxhighlight lang="java" line="1">
CreativeTabs tabs = new CreativeTabs("tab Namu") {
public Item getTabIconItem() {
return Items.bed;
//아이콘을 블록으로 하고 싶다면 Item.getItemFromBlock 함수를 통해 블록을 아이템으로 변경 후 리턴하면 된다.
}
};
</syntaxhighlight>

만약 아이템을 등록하고 싶지만 그럴 수 없는 경우[9] 아이템을 임시로 아이콘 전용으로 만들어 등록하면 된다.

10 아이템 만들기

추가 바람

11 키 바인딩(조작키 추가)

추가 바람

12 파티클 이펙트 추가와 생성

추가 바람

13 월드 생성 추가

추가 바람

14 GUI

추가 바람

15 자바가 뭐예요?

아무래도 위 설명은 프로그래밍의 ㅍ도 모르는 사람이 보면 상당히 어렵게 느껴질 것이다.
다행히도 프로그래밍과의 조우가 없는 일반인들도 손쉽게 간단한 모드를 만들 수 있는 툴이 존재한다.

보통 어려운 프로그래밍을 대신 해주는 개념으로, 예를 들어 블록을 만들고 싶다 하면, 원하는 블록에 대한 정보를 입력하면 '알아서' 프로그래밍해서 모드로 변환해준다. 툴에 따라서 범용성 높은 기능을 통해 훨씬 더 복잡한 모드도 충분히 만들 수 있으니, 관심 있다면 한 번 해보자.
단 영어를 주의해야 한다 어짜피 자바도 영어다
다만, 수준 높고 세심한 툴일 수록 계속해서 업데이트 되고, 제작자가 외국인인 경우가 대다수라 한글 지원은 힘들다..

15.1 모드메이커

일반적인 블록, 액체 등의 단순한 모드를 쉽게 만들 수 있고, 사용자 환경도 단순해 처음 접해보는데 문제가 없다. 경우에 따라 한글로 패치된 버젼도 있어 잘 찾아보자. 다만, 복잡한 모드를 만들기는 쉽지 않다.

이외 자세한 사용 방법 추가 바람

15.2 MCreator

웹 사이트
외국계 모드 툴로, 모드 메이커보다 훨씬 범용성이 높고 모드 수준도 높지만, 안타깝게도 전부 영어 아직은 다른 국어를 지원하지 않을것 같다. 아마 영원히.... 인데다가 복잡한 설정은 왠진 몰라도 상당히 불친절하며, 직관적이지 않다. 최근 추가된 기능일수록 비직관성은 심해지고, 훨씬 복잡해지기 시작한다.
개발 환경을 보자면 상당히 세세한데, 예를 들자면 블럭의 강도, 부수기 용이한 도구, 밝기, 입자 발생 여부 등 다양한 정보를 요구하는데,
문제는 기준이 죄다 게임 내 기준이라 무슨 숫자를 써야 할지 감이 안 온다! 그렇기 때문에 만들고 실행하면서 알아내는 수밖에 없다.
심지어 도움말은 비어있기 십상이며 위키는 아주 기초적인 정보만을 수록하고 있다(...)
하지만 이를 잘 이용할 수 있는 능력을 갖고 있다면 상당히 고퀄인 모드를 만들 수도 있다.
언어 장벽의 경우 크게 높지는 않다. 중고등학교 어휘 수준이다. 다만 나이가 어리다면 어려울 수 있다.

단순한 모드이면서도 세세함을 원한다면 모드메이커보다는 MCreator를 쓰자. 복잡한 기능을 제외하면 이해하기 쉽고 단순하게 되어 있으며, 게임 내에서 단순한 텍스쳐 만들기를 지원하며, 테크네와 큐빅 등 일부 모델링 프로그램과 연계되어 있어서 자신만의 몬스터 만들기에도 용이하다. 네이버 블로그 중에 전문적으로 강의하는 블로거가 있으니 참고하자.

이외에 모드 툴 추가 바람

  1. 그래서 새로운 업데이트가 있을 경우 한번 정도는 크리에이티브로 플래이를 해서 새로운 버전에 익숙해지면 그 때부터 모드 개발에 착수하는 것도 방법이다.
  2. 이상하게 설치후 하단에 올바른 위치가 아니인 다른 위치에 몇몇 jar 파일이 연결된 현상이 보인다. 이때는 해당 오류를 우클릭해 빠른 고치기를 통해서 수동으로 수정해줘야 작동한다.
  3. 앞으로 변경되거나 없어질 물건이니 사용을 자제하라는 일종의 경고문
  4. 아마도 title.null.block 이런 식으로 나올 것이다
  5. 서버를 구동하는 콘솔과 마인크래프트 런처가 분리된 멀티 플레이뿐만 아니라 싱글 플레이도 실제론 싱글 월드를 플레이하는 플레이어가 개인 서버를 돌리고 그 내부에서 각종 동작을 수행하는 방식으로 되어있다.
  6. 이것들만 잘 읽어봐도 자신이 원하는 블록은 대부분 만들어낼 수 있다
  7. 만약 블럭을 정의하지 않은 유체 블럭 클래스에 getBlock() 메소드를 호출하면 NullPointerException 크래시가 뜬다.
  8. 위는 프록시 클래스를 사용한 예제이다. 프록시(Proxy)라는 의미 자체가 대리인이듯, 아이템이나 블록같은 것을 등록해야할 때 메인 코드에서 이벤트 처리를 건네받아 대신 처리해주는 역할을 함으로써 메인 코드의 번잡함을 막을 수 있다.
  9. 예를 들면 아이템 또는 블록을 리턴하는 함수로 등록을 시도할 때 아이템이 유동적으로 만들어지는 구조일 경우에는 해당 아이템 또는 블록을 찾을 수 없다. 대표적으로 묘목이 이런 경우.