Unity杂谈——敌人视野感知的实现

文章写于2016-1-31,后有修改。

本文为本人原创,转载请注明。

以下为正文

……………………………………………………………………………………………………………………………………

 

游戏AI常常分为三大部分——感知层、分析层(包含记忆层)、决策层。

在早期的游戏中,AI只有分析层和决策层,虽然效果也是不错的,但是缺乏了真实感,直到《神偷》在感知层做出了巨大的突破后,游戏才逐渐变得真实有趣。

其中最有意思的是敌人视野的感知,比如说《盟军敢死队》。(童年回忆)

本文目的在于用Unity来实现一个观测敌人的炮台,做出类似盟军敢死队中敌人视野范围的效果(见下图):

视野范围感知的基本原理:

在Unity中,用一个Sphere Collider与角度差的计算来模拟敌人的视角。

当敌人进入Sphere Collider中后,进行角度差计算,如果目标与自己的角度差小于自己的视角,并且朝目标发射一条射线没有其他物体阻挡时,判断为看见敌人。完成后的场景图如下:

 Unity杂谈——敌人视野感知的实现_第1张图片

难点是射线碰撞的运用:

RaycastHit hitInfo;

if(Physics.Raycast(this.transform.position+Vector3.up,vec,out hitInfo,20)){

GameObject gameObj = hitInfo.collider.gameObject;

if(gameObj.tag == "Player"){

//相关操作

}

}

//若射线与物体产生碰撞,则整个表达式为true,否则为false

//起点为this.transform.position+Vector3.up

//方向为vec,这里的vec是指向目标的向量

//out hitInfo碰撞如果返回true,hitInfo将包含碰到器碰撞的更多信息。

//20为射线的长度

//注意:最后还有一个LayerMask参数未写进去,用于只选定Layermask层内的碰撞器,其它层内碰撞器忽略。

 

 

 

然后是敌人(炮台)的状态机设计图:

 Unity杂谈——敌人视野感知的实现_第2张图片

这里要注意的是,敌人有两种模式,正常模式和警戒模式。

警戒模式中,敌人的视野范围和视角都会变得更大,同时视角灯光会呈现黄色。

警戒模式在进入攻击模式时开启,再次回到正常模式时才会关闭。

由以上的情况,C#代码分为几大板块:

①角度差计算:计算目标与自身的角度差。

②目标是否可见的判断:判断目标是否在自身视野范围内。

③状态机:每种状态的切换以及相应要做的动作。

④目标进入与离开感知范围:捕捉目标GameObject或者释放目标GameObject

 

接下来是完整的C#代码:(当时技术较菜,直接用 switch 来写了)

 

using UnityEngine;

using System.Collections;



public class Enemy01Rewrite : MonoBehaviour
{



    //声音

    AudioSource m_audio;

    public AudioClip m_Shot;

    public AudioClip m_EnterAlert;

    public AudioClip m_ExitAlert;

    public AudioClip m_ExitWait;



    //功能

    GameObject Target = null;

    float DeltaShotTime = 0.5f;//攻击间隔

    float AngleSpeed = 90f;//转动速度

    float PerceiveRadius = 8f;//感知范围

    float AlertTime = 10f;//警报时间

    float WaitTime = 4f;//失去目标等待时间

    float LightRange = 0f;//灯光距离(在Start()中根据SpotLight参数设定)

    float LightIntensity = 0f;//灯光亮度(在Start()中根据SpotLight参数设定)

    float ViewAngle = 60f;//视野角度



    bool isInView = false;//是否在扇形视野范围内

    bool isRayCast = false;//是否通过射线能看到

    bool isAlert = false;//是否警报

    bool isWait = false;//是否等待



    float TargetAngle = 0;

    float SelfAngle = 0;

    float AngleDiff = 0;

    float MinAngle = 3f;



    public GameObject m_Light;

    public GameObject m_Bullet;

    public GameObject m_Launcher;

    int state = 0;

    float time_shot = 0;

    float time_alert = 0;

    float time_wait = 0;



    //角度差计算函数

    void CalculateAngle()
    {

        if (Target != null)
        {

            float AtanAngle = (Mathf.Atan((Target.transform.position.z - this.transform.position.z) /

            (Target.transform.position.x - this.transform.position.x))

            * 180.0f / 3.14159f);

            //Debug.Log (this.transform.rotation.eulerAngles+"   "+AtanAngle);



            //1象限角度转换

            if ((Target.transform.position.z - this.transform.position.z) > 0

               &&

            (Target.transform.position.x - this.transform.position.x) > 0

               )
            {

                TargetAngle = 90f - AtanAngle;

                //Debug.Log ("象限1 "+TargetAngle);

            }



            //2象限角度转换

            if ((Target.transform.position.z - this.transform.position.z) <= 0

               &&

            (Target.transform.position.x - this.transform.position.x) > 0

               )
            {

                TargetAngle = 90f + -AtanAngle;

                //Debug.Log ("象限2 "+TargetAngle);

            }



            //3象限角度转换

            if ((Target.transform.position.z - this.transform.position.z) <= 0

               &&

            (Target.transform.position.x - this.transform.position.x) <= 0

               )
            {

                TargetAngle = 90f - AtanAngle + 180f;

                //Debug.Log ("象限3 "+TargetAngle);

            }



            //4象限角度转换

            if ((Target.transform.position.z - this.transform.position.z) > 0

               &&

            (Target.transform.position.x - this.transform.position.x) <= 0

               )
            {

                TargetAngle = 270f + -AtanAngle;

                //Debug.Log ("象限4 "+TargetAngle);

            }



            //调整TargetAngle

            float OriginTargetAngle = TargetAngle;

            if (Mathf.Abs(TargetAngle + 360 - this.transform.rotation.eulerAngles.y)

               <

            Mathf.Abs(TargetAngle - this.transform.rotation.eulerAngles.y)

               )
            {

                TargetAngle += 360f;

            }

            if (Mathf.Abs(TargetAngle - 360 - this.transform.rotation.eulerAngles.y)

               <

            Mathf.Abs(TargetAngle - this.transform.rotation.eulerAngles.y)

               )
            {

                TargetAngle -= 360f;

            }

            //输出角度差

            AngleDiff = Mathf.Abs(TargetAngle - this.transform.rotation.eulerAngles.y);

            Debug.Log("角度差:" + TargetAngle + "(" + OriginTargetAngle + ")-" + this.transform.rotation.eulerAngles.y + "=" + AngleDiff);

        }

    }



    /// 

    /// Judges the view.

    /// 

    //感知视野的相关计算 判断isRayCast和isInView

    void JudgeView()
    {



        //感知角度相关计算

        if (Target != null)
        {

            //指向玩家的向量计算

            Vector3 vec = new Vector3(Target.transform.position.x - this.transform.position.x,

                                    0f,

                                    Target.transform.position.z - this.transform.position.z);

            //射线碰撞判断

            RaycastHit hitInfo;

            if (Physics.Raycast(this.transform.position + Vector3.up, vec, out

                               hitInfo, 20))
            {

                GameObject gameObj = hitInfo.collider.gameObject;

                //Debug.Log("Object name is " + gameObj.name);

                if (gameObj.tag == "Player")//当射线碰撞目标为boot类型的物品 ,执行拾取操作

                {

                    //Debug.Log("Seen!");

                    isRayCast = true;

                }
                else
                {

                    isRayCast = false;

                }

            }

            //画出碰撞线

            Debug.DrawLine(this.transform.position, hitInfo.point, Color.red, 1);

            //视野中的射线碰撞判断结束

            //视野范围判断

            //物体在范围角度内,警戒模式下范围为原来1.5倍

            if (AngleDiff * 2 <

               (isAlert ? ViewAngle * 1.5f : ViewAngle)

               )
            {

                isInView = true;

            }
            else
            {

                isInView = false;

            }

            //Debug.Log ("角度差 "+AngleDiff);

        }

    }



    // Use this for initialization

    void Start()
    {

        LightRange = m_Light.GetComponent().range;

        LightIntensity = m_Light.GetComponent().intensity;

        this.GetComponent().radius = PerceiveRadius;

        m_audio = this.GetComponent();

    }

    // Update is called once per frame

    void Update()
    {

        //Debug.Log("state:" + state + " time_alert:" + time_alert);

        if (isAlert)
        {

            //警戒模式

            m_Light.GetComponent().range = LightRange * 2f;

            m_Light.GetComponent().color = new Color(1f, 1f, 0);

            m_Light.GetComponent().intensity = LightIntensity * 2f;

            m_Light.GetComponent().spotAngle = ViewAngle * 1.5f;

            this.GetComponent().radius = PerceiveRadius * 1.5f;

        }
        else
        {

            //正常模式

            m_Light.GetComponent().range = LightRange;

            m_Light.GetComponent().color = new Color(1f, 1f, 1f);

            m_Light.GetComponent().intensity = LightIntensity;

            m_Light.GetComponent().spotAngle = ViewAngle;

            this.GetComponent().radius = PerceiveRadius;

        }

        //计算角度差

        CalculateAngle();



        //感知视野判断(判断isRayCast与isInView)

        JudgeView();



        //状态机 共4个状态

        switch (state)
        {



            //正常模式——旋转扫视

            case 0:

                this.transform.Rotate(new Vector3(0, 1f, 0f), Time.deltaTime * AngleSpeed);

                //发现敌人 进入攻击模式

                if (isRayCast && isInView)
                {

                    if (!isAlert) m_audio.PlayOneShot(m_EnterAlert);

                    isAlert = true;

                    time_wait = 0;

                    state = 1;

                }

                break;



            //攻击模式——朝目标发射子弹

            case 1:

                //根据角度跟踪Target

                if (this.transform.rotation.eulerAngles.y < TargetAngle)
                {

                    if (AngleDiff >= MinAngle)
                    {

                        //顺时针旋转

                        this.transform.Rotate(new Vector3(0, 1f, 0f), Time.deltaTime * AngleSpeed);

                    }

                }
                else
                {

                    if (AngleDiff >= MinAngle)
                    {

                        //逆时针旋转

                        this.transform.Rotate(new Vector3(0, -1f, 0f), Time.deltaTime * AngleSpeed);

                    }

                }

                //子弹发射计时 子弹发射间隔

                time_shot += Time.deltaTime * 1;

                if (time_shot >= DeltaShotTime)
                {

                    Instantiate(m_Bullet, m_Launcher.transform.position, m_Launcher.transform.rotation);

                    time_shot = 0;

                    m_audio.PlayOneShot(m_Shot);

                }

                //敌人离开视野 进入等待模式

                if (!isRayCast)
                {

                    isWait = true;

                    time_wait = 0;

                    state = 2;

                }

                break;



            //敌人离开可见区域——等待

            case 2:

                //进入旋转警戒模式 计时

                time_wait += Time.deltaTime * 1;

                if (time_wait >= WaitTime)
                {

                    m_audio.PlayOneShot(m_ExitWait);

                    time_alert = 0;

                    isWait = false;

                    state = 3;

                }

                if (isRayCast && isInView)
                {

                    if (!isAlert) m_audio.PlayOneShot(m_EnterAlert);

                    isAlert = true;

                    time_wait = 0;

                    state = 1;

                }

                break;



            //警戒模式——旋转扫视

            case 3:

                //回到正常模式 计时

                time_alert += Time.deltaTime * 1;

                if (time_alert >= AlertTime)
                {

                    m_audio.PlayOneShot(m_ExitAlert);

                    time_alert = 0;

                    isAlert = false;

                    state = 0;

                }

                //发现敌人 进入攻击模式

                if (isRayCast && isInView)
                {

                    if (!isAlert) m_audio.PlayOneShot(m_EnterAlert);

                    isAlert = true;

                    time_wait = 0;

                    state = 1;

                }

                this.transform.Rotate(new Vector3(0, 1f, 0f), Time.deltaTime * AngleSpeed);

                break;

            default:

                break;

        }

    }

    //玩家进入感知层

    void OnTriggerEnter(Collider other)
    {

        if (other.gameObject.tag == "Player")
        {

            Target = other.gameObject;

            //提前计算角度差

            CalculateAngle();

            time_shot = 0;

        }

    }

    //玩家进入视野

    void OnTriggerStay(Collider other)
    {

        if (other.gameObject.tag == "Player")
        {

            if (Target == null)
            {

                Target = other.gameObject;

            }

        }

    }

    //玩家离开感知层

    void OnTriggerExit(Collider other)
    {

        if (other.gameObject.tag == "Player")
        {

            Target = null;

            isInView = false;

            isRayCast = false;

        }

    }

}

 

 

 

你可能感兴趣的:(算法研究,Unity开发,游戏AI)