강의 환경 : forge-1.8.9-11.15.1.1855

1.8.9에서 작성된 강의이지만, NBT를 다루는 개념은 다른 버전과 동일합니다. 단, 텍스처(texture)와 관련된 부분은 완전히 다르니 적절히 코드를 수정하시길 바랍니다.

 

배경 지식

  1. 기본 아이템
  2. 아이템 이벤트 함수
  3. NBT
  4. 아이템 스택

강의목표

  1. NBTTagCompound를 아이템 스택(Item Stack)에 대해 사용하는 방법을 익힌다.

 

1. NBT(Named Binary Tag)

NBT는 마인크래프트에서 데이터를 저장하기 위한 파일에 쓰이는 자료 형식입니다. 다양한 이름표(tags)로 이루어진 트리(tree) 구조를 띄도록 설계 되었습니다. 각 이름표는 다양한 자료형의 데이터를 담을 수 있습니다. 가능한 자료형은 int, float과 같은 숫자부터 String, List, Compound(hash)까지 다양합니다. 트리 구조라는 것은 하나의 이름표가, 또 다른 이름표들을 계층적으로 담을 수 있다는 의미입니다.

여러 기능을 모드에 추가하다 보면 아이템에, 정확히는 아이템 스택에, 정보를 저장해야 할 일이 생깁니다. 모드를 처음 만들기 시작하는 사람들은 종종 아이템 클래스 내부에 멤버 변수를 만들어 문제를 해결하려고 합니다. 하지만 이런 접근은 완전히 잘못된 접근입니다. 왜냐하면 아이템 객체는 모든 아이템 스택이 공유하기 때문입니다. 따라서 데이터를 저장하기 위해서는 아이템 스택에 NBT의 형태로 정보를 저장해야 합니다.

그림 2 객체 표현도

아이템 스택(Item Stack)의 NBT는 항상 Compound의 자료형을 가집니다. Compound는 해쉬(Hash) 구조를 가지고 있어서 문자열(String) 키(Key)를 이용해서 다양한 NBT를 값(Value)으로 저장할 수 있습니다. 때문에 Compound가 가지는 모든 키(Key)는 유일 해야 합니다. 만약 같은 키를 가진 값이 입력되면 덮어쓰게 됩니다.

 

2. NBT를 사용하는 아이템 만들기

이번 강의에서는 특정 좌표를 지정해서 귀환할 수 있는 아이템을 만들어 보도록 하겠습니다. Shift + 우클릭을 하면 현재 좌표가 아이템의 NBT에 저장이 되고, 이후 우클릭을 하면 저장된 위치로 이동합니다. 본 예제에서는 NBT를 쓰고, 읽는 것과 더불어서 엔티티(Entity)를 조작하는 것 또한 부가적으로 다뤄집니다.

먼저 귀환석(ItemTelepoter)의 아이템 클래스를 작성합니다. 지난 강의에서 다뤘던 족집게(ItemTweezer)와 뼈대가 상당히 비슷하니 참고하시길 바랍니다.

ItemTeleporter.java

package oortcloud.basictutorial.item;

 

import net.minecraft.creativetab.CreativeTabs;

import net.minecraft.item.Item;

import net.minecraftforge.fml.common.registry.GameRegistry;

 

public class ItemTeleporter extends Item {

 

    public ItemTeleporter() {

     this.setMaxStackSize(1);

this.setCreativeTab(CreativeTabs.tabTools);

this.setUnlocalizedName("teleporter");

GameRegistry.registerItem(this,"teleporter");

    }    

}

이제 언어(Language) 파일과 텍스처(Texture) 및 모델(Model)을 추가해서 기본적인 틀을 갖추어 보도록 하겠습니다. 1.8.9 버전을 기준으로 하기 때문에, 이 부분에서 지난 강의들과 차이가 있습니다. 이곳을 확인하세요. 언어 파일은 동일합니다. ModItems 또한 적당히 작성해 주시면 됩니다.

그림 3 아이템 뼈대 적용

 

 

3. NBT를 사용하여 기능을 구현하기

이제 이벤트 함수에 NBT를 이용해서 아이템에 좌표를 저장하고, 저장된 좌표로 순간이동을 하는 코드를 작성해보도록 하겠습니다. 아이템을 손에 들고 우 클릭 했을 때 호출되는 이벤트 함수는 onItemRightClick ()입니다.

public ItemStack onItemRightClick(ItemStack itemStackIn, World worldIn, EntityPlayer playerIn)

ItemStack itemStack

사용된 아이템 스택(Item Stack)입니다.

World worldIn

아이템 스택(Item Stack)이 사용된 세계(World) 객체입니다.

EntityPlayer playerIn

아이템 스택(Item Stack)을 사용한 플레이어(Player)입니다.

반환

아이템 스택(Item Stack)이 사용되고 난 후의 아이템 스택(Item Stack)이 반환됩니다.

 

ItemTeleporter.java

    @Override

    public ItemStack onItemRightClick(ItemStack itemStackIn, World worldIn, EntityPlayer playerIn) {

 

        if (worldIn.isRemote)

            return itemStackIn;

 

        if (playerIn.isSneaking()) {

            // Shift + RC

        } else {

            // RC

        }

        

        return itemStackIn;

    }

 

가장 먼저 코드가 서버(Server)에서만 실행될 수 있도록 클라이언트(Client)에서 실행되면 바로 반환하게 했습니다. 아이템의 NBT는 서버에서 수정이 이뤄지면 자동으로 클라이언트로 전달되기 때문에, 서버에서만 수정하는 것이 바람직하기 때문입니다. 엔티티(Entity)의 정보를 수정하는 것도 마찬가지 입니다.

isSneaking()이라는 함수를 이용해서 Shift를 누른 상태인지 확인하였습니다. 이제 각 경우에 맞추어 기능을 구현하겠습니다.

 

Shift+우 클릭을 하는 경우 플레이어의 좌표를 Compound에 "xpos", "ypos", "zpos"라는 이름으로 저장하고, 그것을 다시 아이템 스택에 저장하는 코드입니다. BlockPos는 1.8에서 추가된 개념이며 1.7.10에서는 각 좌표를 직접 플레이어 객체에서 가져오면 됩니다.

ItemTeleporter.java

    // Shift + RC

    NBTTagCompound tag = new NBTTagCompound();

    BlockPos pos = playerIn.getPosition();

    tag.setInteger("xpos", pos.getX());

    tag.setInteger("ypos", pos.getY());

    tag.setInteger("zpos", pos.getZ());

    itemStackIn.setTagCompound(tag);

 

우 클릭만 하는 경우 NBT를 아이템 스택에서 가져오고, 좌표가 저장되어 있으면 해당 좌표로 플레이어를 이동시킵니다.

ItemTeleporter.java

    // RC

    NBTTagCompound tag = itemStackIn.getTagCompound();

    if (tag != null && tag.hasKey("xpos") && tag.hasKey("ypos") && tag.hasKey("zpos")) {

        playerIn.setPositionAndUpdate(tag.getInteger("xpos"), tag.getInteger("ypos"), tag.getInteger("zpos"));

    }

 

추가적으로 여러 개의 좌표를 저장하거나, 메시지나 시각 효과를 연출하는 것도 좋은 연습이 될 것입니다.

 

원하는 대로 기능 구현이 잘 되었습니다.

 

참고자료

http://www.minecraftforge.net/wiki/Creating_NBT_for_items

http://minecraft.gamepedia.com/NBT_format

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

 

배경 지식

  1. 아이템 이벤트 함수
  2. 아이템 내구도

강의목표

  1. 아이템의 손상 값(Damage)을 이용하여 내구도를 가지는 아이템을 구현할 수 있다.
  2. 이벤트 함수를 이용하여 원하는 기능을 구현할 수 있다.

 

1. 아이템의 내구도(Durability)

내구도라는 것은 도구, 무기, 갑옷과 같이 유용한 기능을 하는 아이템들이 가진 속성입니다. 이 값은 아이템이 파괴되기 전까지 몇 번이나 더 기능을 해낼 수 있는지를 나타냅니다. 도구나 무기의 경우는 앞의 설명과 같이 남은 작동 횟수를 나타내고 갑옷은 조금 달라서 얼마만큼의 피해를 더 경감시킬 수 있는 지를 나타냅니다.

모든 아이템들의 잔여 내구도는 아이콘 아래 부분에 초록색 내구도 막대를 통해 확인 할 수 있습니다. 단, 한 번도 사용되지 않은 아이템은 막대가 그려지지 않습니다. 아이템이 손상될수록 막대의 길이가 짧아지고 색상 또한 붉은 색으로 변해갑니다. 내구도가 거의 다 소진 된 경우에는 내구도가 모두 소진된 것처럼 빈칸만이 그려지기도 합니다. 이것은 내구도가 픽셀의 크기에 맞춰져 그려지기 때문에 일어나는 현상입니다.

수치로서 존재하는 내구도는 게임 내에서 F3+H를 누르면 볼 수 있습니다. 이 옵션은 내구도 뿐만 아니라 몇몇 추가적인 설명(Tooltip)을 가방(Inventory)에서 제공합니다. 여기서 보이는 내구도 값은 실제 내구도보다 1이 작습니다. 왜냐하면 이 수치가 0이 되었을 때, 마지막으로 도구를 한 번 더 사용할 수 있기 때문입니다.

 

 

2. 내구도를 사용하는 아이템 만들기 – 족집게

이번 강의에서는 '족집게'를 만들어보며 도구 구현법을 익혀보도록 하겠습니다. 이 아이템은 닭에게 우 클릭을 통해 사용하면 깃털을 하나 얻고, 닭에게 1의 피해를 줄 수 있습니다. 본 예제를 통해서 내구도를 어떻게 다루는지, 이벤트 함수를 통해 다른 엔티티(Entity)와 어떻게 상호 작용하는지, 마지막으로 어떻게 아이템을 월드(World)에 떨어뜨리는 지 확인해보세요.

먼저 족집게의 아이템 종류를 결정할 아이템 클래스를 작성합니다. 이 클래스는 가위(ItemShears)를 바탕으로 작성됩니다. 참고하시면 도움이 되실 겁니다.

이번 족집게(ItemTweezer)는 해당 클래스에서 단 하나의 객체만 존재하는 싱글톤(Singleton)으로 작성합니다. 즉, 이 클래스에서 단 하나의 아이템 객체만이 생성되는 것을 가정하는 겁니다. 따라서 생성자 부분의 코드가 이전과는 조금 다릅니다. 이렇게 코드를 짜면 ModItems에서 아이템 객체를 생성하고 등록하는 과정이 훨씬 간단해진다는 장점이 있습니다.

ItemTweezer.java

package oortcloud.basictutorial.item;

 

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

import net.minecraft.creativetab.CreativeTabs;

import net.minecraft.item.Item;

 

public class ItemTweezers extends Item {

 

    public ItemTweezers() {

this.setMaxStackSize(1);

this.setMaxDamage(238);

this.setCreativeTab(CreativeTabs.tabTools);

this.setUnlocalizedName("tweezers");

this.setTextureName("basictutorial:tweezers");

GameRegistry.registerItem(this,"tweezers");

    }

    

}

이 아이템에서는 손상 값(Damage)이 내구도로 사용이 됩니다. 그렇게 때문에 여러 아이템이 겹쳐지게 되면 손상 값 데이터가 소실 될 수 있으므로, 반드시 setMaxStackSize (1)를 통해 최대 개수를 1개로 설정해야 합니다. 이 후 setMaxDamage(63)는 해당 도구의 최대 손상 값을 결정합니다. 내구도로 환산되면 64번 아이템을 사용할 수 있습니다. 생성자에서 바로 등록이 이뤄지고 이름을 생성자 밖에서 지정해줄 수 없으므로 생성자가 2번 이상 불리면 오류가 나게 됩니다.

이제 언어(Language) 파일과 텍스처(Texture)를 추가해서 기본적인 틀을 갖추어 보도록 하겠습니다.

en_US.lang

item.tweezers.name=Tweezers

ModItems는 이렇게 작성 되겠네요. 지난 강의의 코드도 함께 수록되어 있습니다.

ModItems.java

public final class ModItems {

    

    public static Item itemNumber;

    public static Item itemFortuneCookie;

    public static Item itemTweezers;

    

    public static final void init() {

        itemFortuneCookie = new ItemFortuneCookie().setUnlocalizedName("fortuneCookie").setCreativeTab(CreativeTabs.tabMisc).setTextureName("basictutorial:fortuneCookie");

        GameRegistry.registerItem(itemFortuneCookie, "fortuneCookie");

        itemNumber = new ItemMetadata().setUnlocalizedName("itemNumber").setCreativeTab(CreativeTabs.tabMisc);

        GameRegistry.registerItem(itemNumber, "itemNumber");

        itemTweezers = new ItemTweezers();

    }

    

}

 

 

3. 내구도를 사용하는 아이템 만들기 – 족집게

이제 이벤트 함수를 이용해서 닭을 우 클릭하면 내구도가 소모되고, 깃털이 나오며, 동시에 닭에게 1의 피해를 주도록 해보겠습니다. 아이템으로 엔티티(Entity)를 우 클릭 했을 때 호출되는 이벤트 함수는 itemInteractionForEntity()입니다.

public boolean itemInteractionForEntity(ItemStack itemStack, EntityPlayer entityPlayer, EntityLivingBase entityLivingBase)

ItemStack itemStack

사용된 아이템 스택입니다.

EntityPlayer entityPlayer

아이템을 사용한 플레이어의 엔티티(Entity) 객체입니다.

EntityLivingBase entityLivingBase

플레이어가 우 클릭을 한 대상 엔티티(Entity)입니다.

반환

상호작용이 일어나면 true, 그렇지 않으면 false를 반환해야 합니다.

 

ItemTweezer.java

    @Override

    public boolean itemInteractionForEntity(ItemStack itemStack, EntityPlayer entityPlayer, EntityLivingBase entityLivingBase) {

        return false;

    }

 

이제 여기에 우리가 원하는 기능을 할 코드를 입력하면 됩니다. 기능을 구현하기 전에, 일반적으로 엔티티(Entity)를 생성하는 코드(아이템 드롭)는 서버에서만 실행 되야 합니다. 따라서 다음과 같이 코드를 작성하여 클라이언트에서는 코드가 실행되지 않도록 합니다.

엔티티는 서버에서만 관리되는 대상입니다. 서버에서만 엔티티를 생성해도 그것이 클라이언트로 바로 반영이 되고, 만약 양 쪽에서 모두 생성해버리면 클라이언트에서는 2개의 엔티티가 생성된 것처럼 보이게 됩니다.

ItemTweezer.java

    @Override

    public boolean itemInteractionForEntity(ItemStack itemStack, EntityPlayer entityPlayer, EntityLivingBase entityLivingBase) {

        if (entityPlayer.worldObj.isRemote) {

            return false;

        }

        // IMPLEMENT HERE, 구현은 이곳에 하세요

        return false;

    }

 

이제 기능 구현에 필요한 코드를 입력하겠습니다. 주석을 참조하세요. 아래 코드에서 핵심은 damageItem ()이라고 할 수 있습니다. 이 함수가 결국 내구도를 소모시키기 때문입니다.

ItemTweezer.java

    @Override

    public boolean itemInteractionForEntity(ItemStack itemStack, EntityPlayer entityPlayer, EntityLivingBase entityLivingBase) {

        if (entityPlayer.worldObj.isRemote) {

            return false;

        }

        if (entityLivingBase instanceof EntityChicken) {

            // entityLivingBase 닭인지 확인합니다.

            

            if (entityLivingBase.getHealth() > 1.0F) {

                // 대상의 체력이 1보다 높을 때만 동작합니다. , 도구를 이용한다 해도 대상이 죽지는 않습니다. 죽기 전까지 털이 뽑힐 .

                

                // 아이템을 드롭하는, 자주 쓰이는 코드입니다.

                Random rand = new Random();

                // 임의의 방향으로 아이템이 튕겨 지도록 랜덤 객체를 만듭니다.

                EntityItem ent = entityLivingBase.entityDropItem(new ItemStack(Items.feather), 1.0F);

                // entityLivingBase 깃털의 EntityItem ent 떨어뜨리도록 하고, y 기본 속도를 1.0으로 설정합니다.

                ent.motionY += rand.nextFloat() * 0.05F;

                ent.motionX += (rand.nextFloat() - rand.nextFloat()) * 0.1F;

                ent.motionZ += (rand.nextFloat() - rand.nextFloat()) * 0.1F;

                // 임의의 속도를 부여합니다.

                

                

                entityLivingBase.attackEntityFrom(DamageSource.causePlayerDamage(entityPlayer), 1.0F);

                // entityLivingBase에게 플레이어(entityPlayer)로부터 피해를 1 받도록 합니다.

                

                itemStack.damageItem(1, entityLivingBase);

                // 내구도를 1 소모시킵니다.

                

                return true;

            }

        }

        return false;

    }

 

원하는 대로 기능 구현이 잘 되었습니다.

 

참고자료

http://minecraft.gamepedia.com/Item_durability

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

 

배경 지식

  1. 기본 모드 파일
  2. 기본 아이템
  3. 서버와 클라이언트

 

강의목표

  1. 사건 기반 프로그래밍(Event-driven Programming, EDP)의 원리를 이해한다.
  2. 아이템 클래스의 이벤트 함수 각각을 이해한다.
  3. 원하는 기능을 구현하는 아이템을 만들 수 있다.

 

1. 사건 기반 프로그래밍(Event-driven Programming, EDP)

이 단락은 아이템의 동작에 대한 보충 설명이므로 굳이 읽지 않으셔도 됩니다.

사건 기반 프로그래밍은 사용자의 행동(마우스, 키보드 입력)이나 다른 프로그램이 보낸 메시지와 같은 '사건(Event)'에 따라 프로그램의 동작을 결정하는 프로그래밍 패러다임(Paradigm)입니다. 이러한 EDP(Event-driven Programming)는 주로 GUI나 사용자의 입력에 반응하는 것이 주 기능인 프로그램들에 사용됩니다. 주요 반복문(Main Loop)이 이벤트를 인식하고 관련된 함수를 호출하는 방식으로 구현됩니다.

마인크래프트의 아이템도 사건 기반 프로그래밍에 기반하여 동작합니다. 사용자가 아이템으로 무엇을 하냐에 따라서 아이템의 기능을 구현하는 것입니다. 예를 들어 아이템을 버리고, 세계(World)와 상호작용할 때마다 특수한 함수가 불려집니다. 개발자는 Item 클래스 내부의 이벤트 함수를 오버라이드하여 이러한 기능들을 조작할 수 있습니다.

 

 

2. 예시를 통한 개념 학습 – 포춘 쿠키(Fortune Cookie)

이번 단락에서는 아이템을 사용할 때 오늘의 운세를 출력하는 간단한 아이템을 만들어 보겠습니다. 포춘 쿠키라는 이 아이템은 우 클릭으로 사용하면 하나가 소모되고 채팅 창에 임의의 운세를 출력합니다. 크리에이티브 모드인 경우에는 소모되지 않도록 하겠습니다. 본 예시를 통해서 이벤트 함수를 어떻게 조작하는지 유심히 살펴보시고 지금까지 다뤄왔던 내용들을 복습하는 시간을 가지시길 바랍니다. 또한 실제 구현에서 자주 사용되는 함수들을 따로 강조해 드리겠습니다

1) 아이템 틀 만들기

지난 강의에서처럼 기능을 구현해야하기 때문에 Item을 상속하는 클래스를 만듭니다. 그리고는 이 클래스에는 추가적인 코드없이 기본 과정을 따라 등록해보도록 합시다. 텍스처는 이것을 이용하겠습니다.

ItemFortuneCookie.java

public class ItemFortuneCookie extends Item {

 

}

 

ModItems.java

public final class ModItems {

    

    public static Item itemFortuneCookie;

    

    public static final void init() {

        itemFortuneCookie = new ItemFortuneCookie().setUnlocalizedName("fortuneCookie").setCreativeTab(CreativeTabs.tabMisc).setTextureName("basictutorial:fortuneCookie");

        GameRegistry.registerItem(itemFortuneCookie, "fortuneCookie");

    }

    

}

 

en_US.lang

item.fortuneCookie.name=Fortune Cookie

 

그림 1 등록을 마친 아이템

 

2) 이벤트 함수 오버라이드

이제 우 클릭을 할 때 호출되는 함수를 오버라이드하여 기능을 구현하도록 하겠습니다. 이벤트 함수는 일반적으로 'on'으로 시작하고 주석 처리가 잘 되어 있어서 어떤 기능인지 확인하기는 용이합니다. onItemRightClick이라는 메소드(Method)는 아이템을 들고 우 클릭을 하는 모든 경우에서 호출됩니다. 플레이어의 손에 들린 아이템 스택, 플레이어가 속한 세계(World), 그리고 플레이어의 객체를 인자로 넘겨 받습니다. 그리고 다시 해당 아이템 스택을 반환합니다. 이 메소드는 클라이언트와 서버 모두에서 실행됩니다.

ItemFortuneCookie.java

@Override

public ItemStack onItemRightClick(ItemStack itemStack, World world, EntityPlayer player) {

 

    return itemStack;

 

}

 

먼저 플레이어에게 채팅 메시지를 출력하는 메소드를 살펴보겠습니다. 채팅 메시지는 player.addChatComponentMessage(IChatComponent msg)를 호출하여 출력합니다. IChatComponent 인터페이스를 반드시 구현할 필요는 없으며 단순한 문자 출력이 목적이라면 new ChatComponentText(String text)를 이용해서 간단히 객체를 생성할 수 있습니다. 그러면 간단한 메시지를 출력해보도록 하겠습니다

ItemFortuneCookie.java

@Override

public ItemStack onItemRightClick(ItemStack itemStack, World world, EntityPlayer player) {

 

    player.addChatComponentMessage(new ChatComponentText("Example Message"));

 

    return itemStack;

}

그림 2 2번 출력되는 메시지

아이템을 한 번만 사용해도 메시지가 2번 출력됩니다. 앞서 말했듯이 addChatComponentMessage 가 클라이언트와 서버 모두에서 실행되기 때문에 일어나는 문제입니다. 클라이언트에서 addChatComponentMessage 가 호출되면 바로 채팅창에 출력되고, 서버에서 호출되면 클라이언트로 해당 메시지를 전송합니다. 이 기능은 플레이어에게 메시지를 출력하는 것이 목적이므로 클라이언트에서만 메시지를 출력하도록 바꾸어 보도록 하겠습니다.

World 클래스에 존재하는 isRemote라는 멤버 변수는 클라이언트 세계면 true, 서버 세계면 false의 값을 가집니다. 따라서 다음과 같이 코드를 작성하면 클라이언트에서만 코드가 실행되며 문제가 해결됩니다.

ItemFortuneCookie.java

@Override

public ItemStack onItemRightClick(ItemStack itemStack, World world, EntityPlayer player) {

 

    if (world.isRemote) {

        player.addChatComponentMessage(new ChatComponentText("Example Message"));

    }

 

    return itemStack;

}

 

본래의 목적은 임의의 메시지를 출력하는 것이므로 코드를 조금 추가 하겠습니다. 내용은 아이작의 구속에 나오는 운세들입니다. EntityLivingBase 클래스에는 Random 객체를 반환하는 getRNG()라는 메소드가 있습니다. 이 객체를 이용해서 임의의 값을 획득하는게 가능합니다. EntityPlayer 클래스가 EntityLivingBase의 하위 클래스이기 때문에 해당 메소드를 호출할 수 있습니다.

ItemFortuneCookie.java

public static String[] fortune = {"BRING HIM THE PHOTO", "YOU WILL DIE ALONE", "YOU ARE THROWING YOUR LIFE AWAY", "GO OUTSIDE!", "ASK AGAIN LATER", "THINK FOR YOURSELF", "QUESTION AUTHORITY", "YOU ARE WORSHIPING A SUN GOD", "DON'T LEAVE THE HOUSE TODAY", "GIVE UP!", "MARRY AND REPRODUCE", "STAY ASLEEP", "WAKE UP", "WE WILL ALL DIE ONE DAY", "LOOK TO LA LUNA", "STEVEN LIVES ", "MEET STRANGERS WITHOUT PREJUDICE", "A HANGED MAN WILL BRING YOU NO LUCK TODAY", "WHAT DO YOU WANT TO DO TODAY?", "YOU ARE DARK INSIDE", "HAVE YOU SEEN THE EXIT?", "GET A BABY PET IT WILL CHEER YOU UP", "YOUR PRINCESS IS IN ANOTHER CASTLE", "YOU ARE PLAYING IT WRONG GIVE ME THE CONTROLLER", "TRUST GOOD PEOPLE", "LIVE TO DIE", "WHEN LIFE GIVES YOU LEMONS REROLL!"};

 

@Override

public ItemStack onItemRightClick(ItemStack itemStack, World world, EntityPlayer player) {

 

    if (world.isRemote) {

        player.addChatComponentMessage(new ChatComponentText(fortune[player.getRNG().nextInt(fortune.length)]));

    }

 

    return itemStack;

}

 

마지막으로 아이템의 개수를 하나 감소시키는 과정을 추가하도록 하겠습니다. 이때 일반적으로 크리에이티브 모드에서는 아이템이 소모되지 않도록 합니다. 아이템 스택의 stacksize 변수가 개수를 나타냅니다. player.capabilities.isCreativeMode는 플레이어가 크리에이티브 모드이면 true, 아니면 false의 값을 가집니다. 아이템 스택의 개수가 0이 되면 onItemRightClick을 호출한 코드가 알아서 인벤토리에서 해당 아이템을 삭제합니다. 지금 추가된 구문은 아이템을 소모시킬 때 널리 사용됩니다.

ItemFortuneCookie.java

@Override

public ItemStack onItemRightClick(ItemStack itemStack, World world, EntityPlayer player) {

 

    if (!player.capabilities.isCreativeMode) {

        --itemStack.stackSize;

    }

 

    if (world.isRemote) {

        player.addChatComponentMessage(new ChatComponentText(fortune[player.getRNG().nextInt(fortune.length)]));

    }

 

    return itemStack;

}

그림 3 완성본

이렇게 해서 포춘 쿠키가 완성 되었습니다. Item 클래스 내의 이벤트 함수들에 대한 구체적인 정보도 따로 정리하여 올리겠습니다.

 

 

참고자료

https://en.wikipedia.org/wiki/Event-driven_programming

http://bindingofisaac.wikia.com/wiki/Fortune_Telling_Machine

http://bedrockminer.jimdo.com/modding-tutorials/advanced-modding/modding-tips-and-tricks/#chatSP

https://en.wikipedia.org/wiki/Fortune_cookie

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

 

배경 지식

  1. 기본 아이템
  2. 아이템 스택

 

강의목표

  1. 아이템의 메타데이터(metadata)를 이해한다.
  2. 아이템의 손상 값(Damage)을 이해한다.
  3. 손상 값(Damage)을 이용해서 하위 아이템을 만들 수 있다.

 

1. 아이템의 메타데이터

메타데이터(Metadata)는 데이터를 설명하기 위한 추가적인 데이터입니다. 예를 들면, 디지털 카메라에서는 사진을 찍을 때마다 촬영 시간, 노출, 플래시 사용 여부, 해상도, 사진 크기 등의 정보를 화상 데이터와 같이 저장하게 되어 있습니다. 이런 추가적인 데이터는 훗날 사진을 정렬하고 관리할 때 상당히 유용한 지표가 됩니다. 메타데이터는 일반적으로 이처럼 데이터의 검색과 관리를 편리하게 하기 위해서 부여됩니다.

마인크래프트에서 말하는 메타데이터는 약간 차이가 있습니다. 마인크래프트 세계에는 최소 수 백만 개의 블록이 존재하기 때문에 각 블록에 저장될 수 있는 데이터가 많지 않습니다. 일단 서로 다른 블록을 구분하도록 ID값이 저장되고, 거기에 추가적으로 4비트만큼의 저장 공간이 제공됩니다. 0~15까지 저장할 수 있는 이 공간을 메타데이터라고 부릅니다.

이러한 메타데이터는 단순한 데이터를 저장할 때 사용됩니다. 양털의 색, 화로의 방향, 도가니의 물과 같이 몇 개의 숫자만으로 충분히 표현 가능한 자료들을 저장합니다. 양털은 파괴되어 아이템이 된 후에도 그 색이 보존되기 때문에 아이템 스택에도 메타데이터를 저장하는 공간이 있다는 것을 유추할 수 있습니다. 이 값이 바로 아이템의 메타데이터입니다.

그림 1서로 다른 양털

 

 

2. 손상 값(Damage)

앞서 말한 메타데이터는 damage라는 변수에 저장됩니다. 이름 그대로 도구 아이템의 내구도를 나타낼 때 흔히 사용됩니다. 그렇기 때문에 내구도가 있는 도구는 메타데이터까지 따로 저장할 수는 없습니다. 한 변수에 두가지 정보를 저장하는 것은 굉장히 번거로운 일이니까요. 블록과 달리 아이템의 메타데이터는 -32768~32767의 훨씬 넓은 저장공간을 제공합니다.

이번 강의에서는 손상 값을 이용해서 하위 아이템을 만드는 법을 알아볼 것입니다. 이러한 방식이 가진 장점은 1) 32000까지 제한되어 있는 아이템 ID를 절약할 수 있고 2) 메타데이터를 이용하는 블록과 연계된 아이템을 만들 수 있으며, 3) 마지막으로 손상 값 자체를 응용해서 유연한 프로그래밍을 할 수 있습니다.

 

 

3. 하위 아이템을 사용하는 Item 클래스

지난 강의에서 추가한 기본 아이템과는 다르게 특수한 기능을 가지고 있기 때문에 새로 클래스를 작성해야 합니다. 객체지향의 원리에 따라 Item 클래스를 상속해야만 호환이 됩니다. 우선은 다음과 같이 클래스를 작성합니다. 패키지는 지난 강의와 같은 oortcloud.basictutorial.item으로 하겠습니다.

ItemMetadata.java

package oortcloud.basictutorial.item;

 

import net.minecraft.item.Item;

 

public class ItemMetadata extends Item {

 

}

여기서 하위 아이템을 가지는 아이템은 반드시 setHasSubtypes 메소드를 이용해서 해당 아이템이 하위 아이템을 가지는 것을 명시해야 합니다. 그렇지 않으면 서로 다른 손상 값이 모두 무시되어 인벤토리에서 그냥 겹쳐집니다. 이 사항은 하위 아이템을 가지는 모든 아이템 객체에 대해서 해당되는 사항이므로 생성자에 포함시키도록 하겠습니다.

ItemMetadata.java

public ItemMetadata() {

    setHasSubtypes(true);

}

그럼 이대로 적절한 이름(Unlocalized Name)을 부여해서 등록해보도록 하겠습니다.

ModItems.java

public final class ModItems {

    

    public static Item itemNumber;

    

    public static final void init() {

        itemNumber = new ItemMetadata().setUnlocalizedName("itemNumber").setCreativeTab(CreativeTabs.tabMisc);

        GameRegistry.registerItem(itemNumber, "itemNumber");

    }

    

}

/give 명령어를 이용해서 서로 다른 손상 값을 가지는 아이템을 받아보면 서로 겹쳐지지 않는 것을 확인할 수 있습니다. 반대로 조약돌은 아무리 손상 값을 달리해도 그냥 한 종류의 아이템으로 취급됩니다.

그림 2하위 아이템

여기서 우리는 총 세가지 문제를 확인할 수 있습니다. 1) 이름이 올바로 표시되지 않는다. 2) 텍스처가 올바로 표시되지 않는다. 3) 크리에이티브 탭에 한 종류(손상 값이 0)만 나타난다. 하나씩 해결해보도록 합시다.

 

1) 손상 값에 따른 이름(Unlocalized Name) 지정

아바스토로님의 강의를 참고하면 언어 설정에 맞게 올바른 이름이 표시되게 할 수 있습니다. 하지만 이 방법만으로는 setUnlocalizedName을 통해 부여한 이름이 단 하나의 번역에 대응되기 때문에 손상 값에 따라 다른 이름을 부여할 수는 없습니다. 따라서 손상 값에 따라 현지화 되지 않은 이름(Unlocalized Name)이 바뀌도록 해야합니다. lang파일이 다음처럼 서로 다른 손상 값에 대해 대응될 수 있도록 말이죠.

en_US.lang

item.itemNumber_0.name=Number Zero

item.itemNumber_1.name=Number One

item.itemNumber_2.name=Number Two

item.itemNumber_3.name=Number Three 

 

우리가 작성한 ItemMetadata 클래스 안에서 getUnlocalizedName(ItemStack itemStack)이라는 메소드를 오버라이드하면 아이템 스택의 손상 값이나 NBT 값에 따라서 서로 다른 이름을 대응시킬 수 있습니다. 우리는 손상 값에 따라 이름을 달리 지정할 것이므로 간단히 본래의 이름 뒤에 _(손상값)을 붙이도록 합시다.

ItemMetadata.java

@Override

public String getUnlocalizedName(ItemStack itemStack) {

    return getUnlocalizedName()+"_"+itemStack.getItemDamage();

}

getUnlocalizedName()은 아이템 스택의 상태(손상 값, NBT)에 영향 받지 않는 setUnlocalizedName()으로 등록한 이름 그 자체를 반환하고, itemStack.getItemDamage() itemStack 의 손상 값을 반환합니다.

 

다시 실행해보면 이름 문제는 해결된 것을 확인할 수 있습니다.

 

2) 손상 값에 따른 텍스처 지정

텍스처를 지정하는 과정은 2단계로 이루어집니다. 먼저 다수(혹은 하나)의 텍스처를 등록을 하고, 아이템 스택의 정보를 받아 상황에 맞는 텍스처를 반환하는 형식입니다. 우리는 사용할 텍스처를 모두 등록해서 배열에 저장한 후 주어진 아이템 스택의 손상 값에 맞는 텍스처를 반환하도록 하겠습니다.

텍스처 등록은 해당 아이템의 클래스 내에서 registerIcons(IIconRegister iconRegister) 메소드를 오버라이드하여 제어할 수 있습니다. 앞서 말했듯이 이렇게 등록한 텍스처(IIcon) 객체를 저장해 두어야하기 때문에 해당하는 배열 또한 클래스의 멤버 변수로서 생성해두도록 하겠습니다. 본 강의의 경우에는 ItemMetadata 클래스 내부에 다음의 코드를 추가로 작성하게 됩니다.

ItemMetadata.java

public IIcon[] icons = new IIcon[4];

 

( … )

 

@Override

public void registerIcons(IIconRegister iconRegister) {

for (int i = 0; i < icons.length; i++) {

this.icons[i] = iconRegister.registerIcon("basictutorial:itemNumber_" + i);

}

}

iconRegister.registerIcon 에 전달되는 인자는 setTectureName 메소드와 형식이 동일하여 (Mod ID):(파일 이름)의 형태로 작성해주시면 됩니다.

이제 getIconFromDamage(int damage) 메소드를 오버라이드해서 아이템의 손상 값에 따라 서로 다른 텍스처(IIcon)를 전달할 수 있습니다. /give 명령어를 사용하면 우리가 고려하지 않은 손상 값을 가진 아이템 스택도 생성될 수 있으므로, 사전에 꼭 손상 값을 확인하도록 합시다. 이 또한 당연히 ItemMedata 클래스 내부에서 작성됩니다.

ItemMetadata.java

@Override

public IIcon getIconFromDamage(int damage) {

    if (damage > icons.length) {

        damage = 0;

    }

    return icons[damage];

}

 

텍스처 등록 및 지정과 관련된 모든 과정을 구현했습니다. 이제 텍스처 파일이 있어야 하는데, 4개의 파일을 제공해 드리겠습니다. 각 파일의 경로는 이전 강의와 당연히 동일하고, 파일 이름은 텍스처를 등록하는 과정에서 지정한 것과 동일합니다.

그림 3텍스처 등록

 

3) 크리에이티브탭에 등록

이 과정은 구현에 필수는 아니지만 사용자의 편의를 위해 구현 해두면 좋습니다. 마인크래프트에 등록된 아이템은 기본적으로 손상 값이 0인 아이템 하나만 크리에이티브 탭에 표시됩니다. 크리에이티브 탭에 표시될 아이템 스택들을 제어하려면 getSubItems(Item item, CreativeTabs tab, List list) 구현해야 합니다.

ItemMetadata.java

@Override

public void getSubItems(Item item, CreativeTabs tab, List list) {

    for (int i = 0; i < icons.length; i ++) {

list.add(new ItemStack(item, 1, i));

}

}

Item은 현재 등록하려는 item의 객체, tab은 현재 추가하려는 크리에이티브 탭의 종류, list는 지금까지 추가된 아이템들을 모두 보관하고 있는 리스트입니다. add명령어를 이용해서 우리가 원하는 아이템 스택을 등록해주면 됩니다.

그림 4크리에이티브 탭

 

최종적으로 작성된 코드를 보여드리겠습니다.

ItemMetadata.java

package oortcloud.basictutorial.item;

 

import java.util.List;

 

import net.minecraft.client.renderer.texture.IIconRegister;

import net.minecraft.creativetab.CreativeTabs;

import net.minecraft.item.Item;

import net.minecraft.item.ItemStack;

import net.minecraft.util.IIcon;

 

public class ItemMetadata extends Item {

    

    public IIcon[] icons = new IIcon[4];

    

    public ItemMetadata() {

        setHasSubtypes(true);

    }

    

    @Override

    public void getSubItems(Item item, CreativeTabs tab, List list) {

        for (int i = 0; i < icons.length; i ++) {

     list.add(new ItemStack(item, 1, i));

     }

    }

    

    @Override

    public void registerIcons(IIconRegister iconRegister) {

     for (int i = 0; i < icons.length; i++) {

     this.icons[i] = iconRegister.registerIcon("basictutorial:itemNumber_" + i);

     }

    }

    

    @Override

    public IIcon getIconFromDamage(int damage) {

        if (damage > icons.length) {

            damage = 0;

        }

        return icons[damage];

    }

    

    @Override

    public String getUnlocalizedName(ItemStack itemStack) {

        return getUnlocalizedName()+"_"+itemStack.getItemDamage();

    }

      

    

}

 

ModItems.java

package oortcloud.basictutorial.item;

 

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

import net.minecraft.creativetab.CreativeTabs;

import net.minecraft.item.Item;

 

public final class ModItems {

    

    public static Item itemNumber;

    

    public static final void init() {

        itemNumber = new ItemMetadata().setUnlocalizedName("itemNumber").setCreativeTab(CreativeTabs.tabMisc);

        GameRegistry.registerItem(itemNumber, "itemNumber");

    }

    

}

 

 

 

참고자료

http://bedrockminer.jimdo.com/modding-tutorials/basic-modding-1-7/metadata-blocks-and-items/

https://ko.wikipedia.org/wiki/%EB%A9%94%ED%83%80%EB%8D%B0%EC%9D%B4%ED%84%B0

http://minecraft-ko.gamepedia.com/%EB%8D%B0%EC%9D%B4%ED%84%B0_%EA%B0%92

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

 

배경 지식

  1. 기본 모드 파일

 

강의목표

  1. 아이템 객체를 생성한다.
  2. 생성된 객체를 등록한다.
  3. 등록된 아이템에 텍스처(Texture)를 부여한다.

 

1. ModItems 클래스 만들기

아이템을 만들기에 앞서, 먼저 우리가 추가할 아이템들을 관리할 수 있는 클래스를 준비해야 합니다. 클래스의 이름이나 패키지는 기능과는 크게 관련 없지만 훗날 능률을 높이기 위해서 정해진 약속대로 정의하는 것이 좋습니다. 본 강의에서는 이름은 ModItems, 패키지는 oortcloud.basictutorial.item으로 설정하겠습니다.

그림 1패키지 탐색기

이제 ModItems 클래스의 뼈대를 구성해보도록 하겠습니다. 먼저, ModItems 클래스가 다른 클래스로부터 상속되지 않도록 final로 선언을 합니다. 그리고 아이템 객체를 생성하고 등록할 init() 메소드를 만듭니다. 이때 init() 메소드는 1) 외부에서 메소드를 실행할 수 있도록 public으로, 2) ModItems는 객체가 생성되지 않으므로 static으로, 3) 클래스와 마찬가지로 오버라이드 될 수 없도록 final로 선언해줍니다.

ModItems.java

package oortcloud.basictutorial.item;

 

public final class ModItems {

    

    public static final void init() {

        

    }

    

}

 

 

2. 아이템 객체 생성 및 성질 부여

뼈대가 완성되었기 때문에 간단히 Item 객체를 만들어 보도록 하겠습니다. 위에서와 같이 public static으로 선언해줍니다. 처음 아이템 변수를 선언할 때는 임포트와 관련된 에러가 발생할 수 있습니다. Ctrl + Shift + O를 누르면 이클립스에서 필요한 클래스를 자동으로 임포트 해줍니다.

ModItems.java

package oortcloud.basictutorial.item;

 

import net.minecraft.item.Item;

 

public final class ModItems {

    

    public static Item tutItem;

    

    public static final void init() {

        

    }

    

}

 

이제 tutItem 에 적용될 아이템 객체를 만들겠습니다. Item 객체는 하나 또는 여럿의 아이템 종류를 대표하여 생성되는 아이템 스택(Item Stack)의 이름, 텍스쳐, 성질을 결정합니다. 오늘 우리가 다룰 아이템은 단순히 Item 클래스의 생성자를 사용하면 됩니다.

ModItems.java

package oortcloud.basictutorial.item;

 

import net.minecraft.item.Item;

 

public final class ModItems {

    

    public static Item tutItem;

    

    public static final void init() {

        tutItem = new Item();

    }

    

}

 

이렇게 만든 객체는 당연히 아무런 기능이나 성질을 가지고 있지 않습니다. 심지어 이름조차 없기 때문에 등록할 수도 없습니다. 성질을 부여할 때는 다음과 같은 메소드를 사용합니다. 이들은 모두 조작한 객체를 다시 반환합니다. (이 부분은 예시를 보면서 추가로 설명하겠습니다)

setUnlocalizedName(String name)

아이템의 이름을 name으로 설정합니다.

setCreativeTab(CreativeTabs tab)

크리에이티브 모드의 tab에서 이 아이템을 찾을 수 있습니다.

setTextureName(String name)

텍스쳐의 이름을 name으로 설정합니다.

setMaxStackSize(int size)

중첩될 수 있는 최대 개수를 size로 설정합니다.

 

각 메소드가 다시 아이템 객체를 반환하기 때문에 예시와 같이 한 줄에 나열해서 호출할 수 있습니다. 아래의 아이템은 tutItem이라는 이름을 가지게 되고 크리에이티브 모드에서 기타 항목에 들어가게 됩니다.

tutItem = new item().setUnlocalizedName("tutItem").setCreativeTab(CreativeTabs.tabMisc);

 

 

3. 아이템 등록

이렇게 객체를 생성하더라도 마인크래프트 내부에 이식된 것이 아니라서 실제로는 게임에 아무런 영향도 주지 않습니다. 따라서 등록(registration)이라는 과정을 통해 아이템을 내부에 이식해야 합니다.

public static void GameRegistry.registerItem(Item item, String name)

Item item

등록할 아이템 객체입니다.

String name

setUnlocalizedName()에서 사용한 이름과 같은 이름입니다.

 

ModItems.java

package oortcloud.basictutorial.item;

 

public final class ModItems {

    

    public static Item tutItem;

    

    public static final void init() {

        tutItem = new Item().setUnlocalizedName("tutItem").setCreativeTab(CreativeTabs.tabMisc);

        GameRegistry.registerItem(tutItem, "tutItem");

    }

    

}

 

마지막으로 init()을 호출하기만 하면 등록이 완료 됩니다. 아이템의 등록은 다른 모드와 호환을 위해 최대한 빨리 진행해야 하기 때문에 Pre-Initialization 단계가 적합합니다. 다음과 같이 기본 모드 파일의 해당 이벤트에서 호출하면 됩니다.

BasicTutorial.java

( … )

    @Mod.EventHandler

    public static void preInit(FMLPreInitializationEvent event) {

        ModItems.init();

    }

( … )

 

그림 2추가된 아이템(1)

아이템이 추가되어 크리에이티브 탭에서 찾아볼 수 있습니다. 하지만 이름은 item.tutItem.name이고 텍스쳐는 검정/분홍 체스판입니다. 등록은 완료되었으나 아직 (현지화 된)이름과 텍스쳐가 올바로 지정되지 않았기 때문입니다. 이 중 텍스쳐는 곧 바로 추가할 것이고 현지화는 아바스트로님의 강의를 확인하시면 됩니다.

 

 

4. 텍스처 등록

먼저 등록할 텍스처를 준비해 보겠습니다. 크기는 기본적으로 16*16에 png 형식입니다. 아래의 텍스처는 제 모드인 토지 중개사(Estate Agent)에 사용된 파일입니다.

일단 이 텍스쳐를 "(WorkspaceRoot)/src/main/resources/assets/(modid)/textures/items/(texture_name).png"에 저장해야 합니다. 괄호로 표시된 값은 변수라고 생각하시면 됩니다. 본 강의에서 다루는 예제라면 다음의 경로에 저장해야 합니다.

그림 3텍스처 경로

 

이제 아이템 객체와 텍스처 파일을 연결해야 합니다. ModItems 클래스에 선언된 객체에 .setTextureName("(modid):(file_name)") 메소드를 호출합니다. 이 역시 괄호로 표시된 부분은 변수입니다. ":" 기호로 구분되어 앞은 모드 ID, 뒤는 연결할 PNG 파일 이름입니다.

.setTextureName("basictutorial:tutItem")

본 강의에서는 위와 같이 호출해야 합니다.

 

ModItems.java

package oortcloud.basictutorial.item;

 

public final class ModItems {

    

    public static Item tutItem;

    

    public static final void init() {

        tutItem = new Item().setUnlocalizedName("tutItem").setCreativeTab(CreativeTabs.tabMisc).setTextureName("basictutorial:tutItem");

        GameRegistry.registerItem(tutItem, "tutItem");

    }

    

}

 

 

 

참고 자료

http://bedrockminer.jimdo.com/modding-tutorials/basic-modding-1-7/first-item/

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

 

배경 지식

  1. 무형 조합법(Shapeless Recipe) 및 화로 조합법(Smelting Recipe) 추가하기
  2. 유형 조합법(Shaped Recipe) 추가하기
  3. 광물 사전

 

강의목표

  1. 광물 사전을 이용한 조합법을 추가한다.
  2. 아이템의 내구도(damage)에 영향을 받지 않는 조합법을 추가한다.

 

1. 광물 사전을 이용한 조합법

마인크래프트 포지에는 광물 사전이라는 개념이 있어서, 비슷하거나 완전히 동일한 아이템을 하나의 이름으로 묶습니다. 원목의 경우에는 참나무, 가문비나무 등 여러 종류가 있지만 모두 "logWood"라는 이름으로 대표되고, 구리 주괴도 IC2에서, 포레스트리(Forestry)에서 각각 아이템을 추가하지만 "ingotCopper"라는 이름으로 연결되어 있습니다. 자주 사용되는 목록여기에서 찾아보실 수 있습니다. 다만 최신 버전은 아닙니다.

조합법을 추가할 때, 광물 사전을 이용하지 않으면 원치 않은 결과가 발생할 수 있습니다. 나무를 이용한 조합법이 등록할 때 사용한 한 종류의 나무만 지원하고, 광물을 사용하는 조합법이 다른 모드의 광물은 인식하지 않는다면, 사용자 입자에서 굉장히 불편할 것입니다. 광물 사전은 이와 같이 조합법을 추가할 때, 사실상 반드시 이용해야하는 기능입니다.

광물 사전 조합법은 IRecipe를 구현하고 있으며 유형 조합법, 무형 조합법이 각각 따로 작성되어 있습니다. 다만, 유형 조합법/무형 조합법과 같이 GameRegistry에서 따로 추가하는 함수가 정의되어 있지 않아 addRecipe라는 일반적인 등록 함수에 IRecipe 객체를 전달하는 방식으로 조합법을 추가하게 됩니다.

void GameRegistry.addRecipe(IRecipe recipe)

IRecipe recipe

등록될 조합법

 

광물 사전 조합법은 IRecipe를 상속하고 있기 때문에 위 함수의 인자로서 적합합니다. 즉, 우리가 원하는 조합에 대한 광물 사전 조합법의 객체를 만든 뒤 인자로 전달하기만 하면 등록이 끝나는 것입니다.

new ShapedOreRecipe(ItemStack result, Object... recipe)

ItemStack result

조합법 결과

Object... recipe

조합법 배치 및 재료들

 

new ShapelessOreRecipe(ItemStack result, Object... recipe)

ItemStack result

조합법 결과

Object... recipe

조합법 재료들

 

위 두 생성자는 지난 강의에서 살펴본 GameRegistry.addShapelessRecipe/GameRegistry.addShapedRecipe와 거의 같습니다. 다만 아이템 스택(Item Stack)뿐만 아니라 광물 사전 이름도 주어 줄 수 있습니다. 서로 거의 적용 방법이 비슷하니 유형 조합법만 예시를 살펴보겠습니다.

GameRegistry.addRecipe(new ShapedOreRecipe(new ItemStack(Blocks.chest,8), "aaa","a a","aaa", 'a', "logWood"));

 



그림 1추가된 광물 사전 조합

위의 코드가 아래와 같은 조합법을 추가합니다. 상자 1개를 만드는 조합은 기본 조합이고, 포지(Forge)에서 자체적으로 광물 사전 조합으로 변경한 것을 알 수 있습니다. 아래가 추가된 조합법입니다. NEI를 사용해 보신 분들은 아시겠지만 광물 사전 조합법은 해당 광물 사전의 모든 아이템들이 무작위로 표시되며 바뀝니다.

 

 

2. 내구도에 영향을 받지 않는 조합법

아이템 스택(Item Stack)을 생성할 때, 인자로 내구도(damage) 값을 줄 수 있는 것은 이미 알고 계실 겁니다. 내구도 값에 따라 서로 다른 염료가 결정되고, 실제 도구의 손상 정도를 나타내기도 합니다. 조합법을 등록할 때도 적절한 내구도 값을 전달해서 명확한 조합법이 만들어 질 수 있습니다. 하지만 무기나 곡괭이를 이용한 조합의 경우에는 내구도 값이 조합법에서 문제가 되는 경우도 있습니다.

그림 2발사기 조합법(1)

한 번도 사용하지 않은 활이라면 위의 조합은 발사기(Dispenser)를 만들어야 합니다. 하지만 한 번 이상 사용한 활은 내구도 값이 다르기 때문에 조합법에서 다른 재료로 인식됩니다. 하지만 여기서 활의 가능한 모든 내구도(0, 1, 2, …)에 대해서 조합법을 추가하는 것은 성능을 저하시키는 요인이 됩니다. 결코 올바른 방법이 아니죠.

이런 상황에서는 아이템 스택(Item Stack)의 생성자에 일반적인 상수를 내구도 값으로 전달하는 것이 아닌 OreDictionary.WILDCARD_VALUE (=32767)라는 특수한 값을 전달하면 모든 내구도에 대해서 조합법이 재료를 인식합니다.

방법은 간단하기 때문에 바로 예시를 보며 강의를 마치겠습니다.

GameRegistry.addShapedRecipe(new ItemStack(Blocks.dispenser),

"aaa", "aba", "aca",

'a' , Blocks.cobblestone, 'b', new ItemStack(Items.bow, 1, OreDictionary.WILDCARD_VALUE), 'c', Items.redstone);

 

그림 3발사기 조합법(2)

 

 

참고 자료

http://greyminecraftcoder.blogspot.com.au/2015/02/recipes.html

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

 

배경지식 및 선행과정 :

1. 무형 조합법(Shapeless Recipe) 및 화로 조합법(Smelting Recipe) 추가하기

 

강의 목표 :

1. 유형 조합법(Shaped Recipe)의 구성 요소를 이해한다.

2. 유형 조합법(Shaped Recipe)을 등록한다.

 

1. 유형 조합법

유형 조합법은 조합대의 아이템 배치에 따라 결과물이 바뀌는 조합법입니다. 3*3 이 최대 크기이고 그보다 작은 경우에는 모양은 유지한 채 자유롭게 옮겨도 같은 조합법으로 적용됩니다. 몇몇 조합법은 좌우로 대칭을 시킬 수도 있습니다. 조합법 자체는 항상 직사각형으로 정의되지만, '빈칸'을 설정할 수 있어서 다양한 모양을 가집니다. IC2처럼 해당 모드만의 유형 조합법을 따로 구현한 사례도 있고 광물 사전(Ore Dictionary)을 이용해서 목재와 같이 종류가 다양한 아이템을 모두 받아들이게 만든 조합법도 있습니다.

그림 1유형조합법

 

 

2. 유형 조합법 정의하기

1) 형태 정의하기

우선 유형 조합법을 어떻게 정의하는 지 알아보겠습니다. 유형 조합법은 형태와 구성요소로 이루어져 있습니다. 즉, 어떤 모양으로 어떤 아이템을 놓느냐에 따라 결과가 결정되는 것입니다. 형태를 어떻게 구성하는지 그 원리만 파악하면 각 위치에 아이템을 지정해서 프로그램 상에서 조합법을 추가하는 것은 크게 어렵지 않습니다.

앞서 모든 조합법은 직사각형이 기본 형태라고 말씀드렸습니다. 가로 N, 세로 M의 조합법은 M개의 길이 N짜리 문자열로 그 형태가 정의됩니다. 즉, 직사각형으로 표현한 후 위쪽부터 행 별로 문자열을 늘어놓은 꼴이 됩니다. 말로만 하면 굉장히 복잡하기 때문에 예를 통해 알아보겠습니다.

A

  

A

A

 

A

A

A

위와 같은 조합법이라면 "A ", "AA ", "AAA"으로 적을 수 있습니다. 직사각형의 조건을 맞추기 위해서 공백을 추가한 것을 주의해서 보세요.

 

A

 

B

 

B

이번에는 " A ", "B B"으로 적을 수 있습니다. 반드시 정사각형일 필요도 없습니다. 서로 다른 알파벳은 서로 다른 아이템을 의미합니다. 이번에도 A의 양쪽에 공백이 있으니 주의하세요.

 

A

A

B

B

이 조합법은 "AA", "BB"으로 표기 되고,

A

A

 

B

B

 
   

이 조합법은 "AA ", "BB ", " "으로 표기됩니다. 얼핏 보면 두 조합법이 같아 보이지만 첫 번째 조합법은 크기가 2*2이고, 두 번째 조합법은 3*3입니다. 그렇기 때문에 아래의 조합법은 다음과 같은 배치에서 조합이 되지 않습니다.

그림 2두 조합법의 차이

 

2) 구성 아이템 정의하기

다양한 예를 다루면서 주의점도 파악했기 때문에, 이제 각 문자에 대해서 아이템을 지정하는 방법을 가르쳐드리겠습니다. 형태를 정의하는 과정에서 여러 문자를 사용하여 다른 아이템이란 것을 나타냈기 때문에, 각 문자 별로 구성 아이템을 지정해주면 됩니다. 이때 주의하실 점은 문자열(String)이 아닌 문자(char)에 값을 대응 시켜야 한다는 점입니다.

"aa", "bb", 'a', new ItemStack(Items.potato), 'b', new ItemStack(Items.carrot)

위에서 언급된 조합법을 나타내면 위와 같습니다. 각 문자는 "가 아닌 '을 이용해서 문자(char)로 표현했다는 것을 다시 확인해주시길 바랍니다.

 

 

3. 유형 조합법 추가하기

유형 조합법을 추가하는 위치나 상태는 이전 강의와 동일합니다. 기본 모드 파일도 동일한 상태입니다.

 

GameRegistry.addShapedRecipe(ItemStack output, Object... params)

ItemStack output : 조합법의 결과인 ItemStack입니다.

Object... params : 조합법의 형태와 재료를 결정하는 다수의 문자열(String), 문자(char), ItemStack입니다.

조합법을 추가하는 함수도 무형 조합법과 거의 같습니다. 다만 params에 들어갈 인자가 지금까지 살펴본 문자열(String), 문자(char), 아이템스택(Item Stack)의 조합입니다.

구체적인 부분은 위에서 모두 살펴보았기 때문에 간단한 예를 통해 강의를 마무리하도록 하겠습니다. 다음의 코드가 그림에 해당하는 조합법을 추가합니다.

GameRegistry.addShapedRecipe(new ItemStack(Items.apple, 4), "ab", "ba", 'a', new ItemStack(Items.potato), 'b', new ItemStack(Items.carrot));

그림 3조합법 예

동물

컨피그가 많이 변화되었습니다. Wiki 참조

토끼가 기본적으로 본 모드의 영향을 받습니다.

기계

크랭크

대형 크랭크에는 크랭크를 돌릴 수 있고, 길들여진 동물만이 묶일 수 있습니다.

벨트

크리에이티브 탭에서 기본적으로 16의 길이를 가집니다.

더 이상 블록의 형태로 존재하지 않고, 두 축을 연결하는 용도로 사용됩니다. 두 축이 같은 높이에 있고 거리가 8이하면 벨트를 이용해서 연결할 수 있습니다.

인공지능

전반적인 코드를 개선했습니다.

디버그 돋보기를 핫바에 놓으면, 동물들이 도망가지 않습니다.

버그 개선

각종 크래쉬 및 렌더링 오류

여물통이 부서졌을 때, 내부 아이템이 나오지 않던 문제

동물이 Dead bush를 먹지 않습니다.

닭이 자신이 얻은 씨앗으로부터 성장 가속 효과를 받지 않습니다.

호환성

NEI

모드에서 추가된 다양한 조합법을 지원합니다.

GUI에서 번역 파일을 지원합니다.

기타

아주 다양한 동물 추가 모드랑 호환될 수 있습니다. Wiki 참조

'마인크래프트 자작 모드 > 배고픈동물들' 카테고리의 다른 글

2.0 업데이트  (3) 2015.08.10
1.0.4.1 업데이트  (6) 2015.02.06
1.0.4.0 업데이트  (2) 2015.02.05
1.0.3.1 업데이트  (0) 2015.02.05
1.0.3 업데이트  (1) 2015.02.05

이전의 토지 중개사 모드에 여러 버그가 있었고, 사용성도 좋지 않아 여러 부분에서 개편을 진행했습니다. 가장 치명적이었던 청크가 로딩이 안되는 버그(ㅡㅡ;), 그리고 자신이 추가한 청크를 확인하기 힘들고 땅문서를 잃기도 쉬웠던 문제를 종합적으로 개선했습니다.

 

1. 토지 대장

토지 중개사 모드는 2가지 아이템을 추가합니다. 하나는 토지 대장이고 나머지 하나는 땅 문서입니다. 이 두 아이템만 있으면 청크 로딩을 제어할 수 있습니다. 먼저 토지 대장에 대해서 알아보도록 하겠습니다.

 


토지 대장은 위의 조합법으로 만들 수 있습니다.

 


우 클릭으로 토지 대장을 열어볼 수 있고, 플레이어는 처음 허가된 청크가 0개 임을 알 수 있습니다. OP 권한을 가진 플레이어는 제한이 없습니다.

 

 

2. 땅문서

땅 문서는 허가되는 청크의 수를 증가시켜줍니다. 다만, 기본적으로는 만들 수 없고 서버 관리자가 다른 모드를 이용해서 적절한 레시피를 추가해주시면 됩니다.

 


각각의 땅문서는 사용하기 위해서 레벨 제한이 있습니다. 즉 자신에게 허가된 청크 수에 맞는 레벨을 가진 땅 문서를 사용해야만 추가 청크를 얻을 수 있습니다. 크리에이티브 탭에는 16종의 땅문서가 있는데, 데미지(Damage) 값만 달리하면 상위의 땅문서도 쉽게 만들 수 있습니다.

 

알맞은 땅문서를 사용하면, 추가적인 청크가 허가됩니다. 토지 대장에서의 수치도 증가합니다.


 

3. 청크 관리

토지 대장을 손에 들고 좌클릭을 하면 자신이 있는 청크를 로드하게 됩니다. 핫바에 토지 대장을 놓고 있으면 흰색 경계가 표시됩니다.


자신에게 허가된 개수 이내로 다수의 청크를 로드할 수 있고, 이미 로드된 청크 안에서 좌클릭을 다시 하면 로드가 취소됩니다.

 

토지 대장의 GUI에서도 로드된 청크들을 확인할 수 있고, 각 청크 좌표 우측의 X 아이콘을 누르면 해당 지점으로 가지 않아도 로딩을 취소할 수 있습니다.

'마인크래프트 자작 모드 > 토지중개사' 카테고리의 다른 글

토지 중개사 모드 소개  (1) 2015.02.13

조립형 드릴은 산업 모드(IndustrialCraft) 애드온(Addon)으로서 만든 모드입니다. 산업 모드에서도 3종의 드릴을 추가하고, 그 외 다른 애드온들도 드릴을 추가하지만 서로 기능이 충돌하고 밸런스도 잘 맞지 않아서 산업 모드에서 사용될 드릴만 다루는 모드를 만들어 보았습니다. 이 모드 자체의 아이디어는 그레그 테크(GregTech)에서 가져왔으며 아직 산업 모드 팀에게 허가 받지 않은 애드온이기 때문에 공개 배포가 되지 않는다는 점 이해해주시길 바랍니다.

 

1. 기본 드릴 만들기

조립형 드릴의 가장 기본 형태는 위의 조합법으로 만들어집니다. 간략하게 설명하면 드릴 날, 배터리, 모터로 구성되어 있습니다. 드릴은 전력 소모 효율 증가, 채광 레벨, 그리고 채광 범위에 영향을 끼칩니다. 배터리는 충전 용량과 충전에 요구되는 티어를 결정하고 모터는 전력 소모 효율 감소, 채광 속도와 관련됩니다.

조립형 드릴에 마우스를 올리면 현재 부품의 단계와 스펙을 확인할 수 있습니다. 블록 하나를 파괴할 때 사용되는 EU, 최대로 저장할 수 있는 EU, 채광 속도와 채광 범위가 위에 먼저 표기되고, 각 부품의 단계가 아래에 표시됩니다.

 

 

2. 부품과 조립

조립형 드릴을 더욱 더 유용하게 만들기 위해서는 추가적인 부품이 필요합니다. 위와 같이 드릴 날은 6단계로, 모터와 배터리는 각 8단계로 구분되어 있습니다. 당연히 상위 단계일수록 요구하는 자원이 많아지고, 성능이 좋아집니다.

 

부품 조립은 위와 같이 간단한 조합으로 이루어집니다. 다만, 각 부품을 조립하기 위해서는 바로 아래 단계의 부품이 조립되어 있어야합니다. 위 사진의 경우에는 청동 드릴 날이 기본 드릴 날인 철 드릴 날의 바로 다음 단계이기 때문에 조립할 수 있는 것입니다. 서로 다른 부품의 단계는 영향 받지 않습니다. 즉 한 부품만 강화시켜 가는 것도 가능합니다, 다만 배터리 소모와 용량, 속도 등의 밸런스가 맞지 않아 모든 부품을 단계적으로 강화하는 것이 가장 좋을 것입니다.

 

최고 단계의 드릴 날은 다음과 같이 7*7의 범위를 팝니다. 상당히 강력한 성능을 자랑하는 드릴을 만들 수 있습니다.

 

 

 

곧 산업 모드 팀에 모드 배포와 관련해서 문의를 할 예정이지만 그 내용이 확정되기 전에 모드가 필요하신 분은 따로 연락해주시길 바랍니다.

+ Recent posts