Benutzerdefinierte Projektile 26.2
Lerne, wie man benutzerdefinierte Projektile hinzufügt.
Projektile sind Entitäten, die von Spielern oder anderen Entitäten geworfen oder abgefeuert werden können. In diesem Leitfaden werden wir uns die Implementierung eines einfachen Projektils wie beispielsweise eines Schneeballs anschauen.
Wir werden unser Projektil Hot Tater (Heiße Kartoffel) nennen. Es wird sich um eine Kartoffel handeln, die den Block oder die Entität, auf die sie trifft, in Brand setzt.
VORAUSSETZUNGEN
Um ein Projektil zu erstellen, musst du sowohl ein Item als auch eine Entität registrieren. Wir empfehlen dir dafür, den Leitfaden Erstellen deines ersten Items und Erstellen deiner ersten Entität durchzugehen.
Die Entität für das Projektil erstellen
Lasst uns eine HotTaterEntity, indem wir von ThrowableItemProjectile erben. Diese Klasse sollte sich in deinem main Quellensatz befinden.
Die Klasse ThrowableItemProjectile ist für die Physik und die Itemform des Projektils zuständig.
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
Hier ist ganz schön was los. Lasst uns die wichtigen Codeabschnitte einmal anschauen.
Konstruktor
Wir definieren 3 Konstruktoren. Sie werden jeweils für die Registrierung von Entitäten, die Erzeugung von Projektilen und die Umwandlung von Projektilen verwendet.
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
Überschreiben von getDefaultItem()
Definiert die Itemform dieses Projektil.
java
@Override
protected Item getDefaultItem() {
return ModItems.HOT_TATER;
}1
2
3
4
5
2
3
4
5
WICHTIG
Möglicherweise meldet deine IDE, dass sie das Item nicht auflösen kann: Wir werden es in Kürze im Abschnitt Registrierung erstellen.
Überschreiben von onHitBlock()
Definiert das Verhalten, wenn das Projektil einen Block trifft. Wir prüfen, wo das Projektil getroffen hat, und setzen dann die Trefferfläche dieses Blocks in Brand. Diese Logik findet serverseitig statt.
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
Überschreiben von onHitEntity()
Definiert das Verhalten, wenn das Projektil eine Entität trifft. Wir setzen die Entität, die getroffen wurde, für 5 Sekunden in Brand.
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
Überschreiben von onHit()
Definiert, wie sich das Projektil verhält, wenn es auf etwas trifft, sei es ein Block oder eine Entität. Wir werden dies verwenden, um das Projektil zu verwerfen, sodass es bei einem Treffer entfernt wird; ohne dies würde das Projektil einfach weiterfliegen.
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
Das Item erstellen
Wir registrieren ein einfaches Item. Da wir die Logik zum Werfen implementieren müssen, wird unsere Klasse HotTaterItem von der Klasse Item erben und ProjectileItem implementieren. Diese Klasse sollte sich in deinem main Quellensatz befinden.
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
Es ist eine Standard-Item-Implementierung mit einigen speziellen Methoden aus ProjectileItem. Lasst uns diese anschauen:
Überschreiben von asProjectile()
Diese Methode wandelt das Item in seine Entitätsform um.
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
Überschreiben von use()
Definiert die Aktion, die ausgeführt wird, wenn das Item verwendet wird. In unserem Fall rufen wir die Hilfsmethode Projectile.spawnProjectileFromRotation() auf, um das Projektil zu erzeugen.
Zusätzlich zu den Standardparametern (Level, Item Stack und Spieler) nimmt diese Hilfsmethode drei weitere Float-Werte entgegen:
yOffset: Versatz für die Neigung (Drehung um die X-Achse, nach oben oder unten), in Grad. Negative Werte richtigen die Anfangsgeschwindigkeit nach oben.pow: Multiplikator für die Geschwindigkeit der Projektilbewegung.uncertainty: Ungenauigkeit des Projektils. 0 bedeutet keine zufällige Streuung. Je höher dieser Wert ist, desto stärker streuen die Projektile, selbst wenn sie mit derselben Anfangsposition und Rotation geworfen werden.
Für weitere Informationen, siehe den Minecraft Wiki Artikel zu Projektilen.
Warum wird dieser Parameter yOffset genannt?
Das wissen wir auch nicht, lieber Leser. Trotz des Namens wird der Versatz auf die Neigung angewendet, welche die Rotation der Geschwindigkeit um die X-Achse ist:
java
float yd = -Mth.sin((xRot + yOffset) * (float) (Math.PI / 180.0));1
Zum Beispiel, wenn Vanilla einen yOffset von -20.0F für Wurftränke verwendet, verändert dies die Neigung der initialen Geschwindigkeit von source.getXRot() - 20.0F (20 Grad nach oben, in Richtung Himmel).
Vielleicht wäre xRotOffset ein passenderer Name gewesen.
Schließlich setzen wir den Status ITEM_USED, entnehmen ein Item vom Stack und markieren die Interaktion als erfolgreich.
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
Registrierung
Registriere das Item, wie wir es in dem Leitfaden Erstellen deines ersten Items gemacht haben. Definierte zuerst den Schlüssel des Items in ModItemIds:
java
public static final ResourceKey<Item> HOT_TATER = create("hot_tater");1
Registriere dann das Item in ModItems, zusammen mit den anderen:
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
Vergiss nicht, ein Modell, eine Textur und ein Client-Item und einen Namen hinzuzufügen, unter Verwendung des Bezeichner hot_tater. Du solltest das Item zu einem Kreativtab hinzufügen. Hier ist eine Beispiel Textur:
Registriere auch die Entität, wie wir es in dem Leitfaden Erstellen deiner ersten Entität getan haben, indem du als statisches Feld in ModEntityTypes hinzufügst. Da Entitäten und Items in getrennten Registrierungen gespeichert sind, verwendet der Entitätstyp einfach denselben Pfad wie das Item, wie beispielsweise bei snowball in Vanilla:
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
Lasst uns schließlich den Vanilla ThrownItemRenderer im Client-Initialisierer verwenden:
java
EntityRenderers.register(ModEntityTypes.HOT_TATER, ThrownItemRenderer::new);1
Und du bist fertig!













