Unity3D 利用FSM设计相机跟随实现
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity3D 利用FSM设计相机跟随实现相关的知识,希望对你有一定的参考价值。
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 classes public 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 members public bool manualUpdate = false; public UpdateFunction updateFunction = UpdateFunction.Update; public StateType[] states; public int startState; #endregion #region Protected members protected FSMObject fsmObject = null; #endregion #region Public properties public 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 methods public void ForceUpdate() { fsmObject.Update(); } #endregion #region Unity callbacks protected 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 2 protected Vector3 newCamTargetOffset = new Vector3(0.0f, 1.35f, 3.36f);//Camera 2 protected 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 Classes public 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; } } #endregion public 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; //PIETRO transform.LookAt(camPivot + lastTargetOffset + offset * 0.1f, Vector3.up); if (!TimeManager.Instance.MasterSource.IsPaused) { //Camera Shake if (shakeCameraActive) ShakeCamera(dt); //tremor (always active this.UpdateTremor(dt); } //check if is dead if (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); else noiseStrength = currentShakeData.noise; // go directly if (noiseStrength > 0.0f) { Vector3 v = UnityEngine.Random.onUnitSphere; noise += (v - noise) * deltaTime * 8.0f; } else noise = 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; } }
以上是关于Unity3D 利用FSM设计相机跟随实现的主要内容,如果未能解决你的问题,请参考以下文章