Custom Projectile
Your projectile is an example of an entity. Entities have a position in the world, are bound to a renderer so the player can see them, have a bounding box to detect collisions, and react to ticks (move around, etc). Each individual entity in the world is an instance of the Entity
class. There are many classes that extend Entity
to give more interesting behavior.
Each entity also has an EntityType
. This must be registered just like blocks/items. The EntityType
tells the game which renderer to use and which Entity
class to use to define your entity's behavior when it is loaded from disk.
Init
Start by setting up a deferred register for entities.
public class EntityInit {
public static DeferredRegister<EntityType<?>> ENTITY_TYPES = DeferredRegister.create(ForgeRegistries.ENTITIES, FirstModMain.MOD_ID);
}
Then you will use it to register an EntityType
for your custom arrow. As usual, you pass in a string to use as a registry name and a supplier for your entity type. Entity types are created with the EntityType.Builder
class. This allows you to set a few traits of your entity.
The builder needs a supplier for your entity class. If you're entity class has multiple constructors, it seems to get confused which you mean so you have to cast it to EntityType.IFactory
. You also give it a EntityClassification
(better description of this will be in the hostile entities tutorial), for projectiles you should use MISC
. The sized
method sets the width and height of the bounding box, so how big an area your projectile checks for hitting something (vanilla arrows use 0.5 by 0.5). Finally call the build
method, the argument here should be the same as the registry used earlier name (idk why but that's what vanilla does. if anyone finds out, pls tell me).
public static final RegistryObject<EntityType<ExplosiveArrowEntity>> EXPLOSIVE_ARROW = ENTITY_TYPES.register("explosive_arrow",
() -> EntityType.Builder.of((EntityType.IFactory<ExplosiveArrowEntity>) ExplosiveArrowEntity::new, EntityClassification.MISC).sized(0.5F, 0.5F).build("explosive_arrow"));
Remember to call this registry from the constructor of your main class.
EntityInit.ENTITY_TYPES.register(modEventBus);
Arrow Class
Now we will actually create the class referenced above to define the behavior of the projectile. There's a kinda elaborate tree of classes with different behaviors already implemented, you can read the descriptions below to choose which to extend for your arrow. Each class has its own behavior and keeps the behavior of anything lower down the chain.
Vanilla Classes
Entity
- does the absolute minimum, you must define all behavior
ProjectileEntity > Entity
- saves its owner
- has methods for shooting: sets its movement direction
- has methods for what happens for reacting to impacts
- does not have logic for moving or detecting collisions, you must write this yourself or extend something lower down.
AbstractArrowEntity > ProjectileEntity > Entity
- movement and collision detection logic in the tick method
- rotates to face direction of movement
- piercing logic (
setPierceLevel
, used by crossbows) - deals damage on hit based on velocity and
setBaseDamage
- sticks in the ground and can be picked up
- able to be shot from normal bows by using the ArrowItem class
ArrowEntity > AbstractArrowEntity > ProjectileEntity > Entity
- logic for holding potion effects (apply on hit, make particles)
ThrowableEntity > ProjectileEntity > Entity
- movement and collision detection logic in the tick method
ProjectileItemEntity > ThrowableEntity > ProjectileEntity > Entity
- renders as an item by implementing
IRendersAsItem
and having an entity type bound toSpriteRenderer
Your Class
I will be extending AbstractArrowEntity
. Below are the constructors that give you. The getPickupItem
method returns the item stack to give the player when they walk over your arrow stuck in the ground.
public class ExplosiveArrowEntity extends AbstractArrowEntity {
public ExplosiveArrowEntity(EntityType<ExplosiveArrowEntity> entityType, World world) {
super(entityType, world);
}
public ExplosiveArrowEntity(EntityType<ExplosiveArrowEntity> entityType, double x, double y, double z, World world) {
super(entityType, x, y, z, world);
}
public ExplosiveArrowEntity(EntityType<ExplosiveArrowEntity> entityType, LivingEntity shooter, World world) {
super(entityType, shooter, world);
}
@Override
protected ItemStack getPickupItem() {
return ItemStack.EMPTY;
}
}
To do something when we hit an entity, call the onHitEntity
. If you want it to do damage normally and respect piercing levels, keep the call to the super method.
@Override
protected void onHitEntity(EntityRayTraceResult ray) {
super.onHitEntity(ray);
}
To access the entity you hit, use ray.getEntity()
. I want to create an explosion but still respect piercing so I'll keep the super call. Note that because of invincibility time, if you keep the super call and do extra damage here only the greater of vanilla's damage and your damage will apply (you can change vanilla's damage by with this.setBaseDamage(amount);
)
@Override
protected void onHitEntity(EntityRayTraceResult ray) {
super.onHitEntity(ray);
// this, x, y, z, explosionStrength, setsFires, breakMode
this.level.explode(this, this.getX(), this.getY(), this.getZ(), 4.0f, true, Explosion.Mode.BREAK);
}
To do something when you hit a block, you can override the following method. Keeping the super call will cause the arrow to stick in the block you hit to be picked up or despawn after 60 seconds.
@Override
protected void onHitBlock(BlockRayTraceResult ray) {
super.onHitBlock(ray);
BlockState theBlockYouHit = this.level.getBlockState(ray.getBlockPos());
}
I'm going to leave that method as it is in vanilla and override the tickDespawn
method (called every tick while its in the ground) to make an explode after 3 seconds.
@Override
protected void tickDespawn() {
if (this.inGroundTime > 60){
this.level.explode(this, this.getX(), this.getY(), this.getZ(), 4.0f, true, Explosion.Mode.BREAK);
this.remove();
}
}
Rendering
To allow players to see your arrow in the world, you must bind it to an EntityRenderer
that defines its appearance.
Before we do this, you must add this method to your entity class. If you do not do this, the arrow will behave normally and give no helpful error message but it will not render. This simply returns a packet to sync the entity from the server side to the client side and is called automatically whenever the arrow is added to the world.
// in ExplosiveArrowEntity.java
@Override
public IPacket<?> getAddEntityPacket() {
return NetworkHooks.getEntitySpawningPacket(this);
}
Next, create a renderer class for your arrow (create the client.render
package). I will copy the vanilla TippedArrowRenderer
and just change a few values. Since I'm extending the ArrowRenderer
class it will have the same shape as vanilla arrows. The getTextureLocation
method just returns a resource location to use as the texture for the entity. The path used for the resource location is just the file path to your image in your src/main/resources/mod_id/assets
directory.
public class ExplosiveArrowRenderer extends ArrowRenderer<ExplosiveArrowEntity> {
public static final ResourceLocation TEXTURE = new ResourceLocation(FirstModMain.MOD_ID, "textures/entity/explosive_arrow.png");
public ExplosiveArrowRenderer(EntityRendererManager manager) {
super(manager);
}
public ResourceLocation getTextureLocation(ExplosiveArrowEntity arrow) {
return TEXTURE;
}
}
We will need to make the texture image for our arrow. I will use an online pixel art editor to recolor the vanilla arrow texture. Textures are a net of all the faces of the 3d object in the game unfolded onto a flat image. You can find all the vanilla assets on github at InventivetalentDev/minecraft-assets. This is what the vanilla arrow texture looks like:
Once you're happy with your texture image, you can make a folder called entity
in src/main/resources/mod_id/assets/textures
and put your image file in it. Make sure to name the file the same as used in the texture resource location (for me that's explosive_arrow.png
).
You can also have multiple textures and switch between them based on properties of the projectile object passed to getTextureLocation
. For example, you could check the value of arrow.tickCount
and cycle between different textures every second. Note that you can only access values present on the client side so you may need to use a DataParameter
to sync data from the server. This will be covered in more detail in the entities tutorial. Join the discord server to be notified when it is released.
Bind Renderer
For the game to know that your new renderer class should be used whenever your arrow exists in the world, you must register it on the client setup event. The forge events system is described in more depth in the events tutorial.
Start by making a new class called ClientSetup
in your client
package with a method listening for the FMLClientSetupEvent
. Note the value = Dist.CLIENT
in the annotation on the class because the event only fires on the client side. In this method, we use the registerEntityRenderingHandler
method to register our renderer. It just needs the entity type reference to the constructor of your renderer class.
@Mod.EventBusSubscriber(modid = FirstModMain.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
public class ClientSetup {
@SubscribeEvent
public static void doSetup(FMLClientSetupEvent event) {
RenderingRegistry.registerEntityRenderingHandler(EntityInit.EXPLOSIVE_ARROW.get(), ExplosiveArrowRenderer::new);
}
}
Using Your Arrow
You can add your arrow to the world whenever you want. The next section of this tutorial will cover creating a custom arrow item so vanilla bows can shoot your projectile. You could also create a custom bow (by extending BowItem
) that specificity shoots the projectile we just made. A tutorial for this is coming soon. Join the discord server to be notified when it is released.
Example of summoning your arrow entity that could be used anywhere you have access to a player (ie, on item right click):
if (!player.level.isClientSide()){
ExplosiveArrowEntity arrow = new ExplosiveArrowEntity(EntityInit.EXPLOSIVE_ARROW.get(), player, player.level);
arrow.setDeltaMovement(0, 1, 0); // directly up
player.level.addFreshEntity(arrow);
}
Arrow Item
Arrows are just an item like any other with a special method that allows bows to create the correct arrow entity to shoot. Create a class that extends ArrowItem
and simply override the createArrow
method to return a new instance of the arrow entity you created above.
public class ExplosiveArrowItem extends ArrowItem {
public ExplosiveArrowItem(Properties props) {
super(props);
}
@Override
public AbstractArrowEntity createArrow(World world, ItemStack ammoStack, LivingEntity shooter) {
return new ExplosiveArrowEntity(EntityInit.EXPLOSIVE_ARROW.get(), shooter, world);
}
}
Then register an item for your arrow like normal.
// in ItemInit.java
public static final RegistryObject<Item> EXPLOSIVE_ARROW = ITEMS.register("explosive_arrow",
() -> new ExplosiveArrowItem(new Item.Properties().tab(ModCreativeTab.instance)));
Remember to go back to your arrow class and tell it to give your item when picked up off the ground.
// in ExplosiveArrowEntity.java
@Override
protected ItemStack getPickupItem() {
return new ItemStack(ItemInit.EXPLOSIVE_ARROW.get());
}
Tag
To allow vanilla bows and crossbows to shoot your arrow, it must be in the arrows
item tag. Tags are a data driven system for categorizing registered objects with similar behavior.
Create the folders src/main/resources/data/minecraft/tags/items
and within that folder create a file called arrows.json
. This is a json object that defines the changes you want to make to the tag specified by the file name. The replace
key being false makes it add to vanilla's arrow list instead of replacing it. The values
list contains the resource locations of the items you want to add to the arrows category (so a bow can shoot them).
{
"replace": false,
"values": [
"firstmod:explosive_arrow"
]
}
Assets
The arrow item requires model and texture files as well as a lang entry as covered in the basic items tutorial