本篇教程的视频:

本篇教程源代码

GitHub地址:TutorialMod-AttackEntity-1.21

介绍

有了前两期的教程,相信大家都已经对生物实体的创建有了一定的了解,那么本篇教程我们将为我们的生物实体添加攻击性

我们将自己编写生物实体的AI,为其编写对应的目标,具有攻击性

生物实体攻击目标

这个攻击目标就是我们生物实体的AI,因像前面写的那些目标一样,我们得让它知道应该攻击谁

创建TigerAttackGoal类

1
2
3
4
5
6
public class TigerAttackGoal extends MeleeAttackGoal {

public TigerAttackGoal(PathAwareEntity mob, double speed, boolean pauseWhenMobIdle) {
super(mob, speed, pauseWhenMobIdle);
}
}

这里我们继承了MeleeAttackGoal,这个类是用于近战攻击的,其实这种攻击目标完全可参考原版的那些怪物的攻击目标,包括近战远程的都可以写

接下来我们创建一些变量

1
2
3
4
5
6
7
private final TigerEntity entity;

private int attackDelay = 20;

private int ticksUntilNextAttack = 20;

private boolean shouldCountTillNextAttack = false;

这里我们创建了一个TigerEntity的变量,这个是我们的生物实体

attackDelay是攻击延迟,这个是根据你设置的动画来的,这里我们设置为20,单位是tick,也就是1秒。在我们的动画中,前1秒我设置了攻击的前摇,还没开始攻击,而到了第1秒这一刻才开始攻击,所以这里设置为20

ticksUntilNextAttack是下一次攻击的时间,也就是攻击完之后剩下的时间(相当于cd,冷却时间)

shouldCountTillNextAttack是是否应该计算下一次攻击的时间

先初始化一下entity

1
2
3
4
public TigerAttackGoal(PathAwareEntity mob, double speed, boolean pauseWhenMobIdle) {
super(mob, speed, pauseWhenMobIdle);
this.entity = (TigerEntity) mob;
}

重写start方法

1
2
3
4
5
6
@Override
public void start() {
super.start();
attackDelay = 20;
ticksUntilNextAttack = 20;
}

这里我们重写了start方法,这个方法是在开始攻击时调用的

我们在这里重置了attackDelayticksUntilNextAttack,这样就可以保证每次攻击都是在我们设置的时间内

重写stop方法

1
2
3
4
5
@Override
public void stop() {
super.stop();
entity.setAttacking(false);
}

这里我们重写了stop方法,这个方法是在停止攻击时调用的

我们在这里调用了entityattacking方法,设置为false,当然这个方法还没有写

重写tick方法

1
2
3
4
5
6
7
@Override
public void tick() {
super.tick();
if (shouldCountTillNextAttack) {
this.ticksUntilNextAttack = Math.max(this.ticksUntilNextAttack -1, 0);
}
}

这里我们重写了tick方法,判定是否应该计算下一次攻击的时间,如果是的话,就减少ticksUntilNextAttack的时间

重写attack方法

这个方法可是这个类的核心,这个方法是在攻击时调用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
protected void attack(LivingEntity target) {
if (isEnemyWithinDistance(target)) {
shouldCountTillNextAttack = true;
if (isTimeToStartAttackAnimation()) {
entity.setAttacking(true);
}
if (isTimeToAttack()) {
this.mob.getLookControl().lookAt(target.getX(), target.getEyeY(), target.getZ());
performAttack(target);
}
} else {
resetAttackCooldown();
this.shouldCountTillNextAttack = false;
entity.setAttacking(false);
entity.attackAnimationTimeOut = 0;
}
}

这里我们判断了一些条件

首先用isEnemyWithinDistance方法(待会创建),判断是否有敌人在攻击范围内,如果有的话,就设置shouldCountTillNextAttacktrue

isTimeToStartAttackAnimation方法(待会创建),判断是否是攻击动画的时间,如果是的话,就设置entityattackingtrue

isTimeToAttack方法(待会创建),判断是否是攻击的时间,如果是的话,先让生物实体看向目标,然后调用performAttack方法(待会创建),进行攻击

如果没有敌人在攻击范围内,就重置攻击冷却时间(待会创建),设置shouldCountTillNextAttackfalse,设置entityattackingfalse,并且重置entityattackAnimationTimeOut0

创建isEnemyWithinDistance方法

1
2
3
private boolean isEnemyWithinDistance(LivingEntity target) {
return this.entity.distanceTo(target) < 2.0f;
}

这个方法是判断敌人是否在攻击范围内(直线距离),这里我们设置为2.0f,两个方块的距离

创建isTimeToStartAttackAnimation方法

1
2
3
private boolean isTimeToStartAttackAnimation() {
return this.ticksUntilNextAttack <= this.attackDelay;
}

这个方法是判断是否是攻击动画的时间,这里我们设置为ticksUntilNextAttack小于等于attackDelay的时候,就是攻击动画的时间

创建isTimeToAttack方法

1
2
3
private boolean isTimeToAttack() {
return this.ticksUntilNextAttack <= 0;
}

这个方法是判断是否是攻击的时间,这里我们设置为ticksUntilNextAttack小于等于0的时候,就是攻击的时间

创建performAttack方法

1
2
3
4
5
private void performAttack(LivingEntity target) {
this.resetAttackCooldown();
this.mob.swingHand(Hand.MAIN_HAND);
this.mob.tryAttack(target);
}

这个方法是进行攻击的方法,这里我们重置攻击冷却时间,然后挥动手臂(如果有手的话),最后调用tryAttack方法,尝试进行攻击

创建resetAttackCooldown方法

1
2
3
private void resetAttackCooldown() {
this.ticksUntilNextAttack = this.getTickCount(attackDelay * 2);
}

这个方法是重置攻击冷却时间的方法,这里我们设置为attackDelay的两倍,也是40,因为前摇加上攻击的冷却时间正好是40tick

那么这个类就是这样,我们还得在TigerEntity写一些东西

修改TigerEntity类

添加数据追踪器

首先我们要编写一个数据追踪器,用于追踪生物实体的攻击状态

1
2
private static final TrackedData<Boolean> IS_ATTACKING =
DataTracker.registerData(TigerEntity.class, TrackedDataHandlerRegistry.BOOLEAN);

这里我们注册了一个IS_ATTACKING的数据追踪器,用于追踪生物实体的攻击状态,这个是boolean类型的

然后我们要初始化这个数据追踪器,重写initDataTracker方法

1
2
3
4
5
@Override
protected void initDataTracker(DataTracker.Builder builder) {
super.initDataTracker(builder);
builder.add(IS_ATTACKING, false);
}

我们将IS_ATTACKING的值设置为false,并加入到builder

接下来创建刚才在TigerAttackGoal中用到的attacking方法

1
2
3
public void setAttacking(boolean attacking) {
this.dataTracker.set(IS_ATTACKING, attacking);
}

这个方法是设置生物实体的攻击状态

再接下来创建一个isAttacking方法,这个我们等会会用到

1
2
3
public boolean isAttacking() {
return this.dataTracker.get(IS_ATTACKING);
}

这个就是获取生物实体的攻击状态

设置目标

我们在TigerEntityinitGoals方法中,添加我们的攻击目标

1
2
this.targetSelector.add(1, new RevengeGoal(this));
this.goalSelector.add(1, new TigerAttackGoal(this, 1.0D, true));

这里我们添加了RevengeGoal(复仇目标)和TigerAttackGoal(我们刚才写的攻击目标),这个复仇目标是当生物实体受到攻击时,会攻击攻击它的生物实体

虽然说我们这里加了我们写的攻击目标,但准确来讲我们没有设置它自己会攻击的目标,这里可能你得参考参考狼主动攻击羊的代码了,在未来的教程中我们再修

设置动画

接下来我们来设置动画,同样首先是一些动画状态

1
2
3
public static final AnimationState attackAnimation = new AnimationState();

public int attackAnimationTimeOut = 0;

这里我们创建了一个attackAnimation的动画状态,这个是攻击动画的状态

以及一个attackAnimationTimeOut的变量,这个是攻击动画的时间

然后我们在setupAnimation方法中,设置攻击动画

1
2
3
4
5
6
7
8
9
10
11
if (this.isAttacking() && attackAnimationTimeOut <= 0) {
attackAnimationTimeOut = 40;
attackAnimation.start(this.age);

} else {
attackAnimationTimeOut--;
}

if (!this.isAttacking()) {
attackAnimation.stop();
}

这里我们判断了一些条件

首先判断生物实体是否在攻击状态,以及攻击动画的时间是否小于等于0,如果是的话,就设置attackAnimationTimeOut40,也就是攻击动画的时间,然后开始攻击动画

然后判断生物实体是否不在攻击状态,如果是的话,就停止攻击动画

设置模型

接下来我们会到TigerRenderer中设置模型

setAngles方法中,设置攻击动画

1
this.updateAnimation(TigerEntity.attackAnimation, TigerAnimation.ATTACK, animationProgress, 1f);

这样我们的攻击动画就设置好了

注册刷怪蛋

因为我们之前是用summon来召唤生物实体的,不太方便,所以我们来注册一个刷怪蛋

1
2
public static final Item TIGER_SPAWN_EGG = registerItems("tiger_spawn_egg",
new SpawnEggItem(ModEntities.TIGER, 0x252525, 0x4D4D4D, new Item.Settings()));

这里我们注册了一个刷怪蛋,这个刷怪蛋的实体是我们的TigerEntity,底层颜色是0x252525,第二层颜色是0x4D4D4D(也就是上面的斑块颜色),这里的颜色码是16进制,按照自己的需求设置

将其加入物品栏

1
entries.add(ModItems.TIGER_SPAWN_EGG);

语言文件也写一下

1
translationBuilder.add(ModItems.TIGER_SPAWN_EGG, "Tiger Spawn Egg");

模型文件和贴图文件是不用的,在游戏会自动生成

那么现在,我们就可以在游戏中使用刷怪蛋来召唤我们的生物实体了,并且你打它,它也会攻击你