User Tools

Site Tools


tutorial:projectiles

This is an old revision of the document!


Creating a Custom Projectile

It is important to read through and understand this tutorial as well as this tutorial, as this will help you understand most of the elements included in this tutorial.

This tutorial will teach you how to create your own custom projectile, including the ProjectileEntity, as well as the projectile item itself. This guide will go over how to define the projectile, register the projectile, rendering the projectile, as well as creating the projectile item itself.

ProjectileEntities are used to, well, create and operate projectiles. Some basic projectiles include:

  • Snowballs
  • Ender Pearls

We will be creating a custom snowball-like projectile that applies some very nasty effects to the entity that has been hit.

If you would like to look over the source code yourself, all of the following code was done here. Before the tutorial begins, I would like to let you know that I would be using PascalCase to name the methods. Feel free to change the naming scheme to however you like, but I personally swear by PascalCase. Note that this may be outdated

Creating & Registering a Projectile Entity

To start, we will need to create a new class for the ProjectileEntity, extending ThrownItemEntity.

  1. /*
  2. We will be creating a custom snowball-like projectile that deals some nasty debuffs.
  3. Since this is a thrown projectile, we will extending ThrownItemEntity.
  4. Some ThrownItemEntities include:
  5. - Snowballs
  6. - Ender Pearls
  7.  */
  8. public class PackedSnowballEntity extends ThrownItemEntity {
  9. [. . .]
  10. }

Your IDE should complain about unimplemented methods, so implement that.

  1. public class PackedSnowballEntity extends ThrownItemEntity {
  2. @Override
  3. protected Item getDefaultItem() {
  4. return null; // We will configure this later, once we have created the ProjectileItem.
  5. }
  6. }

Your IDE should complain about not having the required constructors, but I would not recommend using the default constructors, but instead using the following constructors shown in the code, as they were heavily modified from the default constructors and should work fine, if not better.

  1. public class PackedSnowballEntity extends ThrownItemEntity {
  2. public PackedSnowballEntity(EntityType<? extends ThrownItemEntity> entityType, World world) {
  3. super(entityType, world);
  4. }
  5.  
  6. public PackedSnowballEntity(World world, LivingEntity owner) {
  7. super(null, owner, world); // null will be changed later
  8. }
  9.  
  10. public PackedSnowballEntity(World world, double x, double y, double z) {
  11. super(null, x, y, z, world); // null will be changed later
  12. }
  13.  
  14. @Override
  15. protected Item getDefaultItem() {
  16. return null; // We will configure this later, once we have created the ProjectileItem.
  17. }
  18. }

Your IDE should not complain about any more major issues if you followed those instructions correctly.
We will continue adding features related to our projectile. Keep in mind that the following code is entirely customizable, and I am encouraging those who follow this tutorial to be creative here.

  1. public class PackedSnowballEntity extends ThrownItemEntity {
  2. public PackedSnowballEntity(EntityType<? extends ThrownItemEntity> entityType, World world) {
  3. super(entityType, world);
  4. }
  5.  
  6. public PackedSnowballEntity(World world, LivingEntity owner) {
  7. super(null, owner, world); // null will be changed later
  8. }
  9.  
  10. public PackedSnowballEntity(World world, double x, double y, double z) {
  11. super(null, x, y, z, world); // null will be changed later
  12. }
  13.  
  14. @Override
  15. protected Item getDefaultItem() {
  16. return null; // We will configure this later, once we have created the ProjectileItem.
  17. }
  18. @Override
  19.  
  20. @Environment(EnvType.CLIENT)
  21. private ParticleEffect getParticleParameters() { // Not entirely sure, but probably has do to with the snowball's particles. (OPTIONAL)
  22. ItemStack itemStack = this.getItem();
  23. return (ParticleEffect)(itemStack.isEmpty() ? ParticleTypes.ITEM_SNOWBALL : new ItemStackParticleEffect(ParticleTypes.ITEM, itemStack));
  24. }
  25.  
  26. @Environment(EnvType.CLIENT)
  27. public void handleStatus(byte status) { // Also not entirely sure, but probably also has to do with the particles. This method (as well as the previous one) are optional, so if you don't understand, don't include this one.
  28. if (status == 3) {
  29. ParticleEffect particleEffect = this.getParticleParameters();
  30.  
  31. for(int i = 0; i < 8; ++i) {
  32. this.world.addParticle(particleEffect, this.getX(), this.getY(), this.getZ(), 0.0D, 0.0D, 0.0D);
  33. }
  34. }
  35.  
  36. }
  37.  
  38. protected void onEntityHit(EntityHitResult entityHitResult) { // called on entity hit.
  39. super.onEntityHit(entityHitResult);
  40. Entity entity = entityHitResult.getEntity(); // sets a new Entity instance as the EntityHitResult (victim)
  41. int i = entity instanceof BlazeEntity ? 3 : 0; // sets i to 3 if the Entity instance is an instance of BlazeEntity
  42. entity.damage(DamageSource.thrownProjectile(this, this.getOwner()), (float)i); // deals damage
  43.  
  44. if (entity instanceof LivingEntity livingEntity) { // checks if entity is an instance of LivingEntity (meaning it is not a boat or minecart)
  45. livingEntity.addStatusEffect((new StatusEffectInstance(StatusEffects.BLINDNESS, 20 * 3, 0))); // applies a status effect
  46. livingEntity.addStatusEffect((new StatusEffectInstance(StatusEffects.SLOWNESS, 20 * 3, 2))); // applies a status effect
  47. livingEntity.addStatusEffect((new StatusEffectInstance(StatusEffects.POISON, 20 * 3, 1))); // applies a status effect
  48. livingEntity.playSound(SoundEvents.AMBIENT_CAVE, 2F, 1F); // plays a sound for the entity hit only
  49. }
  50. }
  51.  
  52. protected void onCollision(HitResult hitResult) { // called on collision with a block
  53. super.onCollision(hitResult);
  54. if (!this.world.isClient) { // checks if the world is client
  55. this.world.sendEntityStatus(this, (byte)3); // particle?
  56. this.kill(); // kills the projectile
  57. }
  58.  
  59. }
  60. }

We are now finished with the core code of the projectile. We will be adding on to the projectile class, however, once we have defined and registered other items.
We have created the projectile class, but we haven't defined and registered it yet. To register a projectile, you can follow this tutorial or you can follow the code below.

  1. public class ProjectileTutorialMod implements ModInitializer {
  2. public static final String ModID = "projectiletutorial"; // This is just so we can refer to our ModID easier.
  3.  
  4. public static final EntityType<PackedSnowballEntity> PackedSnowballEntityType = Registry.register(
  5. Registry.ENTITY_TYPE,
  6. new Identifier(ModID, "packed_snowball"),
  7. FabricEntityTypeBuilder.<PackedSnowballEntity>create(SpawnGroup.MISC, PackedSnowballEntity::new)
  8. .dimensions(EntityDimensions.fixed(0.25F, 0.25F)) // dimensions in Minecraft units of the projectile
  9. .trackRangeBlocks(4).trackedUpdateRate(10) // necessary for all thrown projectiles (as it prevents it from breaking, lol)
  10. .build() // VERY IMPORTANT DONT DELETE FOR THE LOVE OF GOD PSLSSSSSS
  11. );
  12.  
  13. @Override
  14. public void onInitialize() {
  15.  
  16. }
  17. }

Finally, add the EntityType to our class' constructors.

  1. public PackedSnowballEntity(EntityType<? extends ThrownItemEntity> entityType, World world) {
  2. super(entityType, world);
  3. }
  4.  
  5. public PackedSnowballEntity(World world, LivingEntity owner) {
  6. super(ProjectileTutorialMod.PackedSnowballEntityType, owner, world);
  7. }
  8.  
  9. public PackedSnowballEntity(World world, double x, double y, double z) {
  10. super(ProjectileTutorialMod.PackedSnowballEntityType, x, y, z, world);
  11. }

Creating a Projectile Item

While we're at it, we should quickly create a projectile item. Most of the things that are required to create an item are repeated from this tutorial, so if you're unfamiliar with creating an item, refer to that tutorial.
First, it is necessary to create a new class for the item that extends Item.

  1. public class PackedSnowballItem extends Item {
  2. }

Create the constructor, as shown.

  1. public class PackedSnowballItem extends Item {
  2. public PackedSnowballItem(Settings settings) {
  3. super(settings);
  4. }
  5. }

Now, we will create a new TypedActionResult method. Follow along:

  1. public class PackedSnowballItem extends Item {
  2. public PackedSnowballItem(Settings settings) {
  3. super(settings);
  4. }
  5.  
  6. public TypedActionResult<ItemStack> use(World world, PlayerEntity user, Hand hand) {
  7. ItemStack itemStack = user.getStackInHand(hand); // creates a new ItemStack instance of the user's itemStack in-hand
  8. world.playSound(null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENTITY_SNOWBALL_THROW, SoundCategory.NEUTRAL, 0.5F, 1F); // plays a globalSoundEvent
  9. /*
  10. user.getItemCooldownManager().set(this, 5);
  11. Optionally, you can add a cooldown to your item's right-click use, similar to Ender Pearls.
  12. */
  13. if (!world.isClient) {
  14. PackedSnowballEntity snowballEntity = new PackedSnowballEntity(world, user);
  15. snowballEntity.setItem(itemStack);
  16. snowballEntity.setVelocity(user, user.pitch, user.yaw, 0.0F, 1.5F, 0F);
  17. /*
  18.   snowballEntity.setProperties(user, user.getPitch(), user.getYaw(), 0.0F, 1.5F, 1.0F);
  19.   In 1.17,we will use setProperties instead of setVelocity.
  20.   */
  21. world.spawnEntity(snowballEntity); // spawns entity
  22. }
  23.  
  24. user.incrementStat(Stats.USED.getOrCreateStat(this));
  25. if (!user.abilities.creativeMode) {
  26. itemStack.decrement(1); // decrements itemStack if user is not in creative mode
  27. }
  28.  
  29. return TypedActionResult.success(itemStack, world.isClient());
  30. }
  31. }

Make sure that the projectile that you are launching with this item is indeed your custom ProjectileEntity. Verify this by checking PackedSnowballEntity snowballEntity = new PackedSnowballEntity(world, user);.
Now, we are finished with creating an item for the ProjectileEntity. Keep in mind that if you do not understand how to create an item, refer to the "Item" tutorial.
Finally, register your item.

  1. public static final Item PackedSnowballItem = new PackedSnowballItem(new Item.Settings().group(ItemGroup.MISC).maxCount(16));
  2.  
  3. [...]
  4.  
  5. @Override
  6. public void onInitialize() {
  7. Registry.register(Registry.ITEM, new Identifier(ModID, "packed_snowball"), PackedSnowballItem);
  8. }

Back in our ProjectileEntity class, we must add the getDefaultItem() into our method.

  1. @Override
  2. protected Item getDefaultItem() {
  3. return ProjectileTutorialMod.PackedSnowballItem;
  4. }

Make sure you have the texture for the item in the correct spot, or else neither the entity or the item will have a texture.

Rendering your Projectile Entity

Your projectile entity is now defined and registered, but we are not done. Without a renderer, the ProjectileEntity will crash Minecraft. To fix this, we will define and register the EntityRenderer for our ProjectileEntity. To do this, we will need a EntityRenderer in the ClientModInitializer. Go into your ClientModInitializer and write the following:

  1. @Override
  2. public void onInitializeClient() {
  3. EntityRendererRegistry.register(ProjectileTutorialMod.PackedSnowballEntityType, (context) ->
  4. new FlyingItemEntityRenderer(context));
  5. // older versions may have to use
  6. /* EntityRendererRegistry.INSTANCE.register(ProjectileTutorialMod.PackedSnowballEntityType, (context) ->
  7. new FlyingItemEntityRenderer(context)); */
  8. [. . .]
  9. }

Hoping to God It Works

Now, your projectile should be working in-game! Just make sure your textures are in the right place, and your item and projectile should be working.

If you would like to try out this projectile, download here.

[INSERT USABLE PICTURE HERE]

tutorial/projectiles.1664462266.txt.gz · Last modified: 2022/09/29 14:37 by patrickmsm