Unity3D 利用FSM设计相机跟随实现

笔者介绍:姜雪伟,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 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;
	}
}

其中脚本中加粗的函数是有限状态机执行的具体逻辑。。。。。。。另外其他的变量声明和函数实现是根据策划需求添加的,读者只需要关注加粗的函数实现就可以了。
附图如下所示:

时间: 2024-08-03 15:29:33

Unity3D 利用FSM设计相机跟随实现的相关文章

unity3d简单的相机跟随及视野旋转缩放

1.实现相机跟随主角运动 一种简单的方法是把Camera直接拖到Player下面作为Player的子物体,另一种方法是取得Camera与Player的偏移向量,并据此设置Camera位置,便能实现简单的相机跟随了. 这里我们选取第二种方法,首先给Camera添加一个脚本,取名为FollowPlayer,脚本很简单不做说明了 1 public class FollowPlayer : MonoBehaviour { 2 3 private Transform player; 4 private V

unity3D:游戏分解之角色移动和相机跟随

游戏中,我们经常会有这样的操作,点击场景中某个位置,角色自动移动到那个位置,同时角色一直是朝向那个位置移动的,而且相机也会一直跟着角色移动.有些游戏,鼠标滑动屏幕,相机就会围绕角色旋转. 看似很简单的操作,那么到底是怎么实现的呢? 我们把上述操作分解为以下几个步骤 角色的移动 1. 移动到下一个路点,线性插值.曲线插值 2. 角色朝向,一直面朝下一个路点 相机跟随角色 1. 相机俯视角度,决定相机的高度 2. 相机跟随距离,前向距离或者直线距离(就是三角形的水平边长或者斜边长) 3. 相机一直看

Unity3d 引擎原理详细介绍、Unity3D引擎架构设计 - zhibolife

时间 2014-03-24 11:18:00  博客园-所有随笔区原文  http://www.cnblogs.com/zhibolife/p/3620440.html 体系结构 为了更好地理解游戏的软件架构和对象模型,它获得更好的外观仅有一名Unity3D的游戏引擎和编辑器是非常有用的,它的主要原则. Unity3D 引擎 Unity3D的是一个屡获殊荣的工具,用于创建交互式3D应用程序在多个platforms.Unity3D由游戏引擎和编辑器.该引擎包含的软件组件,在游戏的研究与开发中最常见

利用MVVM设计快速开发个人中心、设置等模块

我们在做iOS开发过程中,静态页面的开发比开发动态页面更让我们开发者抓狂.因为动态页面通常是一个页面一种cell样式,作为开发者只需要专注于定制好一种样式之后,就可以使用数据填充出较好的界面.而静态cell,则可能因为一个页面有多种cell样式,而且很有可能不是标准的cell样式,需要我们自定义,因此容易写出来的代码容易臃肿和重复.可读性差.这不符合开发者的代码优化原则(不主动重复).我时常会因为开发这种页面抓狂,因为会纠结到底怎么写会让代码可读性好点,写重复代码的机会少点.本文就是我做过的几个

按照所给的程序流程图,分别写出语句覆盖、分支覆盖的测试用例,以及它所覆盖的路径,根据程序流程图,写出代码,用JUnit生成单元测试,并利用前面设计的测试用例进行测试。

语句覆盖:路径:abc ,测试用例:x=3,y=2 分支覆盖:路径:aeg ,测试用例:x=4,y=-1 /** * 2016-04-09 * @author 吴思婷 * DoWork类用来根据程序流程图,写出代码(定义一个类和方法来实现) */ public class DoWork { public void doWork(int x,int y){ int k=0,j=0; if((x<4 || y>0)&&(y>1)){ y=y+1; } else { if(x&

unity 常用的几种相机跟随

固定相机跟随 这种相机有一个参考对象,它会保持与该参考对象固定的位置,跟随改参考对象发生移动 1 using UnityEngine; 2 using System.Collections; 3 4 public class CameraFlow : MonoBehaviour 5 { 6 public Transform target; 7 private Vector3 offset; 8 // Use this for initialization 9 void Start() 10 {

利用PYTHON设计计算器功能

通过利用PYTHON 设计处理计算器的功能如: 1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 ))- (-4*3)/(16-3*2)) 我的处理计算基本思路是: 解题思路是,需要优先处理内层括号运算--外层括号运算--先乘除后加减的原则:1.正则处理用户输入的字符串,然后对其进行判断,判断计算公式是否有括号,有就先将计算公式进行正则处理,先获取最里层的每一个数据,然后一一计算 所要用到的正则是: inner

Unity3D 相机跟随主角移动

这里给主相机绑定一个脚本. 脚本写为: using UnityEngine; using System.Collections; public class camerafollow : MonoBehaviour { //主摄像机跟随主角一起移动 public float xMargin = 1f; public float yMargin = 1f; public float xSmooth = 8f; public float ySmooth = 8f; public Vector2 max

Unity3D——相机跟随物体移动

public Transform target; public float moveSmooth=5f; Vector3 offset; void Start () { offset = transform.position - target.position;//获取相对位置 } void Update () { Vector3 targetPostion= offset + target.position; transform.position = Vector3.Lerp(transfor