Первое существо
Понятие создания существ несколько отличается от всего остального, для этого не подойдет скрипт, куда удобнее использовать пакеты поведения и ресурсов. Эта статья описывает множество новых понятий, не описанных ранее, однако мы познакомимся с большинством из них. Начнем с основы нового моба, а уже позже перейдем к описанию его поведения и созданию визуальной части.
Определение существа
Любое существо начинается с создания серверного описания, определяющего общие атрибуты (изменяемые величины, задаваемые по умолчанию), группы атрибутов и события. Прежде всего, создайте пакет поведения (англ.) если вы еще этого не сделали. Информация в пакетах в большинстве случаев определяется JSON-файлами, можете сравнивать их с объектами в нашем основном языке.
Определитесь как будет называться существо, придумав ему идентификатор. Этот идентификатор будет использоваться для призыва существа, команд с ним и для многих других целей. Как и любой другой уникальный идентификатор в пакетах, он должен начинаться с названия вашего проекта. Теперь создайте папку entities
, в ней должны находиться описания любых мобов, которых вы создаете или планируете создать.
Учитывая, что мы создаем существо Хранителя (Keeper) в моде My Mod, конструкция будет выглядеть так:
{
"format_version": "1.10.0",
"minecraft:entity": {
"description": {
"identifier": "mymod:keeper"
}
}
}
Создали файл с этим содержимым? Тогда, давайте дополним! Следующим и последним шагом задания основного объекта описания, будет определение возможности размещения существа в мире. За это отвечают несколько параметров, дополните ваш объект description
ими:
{
"format_version": "1.10.0",
"minecraft:entity": {
"description": {
...
// нужно ли добавлять яйцо для призыва существа
"is_spawnable": true,
// может ли существо быть призвано командой или
// любым модом, используя регионы
"is_summonable": true
}
}
}
Не забудьте отделить прошлое свойство запятой, простой пропуск одного из знаков синтаксиса приведет к тому что объект станет нечитаем. Впринципе, существо уже создано, нам остается лишь добавить элементы его описания и добавить визуальную часть для отображения в мире.
Перейдем к созданию свойств существа, без них не обходится ни один моб. Начнем с определения здоровья, размеров и категорий, к которым относится существо:
{
...
"minecraft:entity": {
...
"components": {
// относя существо к категории (семье), впоследствии
// другие мобы смогут определить какое существо находится
// перед ним, используя семейные группы вместо идентификаторов
"minecraft:type_family": {
// собственно сами семьи, по которым определяется
// существо; к примеру, на семью жителей (villager)
// нападают зомби, а свойство моба (mob) необходимо
// применять ко всем существам, кроме, например, стрел
"family": [ "keeper", "villager", "mob" ]
},
// практически любой объект лишь с парой свойств является
// атрибутом существа, которые вы можете изменить напрямую
// из движка, используя Entity.get/setAttribute
"minecraft:health": {
// количество жизней определяется в половинках сердец
"value": 20, // значение с которым моб появится
"max": 20 // максимально возможное соответственно
},
// в границах этой формы появляется физика, хитбокс,
// внутри которого существу можно наносить урон и
// взаимодействовать с ним
"minecraft:collision_box": {
"width": 0.6, // ширина + долгота
"height": 1.9 // высота
},
// существо не исчезнет после выгрузки чанка, обычно
// "непостоянными" существами являются монстры
"minecraft:persistent": {}
}
}
}
Отлично, главные свойства есть, но наше существо остается без физики. Оно просто висит в воздухе, неподвластное физике. Добавим еще несколько свойств, используя этот же объект:
{
...
"minecraft:entity": {
...
"components": {
...
// если у существа нет физики, ускорение неизменно,
// а значит, что оно двигается линейно и равноускоренно;
// рассмотрите как летит огненный шар, у него нет физики
"minecraft:physics": {},
// как и у игрока (который, кстати, тоже является существом),
// у остальных мобов есть собственное количество кислорода
// и возможности остановки его подачи (что начнет уменьшать
// здоровье из-за удушья)
"minecraft:breathable": {
// время в секундах, прежде чем существо начнет задыхаться,
// находясь в жидкостях (под водой или лавой)
"total_supply": 15,
// время в секундах, прежде чем существо начнет задыхаться,
// находясь в блоках (к примеру, если упадет гравий)
"suffocate_time": 0
},
// по умолчанию существо нельзя отталкивать, оно не имеет
// коллизии соприкосновения с объектами
"minecraft:pushable": {
// могут ли другие существа отталкивать моба
"is_pushable": true,
// может ли поршень отталкивать моба
"is_pushable_by_piston": true
}
}
}
}
И немаловажно добавить получение урона; фактически, ни одна жидкость не может наносить урон, пока свойство не будет задано в объекте описания. Это получение урона от нахождения в лаве:
{
...
"minecraft:entity": {
...
"components": {
...
"minecraft:hurt_on_condition": {
"damage_conditions": [
{
"filters": {
// здесь находится несколько простых условий
// как для самого моба, так и его окружения
"test": "in_lava",
// от кого должно быть получено свойство
"subject": "self",
// если условие выполняется, тогда фильтр сработал
"value": true
},
// причина урона, к примеру удушение или падение
"cause": "lava",
// количество урона за тик (1/20 секунды)
"damage_per_tick": 4
}
]
}
}
}
}
Прежде чем добавить поведение, давайте создадим существо и со стороны клиента. Это поможет определиться с тем, что должно делать существо и как игрок может взаимодействовать с ним.
Визуальная составляющая
Серверная часть создается в пакетах поведения, в то время как клиентская (или же визуальная), использует пакеты ресурсов (англ.). Также создайте новый пакет, если это не обходимо, а мы начнем с объекта описания будущего моба. Поместите его в папку entity, используя формат *.entity.json:
{
"format_version": "1.10.0",
"minecraft:client_entity": {
"description": {
// тот же идентификатор, что и в серверном
"identifier": "mymod:keeper",
// материалы определяют правила отображения
// текстур, прозрачность и режимы смешивания
"materials": {
"default": "entity_alphatest"
},
// несколько текстур обычно нужно описывать
// для нескольких вариаций, обойдемся одной
"textures": {
"default": "textures/entity/keeper"
},
// воспользуе мся стандартной моделью поборника,
// она практически идентична с жителем, но еще
// и имеет руки вне скрещенного вида
"geometry": {
"default": "geometry.vindicator.v1.8"
},
// описывать яйцо для призыва необязательно,
// либо можно указать путь до созданной текстуры
"spawn_egg": {
"base_color": "#3a3a3a", // основной цвет
"overlay_color": "#b89535" // цвета пятнышек
}
}
}
}
Но пока большинство данных лишь описывают будущие свойства моба, определимся что нужно для отображения любого существа в игровом мире. Принципы применимы для каждого моба без исключения.
- Текстура или несколько слоев текстур (к примеру для жителей, их профессия определяет какая текстура будет наложена на основной слой).
- Форма (модель, или же геометрия), а также ее контроллеры для создания анимаций, условий отображения частей модели и прочего.
- Объект описания для связки формы, текстур (используя материалы), форм и их контроллеров.
Здесь нет ничего сложного, разберемся с каждым пунктом подробнее.
Текстура и материалы
Большую роль в визуалиации всего существа берет на себя текстура. Для существ они размещаются в папке textures/entities вашего пакета ресурсов, обычно используя идентификатор моба. К примеру, загрузите готовую текстуру Хранителя, разместив ее по пути textures/entities/keeper.png:
В большинстве случаев одной текстуры будет достаточно, ее можно создать используя различные редакторы моделей или самые обычные редакторы изображений (лишь убедитесь, что у вас есть возможность создавать изображения с прозрачными пикселями и сохранять их в формате .png).
Геометрия и контроллеры
Давайте воспользуемся уже созданными моделями, но усложним их логику, добавив дополнительные контроллеры. Контроллеры описывают поведение обычной статичной модели, добавляя условия отрисовки костей, а также связывая ее с материалами и текстурами. Фактически, контроллер связывает переданные в клиентском описании свойства с самим мобом.
Начнем с контроллера модели (рендера), нам нужно скрывать скрещенные руки если у существа появляется цель и показывать пару обычных:
{
"format_version": "1.10.0",
"render_controllers": {
"controller.render.mymod.keeper": {
// здесь используются названия свойств в объекте
// описания клиентской сущности
"geometry": "Geometry.default",
"part_visibility": [
// руки (и предмет в них) должны отображаться
// только если у существа есть цель для атаки
{ "rightArm": "query.has_target" },
{ "leftArm": "query.has_target" },
{ "rightItem": "query.has_target" },
// если же цели нет, можно отобразить обычные
// скрещенные руки как у жителей
{ "arms": "!query.has_target" }
],
"materials": [
{ "*": "Material.default" }
],
"textures": [
"Texture.default"
]
}
}
}
Допустим, что существо способно искать кровать и спать в ночное время суток (опять же, как жители). Для этого удобно воспользоваться состояниями, используя для этого контроллеры анимаций. Они позволяют, следуя текущему состоянию, создать переход между ними, определив возможные анимации и условия следующего перехода.
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.mymod.keeper": {
// состояние контроллера анимации по умолчанию
"initial_state": "default",
"states": {
// стандартное состояние позволяет мобу перемещаться,
// совершать атаки и просто сто ять на месте
"default": {
"animations": [
"keeper",
"keeper_attack",
"keeper_walk"
],
"transitions": [
{ "get_in_bed": "query.is_sleeping" }
]
},
// переход в состояние сна, мобу в этот момент попросту
// не нужны другие анимации здесь
"get_in_bed": {
"animations": [
"get_in_bed"
],
"transitions": [
{ "default": "!query.is_sleeping" }
]
}
}
}
}
}
Клиентское существо
Используя рассмотренные только что контроллеры, свяжем их с мобом, добавив ему анимации и сами контроллеры. Для этого дополним уже созданный ранее объект описания, добавив контроллеры и анимации для них.
{
"format_version": "1.10.0",
"minecraft:client_entity": {
"description": {
...
// любые названия свойств в списке анимаций используются контроллерами
"animations": {
// анимации, используемые новосозданным нами контроллером
"keeper": "animation.vindicator.base",
"keeper_attack": "animation.vindicator.attack",
"keeper_walk": "animation.vindicator.walk",
"get_in_bed": "animation.villager.get_in_bed",
"controller_keeper": "controller.animation.mymod.keeper",
// моб смотрит на цель, когда это необходимо (к примеру, когда
// смотрит на игрока или существо, которое сейчас атакует)
"look_at_target_default": "animation.humanoid.look_at_target.default",
"look_at_target_gliding": "animation.humanoid.look_at_target.gliding",
"look_at_target_swimming": "animation.humanoid.look_at_target.swimming",
"controller_look_at_target": "controller.animation.humanoid.look_at_target",
// житель приподнимает руки, когда вы показываете ему предмет,
// который может быть использован для торговли с ним
"raise_arms": "animation.villager.raise_arms",
"controller_raise_arms": "controller.animation.villager_v2.raise_arms",
// титеву лука необходимо натянуть, а в игре еще и много других
// предметов, которые существо может использовать
"bow_and_arrow": "animation.humanoid.bow_and_arrow",
"controller_bow_and_arrow": "controller.animation.humanoid.bow_and_arrow",
"use_item_progress": "animation.humanoid.use_item_progress",
"controller_use_item_progress": "controller.animation.humanoid.use_item_progress"
},
"scripts": {
// масштаб существа определяется для некоторых из них, это
// стандартное значение для жителей и разбойников
"scale": "0.9375",
// контроллеры анимаций, перечисленные в прошлом свойстве
"animate": [
"controller_keeper",
"controller_look_at_target",
"controller_raise_arms",
"controller_bow_and_arrow",
"controller_use_item_progress"
]
},
// контроллеры рендера для связки моделей и их состояний
"render_controllers": [
"controller.render.mymod.keeper"
],
// прикрепляемые рендеры используются для отображения брони,
// а также создания других рендеров модами
"enable_attachables": true
}
}
}
Поведение
На этом моменте существо уже фактически создано, для нас остается самое интересное — определение его логики, как существо взаимодействует с игроком и миром. Создадим логику, добавив мобу возможность атаковать существ и просто случайно перемещаться по миру.
-
Учитывая, что существо перемещается по земле, нужно определить логику его движения:
entities/keeper.json{
...
"minecraft:entity": {
...
"components": {
...
"minecraft:movement": {
// атрибут скорости передвижения существа
"value": 0.45
},
// существо может перемещаться по земле
"minecraft:movement.basic": {},
// существо может линейно прыгать (равноускоренно)
"minecraft:jump.static": {},
// существо может забираться по лестнице
"minecraft:can_climb": {}
}
}
} -
Далее установим основные определения для поиска пути к цели:
entities/keeper.json{
...
"minecraft:entity": {
...
"components": {
...
// настроим логику для поиска путей, устанавливая
// или получая цель, существо ищет ближайший путь
"minecraft:navigation.walk": {
"avoid_portals": true, // избегать порталы
"avoid_water": true, // избегать воды
"can_open_doors": true, // может открывать двери
"can_pass_doors": true, // может проходить через двери
"can_path_over_water": true // может строить пути над водой
},
// существо может открывать дверь, это свойс тво требуется
// помимо основной логики для поиска путей
"minecraft:annotation.open_door": {},
// предпочтения в блоках будут оценивать различные пути,
// находя наиболее дешевый для существа
"minecraft:preferred_path": {
// блоки, на которые существо может спрыгнуть при поиске
// пути (пути не будут генерироваться с большим порогом)
"max_fall_blocks": 2,
"default_block_cost": 3, // стоимость перемещения по блоку
"jump_cost": 5, // стоимость прыжка на блок
// стоимость перемещения по определенным блокам
"preferred_path_blocks": [
{
"cost": 0,
"blocks": [
"grass_path",
"gravel"
]
},
{
"cost": 1,
"blocks": [
"cobblestone",
"planks",
"wooden_slab"
]
},
{
"cost": 50,
"blocks": [ "bed" ]
}
]
}
}
}
} -
Начнем создавать сам интеллект, прежде всего существо должно плавать и искать убежище (кровать):
entities/keeper.json{
...
"minecraft:entity": {
...
"components": {
...
// позволяет существу плавать на жидкостях, а не тонуть
"minecraft:behavior.float": {
// чем меньше приоритет, тем быстрее выполнится
// тот или иной этап интеллекта
"priority": 0
},
// искать возможное убежище в виде кровати
"minecraft:hide": {},
"minecraft:behavior.hide": {
"priority": 1,
"duration": 30,
"poi_type": "bed",
"speed_multiplier": 0.75
}
},
// существо не должно открывать дверь, когда есть опасность
"minecraft:behavior.restrict_open_door": {
"priority": 4
}
}
} -
Определим цели для атаки и существ, которых нужно избегать:
entities/keeper.json{
...
"minecraft:entity": {
...
"components": {
...
// целью становится существо, ударившее моба
"minecraft:behavior.hurt_by_target": {
"priority": 2
},
// определим существ, которых нужно избегать
"minecraft:behavior.avoid_mob_type": {
"priority": 2,
"entity_types": [
{
// фильтры определяют условия, в результате которых
// цель будет выполнена и событие произойдет
"filters": {
"test": "is_family",
"subject": "other", // найденная цель
"value": "zombie"
}
}
],
// максимальное расстояние для поиска новой цели
"max_dist": 6,
// максимальное расстояние для удаления от существа
"max_flee": 12,
// невидимость не важна для обнаружения цели
"ignore_visibility": true,
// удалять ли прошлую цель, если она уже есть
"remove_target": false
},
// определим цели в приоритете над тем, кто ударил существо
"minecraft:behavior.nearest_prioritized_attackable_target": {
"priority": 3,
// существо должно встретиться с целью взглядом
"must_see": true,
// существо должно суметь построить путь до цели
// (к примеру, этого не произойдет если цель за каньоном)
"must_reach": true,
// время захвата цели, после, она будет сброшена
"persist_time": 8,
// можно выбирать ближайшую цель, если другая цель уже есть
"reselect_targets": true,
"entity_types": [
{
// фильтры включают в себя логическое и, или и не
"filters": {
"all_of": [
{
"test": "is_family",
"subject": "other",
"value": "zombie"
},
{
"test": "is_family",
"subject": "other",
// операторы позволяют сравнивать значения
"operator": "!=",
"value": "zombie_villager"
},
{
"any_of": [
{
"test": "is_missing_health",
"subject": "self", // само существо
"value": true
},
{
"test": "has_target",
"subject": "other",
"value": true
}
]
}
]
},
// максимальное расстояние для поиска цели
"max_dist": 10
}
]
}
}
}
} -
Теперь можно определить и атаку, а также еще несколько целей, регенерацию и направление взгляда на игрока (давайте не будем ограничивать себя парой простых шагов интеллекта):
entities/keeper.json{
...
"minecraft:entity": {
...
"components": {
...
// определения для вооружения и брони
"minecraft:equipment": {
"table": "loot_tables/entities/skeleton_gear.json"
},
// выпускаемые существом снаряды, определение вооружения
// в этом не играет никакой роли (как например, у гаста)
"minecraft:shooter": {
"type": "Arrow",
"def": "minecraft:arrow"
},
// воспользуемся атакой с дистанции (удаленной)
"minecraft:behavior.ranged_attack": {
"priority": 5,
// минимальное расстояние, с которого существо выстрелит
"attack_radius": 2,
// со случайным интервалом от трех до пяти секунд
"attack_interval_min": 3,
"attack_interval_max": 5
},
// добавим регенерацию с помощью зель я
"minecraft:behavior.drink_potion": {
"priority": 6,
"potions": [
{
"id": 21, // обычное зелье регенерации
"chance": 1, // наибольшая вероятность, т.е. всегда
"filters": [
{
// если нет цели и здоровье не полное
"all_of": [
{
"test": "has_target",
"subject": "self",
"operator": "!=",
"value": true
},
{
"test": "is_missing_health",
"subject": "self",
"value": true
}
]
}
]
}
]
},
// определять как цель существо, оттолкнувшее нашего моба
"minecraft:behavior.target_when_pushed": {
"priority": 10,
"percent_chance": 5 // процентный шанс на определение цели
},
// просто посмотреть на игрока, почему бы и нет
"minecraft:behavior.look_at_player": {
"priority": 12,
"probability": 0.01 // вероятность на это за один тик
}
}
}
} -
Добавим еще и случайные перемещения, иначе получается, что существо способно лишь вступать в атаку и смотреть на игрока, не делая ничего кроме этого:
entities/keeper.json{
...
"minecraft:entity": {
...
"components": {
...
// существо может спать на кровати
"minecraft:behavior.sleep": {
"priority": 7,
"cooldown_time": 0,
"timeout_cooldown": 8
},
// сохраним за существом точку призыва
"minecraft:home": {},
// вокруг которой оно будет гулять, не отходя далеко
"minecraft:behavior.move_towards_home_restriction": {
"priority": 13
},
// а также вернем существо домой, если оно ушло
// от него в результате атаки другого моба
"minecraft:behavior.go_home": {
"priority": 14,
"goal_radius": 10,
"interval": 360 // максимальное время в тиках
},
// случайное перемещение в другую точку
"minecraft:behavior.random_stroll": {
"priority": 13,
"speed_multiplier": 0.4, // понизим скорость
"interval": 150,
// и ограничим зону для перемещения
"xz_dist": 5,
"y_dist": 3
}
}
}
}
Торговля
Давайте добавим ключевую особенность нашего Хранителя, определив для него возможность торговли с игроком. Используя пакеты поведения, это можно сделать буквально парой ресурсов. Прежде дополним этапы поведения, а после определим товары для продажи.
{
...
"minecraft:entity": {
...
"components": {
...
// определения для возможности торговли
"minecraft:trade_table": {
// имя в интерфейсе, определите его в переводах
"display_name": "entity.mymod.keeper",
"table": "trading/keeper_trades.json",
"new_screen": true
},
// возможность предлагать товары прямо в руках, для
// этого мы определяли контроллер raise_arms
"minecraft:behavior.trade_interest": {
"priority": 8,
// время между сменой предметов
"carried_item_switch_time": 2,
"cooldown": 2, // перезарядка после убирания
// сколько существо будет предлагать товары
// таким образом (время подряд)
"interest_time": 45,
// время после убирания предмета, которое существо
// продолжит держать в руке
"remove_item_time": 1,
"within_radius": 6 // дальность предложения
},
// поведение торговли с игроком, она куда менее важна
// чем, к примеру, атака, определение цели или сон
"minecraft:behavior.trade_with_player": {
"priority": 9
},
// существо будет смотреть на игрока во время торговли
"minecraft:behavior.look_at_trading_player": {
"priority": 11
}
}
}
}
Теперь определим список товаров, для этого разместим еще один ресурс в пакете поведения.
{
"tiers": [
{
"trades": [
{
// количество сделок перед ее закрытием
"max_uses": 2,
// что нужно для покупки товара
"wants": [
{
"item": "minecraft:experience_bottle",
"quantity": 48 // количество
},
{
"item": "minecraft:netherite_pickaxe"
}
],
// какой товар будет предложен
"gives": [
{
"item": "minecraft:netherite_pickaxe",
// функции определяют дополнительные свойства предмета
"functions": [
{
"function": "enchant_with_levels",
"treasure": false,
// уровни зачарования формируются как на столе
// зачарований (где максимально возможный 30)
"levels": {
"min": 30,
"max": 45
}
}
]
}
]
},
{
"max_uses": 8,
"wants": [
{
"item": "minecraft:emerald",
"quantity": 64
},
{
"item": "minecraft:emerald",
// количество может определяться пределом
"quantity": {
"min": 16,
"max": 64
}
}
],
"gives": [
{
"item": "minecraft:netherite_ingot"
}
]
}
]
},
{
"trades": [
{
"max_uses": 1,
"wants": [
{
"item": "minecraft:netherite_ingot",
"quantity": {
"min": 2,
"max": 3
}
},
{
"item": "minecraft:compass"
}
],
"gives": [
{
// случайный выбор чего-то одного из списка
"choice": [
{
"item": "minecraft:map",
"functions": [
{
// функции не ограничиваются лишь чарами
"function": "exploration_map",
"destination": "mineshaft"
}
]
},
{
"item": "minecraft:map",
"functions": [
{
"function": "exploration_map",
"destination": "pillageroutpost"
}
]
},
{
"item": "minecraft:map",
"functions": [
{
"function": "exploration_map",
"destination": "stronghold"
}
]
}
]
}
]
}
]
},
{
"trades": [
{
"max_uses": 12,
"wants": [
{
"item": "minecraft:netherite_ingot",
"quantity": {
"min": 12,
"max": 16
}
},
{
"item": "minecraft:dragon_breath"
}
],
"gives": [
{
"item": "minecraft:end_portal_frame",
"functions": [
{
// случайно добавим размещенное око, либо его не будет
"function": "random_block_state",
"block_state": "end_portal_eye_bit",
"values": {
"min": 0,
"max": 1
}
}
]
}
]
}
]
}
]
}
Подведем итоги
Поздравляю, Хранитель создан и готов к работе! Нам фактически даже не пришлось задействовать сам движок, существо описано лишь с помощью внутреигрового контента. Обязательно опробуйте возможности пакетов ресурсов и поведения, они еще неоднократно могут помочь вам в будущем. Готовая же структура файлов теперь должна выглядеть вот так, перепроверьте если что-то вдруг отсутствует:
My Mod
├─ behavior_packs
│ └─ mymod
│ ├─ entities
│ │ └─ keeper.json
│ └─ trading
│ └─ keeper_trades.json
└─ resource_packs
└─ mymod
├─ entity
│ └─ keeper.entity.json
├─ textures
│ └─ entities
│ └─ keeper.png
├─ render_controllers
│ └─ keeper.render_controllers.json
└─ animation_controllers
└─ keeper.animation_controllers.json
Но так как эта документация все же базируется на технологиях Inner Core, последующие шаги как для описания существ, так и в целом использования пакетов ресурсов и поведения будут описаны в официальной документации и сводке API (может не открываться без VPN). Майнкрафт не ограничивается лишь нашим движком, здесь еще немало интересных технологий для контента; помните и о том, что помимо мобов у нас есть неограниченные возможности лаунчера.
Они хранятся с помощью именнованных двоичных тегов, мы уже использовали их в прошлой статье. Взаимодействие с ними не отличается от обычных, а NBT хранится таким же образом. Это касается любых существ, в том числе новосозданных, снарядов вроде стрел и прочих, прочих, прочих!