FANDOM


The animation AI is an important part of animation. It represents the physical behaviors that are made after animating. If these didn't exist, there would be no point in animating at all! So how do we create the AI? There are two kinds of AI that exist: automatic and non-automatic.

Making the Base of the Automatic Animation AI

An automatic animation ai is basically an ai that is triggered by something else. For example, if a turkey gets an angry animation when it is right-clicked, this animation would be triggered by the right-click.

For our example, we will make our EntityTest do a simply attack animation by slamming its head downward thus damaging the target, and then getting back up. This will be triggered by the method attackEntityAsMob (which is called in an entity's typical ai when the entity attacks). To start off we will create a new AI class which we will call AIHeadBang that extends the AIAnimation class from the AnimationAPI source code.

public class AIHeadBang extends AIAnimation {
		
	private EntityTest entityTest;
	
	public AIHeadBang(EntityTest test) {
		super(test);
		entityTest = test;
		attackTarget = null;
	}
	
	public int getAnimID() {
		return 1;
	}
	
	public boolean isAutomatic() {
		return true;
	}
	
	public int getDuration() {
		return 30;
	}
}

Take a look at the three methods that have been written. AIAnimation is actually an abstract class, so it forces you to implement those three methods.

For getAnimID(), you should return this AI's animation id. If you remember before, the field in EntityTest called animID represents the current animation id. In our case, since our getAnimID() returns 1, if the head bang animation is playing, then animID will be equal to 1. Don't worry, you won't have to change the field itself (in fact don't). AnimationAPI handles that for you.

For isAutomatic(), simply return true, because this animation will be triggered from elsewhere.

For getDuration(), return the time that this animation will take. Remember, Minecraft's update method runs 20 ticks per second, so returning the number 30 is equivalent to 1.5 seconds. This will be more important later.

(Although usually your animation AIs will extend AIAnimation, they don't actually have to if AIAnimation causes some conflicts. However, AIAnimation also deals with the pesky little things that need to be added, so it's recommended to just extend it unless you really understand what you're doing. If you aren't extending AIAnimation, check out its source code to see what you're missing.)

Adding Meat to the Automatic AI

Now we have our basic animation AI, but right now it doesn't do anything! It's time to add the logic behind the code. The AIAnimation class actually extends EntityAIBase, so you can still override some methods that you may need, such as updateTask() or resetTask(). Just remember to super the methods, because AIAnimation may override some of those methods.

For our entity to hit the target, we'll need to add an instance of the attack target, and then we'll need to add the damaging logic. First, we add the attack target field:

private EntityLiving attackTarget;

Then, we need to link it to the EntityTest's actual attack target:

public void startExecuting() {
	//remember to super!
	super.startExecuting();
	attackTarget = entityTest.getAttackTarget();
}

That's it! For the damage logic, we override updateTask() from EntityAIBase, which is just a function that will continuously be called. Since we know EntityTest's animTick increases every tick, we can use this to our advantage. Add this method into the AI:

public void updateTask() {
	if(entityTest.getAnimTick() < 14)
		entityTest.getLookHelper().setLookPositionWithEntity(attackTarget, 30F, 30F);
	if(entityTest.getAnimTick() == 14 && attackTarget != null)
		attackTarget.attackEntityFrom(DamageSource.causeMobDamage(entityTest), 1);
}

Basically, the animTick starts at 0 and increases each tick. For now, we assume that at tick 14, our animation will make the EntityTest slam its head, thus being the perfect moment to deal damage. And for any time before tick reaches 14, we want the entity to face its target, which accounts for the earlier two lines.

So now the logic is implemented, but we haven't added the trigger into EntityTest's code. Since we want EntityTest to trigger this animation when it's attacking the target, we call the trigger in the attackEntityAsMob method:

public boolean attackEntityAsMob(Entity entity) {
	if(animID == 0) AnimationAPI.sendAnimPacket(this, 1);
	return true;
}

Here, there are three very important things to remember. First of all, always check with if(animID == 0). Basically, this means, if no animation is already playing, THEN start the head bang animation. This is important because we don't want the animations constantly changing and overriding each other. We want them to play completely, one by one.

The second part to note is the method we call. In order to trigger the animation, we cannot simply change the animID. Because Minecraft is now completely client-server based, simply changing the animID in the server would not change animID for client (meaning actual animations wouldn't show), and vice versa (meaning the logic wouldn't work). Instead, we call this method AnimationAPI.sendAnimPacket(this, 1), which accepts the parameters of an IAnimatedEntity and an integer that represents the animation id (and AIHeadBang.getAnimID() == 1, so we put in 1). This method is made to update the animID for both the server and client by sending a packet.

The last thing to remember is that the method AnimationAPI.sendAnimPacket will only work in the effective server. Basically, what it does is it sends packets to all the clients, updating them about the changed animID, and then changes its own animID. If it is in the effective client, then the method will do nothing. You can check if it's an effective server with the field worldObj.isRemote. If(worldObj.isRemote == false) then you are in the effective server, and the code will work. Otherwise, the code will do nothing.

Last but not least, add the AI into the tasks of EntityTest, and you're done!

public EntityTest(World world) {
	super(world);
	texture = "nothing";
	animID = 0;
	animTick = 0;
	tasks.addTask(1, new EntityAISwimming(this));
	
	//ADD THIS!
	tasks.addTask(2, new AIHeadBang(this));
	
	tasks.addTask(3, new EntityAIAttackOnCollide(this, 0.24F, false));
	tasks.addTask(4, new EntityAIWander(this, 0.2F));
	tasks.addTask(5, new EntityAIWatchClosest(this, EntityPlayer.class, 8F));
	tasks.addTask(5, new EntityAILookIdle(this));
	targetTasks.addTask(1, new EntityAIHurtByTarget(this, false));
}

Finishing Up

By now your files should look a little something like shown below. If you don't need to double check, head on to Step 3: Creating a Non-Automatic Animation AI.

EntityTest.java

public class EntityTest extends EntityCreature implements IAnimals, IAnimatedEntity {
	
	private int animID;
	private int animTick;
	
	public EntityTest(World world) {
		super(world);
		texture = "nothing";
		animID = 0;
		animTick = 0;
		tasks.addTask(1, new EntityAISwimming(this));
		tasks.addTask(2, new AIHeadBang(this));
		tasks.addTask(3, new EntityAIAttackOnCollide(this, 0.24F, false));
		tasks.addTask(4, new EntityAIWander(this, 0.2F));
		tasks.addTask(5, new EntityAIWatchClosest(this, EntityPlayer.class, 8F));
		tasks.addTask(5, new EntityAILookIdle(this));
		targetTasks.addTask(1, new EntityAIHurtByTarget(this, false));
	}
	
	public int getMaxHealth() {
		return 16;
	}
	
	public boolean isAIEnabled() {
		return true;
	}
	
	/*
	 * Implemented method from IAnimatedEntity.
	 * Set the animID field to the id in the parameter.
	 */
	public void setAnimID(int id) {
		animID = id;
	}
	
	/*
	 * Implemented method from IAnimatedEntity.
	 * Set the animTick field to the tick in the parameter.
	 */
	public void setAnimTick(int tick) {
		animTick = tick;
	}
	
	/*
	 * Implemented method from IAnimatedEntity.
	 * Return the animID.
	 */
	public int getAnimID() {
		return animID;
	}
	
	/*
	 * Implemented method from IAnimatedEntity.
	 * Return the animTick.
	 */
	public int getAnimTick() {
		return animTick;
	}
	
	public void onUpdate() {
		super.onUpdate();
		//increment the animTick if there is an animation playing
		if(animID != 0) animTick++;
	}
	
	public boolean attackEntityAsMob(Entity entity) {
		if(animID == 0) AnimationAPI.sendAnimPacket(this, 1);
		return true;
	}
}

AIHeadBang.java

public class AIHeadBang extends AIAnimation {
	
	private EntityTest entityTest;
	private EntityLiving attackTarget;
	
	public AIHeadBang(EntityTest test) {
		super(test);
		entityTest = test;
		attackTarget = null;
	}
	
	public int getAnimID() {
		return 1;
	}
	
	public boolean isAutomatic() {
		return true;
	}
	
	public int getDuration() {
		return 30;
	}
	
	public void startExecuting() {
		super.startExecuting();
		attackTarget = entityTest.getAttackTarget();
	}
	
	public void updateTask() {
		if(entityTest.getAnimTick() < 14)
			entityTest.getLookHelper().setLookPositionWithEntity(attackTarget, 30F, 30F);
		if(entityTest.getAnimTick() == 14 && attackTarget != null)
			attackTarget.attackEntityFrom(DamageSource.causeMobDamage(entityTest), 1);
	}
}

Ad blocker interference detected!


Wikia is a free-to-use site that makes money from advertising. We have a modified experience for viewers using ad blockers

Wikia is not accessible if you’ve made further modifications. Remove the custom ad blocker rule(s) and the page will load as expected.