Перейти к основному содержимому
Версия: Legacy

Добавляем новый блок

no-title



Важное правило

Всё ниже написанное пишется в методе init():

fun init() {
// Тут всё ниже написанное //
}



Создание регистратора блоков

Создание регистратора блоков

Для начала нужно создать регистратор, чтоб в последующем создавать новые блоки и добавлять их в игру.

import net.minecraftforge.registries.DeferredRegister
import net.minecraftforge.registries.ForgeRegistries

val BLOCKS = DefferedRegister.create(ForgeRegistries.BLOCKS, modId: String)

modId - это куда будет добавлятся предмет. Можно вписать сюда "hollowengine" и тогда все предметы будут из мода HollowEngine.




Создание блока

Регистрация блока

Теперь можно создать блок и задать ему параметры.

import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockBehaviour

BLOCKS.register(newBlockId: String) {
Block(BlockBehaviour.Properties
<Параметры блока>
)
}

Параметры блока

Материал блока
import net.minecraft.world.level.material.Material

// Влияет на свойства блока (Цвет, колизию и т.д.)
.of(Material.<MATERIAL_TYPE>)
Отрисовка сторон
/*
* По умолчанию стороны блоков не рендерялся впритык с другими блоками (в угоду оптимизации)
* Так что если модель вашего блока меньше чем размер блока - используйте данный параметр
*/
.noOcculusion()
Колизия с блоком (столкновение)
.noCollision() // Теперь игрок не сталкивается с блоком
Скорость передвижения (Сопротивление)
.friction(percentValue: Float) // Значение указывается в процентах
// 1f = 100%
// 0.01 = 1%
// Блок как бы превращается в аналог льда (он становится скользким)
// Не рекомендуем ставить значения выше 1f (т.е. выше 100%)
Скорость передвижения (Ускорение)
.speedFactor(factorPercent: Float) // значенеи указывается в процентах
// 1f = 100%
// 0.01f = 1%
Сила отпрыгивания
.jumpFactor(jumpFactor: Float) // Как сильно сущность может отпрыгнуть от блока
// 1f = 100%
// 0.01f = 1%
Звук блока (Удар, Хождение, сломан и т.д.)
import net.minecraft.world.level.block.SoundTypes

.sound(SoundType.<SOUND_TYPE>)
Сила излучаемого света
.lightLevel { state -> 15 }
// или
.lightLevel { state ->
// Можно добавить логику, по которой будет работать свет от блока
}
Прочность блока (Против копания и Взрыва)
.strength(destroyTime: Float, explosionResistance: Float)
// или
.strength(destroyTimeAndExplosionResistance: Float)
Неломаемый блок
.instebreak()
Рандомный тик
.randomTick()
Динамический шейп (как у дверей и люков)
.dynamicShape()
Блок без таблицы дропа (т.е. при его разрушении - ничего не выпадает)
.noLootTable()
Это воздух
.air()
Запрещён спавн мобов на блоке
.isValidSpawn(_ -> false) // Всем мобам запрещено спавнится
// или
.isValidSpawn { entityType ->
entityType == EntityType.<ENTITY_TYPE> // Фильт мобов, которым нельзя спавнится
}
Проводник редстоун сигнала
.isRedstoneConductor(state -> true)
// или
.isRedstoneConductor(state ->
// Условия, при которых становится проводником
)
Игрок умирает в блоке
.isSuffocating(state -> true)
// или
.isSuffocating(state ->
// Условия, при которых игрок будет получать урон, находясь в блоке
)
Блокировка обзора (На стороне клиента)
.isViewBlocking(state -> true)
// или
.isViewBlocking(state ->
// Условия, про которых будет блогироваться обзор игрока
)
Требует корректный инстр3умент, чтобы получить дроп с блока при разрушении
.requiresCorrectToolForDrops()
Время разрушения блока
.destroyTime(value: Float)
Защита от взрывов
.explosionResistance(value: Float)
Лёгкое смещение положение модели блока (как у растительности)
.offsetType(BlockBehaviour.OffsetType.<TYPE>)
/*
* NONE - По имолчанию
* XZ
* XYZ
*/

Методы блока

Работоспособность

Т.к. компилятор работает немного криво, не факт что все ниже перечесленные методы будут работать в принцепе.

Для некоторых методов возможна потребуется аннотация:

@Deprecated(message: String)

// Пример //

@Deprecated("Этот метод - устарел")
override fun use(...) {...}
Как использовать

Для начала вам нужно заменить:

Block(BlockBehaviour.Properties.of(Material.<MATERIAL_TYPE>)
<ваши параметры для блока>
)

на следующее:

object: Block(BlockBehaviour.Properties.of(Material.<MATERIAL_TYPE>)
<Ваши параметры для блока>
) {
<Ниже перечисленные методы>
}

так же не забывайте что все импорты пишутся в самом начале скрипта. Исключение только если вы используете импорт @file:Import(), то вот этот импорт должен быть в самом начале, а уже после него - обычные импорты.

Анимированные тики (Как у печек и частиц огня)
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.Level
import net.minecraft.core.BlockPos
import net.minecraft.util.RandomSources

override fun animateTick(
pState: BlockState,
pLevel: Level,
pPos: BlockPos,
pRandom: RandomSource
) {
// Что должно происходить, когда у блок анимируется
}
Когда блок разрушен (сломан)
import net.minecraft.world.level.LevelAccessor
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState

override fun destroy(
pLevel: LevelAccessor,
pPos: BlockPos,
pState: BlockState
) [
// Что должно происходить, когда блок был разрушен
]
Когда блок взорван
import net.minecraft.world.level.Level
import net.minecraft.core.BlockPos
import net.minecraft.world.level.Explosion

override fun wasExploded(
pLevel: Level,
pPos: BlockPos,
pExplosion: Explosion
) {
// Что должно происходить, когда блок был взорван
}
Когда по блоку ходят
import net.minecraft.world.level.Level
import net.minecraft.core.BlockPos
import net.minecraft.world.entity,Entity

override fun stepOn(
pLevel: Level,
pPos: BlockPos,
pEntity: Entity
) {
// Что должно произойти, когда по блоку ходят
}
Стадия блока при размещении
override fun getStateForPlacement(pContext: BlockPlaceContext): BlockState {
// Какая стадия должна быть у блока, когда он будект размещён

super.getStateForPlacement(pContext)
}
Блок сломал игрок
import net.minecraft.world.level.Level
import net.minecraft.world.entity,plaeyr.Player
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.entity.BlockEntity

override fun playerDestroy(
pLevel: Level,
pPlayer: Player,
pState: BlockState,
pBlockEntity: BlockEntity
) {
// Что должно произойти, когда игрок сломал этот блок
}
При размещении блока через предмет
import net.minecraft.world.level.Level
import net.minecraft.world.entity,plaeyr.Player
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.entity.BlockEntity

override fun setPlaceBy(
pLevel: Level,
pPlayer: Player,
pPos: BlockPos,
pState: BlockState,
pBlockEntity: BlockEntity
) [
// Что должно произойти, когда блок был размещён через предмет
]
Урон сущности при падении на блок
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.Entity

override fun fallOn(
pLevel: Level,
pState: BlockState,
pPos: BlockPos,
pEntity: Entity,
pFallDistance: Float
) {
// Что должно произойти, когда сущность упала на этот блок
}
Пере разрущением блока (т.е. когда ударили)
import net.minecraft.world.level.Level
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.entity.player.Player

override fun playerWillDestroy(
pLevel: Level,
pPos: BlockPos,
pState: BlockState,
pPlayer: Player
) {
// Что должно произойти, когда игрок начал разрушать блок (ударил по блоку)
}
Обновление данных сущности после падения на блок
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.entity.Entity

override fun updateEntityfterallOn(
pLevel: BlockGetter,
pEntity: Entity
) {
// Что должно произойти после того, как сущность упала на блок
}
Взаимодействие с блоком
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.Level
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.player.Player
import net.minecraft.world.InteractionHand
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.InteractionResult


override fun use(
pState: BlockState,
pLevel: Level,
pPos: BlockPos,
pPlayer: Player,
pHand: InteractionHand,
pHit: BlockHitResult
): InteractionResult {
// Что должно произойти, когда повзаимодействовали с блоком

return InteractionResult.SUCCESS
}
Максимальное смещени модели блока по Горизонтали и Вертикали
override fun getMaxHorizontalOffset(): Float {
return value: Float // Горизонтально
}

override fun getMaxVerticalOffset(): Float {
return value: Float // Вертикально
}
Каждый тик (+Рандомный)
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.Level
import net.minecraft.core.BlockPos
import net.minecraft.util.RandomSource

override fun tick(
pState: BlockState,
pLevel: Level,
pPos: BlockPos,
pRandom: RandomSource
) {
// Что должно происходить каждый тик
}

override fun randomTick(
pState: BlockState,
pLevel: Level,
pPos: BlockPos,
pRandom: RandomSource
) {
// Что должно происходить каждый рандомный тик
}
Прогресс ломания (разрушения) блола
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.BlockGetter
import net.minecraft.core.BlockPos

override fun getDestroyProgress(
pState: BlockState,
pPlayer: Player,
pLevel: BlockGetter,
pPos: BlockPos
): Float {
// Что должно произойти, когда блок ломает игрок
}
Когда блок ударили
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.Level
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.player.Player

override fun attack(
pState: BlockState,
pLevel: Level,
pPos: BlockPos,
pPlayer: Player
) {
// Что должно произойти, когда игрок атакует блок (начинает ломать)
}
override fun canOcclude()
Шейп блока (его колизия)
import net.minecraft.world.level.BlockGetter
import net.minecraft.core.BlockPos
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.VoxelShape

// Общая //
override fun getShape(
pLevel: BlockGetter,
pPos: BlockPos,
// Дополнительный параметр
// pContext: CollisionContext
): VoxelShape [
// Коробчаный шейп //
Block.box(x1: Double, y1: Double, x2: Double, y2, x2: Double, y2: Double, z2: DOuble)

// Несколько коробок, соединёных в один шейп
Shapes.join(
Block.box(x1: Double, y1: Double, x2: Double, y2, x2: Double, y2: Double, z2: DOuble),
Block.box(x1: Double, y1: Double, x2: Double, y2, x2: Double, y2: Double, z2: DOuble),
BooleanOp.OR
)
]

// Колизионный шейп //
override fun getCollisionShape(
pLevel: BlockGetter,
pPos: BlockPos,
// Дополнительный параметр
// pContext: CollisionContext
): VoxelShape {
// Коробчаный шейп //
Block.box(x1: Double, y1: Double, x2: Double, y2, x2: Double, y2: Double, z2: DOuble)

// Несколько коробок, соединёных в один шейп
Shapes.join(
Block.box(x1: Double, y1: Double, x2: Double, y2, x2: Double, y2: Double, z2: DOuble),
Block.box(x1: Double, y1: Double, x2: Double, y2, x2: Double, y2: Double, z2: DOuble),
BooleanOp.OR
)
}

// Визуальный шейп //
override fun getVisualShape(
pLevel: BlockGetter,
pPos: BlockPos,
// Дополнительный параметр
// pContext: CollisionContext
): VoxelShape {
// Коробчаный шейп //
Block.box(x1: Double, y1: Double, x2: Double, y2, x2: Double, y2: Double, z2: DOuble)

// Несколько коробок, соединёных в один шейп
Shapes.join(
Block.box(x1: Double, y1: Double, x2: Double, y2, x2: Double, y2: Double, z2: DOuble),
Block.box(x1: Double, y1: Double, x2: Double, y2, x2: Double, y2: Double, z2: DOuble),
BooleanOp.OR
)
}

// Интерактивный шейп //
override fun getInteractiveShape(
pLevel: BlockGetter,
pPos: BlockPos
): VoxelShape {
// Коробчаный шейп //
Block.box(x1: Double, y1: Double, x2: Double, y2, x2: Double, y2: Double, z2: DOuble)

// Несколько коробок, соединёных в один шейп
Shapes.join(
Block.box(x1: Double, y1: Double, x2: Double, y2, x2: Double, y2: Double, z2: DOuble),
Block.box(x1: Double, y1: Double, x2: Double, y2, x2: Double, y2: Double, z2: DOuble),
BooleanOp.OR
)
}
Блок установлен
import net.minecraft.world.level.Level
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState

override fun onPlace(
pLevel: Level,
pPos: BlockPos,
pOldState: BlockState,
pIsMoving: Boolean
) {
// Что должно произойти, когда блок был поставлен //
}
Блок убран
import net.minecraft.world.level.Level
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState

override fun onRemove(
pLevel: Level,
pPos: BlockPos,
pNewState: BlockState,
pIsMoving: Boolean
) {
// Что должно произойти, когда блок был убран //
}
Сущности внутри блока
import net.minecraft.world.level.Level
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.Entity

override fun entityInside(
pLevel: Level,
pPos: BlockPos,
pEntity: Entity
) {
// Что должно происходить, когда сущность находится внутри блока //
}
Разрушен после генерации в мире
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.Level
import net.minecraft.core.BlockPos
import net.minecraft.world.item.ItemStack

override fun spawnAfterBreak(
pLevel: ServerLevel,
pPos: BlockPos,
pStack: ItemStack,
value: Boolean
) {
// Что должно произойти, когда блок был сгенерирован генератором мира и после был разрушен //
}
Пригодный блок для Происка пути сущности
import net.minecraft.world.level.BlockGetter
import net.minecraft.core.BlockPos
import net.minecraft.world.level.pathfinder.PathComputationType

override fun isPathfindable(
pLevel: BlockGetter,
pPos: BlockPos,
pType: PathComputationType
): Boolean {
// Логика, при которой блок может быть пригоден для поиска пути сущности //

super.isPathfindable(pLevel, pPos, PathComputationType.<TYPE>)
/*
* - LAND - Твёрдая плоскость
* - WATER - Жидкая плоскость
* - AIR - Пустота
*/
}
Может быть заменён на другой блок
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.material.Fluid

override fun canBeReplaced(pUseContext: BlockPlaceContext): Boolean {
// Логикаа, при котороый блок может быть заменён //

super.canBeReplaced(pUseContext)
}
// или
override fun canBeReplaced(pFluid: Fluid): Boolean {
// Логикаа, при котороый блок может быть заменён //

super.canBeReplaced(pUseContext)
}



Финальная реuистрация всех блоков

Финальная регистрация всех блоков

Теперь для регистрации всех созданных предметов в конце приписываем следующее:

BLOCKS.register(MOD_BUS)



Блок-предмет

Блок-предмет

Всё это конечно круто. Но как размещать блоки? Не бойтесь. Прибегать к команде /setblock x y z <modId>:<newBlockId> не придётся)

Создаём блок-предмет
// Создаём регистраторы для предметов //
val ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, "best_mod")
val BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, "best_mod")

// Записываем наш блок в переменную. Сам блок долнех находится в скрипте - выше блока-предмета //
val COOL_BLOCK = BLOCKS.register("cool_block") {...}

// Создаём наш блок-предмет. ID предмета должен быть тот же, что и у блока ?/
ITEMS.register("cool_block") {

// Обычный предмет //
BlockItem(
COOL_BLOCK.get(), // Передаём блок через переменную
Item.Properties()
<Параметры предмета>
)

// Функциональный предмет //
object: BlockItem(
COOL_BLOCK.get(), // Передаём блок через переменную
Item.Properties()
<Параметры предмета>
) {
<Методы предмета>
}
}

// Регистрируем все созданые предметы и блоки //
ITEMS.register(MOD_BUS)
BLOCKS.register(MOD_BUS)

Параметры предмета
Методы предмета




Ресурсы для предмета

ресурсы для блока

Модель

Теперь если вам вдруг не понравился красивый и сочый куб с фиолетово-чёрными четверть кадратиками, то следуйте иструкции ниже:

Когда игра хочет отобразить модель блока, то она пойдёт искать ссылки на модель в файле: blockstates/<newBlockId>.json, в этом файле и определяется как будет выглядеть модель при таких-то условиях.
Так что создайте json файл с именем newBlockId который вы указали по следующему пути: assets/modId/blockstates/<newBlockId>.json, где за место modId должна стоять папка с именем, который вы указали при создании регистратора.

Пример

Если в регистраторе указан best_mod:

DeferredRegister.create(ForgeRegistries.BLOCKS, "best_mod")

то за место modId - ставлю best_mod: assets/best_mod/blockstates


И если создал предмет с ID cool_block:

BLOCKS.register("cool_block") {...}

то и имя файла должно быть cool_block.json: assets/best_mod/blockstates/cool_block.json.

Дальше заполните файл по следующему шаблону:

Пример. assets/best_mod/blockstates/cool_block.json
{
"variants": {
// Блок без всяких видов. Просто блок //
"": { "model": "best_mod:block/cool_basic_block.json" },

// Если у блока к примеру есть поворот в зависимости с какой стороны он был поставлен //
"facing=north": { "model": "best_mod:block/cool_north_block.json" }, // Северная сторона. FACING: NORTH
"facing=south": { "model": "best_mod:block/cool_south_block.json", "y": 180 }, // Южная сторона. FACING: SOUTH
"facing=west": { "model": "best_mod:block/cool_west_block.json", "y": 270 }, // Западная сторона. FACING: WEST
"facing=east": { "model": "best_mod:block/cool_east_block.json", "y": 90 } // Восточная сторона. FACING: EAST
}
}

Простой пример:

  • Блок будет иметь одну и ту же модель всегда. Поворот (если вы сделали такое):
  • facing=north: Блок будет смотреть лицевой стороной на север (где лицо блока можно узнать по N в Blockbench).
  • facing=south: Блок будет смотреть лицевой стороной на юг, повернув его на 180 градусов (т.е. назад),
  • facing=west: Блок будет смотреть лицевой стороной на запад, повернув его на на 270 градусов (т.е. влево),
  • facing=east: Блок будет смотреть лицевой стороной на восток, повернув его на 90 градусов (т.е. вправо)

model - Как раз этот параметр и определяет, откуда брать модель для блока при таком-то варианте. best_mod:block/cool_basic_block.json - модель лежит по пути: `assets/best_mod/models/block/cool_basic_block.json




Текстура

Текстуру вы можеет размещать где угодно, всё потому что путь к текстуре указывается в файле модели блока. Так что если вдруг модель встала, а текстуры нету (вы всё равно выдете фиолетово-чёрные четверть квадратики на модели) - то проверье, что текстура правильно определила свои META-данные:

texture-metadatas


Если вдруг META-данные вдруг не соответствуют, то сначала сохраните текстуру там, где вам нужно, а уже после уже - сохраните модель.

Если и после META-данные не изменились, то придётся вам изменять вручную.

Имя: Убедитесь что имя текстуры, совпадает с именем файла текстуры,


Папка: Убедитесь что папка указана правильно, где находита текстура (путь указывается относительно папки modId),


Пространство имён: это modId. Убедитесь что оно указано правильно и совпадает с указанным modId который вы указали в регистраторе DefferedRegister.




Результат (Пример)

Скрипт
Пример
import net.minecraftforge.registries.DeferredRegister
import net.minecraftforge.registries.ForgeRegistries
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.block.state.BlockBehaviour
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.TooltipFlag
import net.minecraft.world.level.material.Material
import net.minecraft.world.entity.player.Player
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResultHolder
import net.minecraft.world.level.Level
import net.minecraft.network.chat.Component
import net.minecraft.core.BlockPos
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.InteractionResult
import net.minecraft.core.particles.ParticleTypes
import ru.hollowhorizon.hollowengine.common.tabs.HOLLOWENGINE_TAB

fun init() {
val ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, "test") // Для блок-предмета
val BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, "test")

val AMOGUS_BLOCK = BLOCKS.register("amogus") {
object: Block(
BlockBehaviour.Properties.of(Material.CLOTH_DECORATION)
.noCollission()
.destroyTime(1f)
.explosionResistance(1f)
) {
@Deprecated("Ok")
override fun use(
pState: BlockState,
pLevel: Level,
pPos: BlockPos,
pPlayer: Player,
pHand: InteractionHand,
pHit: BlockHitResult
): InteractionResult {
if(pLevel.isClientSide && pHand == InteractionHand.MAIN_HAND) {
pLevel.addParticle(
ParticleTypes.NOTE,
pPos.x.toDouble(), pPos.y.toDouble(), pPos.z.toDouble(),
0.0, 0.5, 0.0
)
pPlayer.swing(pHand)

return InteractionResult.SUCCESS
} else
return InteractionResult.PASS
}
}
}
// Блок-предмет //
val AMOGUS_ITEM = ITEMS.register("amogus") {
object: BlockItem(
AMOGUS_BLOCK.get(),
Item.Properties()
.stacksTo(8)
.tab(HOLLOWENGINE_TAB)
) {
override fun use(
pLevel: Level,
pPlayer: Player,
pHand: InteractionHand
): InteractionResultHolder<ItemStack> {
if(pLevel.isClientSide && pHand == InteractionHand.MAIN_HAND) {
val pPos = pPlayer.position()
val pVec = pPlayer.getViewVector(1f)
val handOffset = -0.3
val particlePos = pPos.add(
pVec.x * 0.5 + handOffset,
pVec.y * 0.5,
pVec.z * 0.5
)

pLevel.addParticle(
ParticleTypes.NOTE,
particlePos.x, particlePos.y + 1, particlePos.z,
0.0, 0.5, 0.0
)
pPlayer.swing(pHand)
}
return super.use(pLevel, pPlayer, pHand)
}
}
}

BLOCKS.register(MOD_BUS)
ITEMS.register(MOD_BUS) // Для блок-предмета
}

hotbar




Перевод предмета

Перевод предмета

badName

Если вас вдруг не устраивает такое имя предмета, то это легко решается.


Создайте файл ru_ru.json - для Русского языка, или(и) en_us.json - для Англиского языка по следующему пути: assets/<modId>/lang/* и впишите в эти файлы следующее:

lang/ru_ru.json и lang/en_us.json
{
"block.<modId>.<newItemId>": "<TranslationText>"
}

Где за место <modID> - вставте id вашего мода (в моём случает - это test) и за место <newItemId> - id предмета, который вы добавили (в моём случае - это amogus).


goodName

У меня получилось так:

В ru_ru.json
{
"block.test.amogus": "Амогус"
}
В en_us.json
{
"block.test.amogus": "Amogus"
}