Unity 基于状态的简单敌人AI

18 篇文章 0 订阅

本文给出的是一个基于状态枚举的AI代码,代码由两部分组成。第一部分EnemyAIController负责控制敌人的逻辑,第二部分RotationMap负责敌人空闲状态的路径算法。先给出Inspector面板中各个值的说明,最后上代码。
这里写图片描述

EnemyAIController.cs

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


public enum EnemyState
{
    Idle, TurnAround, Walk, Run, Attack, Dead
}


public delegate void ThinkMethod();

[RequireComponent(typeof(RotationMap))]
public class EnemyAIController : MonoBehaviour
{
    [Header("速度")]
    public float walkSpeed;
    public float runSpeed;
    public float rotateSpeed;

    [Header("时间")]
    public float thinkTimeOfIdle;
    public float thinkTimeOfAttack;
    public float animationDampTime;
    public float destroyDelayTime;

    [Header("距离")]
    public float inspectDistance;
    public float standAttackDistance;

    [Header("属性")]
    public EnemyState enemyState;
    public string playerTag;
    public float maxHealth;
    public float causeDamage;

    private float health;
    private bool resetMap;
    private GameObject player;
    private Quaternion targetRotation;

    private const int RotateAngle = 45;

    void Start()
    {
        health = maxHealth;
        player = GameObject.FindGameObjectWithTag(playerTag);
    }

    void Update()
    {
        ControlSlider();
        ControlRotationMap();

        if (enemyState != EnemyState.Dead)
        {
            ControlState();
            ControlPosition();
            ControlRotation();
        }
        ControlAnimation();
    }

    void ControlSlider()
    {
         Debug.DrawRay(transform.position, transform.up * 5, Color.red, 500);
        Slider slider = GetComponentInChildren<Slider>();
        slider.value = (float)health / maxHealth;
    }
    void ControlRotationMap()
    {
        switch (enemyState)
        {
            case EnemyState.Run:
                RotationMap map = GetComponent<RotationMap>();
                if(!resetMap)
                {
                    map.ResetMap();
                    resetMap = true;
                }
                break;
            default:
                resetMap = false;
                break;
        }
    }

    void ControlState()
    {
        float distance = Vector3.Distance(transform.position, player.transform.position);
        if (distance > inspectDistance)
        {
            InvokeThinkMethod(ThinkAttackState, ThinkIdleState, thinkTimeOfIdle);
        }
        else
        {
            if (distance > standAttackDistance)
                InvokeThinkMethod(ThinkIdleState,ThinkAttackState, thinkTimeOfAttack);
            else
                enemyState = EnemyState.Attack;
        }
    }

    void InvokeThinkMethod(ThinkMethod cancelMethod, ThinkMethod invokeMethod, float delay)
    {
        CancelInvoke(GetMethodName(cancelMethod));
        if (!IsInvoking(GetMethodName(invokeMethod)))
            Invoke(GetMethodName(invokeMethod), delay);
    }

    string GetMethodName(ThinkMethod th) { return th.Method.Name; }

    void ThinkIdleState()
    {
        enemyState = (EnemyState)Random.Range((int)EnemyState.Idle, (int)EnemyState.Walk);
        targetRotation = (enemyState == EnemyState.TurnAround) ? ThinkOfRotation() : targetRotation;
        if (enemyState == EnemyState.TurnAround)
            StartCoroutine(ChangeToWalkState());
    }

    void ThinkAttackState() { enemyState = EnemyState.Run; }

    IEnumerator ChangeToWalkState()
    {
        while (true)
        {
            float current = transform.rotation.eulerAngles.y;
            float target = targetRotation.eulerAngles.y;
            if (Mathf.RoundToInt(current) == Mathf.RoundToInt(target))
            {
                string mName = GetMethodName(ThinkIdleState);
                int resultIndex = (int)(targetRotation.eulerAngles.y / RotateAngle);
                float delay = (resultIndex % 2 == 0) ? thinkTimeOfIdle : thinkTimeOfIdle * Mathf.Sqrt(2);
                CancelInvoke(mName);
                Invoke(mName, delay);
                enemyState = EnemyState.Walk;
                break;
            }
            yield return null;
        }
    }

    void ControlAnimation()
    {

        Animator animator = GetComponent<Animator>();
        switch (enemyState)
        {
            case EnemyState.Idle:
                animator.SetFloat(Animator.StringToHash("speed"), 1, animationDampTime, Time.deltaTime);
                animator.SetBool(Animator.StringToHash("attack"), false);
                break;
            case EnemyState.TurnAround:
            case EnemyState.Walk:
                animator.SetFloat(Animator.StringToHash("speed"), 2, animationDampTime, Time.deltaTime);
                animator.SetBool(Animator.StringToHash("attack"), false);
                break;
            case EnemyState.Run:
                animator.SetFloat(Animator.StringToHash("speed"), 3, animationDampTime, Time.deltaTime);
                animator.SetBool(Animator.StringToHash("attack"), false);
                break;
            case EnemyState.Attack:
                animator.SetBool(Animator.StringToHash("attack"), true);
                animator.SetFloat(Animator.StringToHash("speed"), 0);
                break;
            case EnemyState.Dead:
                animator.SetBool(Animator.StringToHash("dead"), true);
                animator.SetFloat(Animator.StringToHash("speed"), 0);
                break;
        }
    }

    void ControlPosition()
    {
        CharacterController charaController = GetComponent<CharacterController>();
        switch (enemyState)
        {
            case EnemyState.Walk:
                charaController.SimpleMove(transform.forward * Time.deltaTime * walkSpeed);
                break;
            case EnemyState.Run:
                charaController.SimpleMove(transform.forward * Time.deltaTime * runSpeed);
                break;
        }
    }

    void ControlRotation()
    {
        switch (enemyState)
        {
            case EnemyState.TurnAround:
                transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * rotateSpeed);
                break;
            case EnemyState.Run:
            case EnemyState.Attack:
                Vector3 center = new Vector3(player.transform.position.x, transform.position.y, player.transform.position.z);
                transform.LookAt(center);
                break;
        }
    }

    Quaternion ThinkOfRotation()
    {
        RotationMap map = GetComponent<RotationMap>();
        List<int> array = map.GetArrayFromMap();

        int index = array[Random.Range(0, array.Count)];
        map.SetPositionToNext(index);

        Vector3 result = Vector3.zero;
        result.y+=index * RotateAngle;
        return Quaternion.Euler(result);
    }

    public void GetDamage(int damage)
    {
        health -= damage;
        if (health <= 0)
        {
            enemyState = EnemyState.Dead;
            Destroy(GetComponent<CharacterController>());
            Destroy(gameObject, destroyDelayTime);
            transform.position = new Vector3(transform.position.x, -1, transform.position.z);
        }
    }

}

RotationMap.cs

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

[System.Serializable]
public class Boolean2D
{
    [HideInInspector]
    public bool[] array;
    [HideInInspector]
    public int oneSideSize;
    public Boolean2D(int size)
    {
        array = new bool[size * size];
        oneSideSize = size;
    }

    public Boolean2D(Boolean2D copy)
    {
        array = new bool[copy.oneSideSize * copy.oneSideSize];
        oneSideSize = copy.oneSideSize;
        for (int i = 0; i < array.Length; i++)
            array[i] = copy.array[i];
    }

    public bool IsEmpty()
    {
        int count = 0;
        foreach(var value in array)
        {
            if (value)
                count++;
            if (count > 1)
                return false;
        }
        return true;
    }

    public bool SetElement(int i, int j, bool value)
    {
        if (i >= 0 && i < oneSideSize && j >= 0 && j < oneSideSize)
        {
            array[i * oneSideSize + j] = value;
            return true;
        }
        else
            return false;
    }
    public bool GetElement(int i, int j, out bool value)
    {
        if (i >= 0 && i < oneSideSize && j >= 0 && j < oneSideSize)
        {
            value = array[i * oneSideSize + j];
            return true;
        }
        else
        {
            value = false;
            return false;
        }
    }
}

[System.Serializable]
public class RotationMap : MonoBehaviour
{
    public Vector2 currentPosition;

    public int mapSize;

    public bool useObliqueLine;

    public bool canWalkBack;

    [HideInInspector]
    public int backUpSize;
    [HideInInspector]
    public Boolean2D pointArray;

    private Boolean2D _pointArray;
    private List<List<int>> _mapList;
    private Vector2 _position = Vector2.down;

    void Awake()
    {
        ResetMap();
        if (!canWalkBack)
            Debug.Log("canWalkBack取消时,请确保无孤立点且路径唯一,否则将出错!");
    }

    public void ResetMap()
    {
        if (_position == Vector2.down)
            _position = currentPosition;
        else
            currentPosition = _position;

        _pointArray = new Boolean2D(pointArray);
        _mapList = GetListFromBoolean2D(pointArray);
    }

    public List<int> GetArrayFromMap()
    {
        if (_pointArray.IsEmpty())
            _pointArray = new Boolean2D(pointArray);

        List<List<int>> mapList = canWalkBack ? _mapList : GetListFromBoolean2D(_pointArray);

       if (!canWalkBack)
           _pointArray.SetElement((int)currentPosition.x, (int)currentPosition.y, false);

       int index = (int)(currentPosition.x * mapSize + currentPosition.y);

        if (mapList[index] == null)
        {
            Debug.LogError("请将PositionInMap的初始值设在路径上!");
            return null;
        }

        return mapList[index];
    }

    public void SetPositionToNext(int index)
    {
        switch (index)
        {
            case 0:
                currentPosition.x--;
                break;
            case 1:
                currentPosition.x--;
                currentPosition.y++;
                break;
            case 2:
                currentPosition.y++;
                break;
            case 3:
                currentPosition.x++;
                currentPosition.y++;
                break;
            case 4:
                currentPosition.x++;
                break;
            case 5:
                currentPosition.x++;
                currentPosition.y--;
                break;
            case 6:
                currentPosition.y--;
                break;
            case 7:
                currentPosition.x--;
                currentPosition.y--;
                break;
        }
    }


    List<List<int>> GetListFromBoolean2D(Boolean2D pArray)
    {
        List<List<int>> mapList = new List<List<int>>();

        for (int i = 0; i < mapSize; i++)
        {
            for (int j = 0; j < mapSize; j++)
            {
                bool value;

                if (pArray.GetElement(i, j, out value) && !value)
                {
                    mapList.Add(null);
                    continue;
                }
                List<int> resultList = new List<int>();

                AddResult(i - 1, j, resultList,pArray, 0);
                AddResult(i, j + 1, resultList,pArray, 2);
                AddResult(i + 1, j, resultList,pArray, 4);
                AddResult(i, j - 1, resultList,pArray, 6);

               if(useObliqueLine)
               {
                   AddResult(i - 1, j + 1, resultList,pArray, 1);
                   AddResult(i + 1, j + 1, resultList,pArray, 3);
                   AddResult(i + 1, j - 1, resultList,pArray, 5);
                   AddResult(i - 1, j - 1, resultList,pArray, 7);
               }

                mapList.Add(resultList);
            }
        }
        return mapList;
    }

    void AddResult(int i, int j, List<int> list,Boolean2D pArray, int result)
    {
        bool value;
        if (pArray.GetElement(i, j, out value) && value)
            list.Add(result);
    }

}

RotationMapWindow.cs(放在Editor目录下)

using UnityEngine;
using System.Collections;
using UnityEditor;

[CustomEditor(typeof(RotationMap))]
public class RotationMapWindow : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        RotationMap map = (RotationMap)target;

        if (map.backUpSize != map.mapSize)
        {
            map.pointArray = new Boolean2D(map.mapSize);
            map.backUpSize = map.mapSize;
        }

        if (map.pointArray == null) return;

        GUILayout.BeginVertical();

        for (int i = 0; i < map.mapSize; i++)
        {
            GUILayout.BeginHorizontal();

            for (int j = 0; j < map.mapSize; j++)
            {
                bool value;
                if (map.pointArray.GetElement(i, j, out value))
                    map.pointArray.SetElement(i, j, EditorGUILayout.Toggle(value, GUILayout.MaxWidth(15)));
            }

            GUILayout.EndHorizontal();
        }

        GUILayout.EndVertical();
    }
}
  • 5
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值