需求:魚兒魚兒水中游

魚兒在海底世界嬉戲
1、觸摸魚兒時候,魚兒受到驚嚇,隨機快速移動。
2、觸摸空白區域,在觸摸點添加魚食,當魚食在魚兒可視范圍內,魚兒快速移動,追趕魚食,把它吃掉。
3、海底下方,隨機產生氣泡,氣泡向上飄浮,當到達生存時間或者被觸摸后,魚兒若在氣泡爆炸范圍,魚兒受到驚嚇,隨機快速移動。
4、后續添加一個魚兒進場過程。
這里魚兒動畫比較簡單,只有一份 idle 動作即可,魚兒受驚快速移動,可以加快動畫播放速度即可。
根據需求,可以給魚兒編寫一份有限無窮狀態機。魚兒在某個時間點只處于一種狀態,只是在該狀態下,做自己相應的事情罷了。
好了步入正題,開始限無窮狀態機的編寫。
FSM.cs
using UnityEngine;
using System.Collections;
public class FSM : MonoBehaviour
{
//Fish GameObject
protected GameObject goFish;
//Player Transform
public Transform oceanTransform;
//Next destination position of the NPC Tank
protected Vector3 destPos;
protected float elapsedTime;
protected virtual void Initialize() {}
protected virtual void FSMUpdate() {}
protected virtual void FSMFixedUpdate() {}
//Use this for initialization
void Start()
{
Initialize();
}
// Update is called once per frame
void Update ()
{
FSMUpdate();
}
void FixedUpdate()
{
FSMFixedUpdate();
}
}
FSMState.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// This class is adapted and modified from the FSM implementation class available on UnifyCommunity website
/// The license for the code is Creative Commons Attribution Share Alike.
/// It's originally the port of C++ FSM implementation mentioned in Chapter01 of Game Programming Gems 1
/// You're free to use, modify and distribute the code in any projects including commercial ones.
/// Please read the link to know more about CCA license @http://creativecommons.org/licenses/by-sa/3.0/
/// This class represents the States in the Finite State System.
/// Each state has a Dictionary with pairs (transition-state) showing
/// which state the FSM should be if a transition is fired while this state
/// is the current state.
/// Reason method is used to determine which transition should be fired .
/// Act method has the code to perform the actions the NPC is supposed to do if it磗 on this state.
/// </summary>
public abstract class FSMState
{
protected Dictionary<Transition, FSMStateID> map = new Dictionary<Transition, FSMStateID>();
protected FSMStateID stateID;
public FSMStateID ID { get { return stateID; } }
// 以下幾個參數,是魚兒相應的參數設置,在繼承各個狀態機時使用 覺得寫這里 不是很好,不過懶得改 就這樣吧
protected Vector3 destPos; // 目標位置
protected float curRotSpeed; // 驚嚇后速度參數
protected float curSpeed; // 自身游動速度參數
protected float chaseDistance = 10.0f; // 魚兒追趕魚食范圍
public void AddTransition(Transition transition, FSMStateID id)
{
//Since this is a Deterministc FSM,
//Check if the current transition was already inside the map
if (map.ContainsKey(transition))
{
Debug.LogWarning("FSMState ERROR: transition is already inside the map");
return;
}
map.Add(transition, id);
Debug.Log("Added : " + transition + " with ID : " + id);
}
/// <summary>
/// This method deletes a pair transition-state from this state磗 map.
/// </summary>
public void DeleteTransition(Transition trans)
{
// Check if the pair is inside the map before deleting
if (map.ContainsKey(trans))
{
map.Remove(trans);
return;
}
Debug.LogError("FSMState ERROR: Transition passed was not on this State磗 List");
}
/// <summary>
/// This method returns the new state the FSM should be if
/// this state receives a transition
/// </summary>
public FSMStateID GetOutputState(Transition trans)
{
return map[trans];
}
/// <summary>
/// Decides if the state should transition to another on its list
/// NPC is a reference to the npc tha is controlled by this class
/// </summary>
public abstract void Reason(GameObject fish);
/// <summary>
/// This method controls the behavior of the NPC in the game World.
/// Every action, movement or communication the NPC does should be placed here
/// NPC is a reference to the npc tha is controlled by this class
/// </summary>
public abstract void Act(GameObject fish);
/// <summary>
/// Find the next semi-random patrol point
/// </summary>
public void FindNextPoint()
{
//Debug.Log("Finding next point");
}
/// <summary>
/// Check whether the next random position is the same as current tank position
/// </summary>
/// <param name="pos">position to check</param>
/*
protected bool IsInCurrentRange(Transform trans, Vector3 pos)
{
float xPos = Mathf.Abs(pos.x - trans.position.x);
float zPos = Mathf.Abs(pos.z - trans.position.z);
if (xPos <= 50 && zPos <= 50)
return true;
return false;
}*/
}
定義倆個枚舉,用來處理狀態機
public enum Transition
{
SawFood = 0, // 看見發現食物
ReachFood, // 追逐抵達食物
LostFood, // 失去食物(有可能被別的魚吃了、脫離視野、掉入海底)
BombBubble, // 氣泡爆炸
TouchFish, // 魚兒被觸摸
Patrolle, // 巡邏(普通)
ComeInto, // 進場
ComeEnd, // 進場結束
NoTime, // 沒有時間了
}
public enum FSMStateID
{
Patrolling = 0, // 巡邏 狀態
Showtiming, // 表演 狀態
Chasing, // 追趕 狀態
Frightening, // 受驚 狀態
Eatting, // 吃食 狀態
Coming, // 進場 狀態
Disappearing, // 消失 狀態
}
AdvancedFSM.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// This class is adapted and modified from the FSM implementation class available on UnifyCommunity website
/// The license for the code is Creative Commons Attribution Share Alike.
/// It's originally the port of C++ FSM implementation mentioned in Chapter01 of Game Programming Gems 1
/// You're free to use, modify and distribute the code in any projects including commercial ones.
/// Please read the link to know more about CCA license @http://creativecommons.org/licenses/by-sa/3.0/
/// </summary>
public class AdvancedFSM : FSM
{
private List<FSMState> fsmStates;
//The fsmStates are not changing directly but updated by using transitions
private FSMStateID currentStateID;
public FSMStateID CurrentStateID { get { return currentStateID; } }
private FSMState currentState;
public FSMState CurrentState { get { return currentState; } }
public AdvancedFSM()
{
fsmStates = new List<FSMState>();
}
/// <summary>
/// Add New State into the list
/// </summary>
public void AddFSMState(FSMState fsmState)
{
// Check for Null reference before deleting
if (fsmState == null)
{
Debug.LogError("FSM ERROR: Null reference is not allowed");
}
// First State inserted is also the Initial state
// the state the machine is in when the simulation begins
if (fsmStates.Count == 0)
{
fsmStates.Add(fsmState);
currentState = fsmState;
currentStateID = fsmState.ID;
return;
}
// Add the state to the List if it磗 not inside it
foreach (FSMState state in fsmStates)
{
if (state.ID == fsmState.ID)
{
Debug.LogError("FSM ERROR: Trying to add a state that was already inside the list");
return;
}
}
//If no state in the current then add the state to the list
fsmStates.Add(fsmState);
}
//This method delete a state from the FSM List if it exists,
public void DeleteState(FSMStateID fsmState)
{
// Search the List and delete the state if it磗 inside it
foreach (FSMState state in fsmStates)
{
if (state.ID == fsmState)
{
fsmStates.Remove(state);
return;
}
}
Debug.LogError("FSM ERROR: The state passed was not on the list. Impossible to delete it");
}
/// <summary>
/// This method tries to change the state the FSM is in based on
/// the current state and the transition passed.
/// </summary>
public void PerformTransition(Transition trans)
{
// Check if the currentState has the transition passed as argument
FSMStateID id = currentState.GetOutputState(trans);
// Update the currentStateID and currentState
currentStateID = id;
foreach (FSMState state in fsmStates)
{
if (state.ID == currentStateID)
{
currentState = state;
break;
}
}
}
}
魚兒實體掛載上 AIController.cs
using UnityEngine;
using System.Collections;
public class AIController : AdvancedFSM
{
private int survivalTime; // 生存時間
public bool isNPC = true;
protected override void Initialize()
{
goFish = this.gameObject;
survivalTime = 60 * 3; // 生存時間 60秒 * 3
//Start Doing the Finite State Machine
ConstructFSM ();
}
//Update each frame
protected override void FSMUpdate()
{
//Check for health
elapsedTime += Time.deltaTime;
// 時間 到了 消失
if (elapsedTime > survivalTime && isNPC == false)
SetTransition(Transition.NoTime);
}
protected override void FSMFixedUpdate()
{
CurrentState.Reason(goFish);
CurrentState.Act(goFish);
}
public void SetTransition(Transition t)
{
PerformTransition(t);
}
public void TouchFishAC()
{
int TouchID = Random.Range(1, 6);
string strTouch = "Sound/yu0" + TouchID.ToString();
AudioClip ACTouch = Resources.Load(strTouch) as AudioClip;
AudioSource.PlayClipAtPoint(ACTouch, transform.localPosition);
}
private void ConstructFSM()
{
GameObject objOcean = GameObject.FindGameObjectWithTag("Ocean");
oceanTransform = objOcean.transform;
PatrolState patrol = new PatrolState (); // 游走
patrol.AddTransition (Transition.SawFood, FSMStateID.Chasing); // 發現魚食 進入追食
patrol.AddTransition (Transition.BombBubble, FSMStateID.Frightening); // 氣泡爆炸 進入受驚
patrol.AddTransition (Transition.TouchFish, FSMStateID.Frightening); // 觸碰魚 進入受驚
patrol.AddTransition (Transition.NoTime, FSMStateID.Disappearing); // 到時間 消失
ChaseState chase = new ChaseState(); // 追食
chase.AddTransition (Transition.ReachFood, FSMStateID.Eatting); // 追到魚食 進入吃食
chase.AddTransition (Transition.LostFood, FSMStateID.Patrolling); // 魚食消失 進入游走
chase.AddTransition (Transition.BombBubble, FSMStateID.Frightening); // 氣泡爆炸 進入受驚
chase.AddTransition (Transition.TouchFish, FSMStateID.Frightening); // 觸碰魚 進入受驚
chase.AddTransition (Transition.NoTime, FSMStateID.Disappearing); // 到時間 消失
//EatState eat = new EatState (); // 吃食
FrightenState frighten = new FrightenState (); // 受驚
frighten.AddTransition (Transition.Patrolle, FSMStateID.Patrolling); // 受驚后 游走
frighten.AddTransition (Transition.NoTime, FSMStateID.Disappearing); // 到時間 消失
DisappearState disappear = new DisappearState (); // 離場
disappear.AddTransition(Transition.NoTime, FSMStateID.Disappearing); // 到時間 消失
ComeState comeinto = new ComeState ();
comeinto.AddTransition (Transition.ComeInto, FSMStateID.Coming);
comeinto.AddTransition (Transition.ComeEnd, FSMStateID.Patrolling);
//AddFSMState (comeinto);
AddFSMState (patrol);
AddFSMState (chase);
//AddFSMState (eat);
AddFSMState (frighten);
AddFSMState (disappear);
//AddFSMState (comeinto);
}
}
好了,我們現在實現
PatrolState、ChaseState、FrightenState、DisappearState、ComeState 在相應的狀態下做相應的事情就ok了。
PatrolState.cs
using UnityEngine;
using System.Collections;
public class PatrolState : FSMState
{
float intervalTime = 4.0f; // 間隔時間
float patrolTime = 0.0f;
int patrolPointID = 2;
Vector3 selfPoint;
GameObject goPatrol;
public PatrolState()
{
intervalTime = Random.Range(3.5f, 8.0f);
stateID = FSMStateID.Patrolling;
curRotSpeed = 3.0f;
curSpeed = 0.1f;
goPatrol = GameObject.FindGameObjectWithTag("PatrolPoint");
destPos = goPatrol.transform.Find (patrolPointID.ToString ()).transform.position;
}
public void FindNextPoint(GameObject fish)
{
intervalTime = Random.Range(5.5f, 8.0f);
selfPoint = fish.transform.position;
destPos = goPatrol.transform.Find (patrolPointID.ToString ()).transform.position;
}
public override void Reason(GameObject fish)
{
//fish.GetComponent<AIController> ().SetTransition (Transition.SawFood);
AIController aiController = fish.GetComponent<AIController> ();
if (aiController != null)
{
OceanManage oceanManage = aiController.oceanTransform.GetComponent<OceanManage> ();
GameObject goFish = oceanManage.FindFishFood (fish);
if (goFish != null) {
float distance = Vector3.Distance(fish.transform.position, goFish.transform.position);
if (distance < chaseDistance)
aiController.SetTransition (Transition.SawFood);
}
}
}
public override void Act(GameObject fish)
{
if ((patrolTime += Time.deltaTime) > intervalTime) {
patrolTime = 0.0f;
patrolPointID = Random.Range(1, goPatrol.gameObject.transform.childCount + 1);
FindNextPoint(fish);
}
Quaternion targetRotation = Quaternion.LookRotation(destPos - fish.transform.position);
Quaternion rotateQuaterntion = Quaternion.identity;
rotateQuaterntion.eulerAngles = new Vector3(0f, 90.0f, 0f);
fish.transform.rotation = Quaternion.Slerp(fish.transform.rotation, targetRotation * rotateQuaterntion, Time.deltaTime * curRotSpeed);
//fish.transform.LookAt(destPos);//魚隨機方向
//fish.transform.Rotate(new Vector3(0, 90, 0));
fish.transform.position = Vector3.Lerp(fish.transform.position, destPos, Time.deltaTime * curSpeed);
Animator animComponent = fish.GetComponent<Animator>();
animComponent.CrossFade("idle", 0);
animComponent.speed = 1f;
}
}
ChaseState.cs
using UnityEngine;
using System.Collections;
public class ChaseState : FSMState
{
public ChaseState()
{
stateID = FSMStateID.Chasing;
curRotSpeed = 3.0f;
curSpeed = 2.75f;
}
public override void Reason(GameObject fish)
{
AIController aiController = fish.GetComponent<AIController> ();
OceanManage oceanManage = aiController.oceanTransform.GetComponent<OceanManage> ();
GameObject goFish = oceanManage.FindFishFood (fish);
if (goFish == null) {
aiController.SetTransition (Transition.LostFood);
}
}
public override void Act(GameObject fish)
{
AIController aiController = fish.GetComponent<AIController> ();
OceanManage oceanManage = aiController.oceanTransform.GetComponent<OceanManage> ();
GameObject goFish = oceanManage.FindFishFood (fish);
if (goFish != null) {
float distance = Vector3.Distance(fish.transform.position, goFish.transform.position);
if (distance < chaseDistance)
{
fish.transform.LookAt(goFish.transform.position);//魚隨機方向
fish.transform.Rotate(new Vector3(0, 90, 0));
fish.transform.position = Vector3.Lerp(fish.transform.position, goFish.transform.position, Time.deltaTime * curSpeed);
Animator animComponent = fish.GetComponent<Animator>();
animComponent.CrossFade("idle", 0);
animComponent.speed = 1f;
}
else
{
aiController.SetTransition (Transition.LostFood);
}
//animComponent.animation.s
}
}
}
FrightenState.cs
using UnityEngine;
using System.Collections;
public class FrightenState :FSMState
{
float intervalTime = 1.0f; // 間隔時間
float patrolTime = 0.0f;
GameObject goPatrol;
int patrolPointID = 2;
public FrightenState()
{
intervalTime = 1.5f;
stateID = FSMStateID.Frightening;
curRotSpeed = 3.0f;
curSpeed = 0.7f;
goPatrol = GameObject.FindGameObjectWithTag("PatrolPoint");
}
public override void Reason(GameObject fish)
{
if (patrolTime == 0)
{
patrolPointID = Random.Range(1, goPatrol.gameObject.transform.childCount + 1);
destPos = goPatrol.transform.Find(patrolPointID.ToString()).transform.position;
}
}
public override void Act(GameObject fish)
{
if ((patrolTime += Time.deltaTime) > intervalTime)
{
patrolTime = 0.0f;
AIController aiController = fish.GetComponent<AIController>();
aiController.SetTransition(Transition.Patrolle);
}
Quaternion targetRotation = Quaternion.LookRotation(destPos - fish.transform.position);
Quaternion rotateQuaterntion = Quaternion.identity;
rotateQuaterntion.eulerAngles = new Vector3(0f, 90.0f, 0f);
fish.transform.rotation = Quaternion.Slerp(fish.transform.rotation, targetRotation * rotateQuaterntion, Time.deltaTime * curRotSpeed);
//fish.transform.LookAt(destPos);//魚隨機方向
//fish.transform.Rotate(new Vector3(0, 90, 0));
fish.transform.position = Vector3.Lerp(fish.transform.position, destPos, Time.deltaTime * curSpeed );
Animator animComponent = fish.GetComponent<Animator>();
animComponent.CrossFade("idle", 0);
animComponent.speed = 2f;
}
}
DisappearState.cs
using UnityEngine;
using System.Collections;
public class DisappearState : FSMState
{
public DisappearState()
{
stateID = FSMStateID.Disappearing;
}
public override void Reason(GameObject fish)
{
}
public override void Act(GameObject fish)
{
GameObject oceanGO = GameObject.Find("Ocean");
if (oceanGO) {
OceanManage ocean = oceanGO.GetComponent<OceanManage> ();
ocean.MakeBubble(fish);
}
GameObject.Destroy (fish);
}
}
ComeState.cs
using UnityEngine;
using System.Collections;
public class ComeState : FSMState
{
int comeIntoPointID = 1;
GameObject goComeInto;
Hashtable pathHashtable;
float intervalTime = 1.0f; // 間隔時間
float comeintoTime = 0.0f;
// Use this for initialization
public ComeState()
{
stateID = FSMStateID.Coming;
goComeInto = GameObject.FindGameObjectWithTag("ComeIntoPoint");
}
public void SetComeIntoPath(GameObject fish)
{
comeIntoPointID = Random.Range(1, goComeInto.gameObject.transform.childCount + 1);
iTweenPath path = goComeInto.transform.Find (comeIntoPointID.ToString ()).GetComponent<iTweenPath>();
destPos = goComeInto.transform.Find (comeIntoPointID.ToString ()).transform.position;
fish.transform.position = path.nodes [0];
pathHashtable = new Hashtable();
//設置路徑的點
pathHashtable.Add("path",path);
//設置類型為線性,線性效果會好一些。
pathHashtable.Add("easeType", iTween.EaseType.linear);
//設置尋路的速度
pathHashtable.Add("time",intervalTime);
//是否先從原始位置走到路徑中第一個點的位置
pathHashtable.Add("movetopath",true);
//是否讓模型始終面朝當面目標的方向,拐彎的地方會自動旋轉模型
//如果你發現你的模型在尋路的時候始終都是一個方向那么一定要打開這個
pathHashtable.Add("orienttopath",true);
//讓模型開始尋路
iTween.MoveTo(fish,pathHashtable);
}
public override void Reason(GameObject fish)
{
if (comeintoTime == 0.0f)
SetComeIntoPath (fish);
if ((comeintoTime += Time.deltaTime) > intervalTime) {
comeintoTime = 0.0f;
AIController aiController = fish.GetComponent<AIController> ();
aiController.SetTransition (Transition.ComeEnd);
}
}
public override void Act(GameObject fish)
{
Quaternion targetRotation = Quaternion.LookRotation(destPos - fish.transform.position);
Quaternion rotateQuaterntion = Quaternion.identity;
rotateQuaterntion.eulerAngles = new Vector3(0f, 90.0f, 0f);
fish.transform.rotation = Quaternion.Slerp(fish.transform.rotation, targetRotation * rotateQuaterntion, Time.deltaTime * curRotSpeed);
Animator animComponent = fish.GetComponent<Animator>();
animComponent.CrossFade("idle", 0);
animComponent.speed = 2f;
}
}
這個思想 就是這樣,但具體實現根據具體功能需求而定和編寫。
posted on 2018-09-20 14:11
vic.MINg 閱讀(833)
評論(0) 編輯 收藏 引用 所屬分類:
Unity 3D