作业十

作业要求

  • 使用“感知-思考-行为”模型,建模 AI 坦克
  • 场景中要放置一些障碍阻挡对手视线
  • 坦克需要放置一个矩阵包围盒触发器,以保证 AI 坦克能使用射线探测对手方位
  • AI 坦克必须在有目标条件下使用导航,并能绕过障碍。(失去目标时策略自己思考)
  • 实现人机对战

场景设计

本来是想要沿用巡逻兵的场景布置,不过在商店中发现了一个坦克的资源包,其中包含了一个非常不错的场景,所以这里就直接使用其给出的场景:
在这里插入图片描述
场景布置完成之后,我们还需要设置游戏对象的Navigation,如果是障碍物则设置Navigation Area为not walkable,然后进行烘焙,以便AI寻路:
在这里插入图片描述

代码解析

在之前的游戏中,我们已经将单例工厂模式运用了很多次了,这里我们的重点是AI坦克的设计。思路和巡逻兵比较类似,如果没有发现玩家,就一直进行巡逻,如果发现了玩家,就进行跟随;在跟随的过程中,如果距离小于某个阈值,就发射炮弹攻击玩家。

其Update函数如下所示:

// Update is called once per frame
void Update ()
{
    gameover = GameDirector.getInstance().currentSceneController.isGameOver();
    if (!gameover)
    {
        target = GameDirector.getInstance().currentSceneController.getPlayerPos();
        // have been destoryed
        if (getHp() <= 0 && recycleEvent != null)
        {
            recycleEvent(this.gameObject);
        }
        else
        {
            // approach the player
            if (Vector3.Distance(transform.position, target) <= 30)
            {
                isPatrol = false;
                agent.autoBraking = true;
                agent.SetDestination(target);
            }
            // patrol
            else
            {
                patrol();
            }
        }
    }
    else
    {
        NavMeshAgent agent = GetComponent<NavMeshAgent>();
        agent.velocity = Vector3.zero;
        agent.ResetPath();
    }
}

在执行巡逻函数的时候,我们判断AI坦克是否处于巡逻状态,在没有发现玩家的时候,进行随机移动:

// patrol
private void patrol()
{
    if (isPatrol)
    {
        if(!agent.pathPending && agent.remainingDistance < 0.5f)
            GoOn();
    }
    else
    {
        agent.autoBraking = false;
        GoOn();
    }
    isPatrol = true;
}

随机移动的函数,是通过选取预设点来实现的:

// move to the next point
private void GoOn()
{
    agent.SetDestination(points[destPoint]);
    destPoint = (destPoint + 1) % points.Length;
}

然后就是需要我们考虑生命值的问题了,也就是子弹击中坦克之后扣减生命值,这里我们是直接找爆炸范围内的所有碰撞体,如果要设计的更加真实的话,可以考虑击中的部位是坦克的前方还是后方、根据击中的部位不同,造成的伤害也不同:

void OnCollisionEnter(Collision other)
{
    GameObjectFactory mf = Singleton<GameObjectFactory>.Instance;
    ParticleSystem explosion = mf.getPs();
    explosion.transform.position = transform.position;

    Collider[] colliders = Physics.OverlapSphere(transform.position, explosionRadius);
    for (int i = 0; i < colliders.Length; ++ i)
    {
        if (colliders[i].tag == "tankPlayer" && this.type == tankType.Enemy || colliders[i].tag == "tankEnemy" && this.type == tankType.Player)
        {
            float distance = Vector3.Distance(colliders[i].transform.position, transform.position);
            float hurt = 100f / distance;
            float current = colliders[i].GetComponent<Tank>().getHp();
            colliders[i].GetComponent<Tank>().setHp(current - hurt);
        }
    }

    explosion.Play();
    if (this.gameObject.activeSelf)
    {
        mf.recycleBullet(this.gameObject);
    }
}

然后,通过工厂来统一管理玩家、 AI、子弹、爆炸粒子系统等游戏对象,比如这里的爆炸粒子效果:

public ParticleSystem getPs()
{
    for (int i = 0; i < psContainer.Count; i++)
    {
        if (!psContainer[i].isPlaying)
            return psContainer[i];
    }
    ParticleSystem newPs = Instantiate<ParticleSystem>(ps);
    psContainer.Add(newPs);
    return newPs;
}

在添加的时候,这个资源包中也已经给出了现成的粒子效果:
在这里插入图片描述
非常方便,我们可以直接利用。
在控制移动方面,我们利用WASD来进行移动,空格进行开火:

if (Input.GetKey(KeyCode.W))
{
    action.moveForward();
}
if (Input.GetKey(KeyCode.S))
{
    action.moveBackWard();
}
if (Input.GetKeyDown(KeyCode.Space))
{
    action.shoot();
}

需要注意的是,这里我们判断了当前坦克是在前进还是后退,如果是在后退的话,为了符合我们的游戏习惯,左右方向需要反向:

if (action.isMovingForward())
    action.turn(offsetX);
else
    action.turn(-offsetX);

如果不进行反向操作,整个移动过程会变得非常奇怪,不符合一般规则。
最后是在本次的相机设置上,由于它已经有一个关于相机的脚本,所以我们可以直接沿用,并重新自主设计了其size的值,使得整个游戏看起来比较清晰:
在这里插入图片描述

可优化部分

  • 在之前所说的,我们可以根据子弹打中坦克的部位来设置不同的伤害值,这样会让游戏变得更有趣;
  • 另外,我们也可以参考上一次作业,给坦克加上血条,这样就可以很清晰的知道自己的生命值;
  • 在控制坦克移动的过程中,有些时候,遇见障碍物的时候转弯幅度会比较大,这一点也是有待优化的部分;
  • AI设计上,可以让AI更加智能,比如设计一个根据当前生命值的多少采取不同的策略:如果生命值偏低,那么AI会主动避开战斗,让其他的AI坦克先去围剿玩家,这样会使得游戏更加丰富。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值