自定义弹射物 26.2
学习如何添加自定义弹射物。
弹射物是可由玩家或其他实体投掷或发射的实体。在本指南中,我们将了解如何实现像雪球一样的简单弹射物。
我们将我们的弹射物称为 Hot Tater。它将是一个能够点燃所击中的方块或实体的马铃薯。
创建弹射物实体
让我们通过继承 ThrowableItemProjectile 来创建 HotTaterEntity。此类应位于你的 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() 工具方法来生成弹射物。
除了标准参数(level、物品堆叠和玩家)外,该工具方法还接受三个额外的浮点数:
yOffset:俯仰角(绕 X 轴旋转,向上或向下)的偏移量,单位为度。负值会将初始速度朝上方倾斜。pow:弹射物运动速度的乘数。uncertainty:弹射物的不精准度。 0 表示没有随机散射。随着该值的增大,即使以相同的初始位置和旋转角度投掷,弹射物的散射范围也会更大。
有关更多信息,请参阅 Minecraft Wiki 上关于弹射物的文章。
为什么这个参数被称为 yOffset?
亲爱的读者,我们其实也不知道。尽管名称如此,该偏移量实际上应用于俯仰角,即 速度 绕 X 轴的旋转:
java
float yd = -Mth.sin((xRot + yOffset) * (float) (Math.PI / 180.0));1
例如,当原版为滞留/喷溅药水使用 -20.0F 的 yOffset 时,它会将初始速度俯仰角从 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 添加模型、纹理、客户端物品和名称。你还应该将物品添加到创造模式标签页。这是一个纹理示例:
请确保将新实体的 ID 添加到 ModEntityTypeIds 中:
java
public static ResourceKey<EntityType<?>> HOT_TATER = create("hot_tater");1
现在注册实体,就像我们在创建你的第一个实体指南中所做的那样,通过在 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
然后就大功告成了!














