Unity敌人的AI

26 篇文章 1 订阅
1 篇文章 0 订阅

好久没有更新我的博客啦!今天提供三种方法类描述一下我们的敌人AI状态如何完成的哦!

我个人习惯呢!开发项目从难到易的去开发项目。所以写博客时一般把最难的写在最前面哦!因为最难的你都会啦!其余的不更是So Easy !各位读者要习惯啦!当然了你也可以从后往前阅读哦!不多说了,进入主题吧!

方法一:使用FSM有限状态机完成

我们都知道现在的人工智能都很强大的,作为一名程序员,这是你必须会的哦!今天我们就来讲讲敌人的AI的人工智能吧!先来张图片吧!有了图以后才能更好的开发游戏嘛!不知道大家在开发游戏项目中是否是这样呢?
状态转换行为图
UML简单类图

下面呢!我们使用一个坦克的案例来来编写一个属于我们自己的状态机。
坦克要求很简单的。有身子有炮杆就行。
首先创建一个FSM基类:

using UnityEngine;
using System.Collections;

public class FSM : MonoBehaviour
{
    protected Transform playerTransform;//玩家坦克



    protected Vector3 destPos;
    protected GameObject[] pointList;

    protected float shootRate;//敌人发射子弹的频率
    protected float elapsedTime;//发射的时间计时器

    public Transform turrent { get; set; }//敌人炮塔的位置
    public Transform bulletSpawnPoint { get; set; }//敌人炮弹的位置


    /// <summary>
    /// 初始化方法
    /// </summary>
    protected virtual void Initialize() { }

    /// <summary>
    /// 更新方法
    /// </summary>
    protected virtual void FSMUpdate() { }

    /// <summary>
    /// 固定帧率的更新方法
    /// </summary>
    protected virtual void FSMFixedUpdate() { }

    void Start()
    {
        Initialize();
    }

    void Update()
    {
        FSMUpdate();
    }

    void FixedUpdate()
    {
        FSMFixedUpdate();
    }

}

然后来一个类来继承父类FSM。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public enum Transition
{
    None = 0,
    SawPlayer,
    ReacherPlayer,
    LoatPlayer,
    NoHealth,
}

public enum FSMStateID
{
    None = 0,
    Patroling,
    Chaseing,
    Attacking,
    Dead,

}


public class AdvanceFSM : FSM
{

    private List<FSMState> fsmStates;

    private FSMState currentState;
    public FSMState CurrentState
    {
        get
        {
            return currentState;
        }

    }

    private FSMStateID currentStateID;
    public FSMStateID CurrentStateID
    {
        get
        {
            return currentStateID;
        }

    }

    public AdvanceFSM()
    {
        fsmStates = new List<FSMState>();
    }

    public void AddFSMState(FSMState fsmState)
    {
        if (fsmState == null)
        {
            Debug.LogError("FSM ERROR : Null reference is not allowed !");
            return;
        }

        if (fsmStates.Count == 0)
        {
            fsmStates.Add(fsmState);
            currentState = fsmState;
            currentStateID = fsmState.ID;

        }

        //遍历集合中所有的状态
        //防止添加重复的状态,那么我们就不再添加了;
        foreach (FSMState state in fsmStates)
        {

            if (state.ID == fsmState.ID)
            {
                Debug.LogWarning("FSM ERROR :Trying to add a state was already indise the list !");
                return;
            }
        }

        fsmStates.Add(fsmState);

    }

    public void DeleteState(FSMStateID fsmState)
    {

        if (fsmState == FSMStateID.None)
        {
            Debug.LogError("FSM ERROR : Null reference is not allowed !");
            return;
        }

        foreach (FSMState state in fsmStates)
        {
            if (state.ID == fsmState)
            {
                fsmStates.Remove(state);
                return;
            }
        }

    }


    public void PreformTransition(Transition trans)
    {
        if (trans == Transition.None)
        {
            Debug.LogError("FSM ERROR : Null transition is not allowed");
            return;
        }

        FSMStateID id = currentState.GetOutState(trans);
        if (id == FSMStateID.None)
        {
            Debug.LogWarning("FSM ERROR :Current state does not have a target state for this transition ");
            return;
        }

        currentStateID = id;

        foreach (FSMState state in fsmStates)
        {
            if (state.ID == CurrentStateID)
            {
                currentState = state;
                break;
            }
        }
    }
}

接着来一个具体的坦克类。

using UnityEngine;
using System.Collections;

public class NPCTankCntroller : AdvanceFSM
{
    public GameObject bullet;
    public int health;

    protected override void Initialize()
    {
        health = 100;

        GameObject objPlayer = GameObject.FindGameObjectWithTag("Player");

        playerTransform = objPlayer.transform;

        turrent = gameObject.transform.GetChild(0).transform;
        bulletSpawnPoint = turrent.GetChild(0).transform;
        shootRate = 2.0f;
        elapsedTime = 0.0f;


        ConstructFSM();

    }

    private void ConstructFSM()
    {
        pointList = GameObject.FindGameObjectsWithTag("WandarPoint");
        //做一个GameObject和Transform类型的转换
        Transform[] waypoints = new Transform[pointList.Length];
        int i = 0;
        foreach (GameObject item in pointList)
        {
            waypoints[i] = item.transform;
            i++;
        }

        PatorlState patrol = new PatorlState(waypoints);
        //看见玩家转化为追逐状态
        patrol.AddTransition(Transition.SawPlayer, FSMStateID.Chaseing);
        patrol.AddTransition(Transition.ReacherPlayer, FSMStateID.Attacking);
        patrol.AddTransition(Transition.NoHealth, FSMStateID.Dead);


        ChaseState chase = new ChaseState(waypoints);
        chase.AddTransition(Transition.LoatPlayer, FSMStateID.Patroling);
        chase.AddTransition(Transition.ReacherPlayer, FSMStateID.Attacking);
        chase.AddTransition(Transition.NoHealth, FSMStateID.Dead);


        AttackState attack = new AttackState(waypoints);
        attack.AddTransition(Transition.LoatPlayer, FSMStateID.Patroling);
        attack.AddTransition(Transition.SawPlayer, FSMStateID.Chaseing);
        attack.AddTransition(Transition.NoHealth, FSMStateID.Dead);

        DeadState dead = new DeadState();


        AddFSMState(patrol);
        AddFSMState(chase);
        AddFSMState(attack);
        AddFSMState(dead);
    }

    protected override void FSMFixedUpdate()
    {

        CurrentState.Reason(playerTransform, transform);
        CurrentState.Act(playerTransform, transform);
    }

    protected override void FSMUpdate()
    {
        elapsedTime += Time.deltaTime;
    }

    public void SetTransition(Transition t)
    {
        PreformTransition(t);
    }

    public void ShootBullet()
    {
        if (elapsedTime >= shootRate)
        {
            Instantiate(bullet, bulletSpawnPoint.position, bulletSpawnPoint.rotation);
            elapsedTime = 0.0f;
        }

    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.CompareTag("Bullet"))
        {
            health -= 50;
        }

        if (health <= 0)
        {
            SetTransition(Transition.NoHealth);
            Explode();
        }

    }

    private void Explode()
    {
        float rndX = Random.Range(10.0f, 30.0f);
        float rndZ = Random.Range(10.0f, 30.0f);
        for (int i = 0; i < 3; i++)
        {
            //Rigidbody.AddExplosionForce 添加爆炸力 应用一个力到刚体来模拟爆炸效果。
            //爆炸力
            //球体的中心。
            //球体的半径,在半径内才有爆炸效果。
            //调节爆炸出现的位置,使其看起来像掀起对象。
            GetComponent<Rigidbody>().AddExplosionForce(100.0f, transform.position - new Vector3(rndX, 10.0f, rndZ), 40.0f, 10.0f);
            //Transform.TransformDirection 变换方向 变换方向从局部坐标转换到世界坐标。 
            //这个操作不会受到变换的缩放和位置的影响。返回的向量与direction有同样的长度。 
            GetComponent<Rigidbody>().velocity = transform.TransformDirection(new Vector3(rndX, 20.0f, rndZ));
        }

        Destroy(gameObject, 1.5f);
    }
}

下面来一个状态的基类:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public abstract class FSMState
{

    protected Dictionary<Transition, FSMStateID> map = new Dictionary<Transition, FSMStateID>();




    protected FSMStateID stateID;

    public FSMStateID ID
    {
        get
        {
            return stateID;
        }

    }

    protected Vector3 destPos;//巡逻的具体目标点
    protected Transform[] waypoints;//巡逻的四个点
    protected float curSpeed;//坦克行进的速度
    protected float curRotSpeed;//坦克转弯的速度

    /// <summary>
    /// 进行状态改变的方法
    /// </summary>
    /// <param name="player"></param>
    /// <param name="npc"></param>
    public abstract void Reason(Transform player, Transform npc);
    /// <summary>
    /// 当坦克处于当前状态是要发起的行为
    /// </summary>
    /// <param name="player"></param>
    /// <param name="npc"></param>
    public abstract void Act(Transform player, Transform npc);
    public void AddTransition(Transition transition, FSMStateID id)
    {
        if (transition == Transition.None || id == FSMStateID.None)
        {
            Debug.LogError("FSMState ERROR: Null transition not allowed ");
            return;
        }

        if (map.ContainsKey(transition))
        {
            Debug.LogWarning("FSMState ERROR:transition is already inside the map");
        }

        map.Add(transition, id);
        Debug.Log("Addea: " + transition + "WithID" + id);
    }

    public void DeleteTransition(Transition trans)
    {
        if (trans == Transition.None)
        {
            Debug.LogError("FSMState ERROR: Null transition not allowed ");
            return;
        }

        if (map.ContainsKey(trans))
        {
            map.Remove(trans);
            return;
        }

        Debug.Log("FSMState ERROR :Transition passed was not no this state List");

    }


    public FSMStateID GetOutState(Transition trans)
    {
        if (trans == Transition.None)
        {
            Debug.LogError("FSMState ERROR :NullTransition is not allowed ");
            return FSMStateID.None;
        }
        if (map.ContainsKey(trans))
        {
            return map[trans];
        }

        Debug.LogWarning("FSMState ERROR :" + trans + "Transition passed to the state was not on the Dictionary! ");
        return FSMStateID.None;
    }

    /// <summary>
    /// 寻找巡逻的目标点
    /// </summary>
    protected void FindNextPoint()
    {
        int rndIndex = Random.Range(0, waypoints.Length);
        destPos = waypoints[rndIndex].transform.position;

    }



    /// <summary>
    /// 是否在循环范围
    /// </summary>
    /// <param name="transform"></param>坦克的位置
    /// <param name="pos"></param>pos目标点的位置(玩家坦克的位置)
    /// <returns></returns>
    protected bool IsInCurrentRange(Transform trans, Vector3 pos)
    {
        float xPos = Mathf.Abs(pos.x - trans.position.x);
        float zPos = Mathf.Abs(pos.z - trans.position.z);

        if (xPos <= 50 && zPos <= 50)
        {
            return true;
        }
        else
        {
            return false;
        }
    }


}

下面就是具体的各个状态类了:

using UnityEngine;
using System.Collections;
using System;

public class PatorlState : FSMState
{

    public PatorlState(Transform[] wp)
    {
        waypoints = wp;
        stateID = FSMStateID.Patroling;

        curRotSpeed = 1.0f;
        curSpeed = 100.0f;

    }


    public override void Act(Transform player, Transform npc)
    {
        if (Vector3.Distance(npc.position, destPos) <= 100.0f)
        {
            FindNextPoint();
        }

        Quaternion targetRotation = Quaternion.LookRotation(destPos - npc.position);
        npc.rotation = Quaternion.Slerp(npc.rotation, targetRotation, Time.deltaTime * curRotSpeed);
        npc.transform.Translate(Vector3.forward * Time.deltaTime * curSpeed);

    }

    public override void Reason(Transform player, Transform npc)
    {
        float dist = Vector3.Distance(npc.position, player.position);
        if (dist < 200.0f)
        {
            npc.GetComponent<NPCTankCntroller>().SetTransition(Transition.ReacherPlayer);
        }
        else if (dist < 300.0f)
        {
            Debug.Log("Switch to Chase State!");
            npc.GetComponent<NPCTankCntroller>().SetTransition(Transition.SawPlayer);
        }



    }
}
using UnityEngine;
using System.Collections;
using System;

public class ChaseState : FSMState
{
    public ChaseState(Transform[] wp)
    {
        waypoints = wp;
        stateID = FSMStateID.Chaseing;

        curRotSpeed = 1.0f;
        curSpeed = 100.0f;
        FindNextPoint();
    }

    public override void Act(Transform player, Transform npc)
    {
        destPos = player.transform.position;
        Quaternion targetPoisition = Quaternion.LookRotation(player.position - npc.position);
        npc.rotation = Quaternion.Slerp(npc.rotation, targetPoisition, Time.deltaTime * curRotSpeed);
        npc.transform.Translate(Vector3.forward * curSpeed * Time.deltaTime);
    }

    public override void Reason(Transform player, Transform npc)
    {
        destPos = player.position;
        float dist = Vector3.Distance(npc.position ,destPos);
        if (dist <200.0f)
        {
            npc.GetComponent<NPCTankCntroller>().SetTransition(Transition.ReacherPlayer);
        }
        if (dist >=300f)
        {
            npc.GetComponent<NPCTankCntroller>().SetTransition(Transition.LoatPlayer);
        }
    }
}
using UnityEngine;
using System.Collections;
using System;

public class AttackState : FSMState
{
    public AttackState(Transform[] wp)
    {
        waypoints = wp;
        stateID = FSMStateID.Attacking;
        curRotSpeed = 1.0f;
        curSpeed = 100.0f;

        FindNextPoint();


    }


    public override void Act(Transform player, Transform npc)
    {
        destPos = player.position;

        Transform turrent = npc.GetComponent<NPCTankCntroller>().turrent;
        Quaternion turrentRotation = Quaternion.LookRotation(destPos - turrent.position);
        turrent.rotation = Quaternion.Slerp(turrent.rotation, turrentRotation, Time.deltaTime * curRotSpeed);

        npc.GetComponent<NPCTankCntroller>().ShootBullet();

    }

    public override void Reason(Transform player, Transform npc)
    {
        float dist = Vector3.Distance(npc.position, player.position);

        if (dist >= 200 && dist < 300)
        {
            npc.GetComponent<NPCTankCntroller>().SetTransition(Transition.SawPlayer);
        }
        else if (dist >= 300.0f)
        {
            npc.GetComponent<NPCTankCntroller>().SetTransition(Transition.LoatPlayer);
        }

        //if (npc.GetComponent<NPCTankCntroller>().health <= 0)
        //{
        //    npc.GetComponent<NPCTankCntroller>().SetTransition(Transition.NoHealth);
        //}

    }
}
using UnityEngine;
using System.Collections;
using System;

public class DeadState : FSMState
{

    public DeadState()
    {
        stateID = FSMStateID.Dead;
    }


    public override void Act(Transform player, Transform npc)
    {
        //DO Nothing
    }

    public override void Reason(Transform player, Transform npc)
    {
        //DO Nothing
    }

}

哎!代码确实有点多啊!都不想往下写了!但是作为一名程序员这又算神马呢?。。。

方法二:使用协成程序完成

使用协成程序来完成敌人与玩家的AI。
案例需求:敌人自动在目标点自动巡逻。发现玩家时去追逐玩家,当玩家跑的太快时,继续执行巡逻任务。
案例场景:四个巡逻点。(空物体标记)一个玩家小球。一个敌人(Cube代替。)

Scene
列表

思路提供:我们都知道协同是我们unity中用来执行另一段进程的任务。就像多线程一样的。可惜我们的unity并不支持多线程哦!
那么我们就要思考了在巡逻中什么时候使用协程呢?首先我们到达巡逻点是,待一会。发现敌人时开启另一段线程来处理发现敌人的事情。

代码编译:
首先给我们的四个标记点添加一个脚本,用来提供标记的。脚本内容不必复杂。一个变量搞定!

这里写图片描述

using UnityEngine;
using System.Collections;

public class Point : MonoBehaviour
{
    public Point nextPoint;
}

然后我们来写核心的代码哦!

using UnityEngine;
using System.Collections;


public class MoveFindEnemy : MonoBehaviour
{

    private Point[] points;
    private Point currentPoint;
    private Transform enemy;
    public float speed = 5f;
    private int index = 0;




    // Use this for initialization
    void Start()
    {
        enemy = GameObject.Find("Enemy").GetComponent<Transform>();
        points = GameObject.Find("Ponits").GetComponentsInChildren<Point>();
        for (int i = 0; i < points.Length; i++)
        {
            if (i == points.Length - 1)
            {
                points[i].nextPoint = points[0];
            }
            else
            {
                points[i].nextPoint = points[i + 1];
            }

        }

        index = Random.Range(0, 4);
        currentPoint = points[index];
        StartCoroutine(Move());

    }

    private IEnumerator Move()
    {
        while (true)
        {
            if (Vector3.Distance(enemy.transform.position, transform.position) < 10.0f)
            {
                yield return StartCoroutine(FollowEnemy());
            }

            if (Vector3.Distance(currentPoint.transform.position, transform.position) < 1.0f)
            {

                currentPoint = currentPoint.nextPoint;
                yield return new WaitForSeconds(2f);
            }

            Vector3 v = currentPoint.transform.position;
            v.y = transform.position.y;
            transform.LookAt(v);
            transform.Translate(Vector3.forward * speed * Time.deltaTime);
            yield return new WaitForFixedUpdate();



        }

    }

    private IEnumerator FollowEnemy()
    {
        while (true)
        {
            if (Vector3.Distance(enemy.position, transform.position) < 1.0f)
            {
                Debug.Log("追到你了!!!");

                yield return new WaitForSeconds(2f);
            }

            if (Vector3.Distance(enemy.position, transform.position) > 10.0f)
            {

                Debug.Log("追不到你了!!!");
                yield break;
            }

            transform.LookAt(enemy);
            transform.Translate(Vector3.forward * speed * Time.deltaTime);
            yield return new WaitForEndOfFrame();
        }
    }


}

方法二还是比较简单的吧!下面还有更简单的哦!往下看哦!加油哦!

方法三:使用BehaviorDesigner 插件来完成

1.首先安装BehaviorDesigner插件,(插件作为一名程序员应该都会有办法弄到的哦!这里就不提供啦!)然后安装它的一个扩展包,Behavior Designer - Movement_Pack。这样你的BehaviorDesigner编译器中就会有了一个Movement了。

这里写图片描述

这个主要利用unity中自带的Navigation组件。
2.下面通过小案例来讲解:
首先创建一个平面,调整合适的大小,然后创建一个胶囊体作为主角。
然后创建两个空物体作为要巡逻的点。
接着给胶囊体添加Behaviour Tree组件。

这里写图片描述

然后进入到编辑面板。

这里写图片描述

然后在Task中添加一个巡逻的状态。

这里写图片描述

然后将巡逻点添加上去。设置一下速度等参数。
这里写图片描述

最后烘焙一下导航组件和给敌人添加上Nav Mesh Agent 组件。
运行时可以看见敌人在巡逻点之间来回巡逻的。

3.接着我们继续完善这个案例。

当我们的主角在看见任何一个敌人时都会有追击的状态。

那么我们就必须先判断有没有看到,看到之后再追,否则继续巡逻。
在Conditionals中有一个这样的行为。CanseeObject状态。
这里写图片描述

添加上了以后会发现scene视窗中出现一个扇形的视野。

这里写图片描述

可以自己修改扇形的参数。
这里写图片描述

接着传递敌人:

这里写图片描述

下面就是如何把看见的敌人传递给我们要追的状态(另外一个Seek)呢?(即实现先看再追的过程)这时就需要一个全局变量了!
添加变量

这里写图片描述

然后通过我们自定义的变量去赋值
这里写图片描述

这里写图片描述

这里写图片描述

然后给他们添加一个序列任务。

这里写图片描述

然后做如下连接:

这里写图片描述

再将Sequence 改成这样表示可以打断右边的执行

这里写图片描述

然后运行如下:
这里写图片描述

未看见敌人。
这里写图片描述

看见敌人并追逐敌人。
这里写图片描述

追到敌人自己就会停下。那是因为自己的行为树所有状态已经完成了!要知道行为树中状态都只会执行一次的!

参数说明:

这里写图片描述

Slef表示可以中断比自己层的运行。
Lower Priority 表示可以中断比自己优先级低的。
Both:表示两种情况都会中断的。
一般情况下行为树的执行都是从左到右执行的。不会发生中断的。

.。。。。。。
写了好久,截图都累死了!希望我的付出会对疑惑中的你有点帮助哦!!!

三种方法,当然了实现的效果都差不了多少。但是作为程序员的我建议的能自己写的就代码实现吧!毕竟作为程序员的我们都是代码的搬运工嘛!还有这次代码并没有写好多的注释,不是写,而是写注释太累了哦!相信大家都能看的懂的哦!加油啦!最后感谢大家的阅读哦!

  • 26
    点赞
  • 75
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值