마인크래프트/자바 에디션/모드/개발

  상위 문서: 마인크래프트/모드

  관련 문서: 마인크래프트/개발


마인크래프트 주요 문서

{{{#!folding [ 펼치기 · 접기 ]

{{{#!wiki style="margin:-11px;margin-top:-10px;margin-bottom:-16px"

게임 진행

명령어

마법부여

아이템

회로

업적

세계

차원

생물군계

구조물

진행 외

모드

리소스팩

데이터팩

멀티플레이

플러그인

서버 구동

2차 창작

출시
에디션

자바에디션(JE)

포켓에디션(PE)

콘솔에디션(CE)

베드락에디션(BE)

에듀케이션 에디션(EE)

파이에디션

기타

튜토리얼

여담

닉네임 스킨

커뮤니티

사건사고

조합법

엔딩

MINECON

개발

기초

모드 개발

플러그인 개발

ModPE 개발

업데이트

JE 업데이트 내역

BE 업데이트 내역

게임

마인크래프트: 던전

마인크래프트: 스토리 모드

분류:마인크래프트
문서 열람하기

마인크래프트 문서의
하위 문서 문단

}}}}}}

마인크래프트 프로젝트

1. 개요
2. 개발 도구 설치
2.1. 자바 표준 개발환경 구축
2.2. Forge API
2.3. 개발 도구 통합과 마인크래프트 설치
3. 본격적인 모드 개발
4. 패키지
5. 이름 등록 (GameRegistry)
6. 메인 클래스
7. 클라이언트와 서버 사이드
7.1. @SideOnly
7.2. 프록시
8. 블록 만들기
8.1. 유체 블록 만들기
8.1.1. 유체 컨테이너 만들기
8.2. 블록 엔티티 추가
9. 크리에이티브 탭 만들기
10. 아이템 만들기
11. 키 바인딩(조작키 추가)
12. 파티클 이펙트 추가와 생성
13. 월드 생성 추가
14. GUI
15. 1.8 이후용 팁
16.1. 모드메이커
16.2. MCreator

1. 개요

마인크래프트 플레이의 끝장판! 더 이상은 단순 플레이가 아니다. 게임의 뿌리를 살펴보고 수정하는 일이다. 당연히 프로그래밍 실력(Java)은 물론 마인크래프트에 관한 이해도 또한 요구된다. 애드온 등의 타 모드 관련 개발 시에는 해당 모드에 관한 이해도 필요하며, 업데이트가 출시되면 크리에이티브 모드로 게임에 익숙해지는것도 좋은 방법이다. 또한 엔티티, NBT 등 마인크래프트의 전반적인 메커니즘은 마인크래프트 위키에서 찾아볼 수 있다.

자바 언어로 만들어진 마인크래프트에 모드를 만들기 위해서는 MCP (모드 코더 팩) 을 기반으로 한 Forge API를 이용해야 한다 (역시 자바 언어만 지원한다). non-forge[1] 스타일의 모드 개발은 추가 예정. 또한 현재 작성되지 않은 다른 기법들 또한 있으면 추가 바람.

Forge로 모드를 만드려면 Java을 기본으로 다룰 줄 알아야 한다.

게다가 포지는 마인크래프트 게임 코드를 해독한 것이기 때문에, 새로운 버전이 나올때마다 포지 API의 함수나 구조가 자주 바뀐다.

따라서 최신 버전의 포지로 모드를 개발/업그레이드하려면 포지 API의 변화를 빠르게 잡아내고 익숙해질 필요가 있다.

만약 자바 언어가 서툴거나 프로그래밍에 낯설다면 자동 개발 툴을 이용해 모드를 만드는 것도 방법이다.

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

Forge 공식 개발 강좌문서

2. 개발 도구 설치

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

2.1. 자바 표준 개발환경 구축

JDK (자바 개발 도구)

eclipse (IDE)

이 툴은 작성시 기준으로 자바 10 구동이 불가능하므로 업데이트 전까지는 자바 8 사용을 권장한다.

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

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

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

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

2.2. Forge API

Forge 다운로드

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

먼저 원하는 버전에 해당하는 포지 버전 중 아래의 파일을 선택해 다운받는다.

(가능한 한 권장(Recommended) 버전를 선택하자)

* 마인크래프트 1.7.10 까지는 Src 파일

* 마인크래프트 1.8 부터는 Mdk 파일

다운받은 파일을 원하는 폴더에 넣고 압축을 풀면 된다.

1.6과 다르게 1.7부터는 Gradlew 설치기를 바탕으로 개발환경을 설치할 수 있기 때문에 모든 관리를 명령어로 해야 한다.

기본적으로 필요한 gradlew 명령어의 목록이다. 개발환경을 설치한 폴더에서 명령 프롬프트를 열고

gradlew <명령어> 를 입력해 원하는 동작을 입력할 수 있다.

유닉스 계열(맥OS, 우분투 등) 운영체제를 사용중이라면 ./gradlew <명령어> 를 입력한다.

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. 패키지

소스코드를 만들기 전에 코드파일을 정리할 패키지 폴더를 먼저 만들자.

자바에서 패키지 이름은 주로 도메인명.프로젝트이름 또는 닉네임.프로젝트이름 으로 정하는 관습이 있다.

예제: 자신의 모드 이름이 mymod 이고, 도메인이 namu.wiki 인 경우 패키지명

#!syntax java
wiki.namu.mymod

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

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

다음 패키지는 기본적으로 정해진 것으로 미리 만들어두자.

참고로 패키지 이름에서 a.b 는 a 안에 b폴더가 있다는 의미이다.

assets : 모든 필요한 리소스들이 여기에 들어간다. 모드의 소스폴더와는 다른 resources라는 폴더에 생성한다.
assets.<모드 ID> : 아래와 같은 다양한 폴더들이 들어가는 상위폴더이다.
assets.<모드 ID>.textures : 아이템, 블록 등의 텍스쳐 파일을 넣는다.
assets.<모드 ID>.models : 웨이브 프론트를 이용해 3D 모델을 삽입할 때 사용할 obj 파일들이 들어간다.
assets.<모드 ID>.lang : 아이템, 블록 등의 이름을 정의할 lang파일들이 들어간다.
assets.<모드 ID>.shaders : 쉐이더 파일들이 들어간다. (확장자는 추가바람)
assets.<모드 ID>.sounds : 각종 효과음으로 ogg 파일이 들어간다.

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

패키지 이름은 당연히 영문으로 짓고, 대문자를 사용하지 않도록 한다.

5. 이름 등록 (GameRegistry)

원래 과거 포지에서는 LanguageRegistry 클래스를 이용해 각종 아이템과 블록들의 이름[3]을 등록했다. 그러나 소스코드 안에 이름을 변수로 저장해두면 코드가 난잡해지는 등의 문제가 있었다.

그래서 최근에 이 클래스의 함수들이 대부분 Deprecated[4] 되었고,

GameRegistry 가 등장하면서 아이템과 블록의 이름을 Lang 파일 안에 따로 모아서 기록할 수 있게 되었다.

그리고 1.12.2 버전 부터는GameRegistry가 Event형식으로 바뀌어 RegistryEvent.Register<T> 이벤트를 받아 등록을 할 수 있게 되었다.

먼저 프로젝트 폴더에서 assets.<모드 ID>.lang 패키지 안에 국가 코드.lang 의 이름으로 파일을 만든다. 예) ko_KR.lang

(한국어로 작성하면 UTF-8 인코딩을 하는 것은 잊지 말자. 안그러면 게임안에서는 이름들이 깨져나온다..)

한 개의 아이템 또는 블록 이름을 추가할 때 다음의 한 줄을 추가한다.

아이템일 때:

item.레지스트리 이름.name=표시할 이름

블록일 떄:

tile.레지스트리 이름.name=표시할 이름

이 때 절대로 '=' 앞/뒤에 공백을 만들면 안된다. 파일 형식에 맞게 쓰지 않으면 컴퓨터는 오류를 뿜기 마련이다.

6. 메인 클래스

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

마인크래프트 게임이 부팅되면 게임엔진인 LWJGL 이 로드되고 각 클래스가 만들어져 추상화되는데,

이렇게 추상화된 클래스를 상속하고, 또 역으로 등록하여 엔진에 클래스를 추가한다.

즉, 상속받은 클래스 하나가 게임 속의 요소를 정의하는 하나의 객체가 된다. (객체지향 언어에서 흔하게 볼 수 있는 방식이다.)

모드 또한 위와 비슷한 방식으로 동작한다.

모드는 모드로더인 포지를 상속하는데, 포지에 모드를 등록하기 위해 어노테이션이 사용된다.

그런데 모드의 클래스 파일은 여러개가 존재할 수 있다. 하지만 그 중 메인 클래스 하나만 등록하면

모드 파일 전체가 포지에 등록되어 자신의 모드가 정상적으로 실행된다.

메인 클래스를 포지에 등록하는 법은 다음과 같다:

패키지 아래에 메인 클래스를 만들고 클래스 정의부분 위에 @Mod 어노테이션을 추가한다.

어노테이션의 () 괄호 안에는 모드의 아이디, 이름, 버전과 같은 속성값이 정의된다.

모드로더(포지) 는 모드를 읽어들일 때 메인 클래스의 이 어노테이션을 확인하기 때문에 이는 반드시 필요하며,

@Mod 어노테이션을 사용하려면 cpw.mods.fml.common.Mod 를 임포트해야 한다.[5]

#!syntax java
import cpw.mods.fml.common.Mod;

@Mod(modid=<모드 아이디>, name=<모드 이름>, version=<모드 버전>)

@Mod 어노테이션에 들어가는 속성값 중 modid[6] 는 반드시 입력해야 하며, 나머지는 필수 요소가 아니다. 이러한 속성값들은 각각 메인클래스에 정적 상수로 선언해 값을 정해놓고 불러와 쓰는 것이 일반적이다. (아래 종합 예재 코드 참고)

어노테이션 아래에는 클래스를 선언한다. 보통 클래스명은 자신의 모드 이름 또는 그 뒤에 Main이라는 단어를 붙여 쓰기도 한다.

그리고 Instance 어노테이션을 붙여 자신의 모드에 메인 클래스를 지정한다:

#!syntax java
@Instance(value = <모드 아이디>)
public static MyMod myMod;

마지막으로 이벤트 함수 위에 EventHandler 어노테이션을 붙여 포지에 등록함으로써 함수를 동작시킨다:

#!syntax java
@EventHandler
public void event(FML...Event event) {}

모든 이벤트 처리 함수는 파라미터에 FMLPreInitializationEvent, FMLInitializationEvent, FMLPostInitializationEvent 등

포지 이벤트 클래스가 한 개씩 있어야 한다.

EventHandler 어노테이션을 사용하려면 cpw.mods.fml.common.Mod.EventHandler 를 임포트해야 한다.

또한 각 파라미터의 객체가 되는 이벤트 클래스 또한 임포트해서 사용해야 한다.

예제

#!syntax java
import cpw.mods.fml.common.Mod.EventHandler;
import cpw.mods.fml.common.event.*;

//클래스 내부

@EventHandler
public void preInit(FMLPreInitializationEvent event) {}

@EventHandler
public void init(FMLInitializationEvent event) {}

@EventHandler
public void postInit(FMLPostInitializationEvent event) {}

이렇게 최종적으로 메인클래스 선언이 완료됐다. IDE에서 빌드 후 run해서 마인크래프트를 바로 실행해보면

메인 화면에서 모드 옵션에서 추가된 모드 목록 중에 자신의 모드가 보일 것이다.[7]

종합 예재 코드

#!syntax java
package wiki.namu.mymod;

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 preInit(FMLPreInitializationEvent event) {
		// 모드 로딩 중 init() 보다 먼저 호출되는 이벤트, 모드 설정을 불러오는 부분
	}

	@EventHandler
	public void init(FMLInitializationEvent event) {
		// 모드 로딩 중간에 호출되는 이벤트, 조합법과 광물을 추가하는 부분
	}

	@EventHandler
	public void postInit(FMLPostInitializationEvent event) {
		// 모드 로딩 중 init() 이후에 호출되는 이벤트, 다른 모드와 상호작용함으로써 호환성을 높이는 부분
	}
}

마지막으로 클라이언트와 서버 프록시를 추가하여 게임상에 시각적인 렌더링을 하는 부분, 모드관련 쓰레드, 루프 동작 등을 하는 부분을 따로 작성할 수 있도록 클래스를 선언하는 것이 좋다. 프록시에 관한 부분은 아래에서 자세히 다룬다.

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

마인크래프트는 까다로운 시스템을 가지고 있는데, 클라이언트(Client)와 서버(Server) 사이드이다.

두 사이드는 서로 다른 기능을 하며, 코드가 분리된 채 따로 실행되어야 한다. 그렇지 않으면 예상치 못한 오류가 발생할 수도 있다!

마인크래프트상에서 일어날 법한 상황을 들어 두 사이드에 대해 설명해보면 다음과 같다:

만약 서버가 렉으로 멈추어도 유저들은 서버에 반영되지는 않겠지만 움직이거나 상자를 여는 등의 특정 동작을 수행할 수 있고, 반대의 경우로는 서버를 접속한 플레이어의 클라이언트가 렉이 걸린 시점에서 게임은 멈춘 것처럼 보이지만 서버에 있는 몬스터가 여전히 플레이어를 감지해서 때린다는 일련의 동작은 그대로 수행되어 데미지가 전달되고, 유저의 렉이 풀리면 그제서야 데미지를 받는 현상이 바로 위와 같은 게임상 사이드의 특성 때문이다.

이 개념은 우리가 일반적으로 아는 클라이언트와 서버의 정의와 거의 비슷한데, 클라이언트 사이드는 말 그대로 마인크래프트 게임 런처 또는 프로그램 자체를 뜻한다. 즉, 클라이언트 사이드에서 실행되는 것은 플레이어의 화면에 아이템을 렌더링하여 보여주는 코드, 특정 GUI의 동작 과정을 정의하는 코드, 키보드 입력을 인식하는 코드 등과 같이 플레이어 시점의 게임 화면에 영향을 주는 것이다.

서버 사이드에서 수행되는 코드란, 말 그대로 이 코드를 수행하는 위치가 서버에게 있음을 의미한다.

이러한 코드는 버킷과 같은 멀티플레이 서버 내부에서 수행되는 경우도 있지만, 클라이언트 사이드 내부에서 동작하는 경우도 있다.[8]

화로같이 특정 동작을 수행하는 블록 엔티티나 몬스터, 동물과 같은 엔티티의 움직임과 같은 동작 코드는 보통 이 쪽에서 많이 수행된다.

위에서 언급했다시피 서버 사이드 코드는 서버 쪽에서 수행되기보다는 클라이언트 사이드와 같이 동작하는 경우가 많기에 클라이언트/서버가 같이 공동으로 수행할 코드는 공통 사이드로 묶은 뒤, 특정 사이드에서만 수행될 코드를 따로 편성해 방금 공통적으로 묶은 코드들에 상속하는 경우가 대다수이며 이를 코먼 사이드(Common Side)라고 부른다.

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

7.1. @SideOnly

#!syntax java
@SideOnly(value = Side.CLIENT)
public void registerBlockIcons(IIconRegister p_149651_1_)
{
    this.blockIcon = p_149651_1_.registerIcon(this.getTextureName());
    // 마인크래프트 내 Block 클래스에서 실제 블록의 텍스쳐를 등록하는 메소드이며, 이 메소드는 클라이언트 사이드에서만 수행되어야 한다.
}

간혹 가다 위와 같이 @SideOnly 어노테이션이 붙은 필드나 메소드를 볼 수 있는데, 이것은

해당 필드나 메소드가 실행되는 시점이 주어진 사이드가 아니라면 컴파일하지 말라는 의미이다.

이제 위 예제를 해석해보자면, registerBlockIcons() 메소드가 클라이언트 쪽에서 실행된 것이 아니라면 메소드 실행이 중단될 것이다.

이처럼 @SideOnly 어노테이션을 쓰면 각각의 메소드나 필드를 사이드 별로 분리시킬 수 있다.

하지만 이 어노테이션은 사용이 권장되지 않고, 잘 쓰이지도 않는다.

그 이유는 만약 이 어노테이션을 잘못 사용해서 외부에서 어노테이션이 붙은 이 코드로 접근하면,

해당 코드가 의도하지 않은 사이드에서 실행되버려 오류의 원인이 되고, 코드가 난잡하게 꼬일 수 있기 때문이다.

따라서 위 어노테이션을 직접 사용하기보단 다음과 같은 방법으로 대체하자:

  • world.isRemote() : 해당 월드의 사이드를 알아낸다
  • FMLCommonHandler.getSide() : 이 메소드가 실행되는 시점의 사이드를 알아낸다.

위 메소드를 통해 현 시점의 (물리적) 사이드를 가려내어 코드가 실행될 것인지 말 것인지를 직접 구분지을 수 있다. 또는 아래 문서에 나와있는 프록시 기능을 사용할 수도 있다. 이 @SideOnly 어노테이션은 마인크래프트에서 이미 구현된 조상 클래스에서 해당 어노테이션이 붙어있는 메소드를 오버라이드[9]하고 싶을 때만 사용하는 것을 권장한다.

7.2. 프록시

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

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

두번째 이유는 클래스를 프록시에 따라 나눔으로써 코드의 번잡성을 줄이는 것인데,

만약 모드에 아이템이나 블록을 생성하여 등록하기 위해 GameRegistry 클래스의 메소드를 오버라이드한다면 등록하려는 아이템 수가 많지 않으면 그리 문제가 되지 않을 것이다. 하지만 등록하려는 아이템이 많아질 수록 메인 클래스의 코드 줄 수는 계속 늘어날 것이고 이는 프로젝트 유지 보수에 매우 불리하다.

모드 개발의 초반에는 보통 프록시 구분의 필요성이 부족하게 느껴지겠지만, 앞서 말했다시피 모드의 규모가 커질 수록 코드를 분리할 필요가 생길 것이므로 설령 지금은 쓸 일이 없더라도 클래스를 프록시로 미리 구분해 놓고 개발에 들어가는 것이 바람직하다.

프록시에 대한 실질적인 설명을 하자면 프록시를 통해 호출한 함수는 만약 호출한게 서버라면 코먼(Common) 프록시 클래스의 함수가 호출되고 만약 클라이언트라면 클라이언트(Client) 프록시 클래스의 함수가 자동으로 호출된다.

만약 Minecraft 코드 등에 @SideOnly 어노테이션이 있어서 서버와 클라이언트 둘다 이용하는 클래스 접근에 문제가 생긴다면 사용하는 것이 좋다.

단, 클라이언트(Client) 프록시 클래스에서 코먼(Common) 프록시 클래스의 함수를 오버라이딩하지 않고 프록시를 호출할 경우 당연히 부모 클래스인 코먼 프록시 클래스의 함수가 호출된다.

먼저 클래스 2개 중 하나는 Common이라는 키워드를, 하나는 Client라는 키워드를 포함하여 선언한다.

클래스의 이름은 그다지 중요하지 않으나 구별이 쉽도록 두 키워드를 사용해 짓도록 하자.

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

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

#!syntax java
@SidedProxy(clientSide="<Client의 위치>", serverSide="<Common의 위치>" )
public static CommonProxyClass objCommonProxy;

이것으로 프록시를 준비하는 일은 끝났다. 텅 빈 클래스 2개를 어디다 써먹는가 의문을 품을 수도 있지만 프록시는 사용될 확률이 매우 높은 기술이니 사용되는 일이 있을 경우 후술하도록 한다.

예제

#!syntax java
//메인 클래스 내부
@SidedProxy(clientSide="wiki.bluesky.main.ClientProxyClass", serverSide="wiki.bluesky.main.CommonProxyClass")
public static CommonProxyClass objCommonProxy;

클라이언트(Client) 프록시

#!syntax java
package wiki.namu.mymod;
public class ClientProxyClass extends CommonProxyClass {}

코먼(Common) 프록시

#!syntax java
package wiki.namu.mymod;
public class CommonProxyClass {}

8. 블록 만들기

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

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

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

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

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

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

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

#!syntax java
//블록을 만들고 해당 블록을 어떠한 크리에이티브 탭에 추가할지 정하기.

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

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

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

마지막으로 블록을 등록한다. 블록을 등록하는 일은 Registry.Register<T> 이벤트를 받아 등록한다.

#!syntax java
import net.minecraft.*;

@SubscribeEvent
	public static void registerBlock(RegistryEvent.Register<Block> e){			
                            e.getRegistry().register(<블록 인스턴스>);	
         }

이 상태에서 마인크래프트를 실행하면 그냥 그대로 블록이 추가된다.

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

모델를 추가하기 위해선 먼저 패키지를 선언해야 한다. model 패키지 안에 block이라는 이름의 패키지를 넣어주고 그 안에 마인크래프트json형식의 모델을 넣어주면 된다. 여기서 주의할 점이 몇가지 있는데 바로 절대 패키지 이름이 대문자라면 작동하지 않는다. 이거 하나 실수하면 하루종일 고생한다. 또한 모델에 들어가는 이미지 크기는 반드시 16의 배수만을 사용한다. 즉, 16, 32, 64까지만 크기를 지원한다. 크기의 기준은 반드시 픽셀로 한다.

모델을 로드하려면 ModelRegistryEvent 이벤트 에서 모델을 설정해 주면 된다.

#!syntax java
    ModelLoader.setCustomModelResourceLocation(Item.getItemFromBlock(<블록 인스턴스>), 0, new ModelResourceLocation(<블록 인스턴스>.getRegistryName(), "inventory"));

아마 이것을 복붙하는게 편할것이다.

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

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

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

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

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

set함수들

Block setUnlocalizedName(String) : 블록의 지역화되지 않은 이름을 설정한다.
Block setCreativeTab(CreativeTabs) : 크리에이티브 모드의 탭에 해당 블록을 추가한다.
Block setBlockHardness(float) : 블록의 강도를 설정.
Block setResistance(float) : 블록의 폭발 저항력을 설정.
Block setHarvestLevel(String, int) : 문자열로 해당 도구의 클래스를, 정수로 체취 가능한 레벨을 설정한다.
Block setBlockUnbreakable() : 해당 블록을 기반암, 포탈과 같이 채굴할 수 없게 만든다. setBlockHardness(-1.0F) 함수를 호출하는것과 같은 기능을 한다.

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

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

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

1.7 이전 버전의 마인크래프트에서는 좌표를 나타내기 위한 x, y, z 3개의 int값과 블록 상태를 나타내기 위한 int 값 데이터를 사용했으나, 최신 버전에서는 각각 BlockPos와 IBlockState로 대체되었으므로 구버전 모드의 소스코드를 참조하거나 자신의 모드를 업데이트할 경우 주의해야 한다.

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

추가 중.

블록의 렌더러를 재정의 하여 자신이 원하는 형태를 자유롭게 만들어내는 커스텀 렌더러(블록 엔티티 스페셜 렌더러)는

아래 블록 엔티티 부분에서 따로 서술한다.

8.1. 유체 블록 만들기

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

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

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

#!syntax java
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의 값을 갖는다. 켈빈 온도이므로 설정하기 원하는 온도에 273을 더해야 한다. (예: 액체 온도가 100℃면 373K 이다.)
       int luminosity = 0; // 유체가 얼마나 빛을 발산하는가를 정의하는 변수, 기본 0의 값을 가지며 0~15사이의 값이 유효하다.
       int viscosity = 1000; // 유체가 얼마나 빠르게 확산되는지를 정의하는 변수, 기본 1000의 값을 갖는다.

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

}
#!syntax java
package wiki.namu.mymod;

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를 등록시키는 메소드이다.
       }
}

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

메소드

역할

setDensity(int density)

유체의 밀도의 값을 변환시킨 후, 변환된 값을 리턴한다.

setTemperature(int temperature)

유체의 온도의 값을 변환시킨 후, 변환된 값을 리턴한다.

setLuminosity(int luminosity)

유체의 발광도의 값을 변환시킨 후, 변환된 값을 리턴한다.

setViscosity(int viscosity)

유체의 점성의 값을 변환시킨 후, 변환된 값을 리턴한다.

setGaseous(boolean b)

유체가 액체인지 기체인지를 정하고, 유체의 상태를 리턴한다.

setIcons(IIcon still, IIcon flowing)

유체가 멈춰있을 때와 흐를 때의 텍스쳐를 지정한다.

다음은 정의한 유체에 대응하는 블록을 만들어야 위키러들이 만든 유체를 월드 내에 설치할 수 있다고 위에서 언급하였으니, 새로운 블록을 정의해보자.

액체 블록을 정의할 때 상속받는 클래스는 보통 두 가지로 나눌 수 있는데, BlockFluidBase 클래스와 BlockFluidClassic 클래스로 나눌 수 있다. 전자는 유체의 기본적 특성은 따오지만 자신만의 새로운 특성을 추가하거나 재정의하고 싶을 때 많이 상속되고, 후자는 바닐라 마인크래프트 유체의 특성을 따르는 일반적인 액체 블록을 정의할 때 많이 상속된다. 이 문서 내에선 후자의 경우를 다룬다.

#!syntax java
package wiki.namu.mymod.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은 블록의 기본 재질을 결정한다.
       }
}
#!syntax java
package wiki.namu.mymod;

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.namu.mymod.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"); // 나무즙 유체 *블록* 추가 
       }
}
Texture 지정 방법은 후에 추가할 예정, 혹시 모더 위키러가 있다면 추가 바람.

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

8.1.1. 유체 컨테이너 만들기

8.2. 블록 엔티티 추가

9. 크리에이티브 탭 만들기

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

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

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

예제

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

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

10. 아이템 만들기

아이템을 만드는것은 블럭과 매우 비슷하다. 아니 더 간단하다.

일단 블럭이아니라 아이템을 상속받아 클래스를 만든다.

#!syntax java
public class EGTItem extends Item{
     public EGTItem(String name) {
		this.setUnlocalizedName(name);
		this.setRegistryName(name);
		
	}
}

그다음 블럭과 같이 필드를만들고 등록하고 모델을 적용시킨다.

참고로 모델은 item하위패키지에 넣는다.

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

추가 바람

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

추가 바람

13. 월드 생성 추가

추가 바람

14. GUI

추가 바람

15. 1.8 이후용 팁

  • 1.7.10 및 이전 버전에서 사용되던 코드는, 1.8 이후에서는 대부분 호환되지 않는다.
  • cpw가 포지 팀에서 탈퇴하여 cpw.mods.fml.common.*; 따위의 임포트는 오류를 내고, net.minecraftforge.fml.common.*; 로 임포트해야 한다.
  • 아이템과 블록의 텍스쳐 및 모델 지정 방식이 변경되어 텍스쳐 제작으로 끝나지 않고, json 파일로 모델을 만들어야 한다.
  • x, y, z 3개의 int 값으로 표현되던 좌표가 대부분 BlockPos 로 교체되었다.
  • 블록의 방향, 면 등을 나타내기 위한 포지 클래스인 ForgeDirection은 바닐라에 새로 생긴 EnumFacing으로 대체되었다.
  • 블록의 상태를 나타내기 위한 정수값인 메타데이터가 IBlockState로 대체되었다.
  • 1.12부터는 제작대 조합법도 아이템과 블록처럼 레지스트리 이름을 설정하고나서 등록해야한다.

16. 자바가 뭐예요?

아무래도 위 설명은 프로그래밍의 ㅍ도 모르는 사람이 보면 상당히 어렵게 느껴질 것이다.

다행히도 프로그래밍과의 조우가 없는 일반인들도 손쉽게 간단한 모드를 만들 수 있는 툴이 존재한다.

보통 어려운 프로그래밍을 대신 해주는 개념으로, 예를 들어 블록을 만들고 싶다 하면, 원하는 블록에 대한 정보를 입력하면 '알아서' 프로그래밍해서 모드로 변환해준다. 툴에 따라서 범용성 높은 기능을 통해 훨씬 더 복잡한 모드도 충분히 만들 수 있으니, 관심 있다면 한 번 해보자.

단 영어를 주의해야 한다 어차피 자바도 영어다

다만, 수준 높고 세심한 툴일 수록 계속해서 업데이트 되고, 제작자가 외국인인 경우가 대다수라 한글 지원은 힘들다..

16.1. 모드메이커

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

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

16.2. MCreator

웹 사이트

외국계 모드 툴로, 모드 메이커보다 훨씬 범용성이 높고 모드 수준도 높지만, 안타깝게도 영어밖에 지원하지 않는데다 복잡한 설정은 왠진 몰라도 상당히 불친절하며, 직관적이지 않다. 최근 추가된 기능일수록 비직관성은 심해지고, 훨씬 복잡해지기 시작한다.

개발 환경을 보자면 상당히 세세한데, 예를 들자면 블록의 강도, 부수기 용이한 도구, 밝기, 입자 발생 여부 등 다양한 정보를 요구하는데,

문제는 기준이 죄다 게임 내 기준이라 무슨 숫자를 써야 할지 감이 안 온다! 그렇기 때문에 만들고 실행하면서 알아내는 수밖에 없다.

심지어 도움말은 비어있기 십상이며 위키는 아주 기초적인 정보만을 수록하고 있다(...)

하지만 이를 잘 이용할 수 있는 능력을 갖고 있다면 상당히 고퀄인 모드를 만들 수도 있다.

언어 장벽의 경우 크게 높지는 않다. 중고등학교 어휘 수준이다.

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

단점은 복붙이 안된다[14]

이외에 모드 툴 추가 바람


  1. [1] Risugami 모드로더, MCP를 이용하거나 직접 난독화된 코드를 해석하는 무식한 방법 등이 있다.
  2. [2] 이상하게 설치후 하단에 올바른 위치가 아니인 다른 위치에 몇몇 jar 파일이 연결된 현상이 보인다. 이때는 해당 오류를 우클릭해 빠른 고치기를 통해서 수동으로 수정해줘야 작동한다.
  3. [3] 게임 안에서 아이템을 손에 들면 표시되는 이름 또는 크리모드창에서 마우스가 아이템을 때면 뜨는 것을 말한다.
  4. [4] 해당 메서드가 앞으로 변경되거나 없어지니 사용을 자제하라는 일종의 경고문
  5. [5] 이클립스를 포함한 대부분의 IDE는 임포트 단축키가 내장되어 있다.
  6. [6] 마인크래프트 1.11 부터는 모드 아이디에 대문자를 쓸 수 없다.
  7. [7] 모드 목록에 자신의 모드가 뜨지 않는다면 포지에서 모드 등록 중 오류가 생겼을 가능성이 높다. 콘솔에서 오류 로그를 찾아 문제를 추적해보자.
  8. [8] 이 현상은 싱글 플레이 월드를 플레이할 때 일어나는데, 싱글 플레이에서 LAN 서버를 여는 기능이 있기 때문이다.
  9. [9] 오버라이드는 프로그래밍 용어로, 부모 클래스에 이미 존재하는 메소드를 다시 정의해서 덮어씌우는 것을 말한다.
  10. [10] 이것들만 잘 읽어봐도 자신이 원하는 블록은 대부분 만들어낼 수 있다.
  11. [11] 만약 블록을 정의하지 않은 유체 블록 클래스에 getBlock() 메소드를 호출하면 NullPointerException 크래시가 뜬다.
  12. [12] 위는 프록시 클래스를 사용한 예제이다. 프록시(Proxy)라는 의미 자체가 대리인이듯, 아이템이나 블록같은 것을 등록해야할 때 메인 코드에서 이벤트 처리를 건네받아 대신 처리해주는 역할을 함으로써 메인 코드의 번잡함을 막을 수 있다.
  13. [13] 예를 들면 아이템 또는 블록을 리턴하는 함수로 등록을 시도할 때 아이템이 유동적으로 만들어지는 구조일 경우에는 해당 아이템 또는 블록을 찾을 수 없다. 대표적으로 묘목이 이런 경우.
  14. [14] 사실 툴에 숨겨져 있는 자바 소스 편집기가 존재한다. 하지만 Eclipse처럼 쓸 물건은 아니다.

최종 확인 버전:

cc by-nc-sa 2.0 kr

Contents from Namu Wiki

Contact - 미러 (Namu)는 나무 위키의 표가 깨지는게 안타까워 만들어진 사이트입니다. (169.38ms)