[1.7.2] Урок по созданию блока

329
13
Перед прочтением крайне рекомендую посетить Учебник по моддингу - там информации намного больше.

Код, представленный ниже, актуален для Minecraft v1.7.2 и Forge v.10.12.0.1024. Начнем мы с самого простого: регистрация блока в основном классе мода и создание "каркаса" блока.
Код:
@Mod(modid = TaoModMain.MODID, version = TaoModMain.VERSION)
public class TaoModMain
{
    public static final String MODID = "tutorialblockmod";
    public static final String VERSION = "1.0";
    
    // Объявляем новый блок
    public static Block BlockTest;
    
    @EventHandler
    public void preInit(FMLPreInitializationEvent event)
    {
        // Определяем, что BlockTest - это блок с материалом "Камень (rock)"
        // Далее регистрируем его в Forge под уникальным именем "testblock"
        BlockTest = new BlockTest(Material.rock);
        GameRegistry.registerBlock(BlockTest, "testblock");
    }
}
Тут все довольно просто, достаточно добавить 3 строки в шаблон основного класса мода. Обратите лишь внимание на то, что регистрация блока происходит в preInit(FMLPreInitializationEvent event). Строчкой BlockTest = new BlockTest(Material.rock) мы привязали наш BlockTest к конкретному классу, которого еще нет. Создадим его.

Создадим класс блока, который от нас требует Forge. Старайтесь придерживаться такой же иерархии, как и в ванильных файлах Minecraft. Создайте пакет block а в нем класс BlockTest.
Код:
public class BlockTest extends Block
{
        // Конструктор класса
    public BlockTest(Material material)
    {
        super(material);
        // Указываем вкладку креатива, в которой будет наш блок (вкладка Блоки, можно указать любую)
        setCreativeTab(CreativeTabs.tabBlock);
        // Указываем имя для блока (понадобится для локализации)
        setBlockName(TaoModMain.MODID + "." + "testblock");
    }
}
Шаблон готов. Теперь можно проверить его в игре. Заходим в игру, создаем креатив, и теперь во вкладке с блоками появился новый блок без текстуры.
image.png

Наш новый блок не имеет текстуры, вместо нее отображается стандартная фиолетово-черная сетка. Мы исправим это, добавив текстуру. Для примера я взял следующую:
image.png

Теперь ее нужно добавить в нужное место, а именно в
src/main/resource/assets/имя_мода/textures/blocks/blocktest.png
Теперь модифицируем шаблон следующим образом:
Код:
public class BlockTest extends Block
{
    // Объявляем текстуру для блока
    @SideOnly(Side.CLIENT)
    protected IIcon BlockTestTexture;
    
        // Конструктор класса
    public BlockTest(Material material)
    {
        super(material);
        // Указываем вкладку креатива, в которой будет наш блок (вкладка Блоки, можно указать любую)
        setCreativeTab(CreativeTabs.tabBlock);
        // Указываем имя для блока (понадобится для локализации)
        setBlockName(TaoModMain.MODID + "." + "testblock");
    }
 
    // Этот метод позволяет зарегистрировать новые текстуры
    @SideOnly(Side.CLIENT)
    @Override
    public void registerBlockIcons(IIconRegister par1IconRegister)
    {
        // Регистрируем путь до png-текстуры
        BlockTestTexture = par1IconRegister.registerIcon(TaoModMain.MODID + ":" + "blocktest");
    }
    
    // Этот метод получает текстуры блока
    @SideOnly(Side.CLIENT)
    @Override
    public IIcon getIcon(int par1int, int par2int)
    {
        return BlockTestTexture;
    }
}
Смотрим на результат:
image.png

Теперь сделаем так, чтобы текстура нашего блока с разных сторон отличалась. Для этого нам придется дорисовать еще текстуры для боковых, а также верхней и нижней граней блока:
image.png
-
image.png
-
image.png

Теперь мы должны их зарегистрировать и поставить условия для отрисовки текстур, а также воспользуемся методом onBlockPlacedBy, где рассчитаем угол поворота игрока по горизонтали:
Код:
@SideOnly(Side.CLIENT)
@Override
public void registerBlockIcons(IIconRegister par1IconRegister)
{
    // Регистрируем путь до png-текстур для разных сторон блока
    BlockTestFront = par1IconRegister.registerIcon(TaoModMain.MODID + ":" + "blocktest_front");
    BlockTestSide = par1IconRegister.registerIcon(TaoModMain.MODID + ":" + "blocktest_side");
    BlockTestTop = par1IconRegister.registerIcon(TaoModMain.MODID + ":" + "blocktest_top");
}

// Этот метод определяет, на какую грань блока накладывать ту или иную текстуру
@SideOnly(Side.CLIENT)
@Override
public IIcon getIcon(int par1int, int par2int)
{
        return par1int == 1 ? BlockTestTop : (par1int == 0 ? BlockTestTop : (par2int == 2 && par1int == 2 ? BlockTestFront : (par2int == 3 && par1int == 5 ? BlockTestFront : (par2int == 0 && par1int == 3 ? BlockTestFront : (par2int == 1 && par1int == 4 ? BlockTestFront : BlockTestSide)))));
}

// Когда мы поставим блок - будет вызван этот метод
@Override
public void onBlockPlacedBy(World par1World, int par2int, int par3int, int par4int, EntityLivingBase par5EntityLivingBase, ItemStack par6ItemStack)
{
    // Ниже мы определяем угол поворота игрока
    // И определяем, куда будет повернут блок
    int i = MathHelper.floor_double((double)(par5EntityLivingBase.rotationYaw * 4.0F / 360.0F) + 2.5D) & 3;
    par1World.setBlockMetadataWithNotify(par2int, par3int, par4int, i, 2);
}
Проверяем результат.
image.png

Также можно изменить свойства блока, такие как прочность блока, устойчивость к взрыву, звук при ходьбе по блоку и необходимый для добычи инструмент. Это все мы будем настраивать в конструкторе блока.
Код:
// Конструктор класса
public BlockTest(Material material)
{
     super(material);
     setCreativeTab(CreativeTabs.tabBlock);
     setBlockName(TaoModMain.MODID + "." + "testblock");
     setHardness(1.0F);
     setResistance(20.0F);
     setHarvestLevel("pickaxe", 3);
     setLightLevel(12.0F);
     setStepSound(Block.soundTypeStone);
}
setHardness(1.0F) - прочность блока. Определяет, насколько долго нужно долбить блок, чтобы он был разрушен. Таблицу прочности блоков можно посмотреть здесь.
setResistance(20.0F) - устойчивость блока к взрывам. Таблицу устойчивости можно посмотреть здесь.
setHarvestLevel("pickaxe", 3) - условия добычи блока. В нашем случае, если не добывать блок алмазной киркой, то он не выпадет в виде дропа.
setLightLevel(12.0F) - определяет свечение блока. Яркость стандартных блоков можно посмотреть здесь здесь.
setStepSound(Block.soundTypeStone) - определяет тип звука, издаваемый когда игрок ходит по этому блоку.

image.png

По умолчанию при попытке добыть этот блок мы получаем его же. Попробуем изменить это и сделать, например, выпадение светопыли. Для этого добавим вот такой метод:
Код:
// Метод, устанавливающий что будет падать с блока
@Override
public Item getItemDropped(int par1int, Random par2int, int par3int)
{
    return Items.glowstone_dust;
}
Теперь при добыче с блока будет падать одна светопыль.
image.png


А теперь сделаем так, чтобы при добыче выпадало несколько штук светопыли. Тут нам поможет добавление еще одного метода:
Код:
// Метод, устанавливающий количество выпадающих с блока вещей
@Override
public int quantityDroppedWithBonus(int par1int, Random par2Random)
{
    return par2Random.nextInt(4) + 1;
}
return par2Random.nextInt(4) + 1 - это означает что точно выпадет 1 и, возможно, еще от 0 до 3 дополнительно.

Теперь сделаем так, чтобы при нажатии правой кнопкой мыши на блок происходили какие-то действия. В качестве примера сделаем постепенное увеличение яркости блока. За использование блока отвечает метод onBlockActivated:
Код:
// Действие на правую кнопку мыши
@Override
public boolean onBlockActivated(World par1World, int par2int, int par3int, int par4int, EntityPlayer par5EntityPlayer, int par6int, float par7float, float par8float, float par9float)
{
    // Если яркость блока еще не максимум - увеличиваем ее.
    // Если максимум - сбрасываем яркость на ноль.
    if (getLightValue() < 15)
    {
        setLightLevel((this.getLightValue() / 15.0F) + 0.067F);
    }
    else
    {
        setLightLevel(0);
    }
    return true;
}
Теперь кликая на блок - получаем разное свечение.
image.png

Теперь сделаем так, чтобы при установке этого блока на блок шерсти они образоввывали живого крипера. То бишь сделаем аналог снеговика. Воспользуемся методом onBlockActivated:
Код:
// Метод, вызываемый в момент установки блока
@Override
public void onBlockAdded(World par1World, int par2int, int par3int, int par4int)
{
    // Проверяем, есть ли под нашим блоком шерсть
    if (par1World.getBlock(par2int, par3int - 1, par4int) == Blocks.wool && !par1World.isRemote)
    {
        // Если есть, то превращаем оба блока в воздух (удаляем их)
        par1World.setBlockToAir(par2int, par3int, par4int);
        par1World.setBlockToAir(par2int, par3int - 1, par4int);
        // И оставляем на их месте крипера
        EntityCreeper Creeper = new EntityCreeper(par1World);
        Creeper.setLocationAndAngles((double)par2int + 0.5D, (double)par3int - 0.95D, (double)par4int + 0.5D, 0.0F, 0.0F);
        par1World.spawnEntityInWorld(Creeper);
    }
}
Проверяем результат.
image.png
 
2,955
12
О боже, на других версиях тоже самое, не надо делать хрень.
 
1,990
18
105
Как же вы задолбали везде пихать this., это вам не javascript, ну нахрена при обращении к глобальным свойствам, при этом не имея одноименных локальных, писать грёбаный this?
 
2,955
12
Я вообще не понимаю смысла этого туториала. На сайте все есть.
 
329
13
Oldestkon написал(а):
Как же вы задолбали везде пихать this., это вам не javascript, ну нахрена при обращении к глобальным свойствам, при этом не имея одноименных локальных, писать грёбаный this?
Окей, поправил.
 
Тао, а разве нельзя замутить регистрацию блока/предмета в отдельный класс? Зачем переполнять основной класс мода, если, блоков, к примеру, штук 20. И предметов 15. И рецептов к каждому из них. Плюс генераторы и подключение прокси. То-есть он выйдет перегруженным. Понимаю, если мод 1-2 блока и предмета, то да. А про то, как он будет нагружен говорю по своему моду... Я уже боюсь переходить на 1.7
 
Можно конечно, это пример для новичков, чтобы они не путались в нескольких классах.
 
771
5
Vova_master написал(а):
Тао, а разве нельзя замутить регистрацию блока/предмета в отдельный класс? Зачем переполнять основной класс мода, если, блоков, к примеру, штук 20. И предметов 15. И рецептов к каждому из них. Плюс генераторы и подключение прокси. То-есть он выйдет перегруженным. Понимаю, если мод 1-2 блока и предмета, то да. А про то, как он будет нагружен говорю по своему моду... Я уже боюсь переходить на 1.7
Че он будет нагружаться?
300 строк - это еще не нагрузка.
 
329
13
Vova_master написал(а):
Тао, а разве нельзя замутить регистрацию блока/предмета в отдельный класс?
В данном случае ради одного блока создавать дополнительный класс не стоит. А так да, согласен, намного удобнее вынести регистрации мобов\блоков\предметов в отдельный класс.

Мне не понятна другая вещь: почему во всех зарубежных туториалах при регистрации блока сразу же указывают его параметры через точку? Прямо в главном классе такое:
Код:
BlockTest = new BlockTest(Material.rock).setHardness(0.5F).setStepSound(Block.soundGravelFootstep).setUnlocalizedName("genericDirt").setCreativeTab(CreativeTabs.tabBlock);
В чем профит писать это не в конструкторе?
 
Да в том, что если писать это не в конструкторе, то можно создать два блока с разными свойствами, используя один и тот же класс.
 
329
13
Dimansel написал(а):
Да в том, что если писать это не в конструкторе, то можно создать два блока с разными свойствами, используя один и тот же класс.
Хм, действительно. :) Не думал об этом.
 
А еще там же можно и текстуру как-то подключить. Я где-то в исходниках видел
 
Вот. То есть - файл блока только для каких-то особых функций можно оставить. Но не для тех, которые в public blabla{
super(bla1);
}
А для разных других.
 

necauqua

когда-то был anti344
Администратор
1,216
27
172
Зачем писать кучу регистраций?
Код:
def getObjectInstance(className: String, default: AnyRef = null): AnyRef =
    if(className.endsWith("$"))
      Class.forName(className).getField("MODULE$").get(null)
    else default

  def iterateThroughFields(className: String, instance: AnyRef = null)(act: AnyRef => Unit) = {
    val inst: AnyRef = getObjectInstance(className, instance)
    inst.getClass.getDeclaredFields.foreach{f =>
      f.setAccessible(true)
      act(f.get(inst))
    }
  }

  def loadContents(contentObjName: String) =
    iterateThroughFields(contentObjName + "$"){
      case block: BlockTM =>
        val args: Array[AnyRef] = block.getCustomItemBlockArgs
        if(args != null)
          GameRegistry.registerBlock(block, block.getItemBlockClass, block.getNameForRegistry, null, args)
        else
          GameRegistry.registerBlock(block, block.getItemBlockClass, block.getNameForRegistry, null)
      case item: ItemTM => GameRegistry.registerItem(item, item.getNameForRegistry)
      case _ =>
    }
 
Эмм... что с твоим сообщением?xD. А код все-же на другом языке... во всяком случае не так, как общепринято
 

necauqua

когда-то был anti344
Администратор
1,216
27
172
Такое и на Java можно написать, причем короче, без объектов.
 
Сверху