Obsidian API Tutorial #5 – Entity AI – v0.1.1+

<< PreviousAll tutorialsNext >>

Let's use some Entity AI

In this tutorial, we’re going to take our animations a step further and look at attaching them to entity AI. The video for this tutorial can be found here.

Setting up EntityAI

I’m going to setup a new entity AI class for eating: EntityAIEat. I’ll use this with the Saiga entity.

So usually, entity AI classes extend EntityAIBase, but for animation purposes we want to extend EntityAIAnimationBase. I’m not going to cover how to write AI much, there are other tutorials for that, but I’ll take you through my EntityAIEat class:

public class EntityAIEat extends EntityAIAnimationBase {

    private EntityCreature entity;
    public static String name = "Eat";
    private final int limit = 50;
    private int counter = 0;
   
    public EntityAIEat(EntityCreature entity) {
        super(name);
        this.entity = entity;
        this.setMutexBits(7);
    }
   
    @Override
    public void startExecuting() {
        super.startExecuting();
        counter = 0;
    }
   
    @Override
    public void resetTask() {
        super.resetTask();
        counter = 0;
    }
   
    @Override
    public boolean shouldExecute() {
        return entity.getRNG().nextInt(50) == 0;
    }
   
    public boolean continueExecuting() {
        if(counter < limit) {
            counter++;
            return true;
        }
        return entity.getRNG().nextInt(200) == 0;
    }
}

It looks a bit daunting, but it’s actually really simple. At a high level, the task starts executing and will keep executing for a given amount of time. To make it a bit more interesting, the task will terminate a random amount of time after the minimum length has been met; this means entities will eat for variable lengths of time and for a decent amount each time. I cover this class in more detail in this section of the tutorial video.

Remember to register the AI task in the entity class constructor:

this.tasks.addTask(1, new EntityAIEat(this));

Setting up an AIAnimationWrapper

Now at this point in the video, I go very in depth about how the AnimationRegistry works. It’s interesting, but I do waffle on a bit. I recommend watching this section, but you don’t have to. 

We’re going to register the AI animations in a different way to how we’ve done it before, by using an AIAnimationWrapper. You’ll recognise some parts of this:

new AIAnimationWrapper(String aiName, ResourceLocation resource, int priority, boolean loops);

The only new part of this should be the aiName argument, all the other arguments should be familiar from last tutorial. The aiName is the name you gave your EntityAI class, for me it was “Eat”. I recommend using a public static variable in your entity AI class to keep the names consistent. You’ll need an AIAnimationWrapper for every entity AI animation you have; create them in the CommonProxy.

Registering our wrapper

We’ve defined our wrapper, but not registered it with the AnimationRegistry. To do this, we use a different registerAnimation method, one that expects three arguments – an entity name, an animation name, and an animation wrapper (IAnimationWrapper):

AIAnimationWrapper eatWrapper = new AIAnimationWrapper(EntityAIEat.name, new ResourceLocation("mod_api_tutorial:animations/SaigaEat.oba"), 10, true);
AnimationRegistry.registerAnimation("saiga", EntityAIEat.name, eatWrapper);

Use this is in the CommonProxy registerAnimations method.

Done and dusted

That’ll add our animation, check it works by running Minecraft!

<< PreviousAll tutorialsNext >>

Edited files

EntityAIEat

public class EntityAIEat extends EntityAIAnimationBase {

    private EntityCreature entity;
    public static String name = "Eat";
    private final int limit = 50;
    private int counter = 0;
   
    public EntityAIEat(EntityCreature entity) {
        super(name);
        this.entity = entity;
        this.setMutexBits(7);
    }
   
    @Override
    public void startExecuting() {
        super.startExecuting();
        counter = 0;
    }
   
    @Override
    public void resetTask() {
        super.resetTask();
        counter = 0;
    }
   
    @Override
    public boolean shouldExecute() {
        return entity.getRNG().nextInt(50) == 0;
    }
   
    public boolean continueExecuting() {
        if(counter < limit) {
            counter++;
            return true;
        }
        return entity.getRNG().nextInt(200) == 0;
    }
}

Entity Saiga

public class EntitySaiga extends EntityCreature implements IEntityAnimated {
   
    public EntitySaiga(World world) {
        super(world);
        this.tasks.taskEntries.clear();
        this.tasks.addTask(0, new EntityAIWander(this, 1.0D));
        this.tasks.addTask(1, new EntityAIEat(this));
    }
   
    @Override
    protected boolean isAIEnabled() {
        return true;
    }

    @Override
    protected void applyEntityAttributes() {
        super.applyEntityAttributes();
        this.getEntityAttribute(SharedMonsterAttributes.maxHealth).setBaseValue(10.0D);
        this.getEntityAttribute(SharedMonsterAttributes.movementSpeed).setBaseValue(0.18D);
    }

    @Override
    public boolean isMoving() {
        return limbSwingAmount > 0.02F;
    }
   
}

CommonProxy

public class CommonProxy {

    private ResourceLocation saigaWalk = new ResourceLocation("mod_api_tutorial:animations/Walk.oba");
    private ResourceLocation saigaIdle = new ResourceLocation("mod_api_tutorial:animations/SaigaIdle.oba");
   
    public void init() {   
        ModEntities.registerEntities();
        registerRendering();
        registerAnimations();
    }

    public void registerRendering() {}
   
    public void registerAnimations() {
        AnimationRegistry.init();
       
        IsActiveFunction isWalking = (entity) -> {
            return ObsidianAPIUtil.isEntityMoving(entity);
        };
       
        IsActiveFunction returnTrue = (entity) -> {
            return true;
        };
       
        AnimationRegistry.registerEntity(EntitySaiga.class, "saiga");
        AnimationRegistry.registerAnimation("saiga", "walk", saigaWalk, 0, true, isWalking);
        AnimationRegistry.registerAnimation("saiga", "idle", saigaIdle, 50, true, returnTrue);
       
        AIAnimationWrapper eatWrapper = new AIAnimationWrapper(EntityAIEat.name, new ResourceLocation("mod_api_tutorial:animations/SaigaEat.oba"), 10, true);
        AnimationRegistry.registerAnimation("saiga", EntityAIEat.name, eatWrapper);
    }
}

 

Leave a Reply

Your email address will not be published. Required fields are marked *