/// <summary>
/// 逻辑分析
/// 每隔20帧刷新玩家位置
/// 若可以看见玩家,启动一段时间 ActiveTime=15s
/// 启动时每隔30帧检测是否可以看见玩家
/// 如果看得见
/// 向玩家移动
/// 刷新 ActiveTime
/// 否则触发自动寻路
/// 每隔20帧检测是否卡住
/// 卡住时随机移动
/// 非启动状态下
/// 若看见玩家则启动
/// 否则Idle
/// </summary>
由于敌人自己有一个Collider2D所以需要layer排除Enemy层。
/// <summary>
/// 检测是否可以感知 需要2维Collider
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="maxDis"></param>
/// <param name="layer"></param>
/// <returns></returns>
public static bool PerceiveObject(Vector2 a,GameObject b,float maxDis, int layer)
{
Ray2D ray = new Ray2D(a, ((Vector2)b.transform.position - a).normalized);
//Debug.DrawRay(ray.origin, ray.direction * 100, Color.yellow);
//RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction, maxDis, layer);
if (hit == false)
{
return false;
}
//Debug.Log(hit.collider.gameObject);
return hit.collider.gameObject == b;
}
/*
* Author : Jk_Chen
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Pathfinding;
/// <summary>
/// 逻辑分析
/// 每隔20帧刷新玩家位置
/// 若可以看见玩家,启动一段时间 ActiveTime=15s
/// 启动时每隔30帧检测是否可以看见玩家
/// 如果看得见
/// 向玩家移动
/// 刷新 ActiveTime
/// 否则触发自动寻路
/// 每隔20帧检测是否卡住
/// 卡住时随机移动
/// 非启动状态下
/// 若看见玩家则启动
/// 否则Idle
/// </summary>
public class EnemyAI : MonoBehaviour
{
Helper H;
const float _ActiveTime = 15;
float ActiveTime;// 激活时间
float MaxSpeed = 2; // 最大速度
float SpeedLimit = 0.01f; // 更新动画器的速度阈值
public bool useAI; // 是否AI行动
Path path; // 路径
int nextP = 0; // 当前要去的点下标
Vector2 target; // 每隔一段时间刷新玩家的位置
bool canSee; //是否可以看到玩家
float radius = 10; // 感知范围
Seeker seeker;
Rigidbody2D _rigidbody;
/// <summary>
/// 检测射线需要取出Enemy层
/// </summary>
int layer;
void Start()
{
layer = ~(1 << LayerMask.NameToLayer("Enemy"));
useAI = false;
H = Helper.instance;
_rigidbody = GetComponent<Rigidbody2D>();
seeker = GetComponent<Seeker>();
}
void FixedUpdate()
{
if (Time.frameCount % 20 == 0)
target = ObjectLibrary.player.transform.position;
if (Time.frameCount % 30 == 0)
canSee = Helper.PerceiveObject(transform.position, ObjectLibrary.player, radius, layer);
if (canSee)
{
ActiveTime = _ActiveTime;
// 结束Idle
resTime = 0;
}
if (resTime > 0)
{
MoveRandom();
EndAI("随机走");
}
if (ActiveTime > 0)
{
if (Time.frameCount % 20 == 0)
prePos = transform.position;
else if(canSee)
{
MoveTarget();
EndAI("往角色走");
}
else
{
if (!useAI) StartAI();
MoveAI();
}
CheckStatic();
ActiveTime -= Time.deltaTime;
if (ActiveTime <= 0)
{
EndAI("激活时间结束");
}
}
else
{
if (resTime <= 0)
{
randomV = Helper.instance.RandomDirection();
resTime = timeLen;
}
}
if (ActiveTime > 0)
{
transform.Find("fight").gameObject.SetActive(true);
}
else
{
transform.Find("fight").gameObject.SetActive(false);
}
}
/// <summary>
/// 向目标出发
/// </summary>
void MoveTarget()
{
Vector2 vector = (target - (Vector2)transform.position).normalized;
Vector2 delta = Time.deltaTime * vector * MaxSpeed;
RefreshAnimation(delta);
Vector2 pos = (Vector2)transform.position + delta;
_rigidbody.MovePosition(pos);
}
Vector2 prePos = new Vector2(-1e9f, -1e9f);
float resTime;
Vector2 randomV;
float timeLen = 1f;
/// <summary>
/// 向下一个点移动
/// </summary>
void MoveAI()
{
if (path == null || !useAI)
{
return;
}
// 获取向量
Vector2 vector = (path.vectorPath[nextP] - transform.position);
if (vector.magnitude > SpeedLimit)
{
Vector2 delta = Time.deltaTime * vector.normalized * MaxSpeed;
Vector2 pos = (Vector2)transform.position + delta;
RefreshAnimation(delta);
_rigidbody.MovePosition(pos);
//由于使用的是移动而不是施加力,所以可能出现超过的情况
//第一种处理办法是修改移动向量的长度使得当好落在目标点
// 但是这种办法会导致移动过程速度时快时慢,镜头移动不流畅
// 第二种办法是循环检测下一个目标点是否在当前移动后,已经越过了
while (delta.magnitude > Vector2.Distance(transform.position, path.vectorPath[nextP]))
{
nextP++;
if (nextP == path.vectorPath.Count)
{
EndAI("已到达终点");
break;
}
}
}
else
{
nextP++;
if (nextP == path.vectorPath.Count)
{
EndAI("已到达终点");
}
}
}
/// <summary>
/// 向随机生成的方向移动
/// </summary>
void MoveRandom()
{
Vector2 delta = Time.deltaTime * randomV * MaxSpeed;
RefreshAnimation(delta);
Vector2 pos = (Vector2)transform.position + delta;
_rigidbody.MovePosition(pos);
resTime -= Time.deltaTime;
}
/// <summary>
/// 检测是否停止
/// </summary>
void CheckStatic()
{
if (Time.frameCount % 20 == 19 && Vector2.Distance(prePos, transform.position) < 0.01f)
{
EndAI("检测静止");
randomV = Helper.instance.RandomDirection();
resTime = timeLen;
}
}
/// <summary>
/// 开启AI模式
/// </summary>
/// <param name="pos"></param>
public void StartAI()
{
path = null;
useAI = true;
SeekPath();
}
/// <summary>
/// 结束AI
/// </summary>
public void EndAI(string info)
{
useAI = false;
path = null;
//Debug.Log("END AI: " + info);
}
public void EndAI()
{
useAI = false;
path = null;
}
/// <summary>
/// 向目标寻路
/// </summary>
/// <param name="pos"></param>
void SeekPath()
{
if (useAI && path != null && !seeker.IsDone()) return;
seeker.StartPath(transform.position, target, OnPathComplete);
}
/// <summary>
/// 路径查找完成时的初始化
/// </summary>
/// <param name="p"></param>
void OnPathComplete(Path p)
{
if (!p.error)
{
Debug.Log("Success Seek");
path = p;
nextP = 0;
}
else
{
EndAI("");
}
}
/// <summary>
/// 刷新动画显示
/// </summary>
/// <param name="vector"></param>
void RefreshAnimation(Vector2 vector)
{
if (Mathf.Abs(vector.x) < 0.01) return;
if (vector.x < 0)
{
transform.localScale = new Vector3(-1, 1, 1);
}
else
{
transform.localScale = new Vector3(1, 1, 1);
}
}
}