开启辅助访问
 找回密码
 注册帐号

扫一扫,访问微社区

开发者专栏

关注:1571

当前位置:游戏蛮牛 技术专区 开发者专栏

__________________________________________________________________________________
开发者干货区版块规则:

  1、文章必须是图文形式。(至少2幅图)
      2、文章字数必须保持在1500字节以上。(编辑器右下角有字数检查)
      3、本版块只支持在游戏蛮牛原创首发,不支持转载。
      4、本版块回复不得无意义,如:顶、呵呵、不错......【真的会扣分的哦】
      5、......
__________________________________________________________________________________
查看: 2121|回复: 12

[云图] 简单的有限状态机设计---上

[复制链接]  [移动端链接]
排名
378
昨日变化

43

主题

346

帖子

3749

积分

Rank: 9Rank: 9Rank: 9

UID
42814
好友
31
蛮牛币
5691
威望
0
注册时间
2014-8-31
在线时间
1128 小时
最后登录
2017-2-21

专栏作家

QQ
发表于 2016-2-29 10:07:33 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
   大家好,这次给大家分享下最近学习到的有限状态机的简单制作,我们先介绍下什么是有限状态机。
   有限状态机(Finite State Machine) FSM 就是由一组状态组成,状态机会根据状态 、情况改变当下的状态。
比如我们要设计一个怪物的AI,首先这个怪物有好几组状态: 空闲状态 Idle、攻击状态Attack、巡逻状态、死亡状态。
当然我们还可以加入更多状态,再根据逻辑中的实现,让怪物判断当前环境需要使用什么状态,比如在看到玩家的时候,首先查看自己的血量超不超过50%,如果超过则改变到攻击状态和玩家战斗,否则进行逃跑状态,等。。
这个判断就是一个有限状态机的行为过程。我们可以用if elseif elseif 。。。大量的if else来实现,也或者使用switch  case 来实现判断,另外一种就是使用通用的框架来实现,我们首先使用switch来尝试制作。
   首先我们制作的一个例子,有一个AI角色,它的逻辑有
1,初始状态是巡逻状态 Patrol,这时候它会选择一个我们设定好的点循环移动,(当然我们可以自己修改得更复杂点)如果发现了玩家,它会进入追逐状态 Chase。
2 在追逐状态中,如果玩家离开了AI的攻击范围,则继续追逐,如果在攻击范围内,则进入攻击状态Attack ,发射子弹。
3 在攻击状态下,如果玩家离开攻击范围,再次追逐,如果玩家很远,离开了我们预设的距离,则需要忘记玩家,重新回到巡逻状态。

1.png

然后我们开始搭建一个简单的环境来演示一下 ,先添加一个地面,添加Tag:Player(玩家),PatrolPoint(寻路点),Bullet(子弹)
EnemyAI (怪物AI)
创建一个空物体,然后添加几个立方体,都添加上PatrolPoint标签,我们的怪物AI就可以把这些点当做巡逻点移动了
2.png


然后我们再创建一个子弹预设物,使用一个球形sphere 添加刚体,然后在添加一个脚本 bullet.cs
3.png
脚本的代码应该是这样的
[C#] 纯文本查看 复制代码
using UnityEngine;
using System.Collections;

public class Bullet : MonoBehaviour
{ 
    public float LifeTime = 3.0f;
    public int damage = 50;
	public float beamVelocity = 100;
    public int speed = 5;

	public void Go ()
	{
		GetComponent<Rigidbody>().AddForce(transform.forward * 10, ForceMode.VelocityChange);
	}
	
	void FixedUpdate()
	{
		GetComponent<Rigidbody>().AddForce(transform.forward * beamVelocity, ForceMode.Acceleration);
	}


    void Start()
    {
        Destroy(gameObject, LifeTime);
    }

    void Update()
    {
//transform.position += transform.forward * speed * Time.deltaTime;       
    }

    void OnCollisionEnter(Collision collision)
    {        
        Destroy(gameObject);
    }
}


接下来我们创建一个敌人AI,为了能简单演示,我就直接创建一个第一人称角色控制器 加上碰撞盒,然后添加一个脚本SimpleFSM.cs 。
该脚本能控制敌人AI的移动,这个脚本基于FSM状态制作的,所以还需要继承一个FSM的父类脚本FSM.cs
SimpleFSM.cs的代码
[C#] 纯文本查看 复制代码
using UnityEngine;
using System.Collections;

public class SimpleFSM : FSM 
{
	public enum FSMState
	{
		Patrol,
		Chase,
		Attack,
		Dead,
	}

	public float chaseDistance = 40.0f;
	public float attackDistance = 20.0f;
	public float arriveDistance = 3.0f;

	public Transform bulletSpawnPoint;

    //自身角色控制器
	private CharacterController controller;

	private Animation animComponent;

	//AI的状态
	public FSMState curState;

	//自身移动的速度
	public float walkSpeed = 80.0f;
	public float runSpeed = 160.0f;

	//自转速度
	public float curRotSpeed = 6.0f;

	//子弹
	public GameObject Bullet;

	//是否死亡和血量
	private bool bDead;
	private int health;

	//初始化状态,我们初始化为巡逻状态
	protected override void Initialize()
	{
		curState = FSMState.Patrol;

		bDead = false;
		elapsedTime = 0.0f;
		shootRate = 1.0f;
		health = 100;

		//取巡逻路点
		pointList = GameObject.FindGameObjectsWithTag("PatrolPoint");

		//取角色控制器
		controller = GetComponent<CharacterController>();
		//取自身的动画,我们为了演示简单些,这里就不使用
        //animComponent = GetComponent<Animation>();

		//寻找下一个路点
		FindNextPoint();

		//取玩家
		GameObject objPlayer = GameObject.FindGameObjectWithTag("Player");

		playerTransform = objPlayer.transform;

		if (!playerTransform)
            Debug.Log("玩家不存在"); 

	}


	protected override void FSMUpdate()
	{
		switch (curState)
		{
		case FSMState.Patrol: 
			UpdatePatrolState();
			break;
		case FSMState.Chase:
			UpdateChaseState();
			break;
		case FSMState.Attack:
			UpdateAttackState();
			break;
		case FSMState.Dead:
			UpdateDeadState();
			break;
		}

		//循环时间
		elapsedTime += Time.deltaTime;

		//如果血量小于0切换到死亡状态
		if (health <= 0)
			curState = FSMState.Dead;
	}


	protected void UpdatePatrolState()
	{
        //如果当前的点已经到达,寻找下一个随机巡逻点
		if (Vector3.Distance(transform.position, destPos) <= arriveDistance)
		{
			print ("Reached to the destination point, calculating the next point");
			FindNextPoint();
		}
		else if (Vector3.Distance(transform.position, playerTransform.position) <= chaseDistance)
		{
            //检查距离,如果玩家在附近,切换到追逐状态
			print ("Switch to chase state");
			curState = FSMState.Chase;
		}

		//自身旋转
		Quaternion targetRotation = Quaternion.LookRotation(destPos - transform.position);
		transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime*curRotSpeed);

		//向前移动
		controller.SimpleMove(transform.forward * Time.deltaTime * walkSpeed);
        //播放前进动画
        //animComponent.CrossFade("Walk");

	}


	protected void UpdateChaseState()
	{
		//设置玩家位置为目标位置
		destPos = playerTransform.position;

		//检查玩家距离,过近则切换到攻击状态
		//如果超出距离则重新回到巡逻状态
		float dist = Vector3.Distance(transform.position, playerTransform.position);

		if (dist <= attackDistance)
		{
			curState = FSMState.Attack;
		}
		else if (dist >= chaseDistance)
		{
			curState = FSMState.Patrol;
		}

		//自身旋转
		Quaternion targetRotation = Quaternion.LookRotation(destPos - transform.position);
		transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime*curRotSpeed);

		//向前移动
		controller.SimpleMove(transform.forward * Time.deltaTime * runSpeed);
        //播放追逐动画
        //animComponent.CrossFade("Run");

	}

	protected void UpdateAttackState()
	{
		Quaternion targetRotation;

		//设置玩家位置为目标位置
		destPos = playerTransform.position;

		//检查玩家距离,过近则攻击
		float dist = Vector3.Distance(transform.position, playerTransform.position);

		if (dist >= attackDistance && dist < chaseDistance)
		{
			curState = FSMState.Chase;//FSMState.Attack;
			return;
		}
		else if (dist >= chaseDistance)  //距离过远则切换追逐状态
		{
			curState = FSMState.Patrol;
			return;
		}

		targetRotation = Quaternion.LookRotation(destPos - transform.position);
		transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime*curRotSpeed);
		
		ShootBullet();
        //播放攻击动画
        //animComponent.CrossFade("StandingFire");
	}

	private void ShootBullet()
	{
		if (elapsedTime >= shootRate)
		{
			GameObject bulletObj = Instantiate(Bullet, bulletSpawnPoint.position, transform.rotation) as GameObject;
			bulletObj.GetComponent<Bullet>().Go();
			elapsedTime = 0.0f;
		}
	}


	protected void UpdateDeadState()
	{
		//播放死亡动画
		if (!bDead)
		{
			bDead = true;
			//animComponent.CrossFade("death");
		}
	}


	void onCollisionEnter(Collision collision)
	{
		//被攻击
		if (collision.gameObject.tag == "Bullet")
		{
			health -= collision.gameObject.GetComponent<Bullet>().damage;
		}
	}


	protected void FindNextPoint()
	{
		print ("寻找下一个巡逻点");
		int rndIndex = Random.Range(0, pointList.Length);
		destPos = pointList[rndIndex].transform.position ;


	}

}


父类FSM.cs的代码
[C#] 纯文本查看 复制代码
using UnityEngine;
using System.Collections;

public class FSM : MonoBehaviour 
{
	//玩家位置
	protected Transform playerTransform;

	//下一个巡逻点
	protected Vector3 destPos;

	//巡逻点表单
	protected GameObject[] pointList;

	//子弹信息
	protected float shootRate;
	protected float elapsedTime;

	protected virtual void Initialize() {}
	protected virtual void FSMUpdate() {}
	protected virtual void FSMFixedUpdate() {}

	//初始化信息
	void Start()
	{
		Initialize();
	}

    // 循环执行子类FSMUpdate方法
	void Update () 
	{
		FSMUpdate();	
	}

	void FixedUpdate()
	{
		FSMFixedUpdate();
	}
}



最后,我们实现一下玩家,同样,我们为了简单些,直接使用一个第一人称角色控制器,将Tag选择为Player 具体脚本就不写了,我们直接在Scene 场景中拖动player 玩家移动,
就可以看到我们的怪物AI的一系列AI反应了。 5.png

结尾发下工程代码,大家觉得好的话就帮忙回复下吧,谢谢






simpleFSM1.rar

22.02 KB, 下载次数: 162


回复

使用道具 举报

7日久生情
1979/5000
排名
268
昨日变化

1

主题

179

帖子

1979

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
27813
好友
1
蛮牛币
3992
威望
0
注册时间
2014-6-3
在线时间
493 小时
最后登录
2017-2-21
发表于 2016-2-29 10:48:06 | 显示全部楼层
学习了    ~   

回复 支持 反对

使用道具 举报

5熟悉之中
700/1000
排名
2941
昨日变化
3

0

主题

105

帖子

700

积分

Rank: 5Rank: 5

UID
103542
好友
0
蛮牛币
1455
威望
0
注册时间
2015-5-27
在线时间
272 小时
最后登录
2017-2-20
QQ
发表于 2016-2-29 13:33:24 | 显示全部楼层
学习学习

回复

使用道具 举报

排名
10133
昨日变化
7

1

主题

9

帖子

95

积分

Rank: 2Rank: 2

UID
121010
好友
0
蛮牛币
129
威望
0
注册时间
2015-9-4
在线时间
30 小时
最后登录
2017-2-18
发表于 2016-3-1 14:07:26 | 显示全部楼层
学到了  真的   代码写的真的很好

回复 支持 反对

使用道具 举报

匿名  发表于 2016-3-1 14:58:03
这个看看咯

回复

使用道具

4四处流浪
310/500
排名
5777
昨日变化
5

0

主题

75

帖子

310

积分

Rank: 4

UID
133594
好友
0
蛮牛币
431
威望
0
注册时间
2016-1-5
在线时间
91 小时
最后登录
2016-12-11
发表于 2016-3-1 15:32:12 | 显示全部楼层
支持原创,希望楼主继续分享有教程、有例子、有图有真相的良心教程

回复 支持 反对

使用道具 举报

6蛮牛粉丝
1161/1500
排名
1429
昨日变化

1

主题

186

帖子

1161

积分

Rank: 6Rank: 6Rank: 6

UID
2272
好友
1
蛮牛币
1184
威望
0
注册时间
2013-8-20
在线时间
364 小时
最后登录
2016-10-11

VIP

发表于 2016-5-18 14:29:33 | 显示全部楼层

学习学习

回复

使用道具 举报

排名
21539
昨日变化
16

0

主题

7

帖子

24

积分

Rank: 1

UID
73222
好友
0
蛮牛币
48
威望
0
注册时间
2015-2-5
在线时间
7 小时
最后登录
2016-6-19
发表于 2016-6-13 13:09:55 | 显示全部楼层
学习学习

回复

使用道具 举报

6蛮牛粉丝
1179/1500
排名
1631
昨日变化
1

14

主题

375

帖子

1179

积分

Rank: 6Rank: 6Rank: 6

UID
9671
好友
5
蛮牛币
1057
威望
0
注册时间
2013-12-5
在线时间
232 小时
最后登录
2017-2-20
发表于 2016-8-8 10:16:15 | 显示全部楼层
很好的。很好,学习一下子啦啦啦啦

回复 支持 反对

使用道具 举报

5熟悉之中
579/1000
排名
2800
昨日变化
1

10

主题

50

帖子

579

积分

Rank: 5Rank: 5

UID
136655
好友
0
蛮牛币
980
威望
0
注册时间
2016-2-15
在线时间
177 小时
最后登录
2017-2-17
QQ
发表于 2016-8-24 09:05:56 | 显示全部楼层
很不错,

回复

使用道具 举报

4四处流浪
472/500
排名
4698
昨日变化
7

4

主题

147

帖子

472

积分

Rank: 4

UID
175029
好友
5
蛮牛币
962
威望
0
注册时间
2016-10-12
在线时间
131 小时
最后登录
2017-1-20
发表于 2016-11-22 13:19:42 | 显示全部楼层
非常不错,学习了

回复

使用道具 举报

4四处流浪
331/500
排名
27074
昨日变化
20

0

主题

240

帖子

331

积分

Rank: 4

UID
134576
好友
0
蛮牛币
284
威望
0
注册时间
2016-1-13
在线时间
85 小时
最后登录
2017-2-20
发表于 2016-12-17 11:47:09 | 显示全部楼层

学到了  真的   代码写的真的很好

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则

快速回复 返回顶部 返回列表