跳至正文

经典AI——星尘龙

mina好,我是苏雪,我这次来细嗦一下星尘龙的运动逻辑。

隔壁多节召唤物码太长了拿一点出来

他大致分为三个部分:索敌-攻击-游荡。来看看状态图:

以下是润色过(numXXX大屠杀!)的代码:

public static void StarDustDragonAI(Projectile Projectile, Player Player, float MaxDisToPlayer = 2000, float SearchDis = 700, float MaxSpeed_NoTarget = 15f, float MaxSpeed_HasTarget = 30f)
{
    // 弄成了一个方法,可以直接给弹幕使用哦。前两个参数就不说了,第三个是离玩家的最大距离,第四个是索敌距离(不要超过1000),第五个是无目标限速,最后一个是有目标限速。接着往下看吧。下边的代码都是放在这个方法里面的————
}
// 弹幕超出玩家2000f自动回归,并同步数据
if (Vector2.Distance(Player.Center, Projectile.Center) > MaxDisToPlayer)
{
    Projectile.Center = Player.Center;
    Projectile.netUpdate = true;
}

netUpdate 这个属性为 true 的时候,会执行数据同步,并重置为 false

int TarWho = -1;// 目标whoAmI
NPC target = Projectile.OwnerMinionAttackTargetNPC;// 召唤物自动索敌
if (target != null && target.CanBeChasedBy())
{
    // 虽然但是,为什么要用 两倍 索敌距离
    if (Projectile.Distance(target.Center) < SearchDis * 2f)
    {
        TarWho = target.whoAmI;
    }
}
// 如果自动索敌没有找到目标,就搜索离玩家1000f内且在弹幕索敌距离内的目标
if (TarWho < 0)
{
    foreach (NPC npc in Main.npc)
    {
        if (npc.CanBeChasedBy() && Player.Distance(npc.Center) < 1000f)
        {
            if (Projectile.Distance(npc.Center) < SearchDis)
            {
                TarWho = npc.whoAmI;
            }
        }
    }
}

Projectile.OwnerMinionAttackTargetNPC,这是一个弹幕自带的寻找攻击目标的属性。CanBeChaseBy() 是原版自带的用于判定 npc 能不能被打的 bool 方法,他包括了active,friendly,是否小动物等等。

if (TarWho != -1)// 有攻击目标
{
    NPC npc = Main.npc[TarWho];
    Vector2 tarVel = npc.Center - Projectile.Center;
    float speed = 0.4f;// 追击速度系数
    float dis = tarVel.Length();
    // 越近越快
    if (dis < 600f)
    {
        speed = 0.6f;
    }
    if (dis < 300f)
    {
        speed = 0.8f;
    }
    // 到npc的距离比npc的碰撞箱大小的0.75倍大,也就是说离NPC还比较远
    // 这使得星尘龙在穿过NPC后才会调头,而不是直接粘在它身上鬼畜
    if (dis > npc.Size.Length() * 0.75f)
    {
        // 弹幕速度加上朝着npc的单位向量*追击速度系数*1.5f
        Projectile.velocity += Vector2.Normalize(tarVel) * speed * 1.5f;
        //如果追踪方向和速度方向夹角过大,减速(向量点乘你们自己研究,绝对值越大两向量夹角越大,范围是正负1)
        if (Vector2.Dot(Projectile.velocity, tarVel) < 0.25f)
        {
            Projectile.velocity *= 0.8f;
        }
    }
    // 限制最大速度
    if (Projectile.velocity.Length() > MaxSpeed_HasTarget)
    {
        Projectile.velocity = Vector2.Normalize(Projectile.velocity) * MaxSpeed_HasTarget;
    }
}
有目标时行动示意图,淦不会做动画

注意这是一个连续的行为,虽然图上看起来挺生硬,但是运动起来还是比较丝滑的。

else// 无攻击目标
{
    float speed = 0.2f;// 游荡速度系数
    float dis = Player.Center.Distance(Projectile.Center);// 弹幕到玩家距离,越近越慢
    if (dis < 200f)
    {
        speed = 0.12f;
    }
    if (dis < 140f)
    {
        speed = 0.06f;
    }
    if (dis > 100f)// 在100f外朝着玩家飞
    {
        // abs绝对值,sign正负
        if (Math.Abs(Player.Center.X - Projectile.Center.X) > 20f)// 离玩家水平距离大于20,就给朝向玩家的水平速度
        {
            Projectile.velocity.X += speed * Math.Sign(Player.Center.X - Projectile.Center.X);
        }
        if (Math.Abs(Player.Center.Y - Projectile.Center.Y) > 10f)// 离玩家垂直距离大于10,就给朝向玩家的垂直速度
        {
            Projectile.velocity.Y += speed * Math.Sign(Player.Center.Y - Projectile.Center.Y);
        }
    }
    else if (Projectile.velocity.Length() > 2f)// 在100f内且速度较大就减速
    {
        Projectile.velocity *= 0.96f;
    }
    if (Math.Abs(Projectile.velocity.Y) < 1f)// 没啥速度了就慢慢向上游动
    {
        Projectile.velocity.Y -= 0.1f;
    }
    //无目标最大速度15f,有目标30f
    if (Projectile.velocity.Length() > MaxSpeed_NoTarget)
    {
        Projectile.velocity = Vector2.Normalize(Projectile.velocity) * MaxSpeed_NoTarget;
    }
}
Projectile.rotation = Projectile.velocity.ToRotation();
游荡示意图

不难的逻辑。

但是能留小作业()

从这个运动逻辑中找出为什么星尘龙有的时候会停止运动?并试着修复这个问题。(提示:判定盲区)

《经典AI——星尘龙》有3个想法

发表回复