Tictim과 함께하는 마인크래프트 모딩 101

단편-1. I18n


※ 이 강좌는 1.10 ~ 1.11버전을 기준으로 작성됩니다.


틱팀입니다.

오늘은 모 지인 분께서 I18n에 대한 이해도가 아주 테러블한 것을 보고 충격을 받아서 I18n에 대한 짧은 설명과, 자세한 사용법을 알려드리도록 하겠심미다.


1. 현지화란?


현지화(Localization)란, 프로그램을 여러 문화권의 사람들이 사용할 수 있도록 지원하는 것을 의미합니다. 마인크래프트에서는 주로 번역을 가리키죠.


마인크래프트에서 번역을 하는 원리는 다음과 같습니다. lang 파일이라는 문자열 두 개를 =으로 묶어 놓은 문자열의 쌍으로 이루어진 텍스트 파일이 있습니다. 게임에서 설정된 언어에 따라서 lang 파일을 읽어 문자열의 쌍을 저장해 둡니다.

그리고 특정한 클래스에서 번역될 문장의 키 값을 입력하면, 첫 번째 문자열이 키 값과 일치하는 문자열 쌍을 찾아 그 쌍의 두 번째 문자열을 반환합니다.

사진으로 설명하면 이렇습니다.


2. I18n


마인크래프트에서 번역을 지원하는 클래스는 두 종류가 있습니다. 둘 다 비슷한 방식으로 작동하긴 합니다. 하나는 I18n, 다른 하나는 I18n입니다.


..

..

네. 똑같은 이름의 클래스가 두 개나 있습니다. 하는 일은 똑같지만 알아둬야 할 차이점이 있으니 숙지하세요.


 

(Client-Sided)

I18n

[ net.minecraft.client.resources.I18n ]

(Deprecated)

I18n

[ net.minecraft.util.text.translation.I18n ]

서버에서 존재

 아니오

 예

포매팅 없는 번역

 불가

 가능

Fallback(영어 고정) 번역

 지원 안 함

 지원


어째 client.resources.I18n이 더 구려 보입니다. 말만 Deprecated지, 바닐라에서도 특수한 상황이 아니면 util.text.translation.I18n을 씁니다. 클라이언트에서만 존재한다는 게 꽤나 큰 제약이거든요. 그런 이유로 여러분도 후자를 더 많이 쓰게 될 겁니다.


여담이지만 client.resources.I18n에서는 포매팅 없는 번역을 아주 쉽게 구현 가능한데, 이를 하지 않은 이유가 매우 궁금해집니다. 그냥 발로 만들어 버린 걸까요. 모르겠습니다.



이번에는 I18n 클래스의 접근 가능한 함수들을 알아봅시다. 함수를 알아야 쓰죠. 그렇죠?

먼저 util.text.translation.I18n을 알아봅시다.


@Deprecated public static String translatedToLocal(String)

문자열을 번역합니다. 위에서 봤던 사진처럼, 키 값을 대응되는 문자열로 반환합니다.

대응되는 문자열이 없을 시 키 값을 그대로 반환합니다.


@Deprecated public static String translatedToLocalFormatted(String, Object...)

문자열을 번역하고, 그 결과물을 포매팅합니다. String#format을 사용합니다.

대충 이런 방식으로 작동합니다. 번역된 문자열을 키 값으로 해서 포매팅을 합니다.

포매팅 과정에서 예외가 일어나면, "Format error: "+(번역된 키 값)을 반환합니다.


@Deprecated public static String translatedToFallback(String)

쓸 데는 거의 없을 겁니다. 문자열을 en_us, 즉 영문으로 번역합니다.


@Deprecated public static boolean canTranslate(String)

I18n이 해당 키 값에 대응되는 문자열을 가지고 있는지 반환합니다. 역시 쓸 데는 거의 없을 겁니다.


@Deprecated public static long getLastTranslationUpdateTimeInMilliseconds()

I18n이 가장 최근에 업데이트된 시간을 밀리초 단위로 반환하는 것 같습니다. 쓸 데는 없습니다. 바닐라에서도 단 한 곳에서 I18n이 업데이트되었는지 여부를 체크할 때 쓰며, 이외에는 쓰이는 곳이 없습니다.


예제입니다. 강좌 2편에서 "item.item_first.name"을 "첫 번째 아이템"으로 지정한 lang파일을 그대로 사용합니다.


formattedName이라는 키를 하나 더 추가했습니다.


다음과 같은 구문을 preInit, init 아니면 아무 데에나 집어넣어 실행시켜 봅시다.


짜잔


이번에는 client.resources.I18n을 알아봅시다.


public static String format(String, Object...)

문자열을 번역하고, 그 결과물을 포매팅합니다. String#format을 사용합니다.

이전 클래스의 translatedToLocalFormatted와 같은 기능입니다.


public static boolean hasKey(String)

I18n이 해당 키 값에 대응되는 문자열을 가지고 있는지 반환합니다.

이전 클래스의 canTranslate와 같은 기능입니다.


이번에는 이 클래스를 사용하여 번역해 봅시다. 서버에서는 존재하지 않기 때문에, 저 문장은 프록시에서 실행해야 합니다. 클라이언트에서는 잘 돌아가니 결과물 나오는 것만 보고 지우도록 합시다.


짜잔


1.7에서는 net.minecraft.util.text.translation.I18n없습니다!

그 대신 똑같은 기능을 포함하는 StatCollector [ net.minecraft.util.StatCollector ] 라는 클래스가 있습니다. 그냥 이름하고 패키지만 바뀐 같은 클래스여서 다른 설명은 필요는 없을 듯 합니다.

net.minecraft.client.resources.I18n은 멀쩡히 존재합니다.



3. TextFormatting


TextFormatting [ net.minecraft.util.text.TextFormatting ] 은 마인크래프트의 텍스트 렌더러에게 주는 사인 같은 겁니다. 이걸로 글씨의 색깔을 하얗게 하거나 빨갛게 하거나 기울이거나 밑줄을 긋거나..하는 일이 가능합니다.


마인크래프트의 포매팅은 16개의 색깔과 6개의 포맷 코드를 지정할 수 있습니다. 아니, 포맷 코드 중 하나는 초기화 코드이니 사실상 5개일까요?


모든 포매팅들은 \u00a7(§)라는 문자 뒤에 한 글자를 붙인 식으로 되어 있습니다. String#format의 %s %d %f 등의 사인과 같이 말이죠.


마인크래프트가 문자열을 렌더링할 때 포매팅 코드를 읽으면, 다음부터 출력되는 문자들에 포매팅 코드에 따른 처리를 해 줍니다. 예를 들어 붉은 색을 입히는 코드 §c가 들어간 "§c빨간색"이라는 문자열을 렌더링한다면, §c를 읽은 뒤의 문자열에 붉은 색을 입혀서 결과물은 "빨간색"이 됩니다.


색깔 코드는 0부터 f까지의(16진수) 모든 글자를 붙여서 만들 수 있으며, 각각 이렇게 됩니다.

§0 -> Black

§1 -> Dark Blue

§2 -> Dark Green

§3 -> Dark Aqua

§4 -> Dark Red

§5 -> Dark Purple

§6 -> Gold

§7 -> Gray

§8 -> Dark Gray

§9 -> Blue

§a -> Green

§b -> Aqua

§c -> Red

§d -> Light Purple

§e -> Yellow

§f -> White


포맷 코드는 k, l, m, n, o, r이 존재하며, 다음과 같습니다.

§k -> 난독화. 글자를 그릴 때마다 임의의 글자를 그려서 정신없이 바뀌는 문장을 만듭니다(..)

§l -> 볼드체(굵게)

§m -> 취소선

§n -> 밑줄

§o -> 이태릭(기울임꼴)

§r -> 모든 포매팅 해제


이것들을 응용해보자면, "§c§l빨간색§r검은색"이라는 문장을 렌더링하면 "빨간색검은색"이 됩니다.


예제입니다. 우리 두 번째 아이템의 이름을 노랗게 물들여 봅시다.


짜잔



포매팅을 사용할 때 주의할 점이 있습니다. 색깔 코드를 입력받으면, 이전에 가지고 있던 포맷 코드는 사라진다는 점입니다.

예를 들어 "§o기울임체§c빨간색"이라는 글자를 그릴 때, 기울임체까지는 정상적으로 적용되지만 §c를 읽어들일 때 기존에 가지고 있던 §o에 대한 기록을 날려버리기 때문에 "기울임체빨간색"처럼 되어 버립니다.

붙어 있어도 마찬가지입니다. "§o§c기울임체와 빨간색"이라는 글자를 그릴 때, 기울임체 바로 다음 들어온 빨간색이 기울임체를 먹어 버리기 때문에 "기울임체와 빨간색"이 됩니다.

첫 번째 문장의 에러를 해결하려면  "§o기울임체§c§o빨간색"처럼 색깔 코드 이후 적용할 포맷 코드를 다시 입력해 주셔야 합니다. 두 번째 문장의 에러를 해결하려면, 순서를 바꿔 "§c§o기울임체와 빨간색"처럼 색깔을 맨 먼저 입력해 주셔야 합니다. 


그리고 TextFormatting [ net.minecraft.util.text.TextFormatting ] 이라는 이름의, 모든 포매팅 코드를 열거형으로 나열해 놓은 enum 클래스가 있습니다. 이것들은 toString()으로 변환하면 각각의 포매팅 코드가 됩니다. 예를 들어, TextFormatting.DARK_BLUE.toString()은 짙은 파란색으로 색깔을 바꾸는 코드 "§1"과 같습니다.


+

포매팅 코드 §를 빠르게 그리는 초간단 방법!

Alt 누르고 넘버 패드에서 0167. 참 쉽죠?



1.7에서는 TextFormatting이 존재하지 않습니다!

대신 EnumChatFormatting [ net.minecraft.util.EnumChatFormatting ] 이 이름과 패키지만 바뀐 채 똑같은 기능을 지원합니다.





공지 목록


[TTMP에 관심이 있으신 분들께 드리는 말씀]


[코노조에 어서오세요]


Posted by Tictim indie.

댓글을 달아 주세요

Tictim과 함께하는 마인크래프트 모딩 101

2. 블럭, 아이템 그리고 모델


※ 이 강좌는 1.10 ~ 1.11버전을 기준으로 작성됩니다.


다시 돌아왔습니다. 틱팀입니다.

이 강좌도 1화처럼 쓰는 데 한세월 걸릴지도 모르겠습니다. 안 그랬으면 좋겠습니다만 말입니다.



1. 아이템


간단한 아이템을 만들어 봅시다. 하나는 평범한 아이템, 하나는 우클릭하면 stdout으로 로그를 출력하는 아이템을 말이죠.


먼저, 아이템을 public static final field의 형태로 보관할 클래스를 만듭니다.

이름은 아무거나 해도 되지만 "Items"는 추천드리지 않습니다. 바닐라에 이미 동명의 클래스가 존재하기 때문에 하나를 임포트하면 다른 하나를 임포트할 수 없게 되는 기묘한 일이 벌어집니다.

저는 메인 패키지에 Contents라는 이름으로 클래스를 자주 만듭니다. 사실 이는 모더마다 많이 다릅니다. 서브 패키지에 클래스를 만든다던지. 어느 쪽이 보편적이냐고 물으시면 제 쪽이 많이 후달리는 느낌?(..)

아무튼, 이 클래스는 인스턴스를 생성할 필요도 상속을 받을 필요도 없으니 final class에 생성자도 private로 해 놓읍시다.


그리고, 이런 형태로 Item 필드를 만들어줍시다. 이 Item이 평범한 첫 번째 아이템이 될 겁니다.

Item [ net.minecraft.item.Item ] 클래스는 이름에서 알 수 있듯이 모든 Item 클래스의 어미 클래스이며, 별 다른 하는 일이 없는 단순한 아이템입니다.


하지만 아이템을 등록하려면 몇 가지 필요사항을 거쳐야 하며, 추가적으로 거치면 좋은(?) 사항들도 있습니다.

 setRegistryName(String name)

 필수적인 사항입니다. 마인크래프트에서 아이템에 관한 정보를 관리할 때 쓰입니다. 소문자만으로 지정하는 것이 좋습니다.

 setUnlocalizedName(String name)

 편하게 아이템의 이름을 지정할 수 있는 메소드입니다. [item.***.name] 식으로 표시됩니다.

 setCreativeTab(CreativeTabs tab)

 편하게 크리모드 탭을 지정할 수 있는 메소드입니다.



이 메소드들은 모두 Item 형태의 자기 자신을 반환합니다. 따라서 저런 사슬 형태의 코드를 통해 한 줄에 한 아이템을 만들고 기본적인 정보까지 맞춰 놓을 수 있습니다.


이제 모드 클래스로 돌아가 봅시다. 저번에 pre-Init, init, post-Init에서 로그를 출력해 봤었죠. pre-Init로 가 봅시다. 비밀결사 어쩌구를 출력했던 곳으로요.


그리고, 이 코드를 추가해 주시면 됩니다.

GameRegistry [ net.minecraftforge.fml.common.registry.GameRegistry ] 에서는 아이템, 블럭, 월드젠, 타일엔티티 등을 게임에 등록시킬 수 있는 클래스입니다. 그리고 register 메소드는 아이템과 블럭을 등록시킵니다. 여기서는 아이템을 등록시키죠.


한 번 런쳐를 실행해 봅시다. 모델을 지정해 주지 않아 와꾸가 빻아 버린 모습입니다만, 어쨌든 아이템은 만들어졌습니다. 이름도 이상합니다. item...뭐? 아무튼.

모델과 이름은 아래에서 만들 예정입니다. 그러면 두 번째 아이템으로 넘어가죠.



특수한 일을 하는 Item의 경우 따로 메소드를 받아 처리해 줘야 합니다. 메소드를 받으려면 메소드를 상속해야 하고, 그러려면 Item을 상속하는 새 클래스를 만들어야겠죠.

일단 클래스를 만들기 이전에, 만들어 뒀던 모드 클래스가 위치하는 패키지 아래에 item이라는 서브 패키지를 만듭시다. 여기에 만드는 모든 Item 클래스를 집어넣으시면 됩니다. 정리가 최고예요.


그리고 그 안에 ItemSecond라는 이름의 클래스를 새로 만듭니다.


Item을 상속하게 해 주세요.


+

이 강좌에 나오는 클래스의 이름은 바꾼다고 해도 작동 여부와 큰 관련은 없긴 합니다. 하지만 그래도 이름을 지을 때 최소한의 규칙은 지켜 주시는 것이 좋습니다. 남한테 좋은 게 아니라 바로 당신에게 좋습니다.

클래스를 상속할 때, 예를 들면 이 Item같은 클래스를 상속할 때는 이름 앞에 부모 클래스의 이름을 붙여 주시는 것이 좋습니다. ItemBucket, ItemSnowball처럼요.

가나다순 정렬로도 이쁘게 나오고, 무엇보다 엄마 아빠 클래스가 누군지 한 눈에 보입니다. ItemAAA와 BlockAAA가 모두 AAA라는 이름이었다고 한다면 누가 블럭이고 누가 아이템인지 어떻게 알아요?


조금 코드를 짜 본 모습입니다.


NOTE 1 - Contents에서 ITEM_FIRST에서 호출했던 세 개의 메소드입니다. 밖에서 호출할 수 있으니 당연히 생성자에서도 호출할 수 있겠죠.

NOTE 2 - 해당 메소드는 아이템을 우클릭했을 때 호출되는 메소드입니다. 여기에 로그를 출력시키는 코드를 넣으면, 우클릭할 때 로그가 나오게 됩니다.

NOTE 3 - 아이템과 월드의 상호작용에 쓰이는 값입니다. 지금은 그냥 넘기셔도 됩니다.


두 번째 아이템도 Contents에 추가해서 인스턴스를 만들어 줍시다. 생성자에서 이미 setRegistryName을 비롯한 3개의 메소드를 호출한 상태이기에 첫 번째와 같이 메소드를 호출하지는 않아도 됩니다.


*

더 정확히 말하자면 호출하지 말아야 합니다. setRegistryName은 두 번 설정하려 하면 오류를 터트립니다.


그리고 두 번째 아이템도 preInit에서 등록해 주세요.


이러면 두 아이템 모두 인게임에서 나오게 됩니다. 들고 우클릭하면 로그도 잘 출력됩니다. 그런데 두 번 호출되네요?

로그가 두 번 호출되는 이유는 클라이언트에서 서버가 하는 일을 따라하기 때문입니다. 로그를 출력하는 걸 따라하는 게 아니라 플레이어가 아이템을 들고 우클릭하는 것을 따라 같은 메소드를 호출하고, 별다른 처리를 안 했기에 같은 줄이 동시에 두 번 호출된 겁니다. 로그를 보시면 하나는 서버 스레드, 하나는 클라이언트 스레드에서 터지고 있죠?


이를 방지하기 위해서는 world#isRemote를 참조하시면 됩니다.

World net.minecraft.world.World ] 는 플레이어가 있는 월드입니다. 블럭, 타일엔티티, 엔티티 등등을 가지고 있죠. 그리고 isRemote 필드는 이 월드가 서버를 따라하는 종속된 월드인지 여부를 가지고 있습니다. 즉 true면 클라이언트 월드, false면 서버 월드라고 생각하시면 됩니다.

여기서는 값을 뒤집어서 서버일 때만 로그가 출력되도록 만들었습니다.


정말로 완성입니다. 아이템 끝!


+

1.10 버전에서는 ItemStack(아이템과 스택 수, 대미지값, NBT 등등을 모두 핸들링하는 클래스) 인스턴스의 취급 방법이 약간 다릅니다. ItemStack 자체의 작동 방식도 다르고요. 따라서 1.10에서 해당 코드를 작성한다면..

이렇게 되겠습니다. 함수에 전달되는 인자가 약간 바뀌었습니다. 그 외에는...실수로 SUCCESS를 PASS로 바꿔 버리긴 했지만(...) 이외에 다른 점은 없습니다.


다른 버전에서 개발하실 때면, 앞으로 맞아떨어지는 함수가 없는 사태가 종종 발생하실 겁니다.

그럴 때는 컨트롤+F키를 이용해 이름이 맞는 함수를 대신 상속해서 코드를 짜시면 됩니다.



2. 블럭


블럭 또한 간단하게 만들어 봅시다. 이번에도 하나는 그냥 블럭, 하나는 우클릭하면 로그가 출력되는 블럭으로 말입니다.


편의상 전에 만들어 둔 아이템 담아두는 곳에 블럭 인스턴스를 만들도록 합시다. Block [ net.minecraft.block.Block ] 은 Item과 같이 모든 블럭의 어미 클래스이며, 별 다른 하는 일이 없는 평범한 블럭입니다.

생성자의 인자로 받는 Material [ net.minecraft.block.material.Material ] 은 지도에서 표시되는 색깔부터 시작해서 히트박스 유효 검사, 도구 사용 여부 등등 오만 가지 사항에 쓰이며, Material 클래스에 전부 public static final field의 형태로 존재합니다. 저는 나무로 넣어 주겠습니다.


아이템과 비슷하게 블럭 또한 등록을 위해서는 필요사항을 거쳐야 합니다.

 setRegistryName(String name)

 필수적인 사항입니다. 마인크래프트에서 블럭에 관한 정보를 관리할 때 쓰입니다. 소문자만으로 지정하는 것이 좋습니다.

 setUnlocalizedName(String name)

 편하게 블럭의 이름을 지정할 수 있는 메소드입니다. [tile.***.name] 식으로 표시됩니다.

 setCreativeTab(CreativeTabs tab)

 편하게 크리모드 탭을 지정할 수 있는 메소드입니다.

하는 방법은 아이템과 거의 똑같습니다.


그리고 모드 클래스로 돌아가, 다음 코드를 추가해 줍시다.

GameRegistry의 register(...) 메소드는 앞에서 말씀드렸다시피 블럭 또한 등록할 수 있습니다.


그리고 아래에 ItemBlock [ net.minecraft.item.ItemBlock ] 인스턴스를 생성해서 등록합니다.

ItemBlock은 Item의 하위 클래스로, 블럭을 받는 아이템 클래스입니다. 우클릭하면 블럭을 설치하는 기능을 구현하고 있으며, 기타 여러 가지 일도 해 줍니다.

블럭 인스턴스 자체는 아이템이 아니어서, ItemBlock을 생성해서 넣어 주어야 비로소 아이템 형태의 블럭을 얻을 수 있습니다. 만약 ItemBlock을 생성해서 넣어 주지 않는다면 그 블럭은 아이템을 얻을 수 없게 됩니다.

+

ItemBlock도 Item의 자녀 클래스입니다. 그리고 Item에서 필수적으로 행해야 하는 사항들 또한 그대로 존재합니다. 그런데 Registry Name만 지정해 줘도 되나요?



사실, ItemBlock은 인자로 준 Block 인스턴스에서 많은 것을 참조합니다. 그 예로 아이템의 이름, 크리모드 탭이 있겠습니다.

따라서 꼬리에 꼬리를 무는 뱀같은 코드를 작성하지 않아도 ItemBlock을 생성할 수 있습니다.


또한 몇 가지 더 참조되는 사항이 있습니다만, 이 사항들은 Item과 Block을 다시 강좌할 때 알아보도록 합시다.


이제 런쳐를 실행해서 block_first 블럭을 설치해 봅시다. 가관이군요. 아이템과 비슷하게 모델을 안 지정해 줘서 기본값인 자주검정 텍스쳐에 박스를 그린 겁니다만, 나중에 고칩시다. 아무튼 블럭이 나왔다는 점에서 기뻐해도 되는 부분 아니겠습니까?? ???


이제 런쳐를 끄고, 두 번째 블럭을 만듭시다. 블럭은 아이템과 비슷하게 마인크래프트 모드를 만들 때 가장 많이 바리에이션을 만들게 되는 클래스 중 하나이기 때문에, 잘 정리해 둘 필요가 있습니다. 패키지부터 만들어 주세요.


그리고 패키지 안에 BlockSecond라는 이름의 클래스를 만들어 주세요.


약간 코드를 짜 본 모습입니다.

NOTE 1 - 블럭이므로 당연히 Block을 상속해야 합니다. 안할시곰보

NOTE 2 - 생성자에는 블럭의 초기화 과정을 그대로 행하시면 됩니다.

NOTE 3 - 이 메소드는 블럭이 우클릭되었을 때 터집니다. 쉬프트를 눌렀을 때를 제외하고 말이죠.

인자가 오지게 많긴 합니다만, 중요한 건 몇 안 됩니다.

1 World - 블럭이 존재하는 월드입니다. 오버월드에서 클릭되었으면 오버월드고, 지옥에서 클릭되었으면 지옥이고.

2 BlockPos - BlockPos는 숫자 3개로 이루어졌으며, 블럭의 위치를 담고 있습니다. 즉 클릭된 블럭의 위치입니다.

3 IBlockState - 블럭스테이트는 지금은 설명하기 약간 어렵습니다만, 대충 말해 블럭과 메타데이터를 합친 블럭 정보 집대성 인스턴스입니다.

4 EntityPlayer - 이 블럭을 클릭한 플레이어 엔티티입니다.

5 EnumHand - 어느 쪽 손으로 우클릭했는지에 대한 값입니다. Main Hand(오른손)와 Off Hand(왼손) 둘 중 하나이며, 값을 오른손으로 해서 호출했다가 특별한 일이 없을 시 값을 왼손으로 바꿔서 다시 호출합니다. 즉 따로 처리를 안 해 주면 이 메소드는 두 번 호출됩니다.

6 EnumFacing - 커서가 클릭된 블럭의 면입니다. 위쪽을 클릭했으면 Up. 아래쪽을 클릭했으면 Down. 이하동문

7 8 9 float - 각각 커서가 클릭된 x 좌표, y 좌표, z 좌표. 써먹을 데는 거의 없을 겁니다.


NOTE 4 - ItemSecond에서처럼, 이 메소드도 클라이언트에서 따라 호출합니다. 그러니 world#isRemote로 검사를 확실히 해 줘야 합니다.

NOTE 5 - true를 반환하면 해당 블럭 클릭에 관련된 상호작용 처리를 중단합니다.

예를 들어 화로를 클릭하면 손에 든 횃불이 설치되지 않죠. 횃불이 클릭 이벤트를 처리하기 전에 화로가 해당 메소드에서 true를 반환했기 때문입니다.

여기에서도 화로처럼 바로 true를 반환해서, 오른손 왼손 두 쪽에서 모두 메소드를 호출하지 않고 한 번만 호출 후 중단하도록 만들었습니다.


Contents로 돌아가서 BlockSecond도 인스턴스를 생성해 주시고,


BlockFirst와 같이 등재를 해 줍시다.


잘(?) 나옵니다.

이제 모든 아이템과 블럭을 만들었으니, 제대로 된 이름도 지어 주고 모델까지 입혀 봅시다!



3. 프록시


..그 전에, 만들어야 하는 중요한 클래스가 있습니다. 프록시라는 이름의 뭔가 복잡해 보이는 클래스입니다만, 사실 전혀 복잡하지 않습니다.


마인크래프트 코드를 보면 @SideOnly [ net.minecraftforge.fml.relauncher.SideOnly ] 라는 어노테이션을 심심찮게 보실 수 있습니다. 이 녀석이 말하는 사이드가 뭐냐면.. 클라이언트Dedicated 서버 중 어느 사이드인지에 대한 정보입니다. 그리고 사이드 온리란 말은 한쪽 사이드에만 존재한다는 의미입니다.

이런 사이드 온리 옵션이 붙은 것들은 생각보다 많습니다. 서버에서 존재할 필요가 없는 것들은 거의 다 클라이언트 온리입니다. 아이템에 커서를 대면 나오는 설명들, 모델과 타일엔티티 렌더러 등등. 이런 것들은 서버에서 신경 쓸 필요가 없죠.

해당 어노테이션이 붙은 클래스, 메소드, 혹은 변수의 경우, 지금의 해당되는 사이드와 다른 사이드에서 로드할 때 해당 어노테이션이 붙은 곳을 완전히 날려 버립니다. 이렇게 무참히 분쇄당한 곳을 함부로 참조할 경우, 프로그램은 바로 에러를 일으키고 꺼집니다. 뭣같죠. 날려버릴 거면 말이라도 하던가!

..그래도, 크래시를 일으키지 않고 해당 기능을 사용할 수 있는 방법이 있습니다. 이것이 바로 프록시입니다.


프록시의 작동 원리는 이렇습니다. 서버 프록시(혹은 커먼 프록시)와 이를 상속하는 클라이언트 프록시, 총 두 개의 프록시 클래스가 있습니다. 포지가 모드를 로드할 때 Dedicated 서버서버 프록시를, 클라이언트클라이언트 프록시를 자동으로 생성해서 모드 클래스에 넣어줍니다.


이 다음부터 꽤 재미있습니다. 서버 프록시에 메소드를, 예를 들어 모델을 지정해 주는 메소드를 만들고, 아무 것도 하지 않습니다. 클라이언트 프록시에서는 이 메소드를 상속해서 실제 모델을 지정하는 일을 수행하게 합니다.

나중에 모델을 만들어야 할 시점에 해당 메소드를 호출하면, Dedicated 서버가 가진 서버 프록시에서는 아무 일도 일어나지 않습니다. 반면 클라이언트가 가진 클라이언트 프록시에서는 모델을 지정합니다. 클라이언트 프록시가 있어야만 모델을 지정하기에, 결론적으로 서버에서 존재하지 않는 클래스를 참조하는 일 없이 모델을 지정할 수 있습니다.

참 쉽죠?? 걱정 마세요. 안 쉬워도 프록시를 쓰다 보면 쉬워집니다. 아마도요.


모드의 메인 패키지에 CommonProxy라는 이름의 클래스를 만듭니다. 이 클래스가 서버에서 생성되는 커먼 프록시가 됩니다.

+

프록시를 따로 패키지를 만들어서 분류하는 모더들도 있습니다. 자기 취향이니 알아서 선택하시면 됩니다. 이런 분들은 프록시의 패키지명으로 주로 [메인 패키지].proxy의 형태를 쓰는 것 같습니다.


내용물은 매우 저렴합니다. 쓸 메소드는 나중에 추가하면 됩니다.


또 모드의 메인 패키지(혹은 프록시 패키지)에 ClientProxy라는 이름의 클래스를 만듭니다.


저렴하게 CommonProxy만을 상속시켜 주면 됩니다.


그리고 CommonProxy의 필드를 모드 클래스에 만듭니다.

이 @SidedProxy [ net.minecraftforge.fml.common.SidedProxy ] 어노테이션은 모드 인스턴스가 만들어질 때 자동으로 프록시의 클래스를 찾아서 인자가 없는 생성자를 호출해 인스턴스를 생성해 넣어 줍니다.

넣어주는 값은 다음과 같습니다.

clientSide - 문자열 형태로 Client Proxy의 클래스 이름을 패키지명까지 포함해서 적으시면 됩니다.제 경우에는 이름이 ClientProxy이고 패키지가 com.tictim.example이니 com.tictim.example.ClientProxy가 되겠습니다.

serverSide - 문자열 형태로 Common Proxy의 클래스 이름을 패키지명까지 포함해서 적으시면 됩니다. 클라이언트 프록시와 동일한 방법으로 적으시면 됩니다.

+

Q1. 클래스 이름이 틀리면 어떻게 되나요?

A1. 터집니다.

Q2. 인자가 없는 생성자를 호출한다고 했는데, 프록시가 인자가 없는 생성자를 가지고 있지 않을 시 어떻게 되나요?

A2. 터집니다. 또 생성자를 public으로 내놓지 않아도 터집니다. 접근성에 관련된 처리를 하지 않아서 호출이 불가능하기 때문입니다.


놀랍게도 이제 프록시를 다 만들었습니다. 대단해 보이는 이름에 비하면 별로 복잡하지 않았죠?

이제 모델을 등록하러 가 봅시다.


커먼 프록시에 Item을 인자로 받는 메소드를 만들었습니다. 커먼 프록시 자체에서는 아무 일도 하지 않지만, 클라이언트 프록시에서 이 메소드를 오버라이드하여 모델을 등록할 겁니다.


클라이언트 프록시에서 오버라이드된 메소드의 모습입니다.

ModelLoader [ net.minecraftforge.client.model.ModelLoader ] 는 이름에서 알 수 있듯이 아이템과 블럭의 모델을 로드하고 관리하는 클래스입니다. 이 클래스의 setCustomModelResourceLocation(...) 메소드는 해당 아이템의 모델 위치를 지정하는데요. 다음과 같은 인자를 가집니다.

1 Item -  지정할 아이템입니다.

2 int - 아이템의 대미지값을 받습니다. 석탄과 목탄 같이 한 아이템을 공유하지만 대미지값에 따라 텍스쳐가 달라지는 아이템을 보신 적이 있으실 겁니다. 이 값을 바꿔 호출해서 대미지값마다 다른 모델을 부여할 수 있습니다.

다만 지금 우리가 만든 아이템은 그런 기능을 가지고 있지 않으니 딱히 이 값을 만질 필요는 없습니다. 기본값인 0으로 넣어 줍시다.

3 ModelResourceLocation - ModelResourceLocation [ net.minecraft.client.renderer.block.model.ModelResourceLocation ] 은 마인크래프트 json 모델의 위치를 나타내 줍니다. 생성자로 받는 인자는 ResourceLocation과 모델의 바리에이션?을 설명해 주는 String입니다.

여기에서는 아이템의 이름을 적당히 참조해서 모델을 찾도록 ResourceLocation은 아이템의 이름을 넣었고, 모델의 바리에이션은 inventory로 넣었습니다.

+

그런데 ResourceLocation이 뭐죠?

ResourceLocation [ net.minecraft.util.ResourceLocation ] 은 도메인과 패스 두 가지의 문자열을 묶어서 저장하는 클래스입니다. (도메인):(패스)의 형태로 변환됩니다. 마인크래프트에서 F3+H(아마도)를 누른 상태로 아이템에 커서를 가져다 대면 맨 아래에 콜론으로 나뉜 두 개의 단어가 보일 것입니다. minecraft:stone과 같은 것들 말이죠. 이게 바로 ResourceLocation입니다.

ResourceLocation은 이름 그대로 텍스쳐, 모델 등의 리소스Resource경로Location를 얻는 데 사용될 뿐만 아니라, 마인크래프트에 등재되는 모든 아이템과 블럭에게 고유한 키 값을 부여하는 데도 사용됩니다. 우리가 이전에 아이템과 블럭에서 호출한 setRegistryName(...)도 사실 (모드 ID):(인자로 전달한 문자열) 형태의 키 값을 부여하기 위해 호출한 겁니다.

1.11 이후부터는 ResourceLocation의 도메인과 패스에 포함되는 대문자가 모두 소문자로 변환이 되어 버립니다. 따라서 여러분이 1.11~12, 혹은 그 이상의 버전 포팅에 있어서 골머리를 썩히고 싶지 않으시다면 도메인(모드 ID)와 패스(블럭, 아이템 등의 이름)는 소문자만으로 지정해 주시는 게 좋습니다. 저처럼 1.10에 대문자 썼다가 낭패를 보는 일이 없으시길 바랍니다. 흑흑


이제 프록시의 메소드를 사용하여 모델을 지정하면 됩니다. 결과물에 비해선 설명이 상당히 장황하군요. 흠흠

이제 모델의 위치를 지정했으니, 모델 자체를 만들러 가 봅시다.



4. 모델

이 파트에서 설명하는 대상은 1.7 버전에는 없습니다. 1.7에서는 대신 IIcon 등등의 인스턴스를 이용해서 블럭과 아이템 등등을 직접 그리는데, 이에 대해서는 나중에 기회가 되면 설명드리겠습니다. 여기서는 1.8 이후의 모델만을 간단히 강좌합니다.


1.8 버전 때, 마인크래프트의 렌더링 시스템은 엄청난 변화를 겪었습니다. 바로 악명 높은 JSON 모델의 등장입니다. JSON은 간단한 데이터 상수를 기술하는 쉬운 포맷입니다. 마인크래프트는 Pre-Init과 Init 스테이지 사이에서 게임에 등재된 모든 모델들을 읽어들이고 미리 만들어진 모델 렌더러로 변환시킵니다.


4-1. 아이템의 모델


먼저 특정 경로에 폴더를 만들어야 합니다. MDK폴더로 가서 다음 경로까지 폴더를 만들어 주세요. 여기가 마인크래프트가 아이템의 모델을 참조하는 경로가 됩니다.


[MDK 폴더 이름] -> src -> main -> resources -> assets -> [모드 ID] -> models -> item


이제 Item_First의 모델을 만들어 봅시다. 당연히 JSON으로 쓰여져 있으며 내용물은 다음과 같습니다.

2번째 줄 - parent는 다른 모델의 서식을 그대로 베껴 오는 역할을 합니다. 값은 ResourceLocation으로 지정이 되는데요, 이 경우에는 도메인이 없고 패스만 존재하기 때문에 도메인은 기본값인 minecraft, 즉 마인크래프트 바닐라의 것으로 설정이 됩니다.

여기 보이는 minecraft:item/generated라는 위치에 존재하는 json 모델의 서식을 베껴오는데요, 이 모델은 간단히 말하자면 그냥 평범한 아이템입니다.

3번째 줄~5번째 줄 - textures에서는 위에서 불러온 generated로 무슨 텍스쳐들을 렌더링할 것인지를 ResourceLocation으로 전달합니다.

보통 layer0으로 단 한 개의 텍스쳐를 그리도록 명령하며, 여기서는 example:items/item_first라는 이름의 텍스쳐를 찾도록 지정했습니다.


이 json 모델을 방금 만들어 놓은 모델 파일에 .json 확장자를 붙여 저장해 주세요. 이름은 아이템의 setRegistryName에 넣은 그대로 붙여 주시면 됩니다. 저는 item_first를 사용했으니 item_first.json으로 저장해야겠죠?


텍스쳐를 지정했으니, 이제 텍스쳐를 넣어 줘야 합니다. 마인크래프트가 json 모델에서 지정된 ResourceLocation에서 텍스쳐를 찾는 방법은 다음과 같습니다.

(도메인):(패스)  =  assets -> (도메인) -> textures -> (패스).png

여기서 저는 example:items/item_first라는 이름의 텍스쳐를 찾도록 지정했습니다. 따라서 제가 텍스쳐를 넣어야 할 곳은

assets -> example -> textures -> items -> item_first.png

가 됩니다.

디렉토리를 입력하는 것처럼 빗금으로 세부 폴더를 지정할 수 있습니다. 여기서는 items라는 세부 폴더에 텍스쳐를 지정했죠.


1분도 안 돼서 걸작이 나왔습니다. 와!안두인!가면라이더! 정말 멋잇ㅆ씁니다. 모나리자에 비벼 볼 만한 완성도입니다.

다들 그렇게 생각하죠? 어서 그렇다고 말하십시오


아무튼 텍스쳐를 넣어 줍니다. 이걸로 1번째 아이템이 완성되었습니다.


같은 식으로 item_second도 마무리해 줍시다! 이건 혼자서 한 번 해 보세요. 직접 해 보는 것도 많은 도움이 됩니다.


+

json 모델에 오타가 있으셨나요?

그렇다면 F3+T를 입력해 보세요. F3+T는 마인크래프트에서 로딩한 모든 리소스를 다시 로드합니다. 이는 텍스쳐, 모델, lang 파일 등등을 포함합니다. 인게임에서 누르시면 됩니다.


4-2. 블럭의 모델


아이템이 비교적 만들기 쉬웠던 반면, 블럭의 모델은 조금 복잡합니다. 블럭스테이트가 개입하기 때문입니다.


블럭스테이트는 앞에서 간단히 말해서 블럭+메타데이터라고 말씀드렸습니다. 그리고 모델을 렌더링할 때도 블럭스테이트를 참조하는데요, 이에 따라 블럭스테이트의 모든 바리에이션마다 json 모델을 지정할 필요가 있습니다.

하지만 다행히도 우리가 만든 블럭은 바리에이션이 없는 단순한 블럭이기 때문에, 쉽게 모델을 지정할 수 있습니다.


블럭스테이트에 따른 모델을 지정하는 json입니다. 우리는 바리에이션이 없기 때문에 4열에 있는 example:block_first라는 모델만 만들어 주면 됩니다.


이 블럭스테이트 json 파일을 해당 위치에 저장해 주세요.

assets -> [모드 ID] -> blockstates

여기서 마인크래프트가 블럭의 Registry Name과 동일한 이름을 가진 json 파일을 참조합니다.


이번에는 블럭의 모델입니다.

minecraft:block/cube_all은 모든 면이 한 텍스쳐로 감싸져 있는 블럭입니다. textures로 전달한 all 텍스쳐를 받아서 6면과 파티클을 렌더링합니다.


이 블럭 모델은 models -> block에 저장해 주세요. 블럭스테이트에서 모델을 참조할 때 models/block 폴더에서 모델을 찾기 때문입니다.


블럭의 모델만으로는 아이템을 렌더링할 수 없기에 아이템 버전의 블럭에도 모델을 만들어 줘야 합니다. 여기서 우리는 블럭의 모델을 parent로 지정하겠습니다. 딱히 인벤토리 안의 블럭과 월드에 설치된 블럭이 다를 필요도 없으니 말이죠.

만약 아이템과 블럭이 다르게 보이는 것을 원하신다면, 아이템 모델을 구현하던 것처럼 parent를 item/generated로 설정하고 텍스쳐를 넣어 주세요. 깔때기 비슷한 모양새를 만드실 수 있습니다.


아이템의 렌더링에 사용될 모델이니, item 디렉토리에 넣어줍시다.


이번엔 10초도 안 걸려서 만들었습니다. 네, 귀찮습니다. 어차피 제대로 된 모드도 아니고 예제인데 뭐!빼애액!

여러분은 이런 틱팀을 본받지 말고 열심히 도트를 찍어 보도록 하세요.


이번엔 텍스쳐를 assets -> example -> textures -> blocks -> block_first.png에 넣도록 합시다. 블럭 모델에서 그렇게 지정했기 때문입니다. 원하신다면 바꾸셔도 무방합니다.


만들어야 하는 json 파일의 양이 늘어났고 귀찮음도 제곱으로 늘어난 거 같긴 하지만, 두 번째 블럭도 만들어 보도록 합시다!


결과물입니다. 너무 아름답지 않ㅅ습니까ㅣ??

이번엔 아니라고 말씀하셔도 됩니다. 하지만 그렇다고 말해 주시는 것만으로도 저에게는 큰 힘이 될 수도 있고 아닐 수도 있습니다. 자세한 사항은 그렇다는 말씀을 받은 이후 알려드리도록 하겟습니다. 제가 틱팀입니까 갑팀입니까!!앺X아바타입니까!!중얼,,중얼,,



5. 현지화Localization


제대로 아이템이 렌더링되어도 아직 해결되지 않은 사항이 한 가지 있습니다. 그것은 바로...

이름입니다. 이번엔 이 테러블한 이름을 고쳐서 우리에게 친숙한 이름으로 바꿔 봅시다. 바로 lang 파일을 만들어서 말이죠.


lang 파일은 별 거 없습니다. 현지화 전 이름과 현지화 후 이름을 (이전)=(이후) 식으로 대응해 한 줄 한 줄 나열해 놓은 게 다입니다. 마인크래프트에서는 이 lang 파일을 읽어서 우리에게 보이는 이 이상한 item.뭐시기 뭐시기.name 비슷한 이름을 다른 친숙한 이름으로 자동으로 바꿔 줍니다.


이렇게 전에 보이던 이상한 이름들과 이를 대체할 이름을 =으로 대응시킨 파일을 작성해서


ko_kr.lang으로 저장해 주세요. 소문자 맞춰 주시구요.

마인크래프트에서는 한국어 현지화를 할 때 이 ko_kr.lang 파일을 읽어서 사용합니다.


en_us.lang이라는 이름으로도 파일을 만들어 주세요.

이는 한국어 lang 파일보다 중요합니다. 그 이유는 한국어 이외의 현지화를 할 때 다른 lang 파일이 없으면 기본적으로 en_us.lang 즉 영어의 현지화 결과를 출력하기에, en_us.lang이 없으면 한국어에서만 이름이 나오고 다른 나라 말로 바꾸면 이상한 item.뭐시기 뭐시기가 또 출력되는 현상이 생기게 됩니다.

아무리 영어를 못 해도 en_us.lang 파일은 만들어 줍시다. 그 전에 기본적인 단어만 알면 이름 정도는 다 적을 수 있을 겁니다.


이러면 이름에 모델에 기능까지 전부 만드셨습니다. 여기까지 따라오신 분들 모두 수고하셨습니다. 중간에 막히신 분들도 열심히 노력해서 좋은 성과 얻으시길 빕니다.


+

lang 파일에 오타가 있으셨나요?

그렇다면 F3+T를 입력해 보세요. F3+T는 마인크래프트에서 로딩한 모든 리소스를 다시 로드합니다. 이는 텍스쳐, 모델, lang 파일 등등을 포함합니다. 인게임에서 누르시면 됩니다.

복사,,붙여넣기,,앗,,아앗,,


+

우리 한국어가 좀 아픈 것 같아요

한국어가 이상하게 깨지는 이유는 인코딩 때문입니다. ko_kr.lang파일을 열어서 인코딩을 UTF-8로 변환해 주세요.

변환을 못 하시는 분들께서는,,,,,음,,,,, 여기는 메모장 혹은 노트패드++ 강좌가 아니기에, 직접,,,,,


아뇨 어렵지는 않아요. 그냥 쓰기 귀찮아요. 빼애액!!



6. 차회?


다음 화에서....적을 게 약간 애매하기도 하고, 다른 강좌들처럼 바로 타일엔티티+인벤토리 -> GUI로 넘어가는 심플한 과정이 재미없을 수도 있는지라 댓글로 차회에 무엇을 쓸지를 조사해 보고 싶습니다.


1) 초보자가 따라하기 쉬운 절차적 구성 (이 강좌와 같은 구성)

아무것도 바탕에 깔리지 않은 상태에서 읽기 좋습니다. 따라하기는 쉽지만 수박 겉핥기 수준의 글이라서 그 이상의 레벨에서는 얻을 건 별로 없을 겁니다.

커스텀 화로(?) 제작 (타일엔티티, 인벤토리와 컨테이너, GUI)

커스텀 활/포션 제작 (심도 있는 아이템 강좌, 플레이어 인벤토리의 사용법, 포션 효과)

등등.


2) 심도 있는 하나의 클래스에 대한 고찰

하나의 클래스에 대해 집중적으로 서술합니다. 배경 지식이 필요하지만, 미처 알지 못했던 사항들을 발견할지도 모릅니다. 또한 분량이 이런 큰 강좌보다는 짧게 여러 번 나오기에 틱팀이 글을 더 잘 쓴다는 이점도 있습니다(...)

개인적으로는 이런 종류들을 쓰고 싶네요.

아이템(+IItemColor)

블럭(+블럭스테이트)

모델(+Json 모델)

NBT Data(Compound, List, Primitive...)

등등.


사실 둘 다 쓰면 좋기는 합니다. 하지만 제가 맨날 글만 싸지르는 기계도 아니고 그렇게 빠르게 많은 분량의 글을 쓸 수는 없잖아요?? 라고 변명 좀 해 보겠습니다. 하!하!

그럼 이만 변명하는 기계 틱팀은 변명을 하러 물러가겠습니다.안녕여러분





공지 목록


[TTMP에 관심이 있으신 분들께 드리는 말씀]


[코노조에 어서오세요]



Posted by Tictim indie.

댓글을 달아 주세요

  1. 이거이거 완전 스포자낭?

    2017.10.19 13:12 신고 [ ADDR : EDIT/ DEL : REPLY ]

Q. TTMP Lite 근황은 죽었나요?

A. 님들이 댓글을 안 써서 죽었음.


Tictim과 함께하는 마인크래프트 모딩 101

1. Forge Gradle과 기본 세팅


※ 이 강좌는 1.10 ~ 1.11버전을 기준으로 작성됩니다.


틱팀입니다. 다시 만나 반갑습니다. 처음 보신 분들은 처음 뵙겠습니다. 틱팀입니다.


정말 오랜 시간이 지나고 나서야 모딩 강좌를 쓰게 되었습니다. 사실 처음부터 모딩 강좌를 쓰고 싶었습니다. 멋지잖아요. 어그로도 잘 끌리고

아무튼, 이 모딩 강좌 시리즈로 어그로를 끌어 블로그를 되살ㄹ모딩을 배우고 싶은 많은 분들에게 여러 정보를 드리고 싶습니다.


시작하기 전에, 이 강좌에 관한 몇 가지 사항들을 말씀드리고 싶습니다. 숙지해 주세요.


1. 만약 Java를 배우지 않았다면, 먼저 Java를 배우십시오.

이 모딩 강좌 시리즈는 전반적으로 Java에 대한 이해를 필요로 합니다. 모드 메이커(웃음) 비슷한 것들은 거릅니다.

하지만 Java를 배워라! 해서 막막해하실 필요는 없습니다. 저는 Java는 제대로 된 입문서 한 권(정석이나 뭐 이런 것)과 꾸준히 책을 따라해보는 마음가짐만 있으면 못 배울 게 절대 아니라고 봅니다. 인터넷에 올라온 강좌들은.. 저는 뭔가 보기가 불편하더군요. 잘 찾아보면 좋은 것들도 있겠지만 저는 책 한 권으로 따라해보면서 배웠습니다. 책을 다 읽어도 모르겠으면 아예 책을 끼고 살면서 모르는 것을 그때그때 찾아서 따라 해보시면 될 겁니다.

인터넷 강좌를 보시던, 책을 보시던, 중요한 건 꾸준함입니다.


2. 댓글에 달린 질문은 답변해 드립니다. 하지만 소스코드는 Pastebin을 사용해서 보내 주셔야 합니다.

아무런 하이라이트도 없이 드르르륵 늘어진 코드만큼 보기 싫은 코드도 없습니다. Pastebin은 최소한의 예의라고 생각해 주세요.

또, 답변에 구문 오류입니다라는 말이 있으면, Java 자체의 문법을 잘못 지켰다는 소리입니다. 그리고 이는 곧 틱팀이 문제를 고쳐 주지 않으니 여러분이 찾아서 해결해야 한다는 뜻입니다.


3. 강좌에 사용된 소스코드의 Copy/Paste는 지원하지 않을 예정입니다.

Java를 배우는 제대로 된 방법은 직접 타이핑해 보고 빌드해 보고 실험해 보는 것입니다. 소스코드에 대한 이해와 경험도 없이 Ctrl+C Ctrl+V로 강좌를 마무리해 버리면 기억에도 안 남고 나중에 써먹지도 못합니다.


서문이 너무 길어졌습니다. 시작하죠.


0. Java 세팅


일단, Java를 배우셨다면 설치되었을 JDK환경 변수 세팅은 여기서도 사용합니다. 혹시나 모르시는 분들은 여기에서 설명하기엔 조금 긴 부분이니 구글 하시면 됩니다. 간단하게 말씀드리자면


http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

1. JDK 설치.


https://www.google.com/search?q=jdk+%ED%99%98%EA%B2%BD+%EB%B3%80%EC%88%98+%EC%84%A4%EC%A0%95&oq=jdk+%ED%99%98%EA%B2%BD+%EB%B3%80%EC%88%98+%EC%84%A4%EC%A0%95&aqs=chrome..69i57j0l5.5351j0j9&sourceid=chrome&ie=UTF-8

2. 환경 변수 세팅.


https://eclipse.org/home/index.php

3. 그리고 이클립스 설치.



1. Forge Gradle


포지팀에서는 마인크래프트 코드를 디컴파일해 주고, 그 와중에 버그도 고쳐주고(..), 유용한 클래스도 추가해 주고...해서 유저들이 더 쉽게 마인크래프트를 입맛대로 고칠 수 있게 해 줍니다. 또 마인크래프트 모드를 쉽게 빌드할 수 있는 툴을 배포하는데요, 우리는 이것을 받아서 개발 환경을 만들 겁니다.


http://www.minecraftforge.net/forum/

마인크래프트 포지의 공식 사이트입니다. 여기서 릴리즈된 마인크래프트 포지의 파일을 받거나, 여러 정보를 얻을 수도 있습니다. 영어를 할 줄 안다면 포럼에 질문도 할 수 있겠죠.


일단, 맨 위에 Home Files Docs의 3개 화면을 보실 수 있겠습니다. Home은 당신이 보고 계시는 바로 그 곳이며, Files는 포지 파일을 받을 수 있는 곳입니다. Docs는 포지 모드 개발에 매우 도움이 되는 기본적인 사항들이 영문으로 적혀 있습니다. 걱정 마세요, 제가 다 설명해 드릴 예정입니다.

여기서 우리는 Files로 가시면 됩니다.


개발하려는 마인크래프트 버전을 골라서 최신(혹은 추천하는) 버전의 Mdk라고 되어 있는 것을 내려받습니다. 1.7 이하에서는 Src가 그것입니다. 이 강좌는 1.11을 기준으로 작성될 예정이기에, 저는 1.11 버전의 Mdk를 받았습니다.


해당 파일을 받아서 압축을 해제하시면 됩니다. 보기 좋게 이름도 바꿔 주시구요.

안에는 빌드/라이브러리 관리 전용 툴인 Gradle의 구성요소들과 편리한 Github 연동을 위한 .gitignore 파일, IDK인 이클립스와의 빠른 연동을 위한 eclipse 폴더, 그리고 여러분의 모드에 들어갈 것들을 집어넣는 폴더인 src 폴더가 있습니다. 그 이외에는 크레딧과 라이센스 명시를 위한 텍스트 파일, 체인지로그, 간단한 사용설명서(..)가 있습니다.

먼저 우리는 지금 보고 있는 mdk 폴더에서 cmd를 호출할 겁니다. Shift를 누른 상태로 빈 곳을 클릭한 후 여기서 명령 창 열기를 선택하세요.


cmd 창입니다. gradlew setupDecompWorkspace eclipse를 입력하세요. 맨 앞의 gradlew는 gradlew.bat을 실행한다는 의미이고, setupDecompWorkspace는 포지 모드의 개발 환경을 구축하라는 소리고, eclipse는 eclipse 폴더를 해당 프로젝트의 설정에 맞게 초기화시키라는 소리입니다.

이 작업은 꽤 많은 시간을 잡아먹으니 켜 두시고 할 일 하고 계세요. 끝나면 완료되었다는 콘솔 창의 메시지와 함께 mdk 파일이 늘어나 있을 겁니다.


그리고 eclipse에 [mdk 폴더]/eclipse를 Workspace로 지정하여 프로젝트를 열어서 왼쪽 화면에 있는 패키지 익스플로러에 mdk 안의 파일이 보이면 환경 세팅은 끝난 겁니다.

아, src/main/java 항목 아래에 있는 패키지와 클래스는 지워 버려도 무방합니다. 포지에서 주는 예제 클래스이고, 별 다른 건 없습니다. 이 강좌 한 장 안에서 다 설명하는 내용입니다. 지금 지우지 않아도, 빌드할 때는 반드시 지우셔야 합니다.



2. 모드 클래스 작성


모든 포지 모드들은 모드 클래스를 가지고 있습니다. 모드 클래스는 모드 ID, 모드의 이름 등 모드에 관한 정보를 가지고 있으며 FML에서 터트려 주는 이벤트를 받아서 아이템과 블럭 등등을 게임에 등록하는 일을 맡습니다.


먼저 모드의 이름을 기반으로 패키지를 만들어 봅시다. 정리하는 일은 세상에서 가장 중요하니까요. src/main/java를 우클릭 -> New -> Package 클릭.

패키지 이름은 아무거나 붙여서 만들 수도 있지만, 제가 추천드리는 방법은 이 둘 중 하나입니다.


(도메인 이름) . (팀 혹은 제작자 이름) . (프로젝트 이름) . [세부 패키지]

혹은 (팀 혹은 제작자 이름) . (프로젝트 이름) . [세부 패키지]

여기서 프로젝트 이름은 여러분 모드의 ID를 붙이면 됩니다.

제 모드의 경우, 도메인은 아무거나 붙여서(...) com으로 지었고, 제작자는 tictim을 붙였습니다. 따라서

com.tictim.ttmpcore

com.tictim.hungerkeeper

com.tictim.mobcapabilities

등등의 패키지 이름이 됩니다.

+

참고할 만한 사항으로, 1999년에 Sun에서 패키지, 클래스, 함수 등등의 이름 제정에 관한 규칙에 대해 기술한 아티클이 있습니다.만, 영어입니다.

http://www.oracle.com/technetwork/java/codeconventions-135099.html


어쨌든, 저는 간단하게 com.tictim.example로 할게요. 여러분은 마음에 드시는 걸로 만드시면 됩니다.


패키지 다음엔.. 패키지 안에 모드 클래스를 만듭시다. 방금 만든 패키지를 우클릭 -> New -> Class 클릭.

보통 모드 클래스의 이름은 모드의 이름으로 짓습니다. 물론 클래스 작명의 일반적인 규칙대로 맨 첫 번째 글자를 대문자로 해서 말이죠. 가끔 뒤에 Mod를 붙이기도 합니다. 저만 쓰는 것 같습니다만. 흠흠.

아무튼 모드 이름이 Apple이라면 모드 클래스 이름은 Apple, 혹은 AppleMod. 모드 이름이 Ball이라면 모드 클래스 이름은 Ball, 혹은 BallMod. 이런 식으로 지으시면 됩니다.


그리고, 클래스에 @Mod [ net.minecraftforge.fml.common.Mod ] 어노테이션을 붙입니다. 이 어노테이션이 붙어 있는 클래스는 마인크래프트가 시작될 때 포지가 다 긁어모아서 모드로 인식해 주며, 자동으로 인스턴스를 만들어 주고 여러 일을 합니다.


이 어노테이션이 받는 인자는 이러합니다.

modid = 모드의 고유한 ID. 다른 모드와 겹치면 꽤나 안 좋은 일이 일어납니다. ResourceLocation같은 오만 가지 사항에 들어갈 일이 많으니, 호환성을 위해 소문자만으로 구성하시는 것이 좋습니다.

name = 모드의 이름. 좀 더 유저에게 친숙한 쪽이죠.

version = 모드의 버전입니다. 포지에서는 숫자 4개로 이루어진 버전 카운팅을 추천합니다. 0.0.0.0, 1.0.3.0 이런 식이죠. 우리는 아직 버전 카운팅을 안 했고 아직 개발 중이기에 초기값인 0.0.0.0으로 맞춰 주시면 됩니다.

+

포지도큐에서 버전 카운팅에 대한 페이지를 찾아볼 수 있습니다.만, 영어입니다.

https://mcforge.readthedocs.io/en/latest/conventions/versioning/


이 밖에도 많은 양의 추가 정보를 줄 수도 있지만 더 설명하면 아주 빠르게 복잡해지니 넘어갑시다.

저 3가지의 정보는 보통 모드 클래스 안에 public static final field의 형태로 선언해 둡니다. 특히 Modid는 말이죠.


그 다음에는 포지의 Loading Stages에 맞춰 메소드를 호출받도록 해 봅시다.

포지가 @Mod 어노테이션이 붙은 클래스를 모드 클래스로 인식을 하듯이, 모드 클래스 안의 메소드에 @EventHandler [ net.minecraftforge.fml.common.Mod.EventHandler ] 어노테이션을 붙임으로써 "내가 이 이벤트가 터질 때 메소드를 호출받고 싶다"라고 FML에게 알려 줄 수 있습니다.

하지만 아무 메소드에다 다 붙여서 효과를 보는 것은 아니고, FML Event 인스턴스 하나를 인자로 받는 메소드만 유효한 이벤트 핸들러로 등록이 가능합니다. 이름은 상관이 없습니다.


포지의 Loading Stages는 크게 3가지로 나뉩니다. 각각 단계별로 해야 하는 작업이 다르니 이 점 명심하세요.

이름

설명

역할(?)

Pre-Initialization

[ net.minecraftforge.fml.common.event.FMLPreInitializationEvent ]

마인크래프트가 블럭과 아이템을 등록하고 모델을 만들기 이전 단계입니다.

ItemColors와 같은 렌더링 관련 인스턴스는 이 단계에서는 생성되지 않았을 수도 있습니다.

블럭/ 아이템 등록

타일엔티티 등록

엔티티 등록

오어딕 등록

 Initialization

net.minecraftforge.fml.common.event.FMLInitializationEvent ]

Pre-Init 이후의 단계입니다. 아이템과 블럭의 등록이 끝나고, 렌더링 관련 인스턴스 또한 생성됩니다.

월드젠 등록

레시피 등록

이벤트 핸들러 등록

IMC 메시지

 Post-Initialization

[ net.minecraftforge.fml.common.event.FMLPostInitializationEvent ]

거의 모든 등록이 끝난 시점입니다. 주로 다른 모드와 관련된 일을 합니다.

모드 호환성 개선

기타 타 모드 관련

+

포지도큐에서 Loading Stages에 대한 페이지를 찾아볼 수 있습니다.만, 영어입니다.

https://mcforge.readthedocs.io/en/latest/conventions/loadstages/

또한 숙련된 모더 분께서는 RegistryEvent에 관한 페이지도 읽어 보시는 것을 추천드립니다.만, 영어입니다.

https://mcforge.readthedocs.io/en/latest/concepts/registries/#registering-things


이 강좌에서는 저 역할을 테스트해 보지는 않고, 간단하게 이벤트를 제대로 받았는지 테스트해 보기로 합시다.

이벤트가 터졌다면 stdout으로 해당 이벤트에 있는 메시지를 남기겠죠?


실행해서 결과를 보고 싶으시다면, 위쪽 칸에 있는 재생 버튼을 클릭하시면 됩니다. Server를 클릭하여 Dedicated Server를 실행하실 수도 있습니다. 하지만 로그인은 할 수 없으니, 온라인 모드는 끄셔야 합니다.


또한, java 코드 작성 창 아래에 있는 콘솔에 로그가 출력됩니다. stdout으로 세 개의 로그가 출력된 게 보이시나요?


다음 시간에는 아이템과 블럭을 만들어 보고, 모델을 간단하게 알아 보도록 하겠심미다. 깊은 내용은 아니니 간단할 거예요. 그럼 여러분 안녕




공지 목록


[TTMP에 관심이 있으신 분들께 드리는 말씀]


[코노조에 어서오세요]


Posted by Tictim indie.

댓글을 달아 주세요

  1. berany

    글 잘 보고 갑니다 긴 글 작성하시느라 수고 많으셨어요!
    다름이 아니라 강좌를 부탁드리고 싶어서 댓글을 달았는데요!
    혹시 특정 아이템을(모드로 제작한 아이템) 우클릭하면 제가 지정한 그림이 gui로 마인크래프트 화면에
    나타나게 할 수 있을까요?
    혹시 방법을 알고 계시다면 실례가 안된다면 강좌를 부탁드리고 싶습니다 ㅠㅠ
    좋은하루 되세요!

    2017.10.19 13:02 [ ADDR : EDIT/ DEL : REPLY ]
    • 어느 GUI인지에 따라서 달라집니다. 그냥 텍스쳐만을 렌더링하는 GUI의 경우에는 그냥 클라이언트 사이드에서 처리만 해 주면 됩니다만, 가방 같은 인벤토리를 가지고 상호작용하는 GUI의 경우 서버와의 연동을 위해서 GuiHandler를 사용합니다. 연동할 인벤토리를 생성하기 위해서는 Capability/IEEP(1.7)도 강좌해야 합니다. 어느 쪽을 원하시는지 다시 답변해 주시면, 그 쪽으로 방향을 잡아 보겠습니다.

      2017.10.21 19:27 신고 [ ADDR : EDIT/ DEL ]
  2. 아맞다 여기 가입되있었지

    2017.10.19 13:12 신고 [ ADDR : EDIT/ DEL : REPLY ]
  3. 하이퍼

    mdk 압축 풀고 gradlew setupDecompWorkspace eclipse도 잘 했는데 이클립스 옥시젠으로 eclipse 폴더 들어갈 때 오류가 뜹니다... 로그는 pastebin으로 https://pastebin.com/hKFc3uV7 에 복사했습니다.

    2017.11.03 20:43 [ ADDR : EDIT/ DEL : REPLY ]
    • 이상하군요. mdk는 이클립스 자체에는 전혀 간섭을 하지 않습니다. 아마 이클립스 쪽의 오류가 아닌가 싶습니다만, 저도 이클립스의 오류에 대해서는 자세히 모르니 구글에서 찾아 보거나 하셔야 할 것 같습니다. 제가 말씀드릴 수 있는 부분은 clean 안되면 재설치밖에 없습니다.

      2017.11.04 12:01 신고 [ ADDR : EDIT/ DEL ]
    • 하이퍼

      해결했습니다! C드라이브 경로에 바로 넣었더니 되는군요... .mdk 경로에 한글이 들어가있으면 안되는거 같아요

      2017.11.04 22:23 [ ADDR : EDIT/ DEL ]
  4. 희군

    cmd에 gradlew setupDecompWorkspace eclipse를 입력하니
    FAILURE: Build failed with an exception. 실패했다고 뜨네요.
    원인이 Unable to start the daemon process. 라고 하는데 찾아보니 이게 JVM에 충분한 메모리 공간이 확보되지 않아서 발생하는 문제라고 하더군요. 혹시 어떻게 해결해야 할 지 알려주실 수 있나요?

    2018.06.30 20:44 [ ADDR : EDIT/ DEL : REPLY ]
  5. 방금 모딩시작

    코딩을 다 하고나서 실행하려고하는데 안된다그러네요 그러고나서 보니까 목록창에 mdk폴더 아래에 x표시가 있는걸 발견했어요 뭔가 잘못된것같은데 이유를 모르겠네요

    2020.12.17 16:24 [ ADDR : EDIT/ DEL : REPLY ]