Власні снаряди 26.2
Дізнайтеся, як додати власні снаряди.
Снаряди — це сутності, що можуть бути випущені гравцем або іншою сутністю. У цьому посібнику, ми розглянемо реалізацію простого снаряда, як-от сніжка.
Ми назвемо наш снаряд Hot Tater. Це буде картопля, яка підпалює блок або сутність, в яку влучає.
ПЕРЕДУМОВИ
Для створення снаряда потрібно зареєструвати як предмет, так і сутність, тому ми радимо ознайомитися з посібниками створення вашого першого предмета та сутності.
Створення сутності снаряда
Створімо HotTaterEntity, який розширює ThrowableItemProjectile. Цей клас повинен бути у main початковому наборі.
Клас ThrowableItemProjectile оброблює фізику та форму предмета снаряда.
java
@NullMarked
public class HotTaterEntity extends ThrowableItemProjectile {
public HotTaterEntity(EntityType<? extends ThrowableItemProjectile> type, Level level) {
super(type, level);
}
public HotTaterEntity(Level level, LivingEntity owner, ItemStack itemStack) {
super(ModEntityTypes.HOT_TATER, owner, level, itemStack);
}
public HotTaterEntity(Level level, double x, double y, double z, ItemStack itemStack) {
super(ModEntityTypes.HOT_TATER, x, y, z, level, itemStack);
}
@Override
protected Item getDefaultItem() {
return ModItems.HOT_TATER;
}
@Override
protected void onHitBlock(BlockHitResult hitResult) {
super.onHitBlock(hitResult);
Level level = level();
// Only modify the world on the server.
if (!level.isClientSide()) {
// If the projectile hits a block, place fire on the face it hit.
BlockPos pos = hitResult.getBlockPos().relative(hitResult.getDirection());
if (level.isEmptyBlock(pos)) {
level.setBlockAndUpdate(pos, BaseFireBlock.getState(level, pos));
}
}
}
@Override
protected void onHitEntity(EntityHitResult hitResult) {
super.onHitEntity(hitResult);
// Only modify the world on the server.
if (!level().isClientSide()) {
hitResult.getEntity().igniteForSeconds(5);
}
}
@Override
protected void onHit(HitResult hitResult) {
super.onHit(hitResult);
// Discard the projectile on any hit, or it sinks into the ground forever.
if (!level().isClientSide()) {
this.discard();
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
Тут відбувається чимало всього. Розгляньмо важливий розділ коду.
Конструктори
Ми визначимо 3 конструктори. Вони використовуються реєстрацією сутності, породженням снаряда та його перетворенням.
java
public HotTaterEntity(EntityType<? extends ThrowableItemProjectile> type, Level level) {
super(type, level);
}
public HotTaterEntity(Level level, LivingEntity owner, ItemStack itemStack) {
super(ModEntityTypes.HOT_TATER, owner, level, itemStack);
}
public HotTaterEntity(Level level, double x, double y, double z, ItemStack itemStack) {
super(ModEntityTypes.HOT_TATER, x, y, z, level, itemStack);
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
Перевизначення getDefaultItem()
Визначає форму предмета цього снаряда.
java
@Override
protected Item getDefaultItem() {
return ModItems.HOT_TATER;
}1
2
3
4
5
2
3
4
5
ВАЖЛИВО
Ваше IDE може повідомляти, що не може розпізнати цей предмет: ми створимо його згодом, у розділі реєстрації.
Перевизначення onHitBlock()
Визначає поведінку при влучанні снаряда в блок. Ми перевіряємо де снаряд улучив та встановлюємо сторону влучання блока, в який улучили. Ця логіка обробляється на сервері.
java
@Override
protected void onHitBlock(BlockHitResult hitResult) {
super.onHitBlock(hitResult);
Level level = level();
// Only modify the world on the server.
if (!level.isClientSide()) {
// If the projectile hits a block, place fire on the face it hit.
BlockPos pos = hitResult.getBlockPos().relative(hitResult.getDirection());
if (level.isEmptyBlock(pos)) {
level.setBlockAndUpdate(pos, BaseFireBlock.getState(level, pos));
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Перевизначення onHitEntity()
Визначає поведінку при влучанні снаряда в сутність. Ми встановимо, щоб сутність, в яку влучили, горіла протягом 5 секунд.
java
@Override
protected void onHitEntity(EntityHitResult hitResult) {
super.onHitEntity(hitResult);
// Only modify the world on the server.
if (!level().isClientSide()) {
hitResult.getEntity().igniteForSeconds(5);
}
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Перевизначення onHit()
Визначає поведінку, коли снаряд улучає будь-куди, чи то блоки, чи то сутність. Ми використаємо це для зникання снаряда; без цього, снаряд просто буде існувати.
java
@Override
protected void onHit(HitResult hitResult) {
super.onHit(hitResult);
// Discard the projectile on any hit, or it sinks into the ground forever.
if (!level().isClientSide()) {
this.discard();
}
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Створення предмета
Ми зареєструємо простий предмет. Далі на потрібно реалізувати логіку кидання, наш клас HotTaterItem розширюватиме Item та реалізуватиме ProjectileItem. Цей клас повинен бути у main початковому наборі.
java
@NullMarked
public class HotTaterItem extends Item implements ProjectileItem {
public HotTaterItem(Properties properties) {
super(properties);
}
@Override
public Projectile asProjectile(Level level, Position position, ItemStack itemStack, Direction direction) {
return new HotTaterEntity(level, position.x(), position.y(), position.z(), itemStack);
}
@Override
public InteractionResult use(Level level, Player player, InteractionHand hand) {
ItemStack itemStack = player.getItemInHand(hand);
level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.SNOWBALL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F));
// Spawn the projectile on the server only.
if (level instanceof ServerLevel serverLevel) {
Projectile.spawnProjectileFromRotation(HotTaterEntity::new,
serverLevel,
itemStack,
player,
/* yOffset: */ 0.0F,
/* pow: */ 1.5F,
/* uncertainty: */ 1.0F);
}
player.awardStat(Stats.ITEM_USED.get(this));
itemStack.consume(1, player);
return InteractionResult.SUCCESS;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Це стандартна реалізація предмета, з деякими особливими методами для ProjectileItem. Це виглядатиме якось так:
Перевизначення asProjectile()
Цей метод перетворює предмет у форму сутності.
java
@Override
public Projectile asProjectile(Level level, Position position, ItemStack itemStack, Direction direction) {
return new HotTaterEntity(level, position.x(), position.y(), position.z(), itemStack);
}1
2
3
4
2
3
4
Перевизначення use()
Визначає дію, яка стається при використанні предмета. У нашому випадку ми викликаємо Projectile.spawnProjectileFromRotation(), метод для породження снаряда.
Окрім стандартних параметрів (рівень, стіс предмета і гравець), цей допоміжний метод приймає три додаткові плавучі значення:
yOffset: зміщення для нахилу (обертання навколо осі X, вгору або вниз), у градусах. Від’ємні значення спрямовують кут початкової швидкості вгору.pow: множник швидкості руху снаряда.uncertainty: неточність снаряда. 0 значить нульовий розкид снаряда. Зі збільшенням цього значення снаряди розкидатимуться сильніше, навіть якщо їх запускати з однаковими початковими положенням і орієнтацією.
Подробиці можна знайти в статті на Вікі Minecraft про снаряди.
Чому цей параметр називається yOffset?
Ми теж не знаємо, шановний читачу. Попри назву, зміщення застосовується до кута нахилу — тобто до повороту швидкості навколо осі X:
java
float yd = -Mth.sin((xRot + yOffset) * (float) (Math.PI / 180.0));1
Наприклад, коли стандартна гра використовує значення yOffset з -20.0F, для вибухових зіллів, це змінює кут нахилу початкової швидкості з source.getXRot() на source.getXRot() - 20.0F (на 20 градусів угору, у бік неба).
Можливо, влучнішою назвою була б xRotOffset.
Нарешті, ми нараховуємо показник ITEM_USED, витрачаємо один предмет зі стосу та позначаємо взаємодію як успішну.
java
@Override
public InteractionResult use(Level level, Player player, InteractionHand hand) {
ItemStack itemStack = player.getItemInHand(hand);
level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.SNOWBALL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F));
// Spawn the projectile on the server only.
if (level instanceof ServerLevel serverLevel) {
Projectile.spawnProjectileFromRotation(HotTaterEntity::new,
serverLevel,
itemStack,
player,
/* yOffset: */ 0.0F,
/* pow: */ 1.5F,
/* uncertainty: */ 1.0F);
}
player.awardStat(Stats.ITEM_USED.get(this));
itemStack.consume(1, player);
return InteractionResult.SUCCESS;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Реєстрація
Зареєструйте предмет, як ми це зробили в посібнику створення вашого першого предмета. Для початку, визначимо ключ предмета у ModItemIds:
java
public static final ResourceKey<Item> HOT_TATER = create("hot_tater");1
Потім зареєструймо предмет у ModItems разом з іншими:
java
public static final Item HOT_TATER = register(
ModItemIds.HOT_TATER,
HotTaterItem::new,
new Item.Properties().stacksTo(16)
);1
2
3
4
5
2
3
4
5
Не забудьте додати модель, текстуру, клієнтський предмет та назву, використовуючи ідентифікатор hot_tater. Вам також слід додати предмет до вкладки режиму творчости. Ось приклад текстури:
Також зареєструйте сутність — як ми це робили в посібнику створення вашої першої сутності — додавши її як статичне поле в ModEntityTypes. Оскільки сутності та предмети зберігаються в окремих реєстрах, тип сутності просто використовує той самий шлях, що й предмет — як-от стандартна snowball:
java
public static final EntityType<HotTaterEntity> HOT_TATER = register(
ModEntityTypeIds.HOT_TATER,
EntityType.Builder.<HotTaterEntity>of(HotTaterEntity::new, MobCategory.MISC)
.sized(0.25f, 0.25f) // Hitbox width and height.
.clientTrackingRange(4) // How far (in chunks) clients see the entity.
.updateInterval(10) // Ticks between position updates sent to clients.
);1
2
3
4
5
6
7
2
3
4
5
6
7
Нарешті, використаймо стандартний ThrownItemRenderer в ініціалізаторі клієнта:
java
EntityRenderers.register(ModEntityTypes.HOT_TATER, ThrownItemRenderer::new);1
І готово!













