刚自学Unity,发现Unity 之所以很火,因为很多系统,编辑器,还有一些设计很强大,用起来都比较容易维护。(当然除了:发布程序跨平台之外)
自学中,发现,一个挺有趣的系统:Animator,这个在Unity 中,有一个独立的子面板:Animator的一个动画状态机切换可视化管理系统:FSM,如下图:
(游戏中,动画系统是一个比较复杂的系统,单单一个角色,可能动画就包含了N种,如果要想很好的管理,最好的就是使用FSM)
由于比较好奇,就着手写了个简单的FSM 测试项目:
1.1:修正了1.0版本的一个Pipeline的多个条件之间为:“与”关系,与部份代码优化;
1.2:修正了1.1版本的【删除】了FSM内states都执行update的测试代码;
并增加了对函数类型的参数测试,使用;
增加了:“飞起”状态,测试;
增加了模拟每种状态之间的切换时的输出不同信息;
1.3:完善了,Transition, ChangedStatePipeline,PipelineCondition与FSM之关系
让每个以上三类对象,都分别在各各FSM,或是他们类之间最大化可共用;
增加了FSM静态方法:
FSM.TransitionTo(FSM fsm, string srcStateName, string targetStateName,
List<PipelineCondition> conditions, bool addInSameStateNameTransition
)
使外部调用代部量大大减少,并提高可读性.
1.4:优化:FSM中的AnyState与CurState的进入,与离开的触发位置(放在Update中处理,即下一帧中处理)
添加:FSM中的Blackboard黑板数据设计(有点像BT树中的黑板数据)
1.4.1: 修复了,Param中的_dic[ParamType.Boolean] == XXXX, 的BUG;因为把:ParamType.Boolean写成了:ParamType.Func;
2014-11-24 今天下午用了,1小时,重写了AS3的runtime;
源项目下载:
FSM 1.0
FSM 1.1
FSM 1.2
FSM 1.3
FSM 1.4
FSM 1.4.1
以上,就是单个动画的状态机可视化管理界面;
any state就是每次都会处理的状态;(任意状态)
idle(闲置状态)
run(跑动状态)
dead(死亡状态)
大家应该也发现了,idle、run、dead这些状态之间都有一些带有方向的线段连接起来;
这些带方向的线段就是:Transition(对应类:Transition.cs),意思就是设置了一些过渡参数,来从,原来的状态,过渡到:指定的状态;
如图:
而每个Transition其实是可以有多个:过渡管道;(对应类:ChangStatePipeline.cs),就是管理,状态之间可以过渡的参数管理
以上是单个Pipeline,一个Transition中,含多个Pipeline的话,左边的视图中,带方向的的Transition外观会变成:带有三个方向的箭头:
如图:
以上,一个Transition有,两个Pipeline,第一个是当speed>10时(红色线部份),会从:idle过渡到:run状态,
而第二个Pipeline是,hp>50(看绿色线的部份),也会从:idle过渡到:run状态;
多个Pipeline是“或”关系
多个Pipeline之间的逻辑是:or,运算符:||的关系,意思是:
if (speed > 10 || hp > 50)
// transilated to run
而一个Pipeline里的,含多个Condition,他们是“与”关系(1.1版修正的功能)
如果一个Condition里,设置了,speed,Greater:10,并且,再加了一个:hp,Greater:0,意思是:
这个Condition判断就必须要成立以下条件:
if (speed > 10 && hp > 0)
// condition success
其中,上图中,大家都应该看到了:Condition栏中的:speed,Greater,或是hp,Greater这些设置过渡的参数;
这些参数都对应:“源项目”的FSMParam类,如图:
FSM相关的声明:(注释挺少的,不过代码600多行,我就不贴上来了,有提供百度网盘下载就可以了,往下看)
///
/// 状态机中,当前状态发生改变的事件委托声明
///
public delegate void CurStateChangedEventHandler(FSM sender);
///
/// 状态机中,当有参数发生变化时的事件委托声明
///
public delegate void ParamsChangedEventHandler(FSM sender, ParamsChangedEvent args);
///
/// 过渡参数为函数委托的声明
///
public delegate bool FSMFuncParamHandler(params object[] objs);
///
/// 状态机中,当有参数发生变化时的参数类声明
///
public class ParamsChangedEvent : EventArgs;
///
/// 有限状态机
/// (
/// 设计思路,参考:Unity 4.3.2f 版本的Mecanim动画系统中的状态机,
/// 根据使用上的功能,来猜想实现思路,当然可能我的实现方式不是最好,
/// 如果大家还有比较好的一些见解,那大家一起交流交流吧。
/// 该博文就不多说其它的,我的文采也不好,直接上代码吧。
/// )
/// @author : Jave.Lin(afeng)
/// @time : 2014-03-11
/// @version: 1.0
///
public class FSM;
///
/// 状态
///
public abstract class State;
///
/// 状态之间的过度处理(可包含多个过度管道)
///
public class Transition;
///
/// 状态之间的过度管道(可包含多个过度条件)
///
public class ChangStatePipeline;
///
/// 过度参数的成立条件类型
///
[Flags]
public enum ConditionType;
///
/// 过度参数类型
///
public enum FSMParamType;
///
/// 过度参数是函数类型
///
public class FSMParamFunc : FSMParam;
///
/// 过度参数是值类型
///
public class FSMParamValue : FSMParam;
///
/// 过度的参数类
///
public abstract class FSMParam;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace FSMTestingProject
{
///
/// 测试:有限状态机
/// @author : Jave.Lin(afeng)
/// @time : 2014-03-11
/// @version: 1.0
///
class Program
{
static void Main(string[] args)
{
Console.ForegroundColor = ConsoleColor.Green;
var fsm = new FSM("Jave's FSM");
Console.WriteLine(string.Format("Start testing :{0}", fsm));
// add event show cur state changed info
fsm.CurStateChangedEvent += (CurStateChangedEventHandler)((sender) =>
{
Console.WriteLine(string.Format("fsm : {0}, had changed cur state, last state : {1}, cur state : {2}", fsm, fsm.LastState, fsm.CurState));
});
// add event show params changed info
fsm.ParamsChangedEvent += (ParamsChangedEventHandler)((sender, paramChangedArgs) =>
{
Console.WriteLine(string.Format(
"param:{1}, src:{2}, to: {3}",
fsm, paramChangedArgs.CurValue.Name, paramChangedArgs.LastValue.Value, paramChangedArgs.CurValue.Value));
});
// add some states
fsm.AddState(new MyState(fsm, "idle"));
fsm.AddState(new MyState(fsm, "run"));
fsm.AddState(new MyState(fsm, "dead"));
fsm.AnyState = new MyState(fsm, "any state");
// set cur state
fsm.SetCurState(fsm.GetState("idle"));
// add fsm some params
fsm.AddParam(new FSMParamValue("speed") { Value = 0 }); // init speed = 0;
fsm.AddParam(new FSMParamValue("hp") { Value = 100 }); // init hp = 100
// idle state add some transition to run
var idleState = fsm.GetState("idle");
var runState = fsm.GetState("run");
if (idleState != null && runState != null)
{
// create transition
var toRunTransition = new Transition(idleState, runState);
// create transition pipelines
var pipeline = new ChangStatePipeline(idleState, runState);
// create pipelines conditions , and add condition
var condition = new PipelineCondition(pipeline, "speed", 10, ConditionType.GreaterEquals);
pipeline.AddCondition(condition);
// transition add pipeline
toRunTransition.AddPipeline(pipeline);
// at last, state add transition
idleState.AddTransition(toRunTransition);
}
// run back to idle
if (idleState != null && runState != null)
{
// create transition
var toRunTransition = new Transition(runState, idleState);
// create transition pipelines
var pipeline = new ChangStatePipeline(runState, idleState);
// create pipelines conditions , and add condition
var condition = new PipelineCondition(pipeline, "speed", 10, ConditionType.Less);
pipeline.AddCondition(condition);
// transition add pipeline
toRunTransition.AddPipeline(pipeline);
// at last, state add transition
runState.AddTransition(toRunTransition);
}
// idle or run state add some transition to dead state's transitions
var deadState = fsm.GetState("dead");
if (idleState != null && deadState != null && runState != null)
{
// idle to dead
var trans1 = ToDeadStateTransition(idleState, deadState);
idleState.AddTransition(trans1);
// run to dead
var trans2 = ToDeadStateTransition(runState, deadState);
runState.AddTransition(trans2);
}
//const int updatePerMs = (int)(1f / 60f * 1000);
const int updatePerMs = (int)(1000);
bool speedAdding = true;
// dummy fsm update task
var task = Task.Factory.StartNew((Action)(() =>
{
while (true)
{
fsm.Update();
var speedParam = fsm.GetParam("speed");
if (Convert.ToDouble(speedParam.Value) <= 0)
speedAdding = true;
else if (Convert.ToDouble(speedParam.Value) > 10)
speedAdding = false;
fsm.SetParamValue("speed", Convert.ToDouble(speedParam.Value) + (speedAdding ? 1 : -1)); // to run when speed greater and equals 10, back to idle when speed less than 10
var hpParam = fsm.GetParam("hp");
if (Convert.ToDouble(hpParam.Value) > 0)
{
fsm.SetParamValue("hp", Convert.ToDouble(hpParam.Value) - 5); // minus 10 hp per frame, to dead when hp less and equals 0
}
Thread.Sleep(updatePerMs);
}
}));
Console.ReadLine();
}
static Transition ToDeadStateTransition(State srcState, State targetState)
{
var result = new Transition(srcState, targetState);
var pipeline = new ChangStatePipeline(srcState, targetState);
var condition = new PipelineCondition(pipeline, "hp", 0, ConditionType.LessEquals);
pipeline.AddCondition(condition);
result.AddPipeline(pipeline);
return result;
}
}
class MyState : State
{
public MyState(FSM fsm, string name)
: base(fsm, name)
{
}
public override void EnterState()
{
Console.WriteLine(string.Format("->Enter State : {0}", Name));
}
public override void LeaveState()
{
Console.WriteLine(string.Format("<-LeaveState : {0}", Name));
}
public override void Excute()
{
Console.WriteLine(string.Format("$$Excute State : {0}", Name));
}
}
}
项目运行后,可以看到:
先有:idle 的speed一步一步的+1,到speed >= 10时,状态就变成了:run;
然后再有:run,从speed = 11; 变到speed < 10时,状态变成了:idle;
如图:
总结:
以前我只知道状态机,只用于管理游戏中,一些角色的AI;
但自从使用了Unity 的Mecanim之后,就发现,状态机,可以用于很多地方;
什么时候应用:
当有一个系统,他有N种可例举出来(有限)的状态,并且这些状态之间可能会指定情况下(FSM中的参数:FSMParam),会转换另一种状态的系统结构时;
那么采用FSM是最好的;
自动战斗系统状态:
角色当前身边的怪物,多于一个时,都使用,群攻,否则都有单体攻击;
战士技能,抓敌人过来:
当前可能是处于跑动状态:
当敌人离自己不能直接攻击的范围(参数) && 自己已学习抓人技能(参数)&& 技能CD为0 && CD消耗条件成立(MP,或是AP)
(参数成立,则转换状态)则使用抓敌人过来的技能(另一个状态:使用技能抓人技能状态)
VR:天气状态:
晴天时,显示太阳;
阴天时,太阳不出现;
下雨天时,太阳不出,出一些云;
太阳雨时,太阳出现,也有一些云,并且下雨;
在应对比较多变,复杂,可例举的情况,作对一一对应处理的逻辑,都写在对应的状态中;
而不用传统的一排N长的:switch....case...分支处理;
还有一种比较复杂的情况,FSM中的子状态内,可能会包含另一个FSM;这种情况,在本源项目中,可直接使用,完全不影响;
再来看看一些还不算是很复杂的动画状态管理的“蜘蛛网”状态图:
下图文章的链接是:http://www.gamasutra.com/blogs/HeikkiTormala/20121214/183567/
期待优化
其实从上图,可以看到,这些还不算很复杂的动画状态时,就在可视化管理时,如化的恐怖难管;
箭头多,还是一回事,最主要的是,每个箭头:Transition,都不能复用;这是个“痛”!
如果Unity 再把这些很多逻辑相似的Transition在可视化时可共用处理,那可能会好管理一些;
而在代码中,共用是比较容易的;
转载的话,注声明:
转载于:http://blog.csdn.net/linjf520/article/details/21010637