Создание игровых аддонов и текстур — minecraft

Привет, Хабр! В этой статье я хочу затронуть тему аддонов. Многим чего-то не хватало в игре, и они скачивали аддоны с интернета. Когда вы понимаете, что скачанный аддон настолько безполезный, удаляете его. Я, допустим, хочу сделать аддон в minecraft bedrock. Если я сделаю достойный аддон, он может попасть на рынок. Заманчиво? Давайте попробуем сделать что-нибудь своё. Пригодится базовое знание json и в некоторых случаях javascript.

Первым делом, создадим папку myaddon по пути android/data/com.mojang.../files//games/com.mojang/behaivor_packs если у вас другое сохранение файлов, используйте games/com.mojang.../... Название папки, которую вы создали, никак не влияет на название аддона. За это отвечает manifest.json, который находится в нашей папке. Отредакиируем его

{
    "format_version": 1,
    "header": {
        "description": "Описание вашего аддона"
        "name": "Название вашего аддона",
        "uuid": "вставьте сюда уникальный id",
        "version": [ 1, 0, 5 ],
        "min_engine_version": [ 1, 17, 10 ]
    },
    "modules": [
        {
            "description": "",
            "type": "data",
            "uuid": "вставьте сюда второй уникальный id",
            "version": [1, 0, 0]
        }
    ]
}

Если что, uuid это уникальный id, который используется для сохранения аддона. Уникальный айди можно сгенерировать, например, на сайте uuidgener*tor.net, я не хочу оставлять точную ссылку поэтому вместо «а» поставил «*». Чтобы создать иконку нашему аддону, создайте в папке аддона файл pack_icon.jpg или pack_icon.png. Теперь сохраняем наши файлы, заходим в майнкрафт и проверям — теперь во вкладке создание мира в «наборы параметров» есть пустой аддон, который ещё бесполезный.

Function — так называется функция аддона, которая может выполнять несколько команд одновременно. Например, если вы в чат введёте команду /function myfunction, вам дадут и яблоко, и дерево. Так можно создавать на function даже нормальные античиты, но для этого требуется знание команд minecraft bedrock. Чтобы сделать функции в аддона, создайте в папке аддона папку functions. В ней создадите myfunction.mcfunction, ваша функция будет называться myfunction. Отредактируйте код.

#выдать всём яблоко
give @a apple

#выдать всем дерево
give @a log

#создать скорборд
scoreboard objectives add my score dummy

Функция может не отображаться при вводе /function ****. Теперь, если вы введёте /function myfunction, всем игрокам в мире дадут яблоко и дерево, а ещё создадится новый скорборд. Чтобы функция работала каждый тик непрерывно, не вводя её в чат или командный блок, создадим в папке function файл tick.json. Он будет отвечать за автоматическое выполнение функций каждый тик. Введём небольшой json код

{
  "values": [
    "myfunction"
  ]
}

теперь функция myfunction будет выполняться каждый тик. Если что, одна секунда это 20 тиков.

Идём всё дальше — чат. Вы, наверное, давно хотели оптимизировать чат, сделать ранги, антиспам. Тут всё в одном аддона — антиспам, ранги в чате и тд. Для начала нам придётся изменить manifest.json, чтобы указать в нём содержание javascript.

{

  "format_version": 2,

  "header": {

    "name": "Название аддона",

    "description": "Описание аддона",

    "uuid": "Уникальный id",

    "version": [ 1, 0, 3 ],

    "min_engine_version": [ 1, 14, 0 ]

  },

  "modules": [

    {

      "description": "made by habr @DinoZavr2",

      "type": "data",

      "uuid": "Второй уникальный id",

      "version": [ 1, 0, 0 ]

    },

    {

      "description": "",

      "language": "javascript",

      "type": "script",

      "uuid": "Третий уникальный id",

      "version": [0, 0, 1],

      "entry": "scripts/main/index.js"

    }

  ],

  "dependencies": [

    {

      "uuid": "Четвёртый уникальный id",

      "version": [ 0, 1, 0 ]

    },

    {

      "uuid": "Пятый уникальный id",
      
      "version": [ 0, 1, 0 ]

    }

  ]

}

Если хотите, пробелы можете убрать. Вы наверное заметили, что здесь используется 5 уникальных айди. Это может быть для кого-то странно. Также мы указали путь к индексу. Вы уже наверное понимаете структуру.

В папке аддона создадим папку scripts, в ней хранятся скрипты (тоже очевидно). Создадим в папке scripts папку main. В ней файл index.js, языка javascript. Это наш индекс. Введите туда этот код

import { chatrank } from './misc/chat.js'
import { world } from 'mojang-minecraft'
import { timer } from './misc/second.js'
let tick = 0, worldLoaded = false, loadTime = 0;

world.events.beforeChat.subscribe((data) => {
    chatrank(data)
})
world.events.tick.subscribe((ticks) => {
    tick++
    if (!world.getDimension("overworld").runCommand('testfor @a').error && !worldLoaded) {
        loadTime = tick
        worldLoaded = true;
        world.getDimension("overworld").runCommand(`execute @r ~~~ say §l§aМир был загружен в ${ticks} тиках. Добро пожаловать! `)
        world.getDimension("overworld").runCommand(`scoreboard objectives add chatsSent dummy`)
    }
    if(tick >= 20){
        tick = 0
        timer()
    }
})

Думаю, вы поняли, что это отображение того, сколько тиках загружался мир. Ну, базовых знаний js достаточно, чтобы тут не объяснять. Далее в папке main создадим папку misc, а в ней файл chat.js, это будет отвечать за спам и ранги.

import { world } from "mojang-minecraft"

let messages = new Map()

function chatrank(data){
    const tags = data.sender.getTags()
    data.sender.runCommand(`scoreboard players add @s chatsSent 0`)
    let score = parseInt(data.sender.runCommand(`scoreboard players test @s chatsSent *`).statusMessage.match(/-?\d+/)[0])
    let ranks = [];
    for(const tag of tags){
        if(tag.startsWith('rank:')){
            ranks.push(tag.replace('rank:', ''))
        }
    }
    if(ranks.length == 0)ranks = ["§l§aPlayer"]
    
    if(data.message.startsWith("!*")){
        data.cancel = true
        return
    }
    if(score >= 3){
        data.cancel = true
        return world.getDimension("overworld").runCommand(`ability "${data.sender.nameTag}" mute true`)
    }
    if(!messages.get(data.sender.name)){
        messages.set(data.sender.name, data.message)
    }else {
        const oldMsg = messages.get(data.sender.name)
        if(oldMsg == data.message){
            data.cancel = true
            return world.getDimension("overworld").runCommand(`tellraw "${data.sender.nameTag}" {"rawtext":[{"text":"§l§cНе пишите похожие сообщения"}]}`)
        }
    }
    let text = `§f[${ranks}§r§f] §7${data.sender.nameTag}: §f${data.message}`
    world.getDimension('overworld').runCommand(`tellraw @a {"rawtext":[{"translate":"§l§eM§r - ${JSON.stringify(text)}}]}`)
    messages.set(data.sender.name, data.message)
    data.sender.runCommand(`scoreboard players add @s chatsSent 1`)
    data.cancel = true
}
export { chatrank }

Чтобы создать себе ранг, нужно вписать tag @s add rank:ВАШ_РАНГ. Например, rank:§l§cADMIN, это будет красным ADMIN, а по умолчанию ранг зелёным Player. Но пока что всё равно ранги и анти-спам не будут работать, нужно в misc создать ещё один файл second.js, по названию всё понятно.

import { world } from 'mojang-minecraft'
let seconds = 0

export function timer(){
    seconds++
    if(seconds >= 4){
        world.getDimension("overworld").runCommand(`scoreboard players reset * chatsSent`)
        world.getDimension("overworld").runCommand(`scoreboard players set "dummy" chatsSent 1`)
        seconds = 0
        return seconds
    }
}

Это тоже небольшой js код, который импортирует секунды.Теперь заходим в майнкрафт, создаём мир с этим аддоном, в настройках мира включаем gametest и education edition. Внимание: поддерживаемая версия рангов и антиспама — 1.19.11 и выше. На других, более старых версиях, есть небольшой баг с командой tellraw.

Создание игровых аддонов и текстур — minecraft

Всего лишь одна строка, импортирующая скрипт. Давайте отредактируем команды в прошлом коде и в майнкрафте введём в чат .mycommand, выполнятся введённые команды.

Как поставить авторские права на аддон? Легко. Если аддон не был скачан, вы можете создать в нём файл LICENSE.md или LICENSE.txt, в них всё указать. Вот пример

this addon by @DinoZavr2
AntiSpam and chat rangs

©2022

Лицензия может быть очень длинная, всё на ваш вкус.

Текстуры

Мы только что говорили о аддонах. Теперь поговорим о текстурах. Создадим папку mytextures по пути android/data/com.mojang…/files/games/com.mojang/resource_packs, опять же название папки не влияет на название ресурспака. В папке ресурспака создадим наш manifest.json. Вот собственно его код

{
  "format_version": 1,
  "header": {
    "description": "описание вашего текстурпака",
    "name": "название вашего текстурпака",
    "uuid": "уникальный айди текстурпака",
    "version": [
      0,
      0,
      1
    ],
    "min_engine_version": [
      1,
      8,
      0
    ]
  },
  "modules": [
    {
      "description": "",
      "type": "resources",
      "uuid": "второй уникальный айди текстурпака",
      "version": [
        0,
        0,
        1
      ]
    }
  ]
}

Опять же, как и в аддоне, уникальный айди можно сгенерировать на любом сайте, например, uuidgener*tor.net. Вместо «а» я также поставил «*», чтобы ничего не нарушать. Для создания иконки создайте файл pack_icon.jpg или pack_icon.png.

Чтобы создать текстуры блоков, нужно в папке текстурпака создать папку textures. В ней папку blocks. В blocks запихиваем файл diamond_block.jpg (или .png). Теперь в игре алмазный блок будет выглядеть так, как в diamond_block.jpg.

Чтобы создать текстуры предметов, которые у вас в руках, необходимо в textures создать папку items. В неё запихивайте item.jpg, тоесть у меня например stick.jpg, в игре палка будет выглядеть по другому, тоесть так, как выглядит изображение stick.png.

Субпаки. Subpacks — это паки, использующие json код, который что-то определяет. Например, так можно сделать отображение хитбоксов игрока. Вот манифест для субпаков

{
   "format_version":2,
   "header":{
      "description":"описание текстурпака",
      "name":"название текстурпака", 
      "uuid":"уникальный id",
      "version":[1,0,0],
      "min_engine_version":[1,14,0]
   },
   "modules":[
      {
         "description":"",
         "type":"resources",
         "uuid":"второй уникальный id",
         "version":[1,0,0]
      }
   ],
   "subpacks":[
      {
         "folder_name":"no_ray",
         "name":"",
         "memory_tier":1
      },
      {
         "folder_name":"opaque_model_collision",
         "name":"",
         "memory_tier":2
      },
      {
         "folder_name":"default",
         "name":"",
         "memory_tier":3
      }
   ]
}

По манифесту видно, что будет очень сложно. Но всё таки придётся сделать отображение хитбоксов.

Создайте в папке текстурпака папка subpacks. В ней ещё три папки — default, no_ray, opaque_model_collision. Сразу говорю, если вы не знакомы с субпаками и json, ознакомьтесь с ними, потому что сдесь вам без базовых знаний о субпаках вам не будет понятно. Дальше в папке default создаём папку animations, в которой содержатся все анимации. В ней hitboxrot.json. В ней отредактируем код анимации

{
	"format_version" : "1.8.0",
	"animations" : {
		"animation.player.hitboxrot" : {
			"loop" : true,
			"bones" : {
				"hitbox" : {
					"rotation" : [ 0.0, "-query.body_y_rotation", 0.0 ]
				}
			}
		},
		"animation.player.rayrot" : {
			"loop" : true,
			"bones" : {
				"ray" : {
					"rotation" : [ "query.is_sneaking ? query.target_x_rotation - 1.5 : query.target_x_rotation", "query.target_y_rotation", 0.0 ],
					"position" : [ 0.0, "query.is_sneaking ? -4.25 : 0.0", 0.0 ]
				}
			}
		},
		"animation.player.axis" : {
			"loop" : true,
			"bones" : {
				"axis" : {
					"rotation" : [ 0.0, "-query.body_y_rotation", 0.0 ]
				}
			}
		}
	}
}

Небольшой код в 30 строк для анимаций. Теперь если игрок подвижится, текстуры будут с ним поворачиваться. Далее в subpack/default создаём папку entity, естественно, там сущности, на которых действуют текстуры. В entity необходимо создать файл player.entity.json, это сущность игрока. Вот код

{
  "format_version": "1.10.0",
  "minecraft:client_entity": {
    "description": {
      "identifier": "minecraft:player",
      "materials": {
        "default": "entity_alphatest",
        "cape": "entity_alphatest",
        "animated": "player_animated",
        "emissive": "entity_emissive_alpha_hitbox"
      },
      "textures": {
        "default": "textures/entity/steve",
        "cape": "textures/entity/cape_invisible",
        "hitbox": "textures/models/hitboxoverlay",
        "ray": "textures/models/ray"
      },
      "geometry": {
        "default": "geometry.humanoid.custom",
        "cape": "geometry.cape",
        "hitbox": "geometry.hitbox",
        "hitbox.swimming": "geometry.hitbox.swimming",
        "ray": "geometry.ray"
      },
      "scripts": {
        "scale": "0.9375",
        "initialize": [
          "variable.is_holding_right = 0.0;",
          "variable.is_blinking = 0.0;",
          "variable.last_blink_time = 0.0;",
          "variable.hand_bob = 0.0;"
        ],
        "pre_animation": [
          "variable.helmet_layer_visible = 1.0;",
          "variable.leg_layer_visible = 1.0;",
          "variable.boot_layer_visible = 1.0;",
          "variable.chest_layer_visible = 1.0;",
          "variable.attack_body_rot_y = Math.sin(360*Math.sqrt(variable.attack_time)) * 5.0;",
          "variable.tcos0 = (math.cos(query.modified_distance_moved * 38.17) * query.modified_move_speed / variable.gliding_speed_value) * 57.3;",
          "variable.first_person_rotation_factor = math.sin((1 - variable.attack_time) * 180.0);",
          "variable.hand_bob = query.life_time < 0.01 ? 0.0 : variable.hand_bob + ((query.is_on_ground && query.is_alive ? math.clamp(math.sqrt(math.pow(query.position_delta(0), 2.0) + math.pow(query.position_delta(2), 2.0)), 0.0, 0.1) : 0.0) - variable.hand_bob) * 0.02;",

          "variable.map_angle = math.clamp(1 - variable.player_x_rotation / 45.1, 0.0, 1.0);",
          "variable.item_use_normalized = query.main_hand_item_use_duration / query.main_hand_item_max_duration;"
        ],
        "animate": [
          "root",
          "hitbox_rot",
          "ray_rot",
          "axis"
        ]
      },
      "animations": {
      	"hitbox_rot": "animation.player.hitboxrot",
      	"ray_rot": "animation.player.rayrot",
      	"axis": "animation.player.axis",
        "root": "controller.animation.player.root",
        "base_controller": "controller.animation.player.base",
        "hudplayer":  "controller.animation.player.hudplayer",
        "humanoid_base_pose": "animation.humanoid.base_pose",
        "look_at_target": "controller.animation.humanoid.look_at_target",
        "look_at_target_ui": "animation.player.look_at_target.ui",
        "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",
        "look_at_target_inverted": "animation.player.look_at_target.inverted",
        "cape": "animation.player.cape",
        "move.arms": "animation.player.move.arms",
        "move.legs": "animation.player.move.legs",
        "swimming": "animation.player.swim",
        "swimming.legs": "animation.player.swim.legs",
        "riding.arms": "animation.player.riding.arms",
        "riding.legs": "animation.player.riding.legs",
        "holding": "animation.player.holding",
        "brandish_spear": "animation.humanoid.brandish_spear",
        "charging": "animation.humanoid.charging",
        "attack.positions": "animation.player.attack.positions",
        "attack.rotations": "animation.player.attack.rotations",
        "sneaking": "animation.player.sneaking",
        "bob": "animation.player.bob",
        "damage_nearby_mobs": "animation.humanoid.damage_nearby_mobs",
        "bow_and_arrow": "animation.humanoid.bow_and_arrow",
        "fishing_rod": "animation.humanoid.fishing_rod",
        "use_item_progress": "animation.humanoid.use_item_progress",
        "skeleton_attack": "animation.skeleton.attack",
        "sleeping": "animation.player.sleeping",
        "first_person_base_pose": "animation.player.first_person.base_pose",
        "first_person_empty_hand": "animation.player.first_person.empty_hand",
        "first_person_swap_item": "animation.player.first_person.swap_item",
        "first_person_attack_controller": "controller.animation.player.first_person_attack",
        "first_person_attack_rotation": "animation.player.first_person.attack_rotation",
        "first_person_vr_attack_rotation": "animation.player.first_person.vr_attack_rotation",
        "first_person_walk": "animation.player.first_person.walk",
        "first_person_map_controller": "controller.animation.player.first_person_map",
        "first_person_map_hold": "animation.player.first_person.map_hold",
        "first_person_map_hold_attack": "animation.player.first_person.map_hold_attack",
        "first_person_map_hold_off_hand": "animation.player.first_person.map_hold_off_hand",
        "first_person_map_hold_main_hand": "animation.player.first_person.map_hold_main_hand",
        "first_person_crossbow_equipped": "animation.player.first_person.crossbow_equipped",
        "third_person_crossbow_equipped": "animation.player.crossbow_equipped",
        "third_person_bow_equipped": "animation.player.bow_equipped",
        "crossbow_hold": "animation.player.crossbow_hold",
        "crossbow_controller": "controller.animation.player.crossbow",
        "shield_block_main_hand": "animation.player.shield_block_main_hand",
        "shield_block_off_hand": "animation.player.shield_block_off_hand",
        "blink": "controller.animation.persona.blink"
      },
      "render_controllers": [
        { "controller.render.player.first_person": "variable.is_first_person" },
        { "controller.render.player.third_person": "!variable.is_first_person && !variable.map_face_icon" },
        { "controller.render.player.map": "variable.map_face_icon" },
        { "controller.render.player.hitbox": "!variable.is_first_person && !query.is_in_ui" },
        { "controller.render.player.ray": "!variable.is_first_person && !query.is_in_ui" }
      ],
      "enable_attachables": true
    }
  }
}

Это очень сложно понять без хотя бы базовых знаний json. Тут применяются математические примеры, рендер контроллеры, целых 118 строк. rotation'ы и всё подобное. Можете создать много сущностей для отображения хитбоксов, например, для иссушителя или волка. Но лучше не надо, а то если на многих сущностях будут действовать текстуры, будет лагать. Далее нам нужны материалы - создаём в default папку materials. В ней entity.material (без json), расширение material.

{
  "materials": {
    "version": "1.0.0",
	"entity_emissive_alpha_hitbox:entity_nocull": {
      "+defines": [
        "ALPHA_TEST",
        "USE_EMISSIVE"
      ],
      "depthFunc": "Always"
    }
  }
}

Думаю, здесь объяснять ничего не надо. Функция будет работать всегда. Дальше ещё сложнее - в default создайте папку models, в ней entity. А в entity 3 файла - первый hitbox.json

{
	"format_version": "1.10.0",
	"geometry.hitbox": {
		"texturewidth": 64,
		"textureheight": 64,
		"visible_bounds_width": 3,
		"visible_bounds_height": 3,
		"visible_bounds_offset": [0, 1.5, 0],
		"bones": [
			{
				"name": "hitbox",
				"pivot": [0, 0, 0],
				"cubes": [
					{ "origin": [-5, 0.2, -5], "size": [10, 30, 10], "uv": [0, 0], "inflate": 0.3 },
					{ "origin": [-4.5, 0.5, -4.5], "size": [9, 9, 9], "uv": [0, 42] }
				]
			}
		]
	}
}

В этом файле мы указали размер хитбокса и сами хитбоксы. Второй файл - ray.json

{
	"format_version": "1.12.0",
	"minecraft:geometry": [
		{
			"description": {
				"identifier": "geometry.ray",
				"texture_width": 16,
				"texture_height": 16,
				"visible_bounds_width": 2,
				"visible_bounds_height": 2,
				"visible_bounds_offset": [0, 2, 0]
			},
			"bones": [
				{
					"name": "ray",
					"pivot": [0, 27, 0],
					"cubes": [
						{"origin": [-0.5, 26.5, -0.5], "size": [1, 1, -16], "inflate": -0.41, "uv": [0, 0]}
					]
				}
			]
		}
	]
}

Здесь тоже, думаю, объяснять ничего не надо - мы создали рэй. И третий файл - hitboxhitbox_swimming.json

{
	"format_version": "1.10.0",
	"geometry.hitbox.swimming": {
		"texturewidth": 64,
		"textureheight": 64,
		"visible_bounds_width": 3,
		"visible_bounds_height": 3,
		"visible_bounds_offset": [0, 1.5, 0],
		"bones": [
			{
				"name": "hitbox",
				"pivot": [0, 0, 0],
				"cubes": [
					{ "origin": [-5, 0.2, -5], "size": [10, 9, 10], "uv": [0, 0], "inflate": 0.3 },
					{ "origin": [-4.5, 0.5, -4.5], "size": [9, 9, 9], "uv": [0, 42] }
				]
			}
		]
	}
}

Здесь мы указали отображение хитбоксов в режиме плавания. Это не будет выглядеть странно, как без этого файла. Если этого файла не будет, при плавании у игрока будет хитбокс, как будто он стоя ходит.

Теперь возвращаемся в папку default. В ней нужно создать папку render_controllers - в ней тоже hitbox.json и ray.json. Вот hitbox.json

{
  "format_version": "1.8.0",
  "render_controllers": {
    "controller.render.player.hitbox": {
      "geometry": "Array.geo[query.is_swimming]",
      "materials": [ { "*": "Material.emissive" } ],
      "textures": [ "Texture.hitbox" ],
      "arrays": {
        "geometries": { "Array.geo": [ "geometry.hitbox", "geometry.hitbox.swimming"] }
      },
      "is_hurt_color":{},
      "on_fire_color":{}
    }
  }
}

Тут мы также не забыли указать про плавание. Теперь ray.json

{
  "format_version": "1.8.0",
  "render_controllers": {
    "controller.render.player.ray": {
      "geometry": "Geometry.ray",
      "materials": [ { "*": "Material.emissive" } ],
      "textures": [ "Texture.ray" ],
      "is_hurt_color":{},
      "on_fire_color":{},
      "part_visibility": [
        { "*": "!query.is_swimming" }
      ]
    }
  }
}

Да да, опять указали swimming. Это рендер контроллеры. И создаём последнюю папку в папке default - textures. Как же без текстур хитбокса? Закидываем туда models/hitboxoverlay.png,ray.png

Вот вам hitboxoverlay

hitboxoverlay.png
hitboxoverlay.png

И вот ray.png

ray.png
ray.png

Да, они находятся в models. Вы, наверное, не видите тут модели, но они есть. На этом с папкой default мы закончили

Ну что, с папкой default закончили. В ней мы указали модели, материалы, плаванье и отображение хитбоксов, также модели хитбоксов. Теперь переходим в папку subpacks, в которой и содержался default. В subpacks создаём папку no_ray. Вы наверное уже представляете, насколько это будет долго. В no_ray создадим несколько папок - animations, entity, materials, models, render_controllers, textures.

В папке animations создайте файл hitboxrot.json

{
	"format_version" : "1.8.0",
	"animations" : {
		"animation.player.hitboxrot" : {
			"loop" : true,
			"bones" : {
				"hitbox" : {
					"rotation" : [ 0.0, "-query.body_y_rotation", 0.0 ]
				}
			}
		},
		"animation.player.axis" : {
			"loop" : true,
			"bones" : {
				"axis" : {
					"rotation" : [ 0.0, "-query.body_y_rotation", 0.0 ]
				}
			}
		}
	}
}

hitboxrot - hitboxrotation. Это небольшое сокращение, но файл называется именно hitboxrot.json. Тут есть body_rotation и всё в этом роде, тоесть анимации без ray, это подтверждает папка no_ray. Далее в no_ray в materials создайте папку entity.material, тоесть с расширением material.

{
  "materials": {
    "version": "1.0.0",
	"entity_emissive_alpha_hitbox:entity_nocull": {
      "+defines": [
        "ALPHA_TEST",
        "USE_EMISSIVE"
      ],
      "depthFunc": "Always"
    }
  }
}

Опять анимации в материалах. Ладно, дальше в models создаём папку entity, в которой hitbox_swimming.json и hitbox.json. Вот hitbox.json

{
	"format_version": "1.10.0",
	"geometry.hitbox": {
		"texturewidth": 64,
		"textureheight": 64,
		"visible_bounds_width": 3,
		"visible_bounds_height": 3,
		"visible_bounds_offset": [0, 1.5, 0],
		"bones": [
			{
				"name": "hitbox",
				"pivot": [0, 0, 0],
				"cubes": [
					{ "origin": [-5, 0.2, -5], "size": [10, 30, 10], "uv": [0, 0], "inflate": 0.3 },
					{ "origin": [-4.5, 0.5, -4.5], "size": [9, 9, 9], "uv": [0, 42] }
				]
			}
		]
	}
}

Здесь размер хитбокса в json. Дальше hitbox_swimming.json - плавающий хитбокс

{
	"format_version": "1.10.0",
	"geometry.hitbox.swimming": {
		"texturewidth": 64,
		"textureheight": 64,
		"visible_bounds_width": 3,
		"visible_bounds_height": 3,
		"visible_bounds_offset": [0, 1.5, 0],
		"bones": [
			{
				"name": "hitbox",
				"pivot": [0, 0, 0],
				"cubes": [
					{ "origin": [-5, 0.2, -5], "size": [10, 9, 10], "uv": [0, 0], "inflate": 0.3 },
					{ "origin": [-4.5, 0.5, -4.5], "size": [9, 9, 9], "uv": [0, 42] }
				]
			}
		]
	}
}

Здесь хитбокс в режиме плавания. Кого не смущает стоячий хитбокс плавающего игрока? Ладно, вот hitbox.json в render_controllers

{
  "format_version": "1.8.0",
  "render_controllers": {
    "controller.render.player.hitbox": {
      "geometry": "Array.geo[query.is_swimming]",
      "materials": [ { "*": "Material.emissive" } ],
      "textures": [ "Texture.hitbox" ],
      "arrays": {
        "geometries": { "Array.geo": [ "geometry.hitbox", "geometry.hitbox.swimming"] }
      },
      "is_hurt_color":{},
      "on_fire_color":{}
    }
  }
}

Дальше в папке no_ray/textures/models вставляем hitboxoverlay.png, как же без него

hitboxoverlay.png
hitboxoverlay.png

На этом с no_ray мы закончили. Но это ещё не всё - в субпаках есть папка opaque_model_collision. Напоминаю, мы делаем текстурпак с хитбоксами только для ознакомления с субпаками и json. Теперь, в opaque_model_collision создаём папки animations, entity, models, render_controllers, textures. Давайте немного от редактируем -сначалa папка animations. В ней hitboxrot.json. Вот hitboxrot.json

{
	"format_version" : "1.8.0",
	"animations" : {
		"animation.player.hitboxrot" : {
			"loop" : true,
			"bones" : {
				"hitbox" : {
					"rotation" : [ 0.0, "-query.body_y_rotation", 0.0 ]
				}
			}
		},
		"animation.player.rayrot" : {
			"loop" : true,
			"bones" : {
				"ray" : {
					"rotation" : [ "query.is_sneaking ? query.target_x_rotation - 1.5 : query.target_x_rotation", "query.target_y_rotation", 0.0 ],
					"position" : [ 0.0, "query.is_sneaking ? -4.25 : 0.0", 0.0 ]
				}
			}
		},
		"animation.player.axis" : {
			"loop" : true,
			"bones" : {
				"axis" : {
					"rotation" : [ 0.0, "-query.body_y_rotation", 0.0 ]
				}
			}
		}
	}
}

Мы указали кружение объекта, луп и хитбоксы. Далее указываем сущности - entity/player.entity.json

{
  "format_version": "1.10.0",
  "minecraft:client_entity": {
    "description": {
      "identifier": "minecraft:player",
      "materials": {
        "default": "entity_alphatest",
        "cape": "entity_alphatest",
        "animated": "player_animated",
        "emissive": "entity_emissive_alpha"
      },
      "textures": {
        "default": "textures/entity/steve",
        "cape": "textures/entity/cape_invisible",
        "hitbox": "textures/models/hitboxoverlay",
        "ray": "textures/models/ray"
      },
      "geometry": {
        "default": "geometry.humanoid.custom",
        "cape": "geometry.cape",
        "hitbox": "geometry.hitbox",
        "hitbox.swimming": "geometry.hitbox.swimming",
        "ray": "geometry.ray"
      },
      "scripts": {
        "scale": "0.9375",
        "initialize": [
          "variable.is_holding_right = 0.0;",
          "variable.is_blinking = 0.0;",
          "variable.last_blink_time = 0.0;",
          "variable.hand_bob = 0.0;"
        ],
        "pre_animation": [
          "variable.helmet_layer_visible = 1.0;",
          "variable.leg_layer_visible = 1.0;",
          "variable.boot_layer_visible = 1.0;",
          "variable.chest_layer_visible = 1.0;",
          "variable.attack_body_rot_y = Math.sin(360*Math.sqrt(variable.attack_time)) * 5.0;",
          "variable.tcos0 = (math.cos(query.modified_distance_moved * 38.17) * query.modified_move_speed / variable.gliding_speed_value) * 57.3;",
          "variable.first_person_rotation_factor = math.sin((1 - variable.attack_time) * 180.0);",
          "variable.hand_bob = query.life_time < 0.01 ? 0.0 : variable.hand_bob + ((query.is_on_ground && query.is_alive ? math.clamp(math.sqrt(math.pow(query.position_delta(0), 2.0) + math.pow(query.position_delta(2), 2.0)), 0.0, 0.1) : 0.0) - variable.hand_bob) * 0.02;",

          "variable.map_angle = math.clamp(1 - variable.player_x_rotation / 45.1, 0.0, 1.0);",
          "variable.item_use_normalized = query.main_hand_item_use_duration / query.main_hand_item_max_duration;"
        ],
        "animate": [
          "root",
          "hitbox_rot",
          "ray_rot",
          "axis"
        ]
      },
      "animations": {
      	"hitbox_rot": "animation.player.hitboxrot",
      	"ray_rot": "animation.player.rayrot",
      	"axis": "animation.player.axis",
        "root": "controller.animation.player.root",
        "base_controller": "controller.animation.player.base",
        "hudplayer":  "controller.animation.player.hudplayer",
        "humanoid_base_pose": "animation.humanoid.base_pose",
        "look_at_target": "controller.animation.humanoid.look_at_target",
        "look_at_target_ui": "animation.player.look_at_target.ui",
        "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",
        "look_at_target_inverted": "animation.player.look_at_target.inverted",
        "cape": "animation.player.cape",
        "move.arms": "animation.player.move.arms",
        "move.legs": "animation.player.move.legs",
        "swimming": "animation.player.swim",
        "swimming.legs": "animation.player.swim.legs",
        "riding.arms": "animation.player.riding.arms",
        "riding.legs": "animation.player.riding.legs",
        "holding": "animation.player.holding",
        "brandish_spear": "animation.humanoid.brandish_spear",
        "charging": "animation.humanoid.charging",
        "attack.positions": "animation.player.attack.positions",
        "attack.rotations": "animation.player.attack.rotations",
        "sneaking": "animation.player.sneaking",
        "bob": "animation.player.bob",
        "damage_nearby_mobs": "animation.humanoid.damage_nearby_mobs",
        "bow_and_arrow": "animation.humanoid.bow_and_arrow",
        "fishing_rod": "animation.humanoid.fishing_rod",
        "use_item_progress": "animation.humanoid.use_item_progress",
        "skeleton_attack": "animation.skeleton.attack",
        "sleeping": "animation.player.sleeping",
        "first_person_base_pose": "animation.player.first_person.base_pose",
        "first_person_empty_hand": "animation.player.first_person.empty_hand",
        "first_person_swap_item": "animation.player.first_person.swap_item",
        "first_person_attack_controller": "controller.animation.player.first_person_attack",
        "first_person_attack_rotation": "animation.player.first_person.attack_rotation",
        "first_person_vr_attack_rotation": "animation.player.first_person.vr_attack_rotation",
        "first_person_walk": "animation.player.first_person.walk",
        "first_person_map_controller": "controller.animation.player.first_person_map",
        "first_person_map_hold": "animation.player.first_person.map_hold",
        "first_person_map_hold_attack": "animation.player.first_person.map_hold_attack",
        "first_person_map_hold_off_hand": "animation.player.first_person.map_hold_off_hand",
        "first_person_map_hold_main_hand": "animation.player.first_person.map_hold_main_hand",
        "first_person_crossbow_equipped": "animation.player.first_person.crossbow_equipped",
        "third_person_crossbow_equipped": "animation.player.crossbow_equipped",
        "third_person_bow_equipped": "animation.player.bow_equipped",
        "crossbow_hold": "animation.player.crossbow_hold",
        "crossbow_controller": "controller.animation.player.crossbow",
        "shield_block_main_hand": "animation.player.shield_block_main_hand",
        "shield_block_off_hand": "animation.player.shield_block_off_hand",
        "blink": "controller.animation.persona.blink"
      },
      "render_controllers": [
        { "controller.render.player.first_person": "variable.is_first_person" },
        { "controller.render.player.third_person": "!variable.is_first_person && !variable.map_face_icon" },
        { "controller.render.player.map": "variable.map_face_icon" },
        { "controller.render.player.hitbox": "!variable.is_first_person && !query.is_in_ui" },
        { "controller.render.player.ray": "!variable.is_first_person && !query.is_in_ui" }
      ],
      "enable_attachables": true
    }
  }
}

Ууу, тоже 118 строк. Одни вариаблы и контроллеры. Ну ладно, дальше в папке models создайте папку entity. В ней hitbox.json, hitbox_swimming.json, ray.json, вот hitbox

{
	"format_version": "1.10.0",
	"geometry.hitbox": {
		"texturewidth": 64,
		"textureheight": 64,
		"visible_bounds_width": 3,
		"visible_bounds_height": 3,
		"visible_bounds_offset": [0, 1.5, 0],
		"bones": [
			{
				"name": "hitbox",
				"pivot": [0, 0, 0],
				"cubes": [
					{ "origin": [-5, 0.2, -5], "size": [10, 30, 10], "uv": [0, 0], "inflate": 0.3 },
					{ "origin": [-4.5, 0.5, -4.5], "size": [9, 9, 9], "uv": [0, 42] }
				]
			}
		]
	}
}

Указали размер. Теперь в плаванью хитбокса - hitbox_swimming.json

{
	"format_version": "1.10.0",
	"geometry.hitbox.swimming": {
		"texturewidth": 64,
		"textureheight": 64,
		"visible_bounds_width": 3,
		"visible_bounds_height": 3,
		"visible_bounds_offset": [0, 1.5, 0],
		"bones": [
			{
				"name": "hitbox",
				"pivot": [0, 0, 0],
				"cubes": [
					{ "origin": [-5, 0.2, -5], "size": [10, 9, 10], "uv": [0, 0], "inflate": 0.3 },
					{ "origin": [-4.5, 0.5, -4.5], "size": [9, 9, 9], "uv": [0, 42] }
				]
			}
		]
	}
}

ЗдесьЗдесь используется функция geometry.hitbox.swimming, остался ray.json

{
	"format_version": "1.12.0",
	"minecraft:geometry": [
		{
			"description": {
				"identifier": "geometry.ray",
				"texture_width": 16,
				"texture_height": 16,
				"visible_bounds_width": 2,
				"visible_bounds_height": 2,
				"visible_bounds_offset": [0, 2, 0]
			},
			"bones": [
				{
					"name": "ray",
					"pivot": [0, 27, 0],
					"cubes": [
						{"origin": [-0.5, 26.5, -0.5], "size": [1, 1, -16], "inflate": -0.41, "uv": [0, 0]}
					]
				}
			]
		}
	]
}

А здесь функция minecraft:geometry. Странно, но всё же так. Далее переходим в render_controllers, там 2 файла - hitbox.json и ray.json. Отредактируем hitbox.json

{
  "format_version": "1.8.0",
  "render_controllers": {
    "controller.render.player.hitbox": {
      "geometry": "Array.geo[query.is_swimming]",
      "materials": [ { "*": "Material.emissive" } ],
      "textures": [ "Texture.hitbox" ],
      "arrays": {
        "geometries": { "Array.geo": [ "geometry.hitbox", "geometry.hitbox.swimming"] }
      },
      "is_hurt_color":{},
      "on_fire_color":{}
    }
  }
}

Здесь используется функция render_controllers. Дальше ray.json

{
  "format_version": "1.8.0",
  "render_controllers": {
    "controller.render.player.ray": {
      "geometry": "Geometry.ray",
      "materials": [ { "*": "Material.emissive" } ],
      "textures": [ "Texture.ray" ],
      "is_hurt_color":{},
      "on_fire_color":{},
      "part_visibility": [
        { "*": "!query.is_swimming" }
      ]
    }
  }
}

И опять функция render_controllers. Ладно, что нам осталось? Правильно, textures, в котором models. В models опять 2 файла - hitboxoverlay.png, ray.png. Вот hitboxoverlay.png

�PNG

   
IHDR         \r�f  
�zTXtRaw profile type exif  x��Z[r�:��*f	$@�r�����A�v�ĉ�s�߉+�MI|t7�e������/&N��TRr�%�����W��wa�_���~WO׏�RP�q@�yUE}�]p�÷�z���gC���(�z���� Q�G�gCeR��q��l��'�!�Mr��
J#�#a����������n�`
��9�#,�l��M��G���>��|�g�|�2]XK���S�\���uD|��_A^k��1�M���:�����e_��R�G|��*xeW]GW�u���x��|��W���e�CF$�Ĥ����5���9�U#7��(I�� +��h��P�C�1E��b�5I
)��4Y��*4jRլEk�r�)kι�Z�r`,�hɥ�Z�*:�h������MZh���-��j�|z豧�=����!ib��#�2��4�)f�q��3�2�֖���JKW^e�+k'�_^/��O�x3e��5Ԓ�	o�$g`���j@�l���C`c�8s��1�h����(�s\��ݍ��x����c����9u_y���a>�7cG�N}8>s%��L�ek}��yH������6��Ec��������&�	��v�?,��}b�C���g� =6W�P�:��̚'���J���S";ur�V�0WO�����ZY|i�H�6o44��MQJgSM�{��zh��� H�3�sƴB[��{�H�%x?K��=�٣���S[�3h�n ����S9{��z
�@����jF�h� 	׾]���z�	wV����}]��'$�>p�p�F���5d����
���!�;@��rV�cp���-^�h�u F�<ה�]�:�K�:�GRV��V\�gU��{�##p����W[d�����B�`y"V†�1�B��i�aڭԱ.���kPo��
c�%��y�l��*�Y�ˑE���EyZ k�TK�ő`H;��0s�-����C
�+��c�CGz�EW� 3 ��M1I���*�S1um[0*w���i�
�_m��B��f��y�7u4���v�cy��c�'ޱ�&x])��{lk��pA�B��1�uv��L�_ƛYM�pֹ8l�X���I�`�n5��5���|25h ���z;o`���
���G?Ȅ��ɏ2�w�ٴ�s1O�E�l���Q&H�]����j�mR��.=i�s�O��u�)>����� �=X��Rb0~���g�=��'���a�H��kҟ=?ZcXߑ��[c������m��W��
[v��]m�օ���9�Y�'c��Xj.�vZ����/6I�On��?���(�HOX�c�?�~�=��Q���G�z�{P�}T�둎�O苞��w$�9(G����w$�!�/��G�z�{P�}T�A�G{U_�g�zC��,To�?�[K�r�>����=(�>*�����܃B�&���� ��_��k�
�A��9����-~N�x�|n������M�r��m�%�{|)��Ǯ�h��������G.8��?o����������vG�{����׷7�]�V�=��mʏ!�M�S�~ܻ�#����Ql[H�co7�4�f�)�.����J8�J��fG�νR�ĽW*��+���^��c��k�{�:zMv�U�Zi�UG�����5�=V�&�Ǫ��d�Xu��즻��J��E*N��z���c�%��˂<%ѧ��&�Z�brõ��mW�e�(c�0^m�WBŷF�>B[�Ǥ���`��
k�fY�Ft��n�tŶ:�o�[�yl}�`Jk��=�1U��>���Ț���56���	�˜���;T0rK��z)C�%B�c�^EdK
� !@��Yi�
��������P({z]{(�����w�����|b\%�8B���!�=d�)��%}��e�4�6��5d���4+�85�K�4۵��p���'H��C�)~�`��Ӽ��~�0rG�0�E����˷Xn̄� i���S���l�p#�`�HD�r)HJ+�9Fn�l��PC��#���x���O}[����\, -(�>eב�>|������q���ѯ���(DrI8�O5�=�s�ƪ��ŗvV�X��jM|�
�y���yԞ��xLț�_�dYQ�Q���eP���~É����R>��3�'t���`O�H7��HD��epnсș;!�"�Op934�0�P"�Ý�ҥ�зǩ	s,��/{���R�!S��N%$�C�f��i\:CS�&~��/�.n	��- ?����Z���%�!���Jt��4�>5���K�eq��.�-�f�y��!<��6�
s��p�I����/ �e��(�k:�'��Ê����3��Ҳ��v3NK^s��]\
�G��gd�$L���+C��2)�"�,GWҡ�?�?P=|Q	�9��m�1
�r6�BO��F  �iCCPICC profile  x�}�=H�@�_S�R*�P�!Cu� *�(U,���Vh����/hҐ��8
�?�.κ:�
�������������"ƃ�~����{�S;	@�,#�L��ªxEˆ"*1SOes�_����.γ���9��� �H<�t�"� �ٴt���V��s�q�.H��u��7�e��1r�y��X�a��Y�P���c��Q��wY��Y�5X��������:�$���!��*j��U#�D���aǟ&�L�*9P�
�����n��Ԥ�J �/��1
v�vӶ��m�}���+�믷��Oқ]-v�����&��;@�I�ɑ�4�R	x?�o* C�@p������ G]-� ��X���=�=��ۿg:�� E�r��A   bKGD � � �����   	pHYs     ��   tIME�
()�R�  �IDATx���;�0EQb��ڼ�G���$�!��Ў��B�FsI��mU�l���9 If��� ^9Y   �      �      �      �      �      �      �      �      �   ����d:�
\�l�����@���H2�w��<�~   @     @     @     @      @     @     @     @     @     @     @     @     @    �
@     @     @     @     @     @     @     @     @      @     @     @     @     @     @     `w�dZ_��U�        ��ve�Qcu�'����8,�7%9,�    @     @     @     @     @     @     @     @   ˘����   �      �      �      �      �      �      �      �      ������k�Z��^�huN�H                vw��B?R	��    IEND�B`�

Либо

hitboxoverlay.png
hitboxoverlay.png

А вот и ray.png

�PNG

   
IHDR           szz�  �zTXtRaw profile type exif  x��Q��&E�YE��$��r0�sfY~�rWu�$3�|�L�`�%�]����F���BR�\r�8RI�+���+Ŵ���=��{�'&A/׭ս�®\}y��c���Ք�h��Ħ�,< ���2��T�m�����=�x�5*�PJԩҠs��RL|��gn,��b\��
6)�A���D&�;Zqˊ����2����w����mJD�o��ϺF�ܼb����t	�h|��Y�)���5��C飶dq�S��'D��v �[�	�L��)�AG���Y @�ܑ$'����y��3Fk-+g�f�M �����H���c�QCUE��f5��Ek����-�M��X2�lfnŪ�'W�n�^�.�=PK.V��R+��@�*�WX>�H���ÏrԆ�i�i�͚��j�.�D�ݺ���I��Nq�S�|��g9�@�
i��Æ�2�MmS��~�mj�H�uvS�5�=\��Nt21N�6	��y2�N)�$7����(���N6��$��$�A7�r?�-��7�'ra��/�����;���=���+��F��7�U�P�F�Z�~�o�!�KoGoGoGoGoGoGoG�G�? ��a�9�����m  �iCCPICC profile  x�}�=H�@�_S�R*�P�!Cu� *�(U,���Vh����/hҐ��8
�?�.κ:�
�������������"ƃ�~����{�S;	@�,#�L��ªxEˆ"*1SOes�_����.γ���9��� �H<�t�"� �ٴt���V��s�q�.H��u��7�e��1r�y��X�a��Y�P���c��Q��wY��Y�5X��������:�$���!��*j��U#�D���aǟ&�L�*9P�
�����n��Ԥ�J �/��1
v�vӶ��m�}���+�믷��Oқ]-v�����&��;@�I�ɑ�4�R	x?�o* C�@p������ G]-� ��X���=�=��ۿg:�� E�r��A   bKGD � � �����   	pHYs     ��   tIME�
*5���    IEND�B`�

Либо прошлые картинки ray.png.

На этом всё. Импортируем аддон и заходим в майнкрафт. Запускаем и проверяем - у всех игроков есть хитбоксы.

На этом всё. Теперь перевод. Чтобы в ресурспаке сделать название предметов американцу stick, а русскому палка-копалка, создайте в папке ресурспака папку texts, в ней ru_RU.lang. Далее строка

item.предмет.name=§l§aПалка копалка

На этом всё. Я рассказал про наборы параметров, ресурспаки и субпаки в текстурпаках, отображение хитбокса, перевод и анти-спам с рангами чата. Надеюсь, это статья была полезной.

 

Источник

Читайте также