Unity3D 利用FSM设计相机跟随实现
来源:互联网 发布:windows桌面文件路径 编辑:程序博客网 时间:2024/05/21 15:43
笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。
CSDN视频网址:http://edu.csdn.net/lecturer/144
FSM有限状态机前面已经跟读者介绍过,使用Unity3D引擎实现了动作状态以及技能切换,FSM使用的条件是有限个状态切换,我们可以将FSM应用到相机中,很多人会问在相机中如何使用FSM,不论那种架构其主要目的是将模块之间的耦合性降低,传统的写法就是使用一个相机跟随类,所有的逻辑一股脑的写在一个类或者两个类中,这样一旦逻辑变动,修改起来非常麻烦,可能修改的就不是一个类两个类的事情,而如果我们采用FSM设计相机跟随,这样就容易多了。
接下来就实现FSM有限状态机,FSM作为一个通用类需要将其设置成模版的方式,具体代码如下所示:
using System;using System.Collections.Generic;namespace Core{ public class FSM { public class Object<T, K> where T : Object<T, K> { public delegate void Function(T self, float time); #region Protected members protected TimeSource timeSource = null; protected Dictionary<K, State<T, K>> states = new Dictionary<K,State<T,K>>(); protected State<T, K> state = null; protected State<T, K> prevState = null; #endregion #region Ctors public Object() { timeSource = TimeManager.Instance.MasterSource; } public Object(TimeSource source) { timeSource = source; } #endregion #region Public properties public K PrevState { get { return prevState.key; } } public K State { get { return state.key; } set { prevState = state; if (prevState != null) prevState.onExit(this as T, timeSource.TotalTime); State<T, K> nextState; if (states.TryGetValue(value, out nextState)) { state = nextState; state.onEnter(this as T, timeSource.TotalTime); } else { state = null; } } } public TimeSource TimeSource { get { return timeSource; } set { timeSource = value; } } #endregion #region Public methods public void AddState(K key, Function onEnter, Function onExec, Function onExit) { State<T, K> newState = new State<T, K>(); newState.key = key; newState.onEnter = onEnter; newState.onExec = onExec; newState.onExit = onExit; states.Add(key, newState); } public void Update() { if (null == state) return; state.onExec(this as T, timeSource.TotalTime); } #endregion } public class State<T, K> where T : Object<T, K> { public K key; public Object<T, K>.Function onEnter; public Object<T, K>.Function onExec; public Object<T, K>.Function onExit; } }}在这个类中有三部分最重要,第一部分是定义了状态类,它实现了状态的切换函数,onEnter,onExec,onExit,这个是作为状态切换使用的。代码如下:
public class State<T, K> where T : Object<T, K> { public K key; public Object<T, K>.Function onEnter; public Object<T, K>.Function onExec; public Object<T, K>.Function onExit; }另一个类的函数是增加状态函数,这个需要在Start函数中去执行的,函数代码如下所示:
public void AddState(K key, Function onEnter, Function onExec, Function onExit) { State<T, K> newState = new State<T, K>(); newState.key = key; newState.onEnter = onEnter; newState.onExec = onExec; newState.onExit = onExit; states.Add(key, newState); }最后一个函数就是Update函数,需要每帧去检测执行状态,函数如下所示:
public void Update() { if (null == state) return; state.onExec(this as T, timeSource.TotalTime); }这三个是最重要的,必须要有的,接下来编写挂接到对象上的类FiniteStateMachine类脚本,代码如下所示:
using System;using System.Collections.Generic;using UnityEngine;using Core;public class FiniteStateMachine : MonoBehaviour{public enum UpdateFunction{Update = 0,LateUpdate,FixedUpdate}#region Public classespublic class FSMObject : FSM.Object<FSMObject, int>{public GameObject go;public FSMObject(GameObject _go){go = _go;}}[Serializable]public class StateType{public int id;public string onEnterMessage;public string onExecMessage;public string onExitMessage;public void onEnter(FSMObject fsmObject, float time){fsmObject.go.SendMessage(onEnterMessage, time, SendMessageOptions.RequireReceiver);}public void onExec(FSMObject fsmObject, float time){fsmObject.go.SendMessage(onExecMessage, time, SendMessageOptions.RequireReceiver);}public void onExit(FSMObject fsmObject, float time){fsmObject.go.SendMessage(onExitMessage, time, SendMessageOptions.RequireReceiver);}}#endregion#region Public memberspublic bool manualUpdate = false;public UpdateFunction updateFunction = UpdateFunction.Update;public StateType[] states;public int startState;#endregion#region Protected membersprotected FSMObject fsmObject = null;#endregion#region Public propertiespublic int PrevState{get{return fsmObject.PrevState;}}public int State{get{return fsmObject.State;}set{fsmObject.State = value;}}public TimeSource TimeSource{get{return fsmObject.TimeSource;}set{fsmObject.TimeSource = value;}}#endregion#region Public methodspublic void ForceUpdate(){fsmObject.Update();}#endregion#region Unity callbacksprotected void Start(){fsmObject = new FSMObject(gameObject);foreach (StateType state in states)fsmObject.AddState(state.id, state.onEnter, state.onExec, state.onExit);fsmObject.State = startState;}void Update(){//Debug.Log ("update");if (manualUpdate)return;if (UpdateFunction.Update == updateFunction)fsmObject.Update();}void LateUpdate(){if (manualUpdate)return;if (UpdateFunction.LateUpdate == updateFunction)fsmObject.Update();}void FixedUpdate(){if (manualUpdate)return;if (UpdateFunction.FixedUpdate == updateFunction)fsmObject.Update();}#endregion}该函数需要挂接到对象上,效果如下所示:
以上就是我们所封装的FSM有限状态机,接下来在项目中使用我们的FSM,先实现最基本的逻辑类如下所示:
using System;using System.Collections.Generic;using UnityEngine;public class FollowCharacter : MonoBehaviour{public GameObject player;public Vector3 sourceOffset = new Vector3(0.0f, 2.5f, -3.4f);public Vector3 targetOffset = new Vector3(0.0f, 1.7f, 0.0f);protected bool firstFrame;protected float currHeightSmoothing;protected float groundHeightTest;protected bool slideshowActive = false;protected float slideshowEnterTime = 0.0f;protected float slideshowExitTime = 0.0f;protected bool oldCameraActive = true;protected float oldFov = 70.0f;protected Vector3 oldCamSourceOffset = new Vector3(0.0f, 8.5f, -4.5f);protected Vector3 oldCamTargetOffset = new Vector3(0.0f, 0.9f, 5.3f);protected int cameraIndex = 3;protected float[] cameraFovs = { 55.0f, 60.0f, 55.0f };protected Vector3[] cameraSourceOffsets = {new Vector3(0.0f, 5.8f, -3.8f),new Vector3(0.0f, 6.04f, -4.0f),new Vector3(0.0f, 8.5f, -6.7f)};protected Vector3[] cameraTargetOffsets = {new Vector3(0.0f, 2.2f, 2.5f),new Vector3(0.0f, 1.35f, 3.36f),new Vector3(0.0f, 1.45f, 5.3f)};protected Vector3 newCamSourceOffset = new Vector3(0.0f, 6.04f, -4.0f);//Camera 2protected Vector3 newCamTargetOffset = new Vector3(0.0f, 1.35f, 3.36f);//Camera 2protected Vector3 testNewTurboSourceOffset = new Vector3(0.0f, 5.8f, -4.0f);protected Vector3 testNewTurboTargetOffset = new Vector3(0.0f, 2.1f, 2.5f);protected Vector3 testNewFinalSourceOffset = new Vector3(-6.5f, 5.0f, -5.5f);protected Vector3 testNewFinalTargetOffset = new Vector3(-4.5f, 1.7f, 0.0f);
#region public Classespublic class ShakeData{public float duration;public float noise;public float smoothTime;public ShakeData(float _duration, float _noise, float _smoothTime){duration = _duration;noise = _noise;smoothTime = _smoothTime;}}#endregionpublic void OnFollowCharaEnter(float time){prevPlayerPivot = player.transform.position;firstFrame = true;currHeightSmoothing = heightSmoothing;deadTime = -1.0f;actionTaken = false;}public void OnFollowCharaExec(float time){if (player == null)return;float dt = Time.fixedDeltaTime;float now = TimeManager.Instance.MasterSource.TotalTime;Vector3 playerPivot = player.transform.position;playerPivot.x = 0.0f;playerPivot.y = 0.0f;float targetHeight = playerPivot.y;if (firstFrame){lastPivotHeight = targetHeight;prevPlayerPivot = playerPivot;heightVelocity = 0.0f;firstFrame = false;}else{float targetSmoothTime = 0.1f;smoothTime = Mathf.MoveTowards(smoothTime, targetSmoothTime, 2.5f * dt);lastPivotHeight = Mathf.SmoothDamp(lastPivotHeight, targetHeight, ref heightVelocity, smoothTime, 50.0f, dt);prevPlayerPivot = playerPivot;}Vector3 camPivot = new Vector3(prevPlayerPivot.x * 0.8f, lastPivotHeight, prevPlayerPivot.z);lastSourceOffset = this.EaseTo(lastSourceOffset, goalSourceOffset, sourceOffset);lastTargetOffset = this.EaseTo(lastTargetOffset, goalTargetOffset, targetOffset);transform.position = camPivot + lastSourceOffset + offset * 0.1f + noise * noiseStrength; // +noise * noiseStrength + noiseTremor * 0.00069f * kinematics.PlayerRigidbody.velocity.z; //PIETROtransform.LookAt(camPivot + lastTargetOffset + offset * 0.1f, Vector3.up);if (!TimeManager.Instance.MasterSource.IsPaused){//Camera Shakeif (shakeCameraActive)ShakeCamera(dt);//tremor (always activethis.UpdateTremor(dt);}//check if is deadif (now - deadTime > 3.6f && !actionTaken && deadTime > 0.0f){actionTaken = true;//Debug.Log("GO TO REWARD");LevelRoot.Instance.BroadcastMessage("GoToOffgame"); //GoToReward");}}public void OnFollowCharaExit(float time){}void OnReset(){//Debug.Log("RESET CAM");interpolating = false;shakeCameraActive = false;sourceOffset = defaultSourceOffset;targetOffset = defaultTargetOffset;}void ShakeCamera(float deltaTime){if (TimeManager.Instance.MasterSource.TotalTime - shakeStartTime <= currentShakeData.duration){if (currentShakeData.smoothTime > 0.0f)noiseStrength = Mathf.SmoothDamp(noiseStrength, currentShakeData.noise, ref noiseStrengthVel, currentShakeData.smoothTime, 300.0f, deltaTime);elsenoiseStrength = currentShakeData.noise; // go directlyif (noiseStrength > 0.0f){Vector3 v = UnityEngine.Random.onUnitSphere;noise += (v - noise) * deltaTime * 8.0f;}elsenoise = SRVector3.zero;}if (TimeManager.Instance.MasterSource.TotalTime - shakeStartTime >= currentShakeData.duration)StopShakeCamera();}void StopShakeCamera(){currentShakeData = new ShakeData(0.0f, 0.0f, 0.0f);noiseStrength = 0.0f;noise = SRVector3.zero;shakeCameraActive = false;}public string ChangeCamera(){string buttonText = "";buttonText = cameraIndex == 0 ? "Old camera on" : "camera " + cameraIndex + " on";if (cameraIndex == 0){gameObject.GetComponent<Camera>().fieldOfView = oldFov;lastSourceOffset = defaultSourceOffset = sourceOffset = oldCamSourceOffset;lastTargetOffset = defaultTargetOffset = targetOffset = oldCamTargetOffset;goalSourceOffset = oldCamSourceOffset;goalTargetOffset = oldCamTargetOffset;}else{gameObject.GetComponent<Camera>().fieldOfView = cameraFovs[cameraIndex - 1];lastSourceOffset = defaultSourceOffset = sourceOffset = cameraSourceOffsets[cameraIndex - 1];lastTargetOffset = defaultTargetOffset = targetOffset = cameraTargetOffsets[cameraIndex - 1];goalSourceOffset = cameraSourceOffsets[cameraIndex - 1];goalTargetOffset = cameraTargetOffsets[cameraIndex - 1];}return buttonText;}}
其中脚本中加粗的函数是有限状态机执行的具体逻辑。。。。。。。另外其他的变量声明和函数实现是根据策划需求添加的,读者只需要关注加粗的函数实现就可以了。
附图如下所示:
0 0
- Unity3D 利用FSM设计相机跟随实现
- Unity3d实现相机的跟随缩放旋转
- unity3d中实现相机的跟随
- Unity3d--实现第三人称视角(相机跟随)
- Unity3D-相机跟随抖动
- Unity3D 相机跟随算法
- Unity3d相机跟随角色移动
- Unity3d学习 相机的跟随
- uLua实现相机跟随
- 基于Unity3D的相机功能的实现(五)—— 相机跟随
- Unity3D-FSM的简单实现
- unity3d 简单的相机跟随目标
- Unity3D-FSM有限状态机的简单设计
- Unity3D-FSM有限状态机的简单设计
- FSM设计和实现1
- FSM设计和实现2
- unity 利用差值实现相机跟随与轴值实现人物拉近
- Unity 相机跟随人物移动而移动,利用插值实现
- java的加密与解密
- win10安装python3,tensorflow,numpy,scipy和matplotlib
- Docker run运行容器报错Error ruuning deviceCreate(createSnapDevice) dm_task_run failed
- opt.compute_gradients() 与 tf.gradients 与 tf.stop_gradient()
- Tensorflow一些常用基本概念与函数(2)
- Unity3D 利用FSM设计相机跟随实现
- 深入理解javascript原型和闭包(14)——从【自由变量】到【作用域链】
- Tensorflow一些常用基本概念与函数(3)
- SAP HANA计算两个日期的差值
- 1004. Counting Leaves (30)
- (8)关卡编辑器之视口选项
- 恢复勒索病毒“永恒之蓝”中招的文件,ooops,your files have been encrypted!解决方案
- Nuttx romfs与启动脚本rcS
- 51nod1227 平均最小公倍数