http://blog.csdn.net/litaog00/article/details/50483189
最近做项目的时候用到了状态机,网上搜了一下帖子,大部分都是简单介绍使用方法的,讲解的详细的很少。作者
好好研究了一番,感觉很有必要和大家分享一下。技术和灵感,来源于网络,共享于网络~
好多个human模型有好多个动作片段,这些片段又分出若干大类和若干小类,一个主角可能会应用到所有的这些片
段,当满足一定条件时从一类动作中随机取出一个播放,进游戏的时候有的动作条件满足有的不满足随着玩的进度
又可能会满足#@#@#%@!!&*&,,,,略复杂,总之就是有好多动作(200个左右),有的能播,有的不能播
,作者(头硬)一心想用一个状态机来解决所有问题,所以就用代码创建吧,毕竟这么多动作,让策划去摆,费时
费力还容易出错,特别在设置transition的条件时,呵呵,那就代码创建吧,既能体现情怀又能提高自己。
首先网上搜了搜,只有雨松momo的一篇http://www.xuanyusong.com/archives/2811(冒犯引用一下),介绍了大
概思想和用法,但是比较旧,要知道作者用的可是unity3d5.2.2版的,不同及需要注意的地方依次列出:
1.AnimatorController所在的库由UnityEditorInternal变为UnityEditor.Animations
2.AnimatorController里可以创建layer,每个layer有个statemachine(我们通常叫‘某个角色的状态机’好
像不合适,应该叫‘某个角色的AnimatorController’)
- //创建controller
- AnimatorController controller=AnimatorController.CreateAnimatorControllerAtPath("Assets/Resources/CharacterAnim/Play2Controllor_2.controller");
- AnimatorControllerLayer layer=controller.layers[0];
- AnimatorStateMachine machine=layer.stateMachine;
3.创建参数,
- controller.AddParameter("zhan->pa",AnimatorControllerParameterType.Trigger);
参数类型有4种:int、float、bool、trigger(作者之前用4.2版的时候还没有trigger,只有int、float
、bool、vector)
有了参数,就有判断条件了,判断条件的类型有
- AnimatorConditionMode.If;
- AnimatorConditionMode.IfNot;
- AnimatorConditionMode.Equals;
- AnimatorConditionMode.NotEqual;
- AnimatorConditionMode.Greater;
- AnimatorConditionMode.Less;
4.添加内容
添加oneState
- oneState=machine.AddState(名字,位置)
- AnimatorStateTransition defaultTransition=machine.AddAnyStateTransition(oneState);
创建二级子状态机
- AnimatorStateMachine sub2Machine=machine.AddStateMachine(名字,位置)
- AnimatorStateTransition transition1=machine.AddAnyStateTransition(oneState);
- AnimatorStateTransition transition2=sub2Machine.AddAnyStateTransition(twoState);
transition1和transition2不是从同一个anyState出发的,而是从各自状态机的anyState出发的(子状态
机也有个anyState,但是在controller编辑器里看不出来)
5.雨松momo的帖子里有网友问到:如果fbx里有多个clip,用AssetDatabase.LoadAssetAtPath(xxx.fbx)
如何取到呢?那么答案来了
- Object[] objects=AssetDatabase.LoadAllAssetsAtPath("Assets/Art/Animation/"+fbxName);
- for(int m=0;m<objects.Length;m++)
- {
- <span style="white-space:pre"> </span>if(objects[m].GetType()==typeof(AnimationClip) && !objects[m].name.Contains("Take 001"))
- {
- AnimationClip clip=(AnimationClip)objects[m];
- if(clip.name.Equals("chushidongzuo001"))
- {
- defaultState.motion=clip;
- }
- else
- {
- addAnimationClip(clip,actionDetails,machine,haveSub3);
- }
- }
- }
注意到了吗?AssetDatabase.LoadAllAssetsAtPath可以把fbx里的所有信息取到,clip、骨骼节点、mesh。。。,
然后过滤一下下。而AssetDatabase.LoadAssetAtPath<AnimationClip>("xxx.FBX")只能取到此fbx下第一个clip。
好了,就写到这里,下面把整个代码贴出来,供大家参考~
- using UnityEngine;
- using System.Collections.Generic;
- using UnityEditor;
- //using UnityEditorInternal;
- using UnityEditor.Animations;
- using System.IO;
- public class Play2ControllerGenerator : EditorWindow {
- public enum ActionType:int
- {
- undefine=0,
- zhan,
- tang,
- cewo,
- pa,
- guodu,
- kaichang,
- jieshu,
- wu
- }
- public enum ActionStyle:int
- {
- normal=0,
- qingchun,
- keai,
- gaoleng,
- yundong,
- chengshu,
- xinggan
- }
- private static string[] sub2MachineNames=new string[]{"undefine","zhan","tang","cewo","pa","guodu","kaichang","jieshu","wu"};
- private static string[] sub3MachineNames=new string[]{"normal","qingchun","keai","gaoleng","yundong","chengshu","xinggan"};
- private const string ParamNameTransitionAction="transitionAction";
- private const string ParamNameActionId="actionId";
- private const float TransitionDuring=0.4f;
- //
- [MenuItem("LoveLive/核心玩法2/生成状态机")]
- private static void addWindow()
- {
- Rect rect=new Rect(0,0,600,300);
- Play2ControllerGenerator window=EditorWindow.GetWindowWithRect<Play2ControllerGenerator>(rect,true,"生成核心玩法状态机(省得策划自己摆了)");
- window.minSize=new Vector2(300,100);
- window.maxSize=new Vector2(600,300);
- window.Show();
- }
- void OnGUI()
- {
- EditorGUILayout.BeginVertical();
- EditorGUILayout.LabelField("需要"Assets/Resources/Config/action.txt"");
- EditorGUILayout.LabelField("需要"Assets/Art/Animation"下的动作文件");
- EditorGUILayout.EndVertical();
- EditorGUILayout.Separator();
- EditorGUILayout.LabelField("生成的状态机在这个位置:"Assets/Resources/CharacterAnim/Play2Controllor_2.controller"");
- EditorGUILayout.BeginHorizontal();
- if(GUILayout.Button("点击生成(需要一点时间)"))
- {
- generateStateMachine();
- this.ShowNotification(new GUIContent("创建完毕"));
- }
- EditorGUILayout.EndHorizontal();
- }
- private void generateStateMachine()
- {
- //加载action文件
- Dictionary<string,ActionDetail> actionDetails=parseActionFile("Assets/Resources/Config/action.txt");
- //创建controller
- AnimatorController controller=AnimatorController.CreateAnimatorControllerAtPath("Assets/Resources/CharacterAnim/Play2Controllor_2.controller");
- AnimatorControllerLayer layer=controller.layers[0];
- AnimatorStateMachine machine=layer.stateMachine;
- //创建参数
- controller.AddParameter(ParamNameTransitionAction,AnimatorControllerParameterType.Trigger);
- controller.AddParameter(ParamNameActionId,AnimatorControllerParameterType.Int);
- controller.AddParameter("zhan->pa",AnimatorControllerParameterType.Trigger);
- controller.AddParameter("pa->cewo",AnimatorControllerParameterType.Trigger);
- controller.AddParameter("pa->tang",AnimatorControllerParameterType.Trigger);
- controller.AddParameter("tang->cewo",AnimatorControllerParameterType.Trigger);
- controller.AddParameter("cewo->tang",AnimatorControllerParameterType.Trigger);
- controller.AddParameter("default",AnimatorControllerParameterType.Trigger);
- //创建默认state
- AnimatorState defaultState=machine.AddState("default",new Vector3(300,0,0));
- //defaultState.motion=
- machine.defaultState=defaultState;
- AnimatorStateTransition defaultTransition=machine.AddAnyStateTransition(defaultState);
- defaultTransition.AddCondition(AnimatorConditionMode.If,0,"default");
- defaultTransition.duration=TransitionDuring;
- //3级状态机条件
- List<string> haveSub3=new List<string>();
- haveSub3.Add("zhan");
- haveSub3.Add("tang");
- haveSub3.Add("cewo");
- haveSub3.Add("pa");
- //创建子状态机
- for(int k=1;k<sub2MachineNames.Length;k++)
- {
- AnimatorStateMachine sub2Machine=machine.AddStateMachine(sub2MachineNames[k],new Vector3(500,k*50,0));
- if(haveSub3.Contains(sub2MachineNames[k]))
- {
- for(int m=0;m<sub3MachineNames.Length;m++)
- {
- AnimatorStateMachine sub3Machine=sub2Machine.AddStateMachine(sub3MachineNames[m],new Vector3(500,m*50,0));
- }
- }
- }
- //添加clip
- string[] fileNames=Directory.GetFiles(Application.dataPath+"/Art/Animation");
- for(int k=0;k<fileNames.Length;k++)
- {
- if(fileNames[k].EndsWith("fbx") || fileNames[k].EndsWith("FBX"))
- {
- //获取fbx文件名
- int index=fileNames[k].LastIndexOf("/");
- string fbxName=fileNames[k].Substring(index+1,fileNames[k].Length-index-1);
- //读取fbx里的动作文件
- Object[] objects=AssetDatabase.LoadAllAssetsAtPath("Assets/Art/Animation/"+fbxName);
- for(int m=0;m<objects.Length;m++)
- {
- if(objects[m].GetType()==typeof(AnimationClip) && !objects[m].name.Contains("Take 001"))
- {
- AnimationClip clip=(AnimationClip)objects[m];
- if(clip.name.Equals("chushidongzuo001"))
- {
- defaultState.motion=clip;
- }
- else
- {
- addAnimationClip(clip,actionDetails,machine,haveSub3);
- }
- }
- }
- }
- }
- //AnimationClip clip=AssetDatabase.LoadAssetAtPath<AnimationClip>("Assets/Art/Animation/guodudongzuo-001.FBX");
- }
- private static void addAnimationClip(AnimationClip clip,Dictionary<string,ActionDetail> actionDetails,AnimatorStateMachine machine,List<string> haveSub3)
- {
- if(actionDetails.ContainsKey(clip.name))
- {
- ActionDetail ad=actionDetails[clip.name];
- foreach(ChildAnimatorStateMachine childMachine in machine.stateMachines)
- {
- AnimatorStateMachine sub2Machine=childMachine.stateMachine;
- if(sub2MachineNames[ad.type]==sub2Machine.name)
- {
- //有3级状态机的加3级里,没有的加2级里
- if(haveSub3.Contains(sub2Machine.name))
- {
- foreach(ChildAnimatorStateMachine childMachine3 in sub2Machine.stateMachines)
- {
- AnimatorStateMachine sub3Machine=childMachine3.stateMachine;
- if(sub3MachineNames[ad.style]==sub3Machine.name)
- {
- AnimatorState state=sub3Machine.AddState(clip.name,new Vector3(500,sub3Machine.states.Length*50,0));
- state.motion=clip;
- AnimatorStateTransition transition=machine.AddAnyStateTransition(state);
- transition.AddCondition(AnimatorConditionMode.If,0,ParamNameTransitionAction);
- transition.AddCondition(AnimatorConditionMode.Equals,ad.actionId,ParamNameActionId);
- transition.duration=TransitionDuring;
- }
- }
- }
- else
- {
- //过渡很特殊,这里写死
- if(ad.type==(int)ActionType.guodu)
- {
- string stateName="";
- float speed=1;
- string condition="";
- int indexZhan=clip.name.IndexOf("zhan");
- int indexPa=clip.name.IndexOf("pa");
- int indexCewo=clip.name.IndexOf("cewo");
- int indexTang=clip.name.IndexOf("tang");
- //侧卧和躺之间有正向和反向片段
- if(indexCewo>=0 && indexTang>=0)
- {
- stateName+="cewo->tang";
- speed=(indexCewo<indexTang)?1:-1;
- condition="cewo->tang";
- if(!string.IsNullOrEmpty(condition))
- {
- AnimatorState state=sub2Machine.AddState(stateName,new Vector3(500,sub2Machine.states.Length*50,0));
- state.motion=clip;
- state.speed=speed;
- AnimatorStateTransition transition=machine.AddAnyStateTransition(state);
- transition.AddCondition(AnimatorConditionMode.If,0,condition);
- transition.duration=TransitionDuring;
- }
- stateName="guodu-tang->cewo";
- speed=(indexTang<indexCewo)?1:-1;
- condition="tang->cewo";
- if(!string.IsNullOrEmpty(condition))
- {
- AnimatorState state=sub2Machine.AddState(stateName,new Vector3(500,sub2Machine.states.Length*50,0));
- state.motion=clip;
- state.speed=speed;
- AnimatorStateTransition transition=machine.AddAnyStateTransition(state);
- transition.AddCondition(AnimatorConditionMode.If,0,condition);
- transition.duration=TransitionDuring;
- }
- }
- else
- {
- if(indexZhan>=0 && indexPa>=0)
- {
- stateName="guodu-zhan->pa";
- speed=(indexZhan<indexPa)?1:-1;
- condition="zhan->pa";
- }
- else if(indexPa>=0 && indexCewo>=0)
- {
- stateName="guodu-pa->cewo";
- speed=(indexPa<indexCewo)?1:-1;
- condition="pa->cewo";
- }
- else if(indexPa>=0 && indexTang>=0)
- {
- stateName="guodu-pa->tang";
- speed=(indexPa<indexTang)?1:-1;
- condition="pa->tang";
- }
- if(!string.IsNullOrEmpty(condition))
- {
- AnimatorState state=sub2Machine.AddState(stateName,new Vector3(500,sub2Machine.states.Length*50,0));
- state.motion=clip;
- state.speed=speed;
- AnimatorStateTransition transition=machine.AddAnyStateTransition(state);
- transition.AddCondition(AnimatorConditionMode.If,0,condition);
- transition.duration=TransitionDuring;
- }
- }
- }
- else
- {
- AnimatorState state=sub2Machine.AddState(clip.name,new Vector3(500,sub2Machine.states.Length*50,0));
- state.motion=clip;
- AnimatorStateTransition transition=machine.AddAnyStateTransition(state);
- transition.AddCondition(AnimatorConditionMode.If,0,ParamNameTransitionAction);
- transition.AddCondition(AnimatorConditionMode.Equals,ad.actionId,ParamNameActionId);
- transition.duration=TransitionDuring;
- }
- }
- }
- }
- }
- }
- //解析动作文件
- private Dictionary<string,ActionDetail> parseActionFile(string actionFilePath)
- {
- Dictionary<string,ActionDetail> actionInfos=new Dictionary<string,ActionDetail>();
- TextAsset ta=AssetDatabase.LoadAssetAtPath<TextAsset>(actionFilePath);
- using(MemoryStream stream = new MemoryStream(ta.bytes))
- {
- using(StreamReader reader = new StreamReader(stream))
- {
- //第一行是列名,跳过
- int lineIdx = 0;
- while (reader.Peek() >= 0)
- {
- lineIdx++;
- string source = reader.ReadLine();
- if (lineIdx != 1 && !string.IsNullOrEmpty(source))
- {
- ActionDetail ad=ActionDetail.create(source);
- actionInfos.Add(ad.actionName,ad);
- }
- }
- }
- }
- Debug.Log("共需要获取"+actionInfos.Count+"个动作");
- return actionInfos;
- }
- }
- public class ActionDetail
- {
- public int actionId;
- public string actionName;
- public int type;
- public int style;
- public static ActionDetail create(string line)
- {
- string[] ss=line.Split(' ');
- ActionDetail ad=new ActionDetail();
- ad.actionId=int.Parse(ss[0]);
- ad.actionName=ss[1];
- ad.type=int.Parse(ss[2]);
- ad.style=int.Parse(ss[3]);
- return ad;
- }
- }
参考文章: