在之前的幾篇Blog總,我們已經系統學習了自動尋路插件Navmesh的相關概念和細節。然而,如果要做一個場景精美的手游,需要用到各種復雜的場景地形,而不僅僅是平地上的自動尋路。今天我們將通過一個完整的復雜的實例,來貫穿各個細節。我們將實現一個復雜的場景,角色可以在裡面攀爬,跳躍,爬坡。是不是感覺很像當年的CS游戲呢?本案例將會用得一些基本的動畫函數,大家可以先結合文檔有個大概的了解。本實例是在官方的范例上加工而成。
(轉載請注明原文地址http://blog.csdn.net/janeky/article/details/17598113)
using UnityEngine; using System.Collections; public class AgentLocomotion : MonoBehaviour { private Vector3 target;//目標位置 private NavMeshAgent agent; private Animation anim;//動畫 private string locoState = "Locomotion_Stand"; private Vector3 linkStart;//OffMeshLink的開始點 private Vector3 linkEnd;//OffMeshLink的結束點 private Quaternion linkRotate;//OffMeshLink的旋轉 private bool begin;//是否開始尋路 // Use this for initialization void Start() { agent = GetComponent<NavMeshAgent>(); //自動移動並關閉OffMeshLinks,即在兩個隔離障礙物直接生成的OffMeshLink,agent不會自動越過 agent.autoTraverseOffMeshLink = false; //創建動畫 AnimationSetup(); //起一個協程,處理動畫狀態機 StartCoroutine(AnimationStateMachine()); } void Update() { //鼠標左鍵點擊 if (Input.GetMouseButtonDown(0)) { //攝像機到點擊位置的的射線 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { //判斷點擊的是否地形 if (hit.collider.tag.Equals("Obstacle")) { begin = true; //點擊位置坐標 target = hit.point; } } } //每一幀,設置目標點 if (begin) { agent.SetDestination(target); } } IEnumerator AnimationStateMachine() { //根據locoState不同的狀態來處理,調用相關的函數 while (Application.isPlaying) { yield return StartCoroutine(locoState); } } //站立 IEnumerator Locomotion_Stand() { do { UpdateAnimationBlend(); yield return new WaitForSeconds(0); } while (agent.remainingDistance == 0); //未到達目標點,轉到下一個狀態Locomotion_Move locoState = "Locomotion_Move"; yield return null; } IEnumerator Locomotion_Move() { do { UpdateAnimationBlend(); yield return new WaitForSeconds(0); //角色處於OffMeshLink,根據不同的地點,選擇不同動畫 if (agent.isOnOffMeshLink) { locoState = SelectLinkAnimation(); return (true); } } while (agent.remainingDistance != 0); //已經到達目標點,狀態轉為Stand locoState = "Locomotion_Stand"; yield return null; } IEnumerator Locomotion_Jump() { //播放跳躍動畫 string linkAnim = "RunJump"; Vector3 posStart = transform.position; agent.Stop(true); anim.CrossFade(linkAnim, 0.1f, PlayMode.StopAll); transform.rotation = linkRotate; do { //計算新的位置 float tlerp = anim[linkAnim].normalizedTime; Vector3 newPos = Vector3.Lerp(posStart, linkEnd, tlerp); newPos.y += 0.4f * Mathf.Sin(3.14159f * tlerp); transform.position = newPos; yield return new WaitForSeconds(0); } while (anim[linkAnim].normalizedTime < 1); //動畫恢復到Idle anim.Play("Idle"); agent.CompleteOffMeshLink(); agent.Resume(); //下一個狀態為Stand transform.position = linkEnd; locoState = "Locomotion_Stand"; yield return null; } //梯子 IEnumerator Locomotion_Ladder() { //梯子的中心位置 Vector3 linkCenter = (linkStart + linkEnd) * 0.5f; string linkAnim; //判斷是在梯子上還是梯子下 if (transform.position.y > linkCenter.y) linkAnim = "Ladder Down"; else linkAnim = "Ladder Up"; agent.Stop(true); Quaternion startRot = transform.rotation; Vector3 startPos = transform.position; float blendTime = 0.2f; float tblend = 0f; //角色的位置插值變化(0.2內變化) do { transform.position = Vector3.Lerp(startPos, linkStart, tblend / blendTime); transform.rotation = Quaternion.Lerp(startRot, linkRotate, tblend / blendTime); yield return new WaitForSeconds(0); tblend += Time.deltaTime; } while (tblend < blendTime); //設置位置 transform.position = linkStart; //播放動畫 anim.CrossFade(linkAnim, 0.1f, PlayMode.StopAll); agent.ActivateCurrentOffMeshLink(false); //等待動畫結束 do { yield return new WaitForSeconds(0); } while (anim[linkAnim].normalizedTime < 1); agent.ActivateCurrentOffMeshLink(true); //恢復Idle狀態 anim.Play("Idle"); transform.position = linkEnd; agent.CompleteOffMeshLink(); agent.Resume(); //下一個狀態Stand locoState = "Locomotion_Stand"; yield return null; } private string SelectLinkAnimation() { //獲得當前的OffMeshLink數據 OffMeshLinkData link = agent.currentOffMeshLinkData; //計算角色當前是在link的開始點還是結束點(因為OffMeshLink是雙向的) float distS = (transform.position - link.startPos).magnitude; float distE = (transform.position - link.endPos).magnitude; if (distS < distE) { linkStart = link.startPos; linkEnd = link.endPos; } else { linkStart = link.endPos; linkEnd = link.startPos; } //OffMeshLink的方向 Vector3 alignDir = linkEnd - linkStart; //忽略y軸 alignDir.y = 0; //計算旋轉角度 linkRotate = Quaternion.LookRotation(alignDir); //判斷OffMeshLink是手動的(樓梯)還是自動生成的(跳躍) if (link.linkType == OffMeshLinkType.LinkTypeManual) { return ("Locomotion_Ladder"); } else { return ("Locomotion_Jump"); } } private void AnimationSetup() { anim = GetComponent<Animation>(); // 把walk和run動畫放到同一層,然後同步他們的速度。 anim["Walk"].layer = 1; anim["Run"].layer = 1; anim.SyncLayer(1); //設置“跳躍”,“爬樓梯”,“下樓梯”的動畫模式和速度 anim["RunJump"].wrapMode = WrapMode.ClampForever; anim["RunJump"].speed = 2; anim["Ladder Up"].wrapMode = WrapMode.ClampForever; anim["Ladder Up"].speed = 2; anim["Ladder Down"].wrapMode = WrapMode.ClampForever; anim["Ladder Down"].speed = 2; //初始化動畫狀態為Idle anim.CrossFade("Idle", 0.1f, PlayMode.StopAll); } //更新動畫融合 private void UpdateAnimationBlend() { //行走速度 float walkAnimationSpeed = 1.5f; //奔跑速度 float runAnimationSpeed = 4.0f; //速度閥值(idle和walk的臨界點) float speedThreshold = 0.1f; //速度,只考慮x和z Vector3 velocityXZ = new Vector3(agent.velocity.x, 0.0f, agent.velocity.z); //速度值 float speed = velocityXZ.magnitude; //設置Run動畫的速度 anim["Run"].speed = speed / runAnimationSpeed; //設置Walk動畫的速度 anim["Walk"].speed = speed / walkAnimationSpeed; //根據agent的速度大小,確定animation的播放狀態 if (speed > (walkAnimationSpeed + runAnimationSpeed) / 2) { anim.CrossFade("Run"); } else if (speed > speedThreshold) { anim.CrossFade("Walk"); } else { anim.CrossFade("Idle", 0.1f, PlayMode.StopAll); } } }
今天的這個例子比較復雜,要根據尋路網格的類型,來處理角色的動作是普通尋路,還是攀爬,抑或跳躍。這個例子應該是比較接近真實項目了。大家在實際項目中如果還有更加復雜的尋路,歡迎探討。[email protected]
http://pan.baidu.com/s/1i35cVOD