本人游戏策划一枚,爱好游戏设计开发
今天实现的内容是怪物AI,看了一些网上的AI,不是特别符合我的需求,于是就自己研究了一种AI,大致和魔兽类的RPG游戏效果差不多。
AI效果如下:
1. 将怪物分为如下几个状态:
待机状态(该状态内有3种行为:原地呼吸、原地观察、和游走,可通过权重配置待机状态下三种行为发生的比例);
警戒状态(怪物向玩家发出警告,并持续盯着玩家);
追击状态(怪物跑向玩家)
返回状态(跑回出生位置,该状态下不再理会玩家)
2. 可以为怪物配置各项状态的数值
游走半径,待机状态下,怪物游走时不会超出这个范围,根据出生点计算
警戒半径,当玩家与怪物之间距离小于警戒半径时进入警戒状态,根据怪物实时位置计算
自卫半径,当玩家与怪物之间距离小于自卫半径时进入追击状态
追击半径,怪物追击玩家时,自身不会超出这个范围,超出后跑回出生点,可以理解为最大活动范围
攻击距离,当玩家与怪物之间的距离小于攻击距离时触发战斗(我这边是进入之前做好的回合制战斗场景)
3. 各项数值设定的关系如下
比较合理的关系是:追击半径>警戒半径>自卫半径>攻击距离,游走半径只需要小于追击半径即可
1)自卫半径不建议大于警戒半径,否则就无法触发警戒状态,直接开始追击了
2)攻击距离不建议大于自卫半径,否则就无法触发追击状态,直接开始战斗了
3)游走半径不建议大于追击半径,否则怪物可能刚刚开始追击就返回出生点
我这边自己测试时,设置的属性如下:
接下来就是脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class MonsterWander : MonoBehaviour {
private GameObject playerUnit; //获取玩家单位
private Animator thisAnimator; //自身动画组件
private Vector3 initialPosition; //初始位置
public float wanderRadius; //游走半径,移动状态下,如果超出游走半径会返回出生位置
public float alertRadius; //警戒半径,玩家进入后怪物会发出警告,并一直面朝玩家
public float defendRadius; //自卫半径,玩家进入后怪物会追击玩家,当距离<攻击距离则会发动攻击(或者触发战斗)
public float chaseRadius; //追击半径,当怪物超出追击半径后会放弃追击,返回追击起始位置
public float attackRange; //攻击距离
public float walkSpeed; //移动速度
public float runSpeed; //跑动速度
public float turnSpeed; //转身速度,建议0.1
private enum MonsterState
{
STAND, //原地呼吸
CHECK, //原地观察
WALK, //移动
WARN, //盯着玩家
CHASE, //追击玩家
RETURN //超出追击范围后返回
}
private MonsterState currentState = MonsterState.STAND; //默认状态为原地呼吸
public float[] actionWeight = { 3000, 3000, 4000 }; //设置待机时各种动作的权重,顺序依次为呼吸、观察、移动
public float actRestTme; //更换待机指令的间隔时间
private float lastActTime; //最近一次指令时间
private float diatanceToPlayer; //怪物与玩家的距离
private float diatanceToInitial; //怪物与初始位置的距离
private Quaternion targetRotation; //怪物的目标朝向
private bool is_Warned = false;
private bool is_Running = false;
void Start () {
playerUnit = GameObject.FindGameObjectWithTag("Player");
thisAnimator = GetComponent();
//保存初始位置信息
initialPosition = gameObject.GetComponent().position;
//检查并修正怪物设置
//1. 自卫半径不大于警戒半径,否则就无法触发警戒状态,直接开始追击了
defendRadius = Mathf.Min(alertRadius, defendRadius);
//2. 攻击距离不大于自卫半径,否则就无法触发追击状态,直接开始战斗了
attackRange = Mathf.Min(defendRadius, attackRange);
//3. 游走半径不大于追击半径,否则怪物可能刚刚开始追击就返回出生点
wanderRadius = Mathf.Min(chaseRadius, wanderRadius);
//随机一个待机动作
RandomAction();
}
///
/// 根据权重随机待机指令
///
void RandomAction()
{
//更新行动时间
lastActTime = Time.time;
//根据权重随机
float number = Random.Range(0, actionWeight[0] + actionWeight[1] + actionWeight[2]);
if (number <= actionWeight[0])
{
currentState = MonsterState.STAND;
thisAnimator.SetTrigger("Stand");
}
else if (actionWeight[0] < number && number <= actionWeight[0] + actionWeight[1])
{
currentState = MonsterState.CHECK;
thisAnimator.SetTrigger("Check");
}
if (actionWeight[0] + actionWeight[1] < number && number <= actionWeight[0] + actionWeight[1] + actionWeight[2])
{
currentState = MonsterState.WALK;
//随机一个朝向
targetRotation = Quaternion.Euler(0, Random.Range(1, 5) * 90, 0);
thisAnimator.SetTrigger("Walk");
}
}
void Update () {
switch (currentState)
{
//待机状态,等待actRestTme后重新随机指令
case MonsterState.STAND:
if (Time.time - lastActTime > actRestTme)
{
RandomAction(); //随机切换指令
}
//该状态下的检测指令
EnemyDistanceCheck();
break;
//待机状态,由于观察动画时间较长,并希望动画完整播放,故等待时间是根据一个完整动画的播放长度,而不是指令间隔时间
case MonsterState.CHECK:
if (Time.time - lastActTime > thisAnimator.GetCurrentAnimatorStateInfo(0).length)
{
RandomAction(); //随机切换指令
}
//该状态下的检测指令
EnemyDistanceCheck();
break;
//游走,根据状态随机时生成的目标位置修改朝向,并向前移动
case MonsterState.WALK:
transform.Translate(Vector3.forward * Time.deltaTime * walkSpeed);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, turnSpeed);
if (Time.time - lastActTime > actRestTme)
{
RandomAction(); //随机切换指令
}
//该状态下的检测指令
WanderRadiusCheck();
break;
//警戒状态,播放一次警告动画和声音,并持续朝向玩家位置
case MonsterState.WARN:
if (!is_Warned)
{
thisAnimator.SetTrigger("Warn");
gameObject.GetComponent().Play();
is_Warned = true;
}
//持续朝向玩家位置
targetRotation = Quaternion.LookRotation(playerUnit.transform.position - transform.position, Vector3.up);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, turnSpeed);
//该状态下的检测指令
WarningCheck();
break;
//追击状态,朝着玩家跑去
case MonsterState.CHASE:
if (!is_Running)
{
thisAnimator.SetTrigger("Run");
is_Running = true;
}
transform.Translate(Vector3.forward * Time.deltaTime * runSpeed);
//朝向玩家位置
targetRotation = Quaternion.LookRotation(playerUnit.transform.position - transform.position, Vector3.up);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, turnSpeed);
//该状态下的检测指令
ChaseRadiusCheck();
break;
//返回状态,超出追击范围后返回出生位置
case MonsterState.RETURN:
//朝向初始位置移动
targetRotation = Quaternion.LookRotation(initialPosition - transform.position, Vector3.up);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, turnSpeed);
transform.Translate(Vector3.forward * Time.deltaTime * runSpeed);
//该状态下的检测指令
ReturnCheck();
break;
}
}
///
/// 原地呼吸、观察状态的检测
///
void EnemyDistanceCheck()
{
diatanceToPlayer = Vector3.Distance(playerUnit.transform.position, transform.position);
if (diatanceToPlayer < attackRange)
{
SceneManager.LoadScene("Battle");
}
else if (diatanceToPlayer < defendRadius)
{
currentState = MonsterState.CHASE;
}
else if (diatanceToPlayer < alertRadius)
{
currentState = MonsterState.WARN;
}
}
///
/// 警告状态下的检测,用于启动追击及取消警戒状态
///
void WarningCheck()
{
diatanceToPlayer = Vector3.Distance(playerUnit.transform.position, transform.position);
if (diatanceToPlayer < defendRadius)
{
is_Warned = false;
currentState = MonsterState.CHASE;
}
if (diatanceToPlayer > alertRadius)
{
is_Warned = false;
RandomAction();
}
}
///
/// 游走状态检测,检测敌人距离及游走是否越界
///
void WanderRadiusCheck()
{
diatanceToPlayer = Vector3.Distance(playerUnit.transform.position, transform.position);
diatanceToInitial = Vector3.Distance(transform.position, initialPosition);
if (diatanceToPlayer < attackRange)
{
SceneManager.LoadScene("Battle");
}
else if (diatanceToPlayer < defendRadius)
{
currentState = MonsterState.CHASE;
}
else if (diatanceToPlayer < alertRadius)
{
currentState = MonsterState.WARN;
}
if (diatanceToInitial > wanderRadius)
{
//朝向调整为初始方向
targetRotation = Quaternion.LookRotation(initialPosition - transform.position, Vector3.up);
}
}
///
/// 追击状态检测,检测敌人是否进入攻击范围以及是否离开警戒范围
///
void ChaseRadiusCheck()
{
diatanceToPlayer = Vector3.Distance(playerUnit.transform.position, transform.position);
diatanceToInitial = Vector3.Distance(transform.position, initialPosition);
if (diatanceToPlayer < attackRange)
{
SceneManager.LoadScene("Battle");
}
//如果超出追击范围或者敌人的距离超出警戒距离就返回
if (diatanceToInitial > chaseRadius || diatanceToPlayer > alertRadius)
{
currentState = MonsterState.RETURN;
}
}
///
/// 超出追击半径,返回状态的检测,不再检测敌人距离
///
void ReturnCheck()
{
diatanceToInitial = Vector3.Distance(transform.position, initialPosition);
//如果已经接近初始位置,则随机一个待机状态
if (diatanceToInitial < 0.5f)
{
is_Running = false;
RandomAction();
}
}
}
以上脚本经过多次测试,运行结果均较为理想。