在之前的幾篇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);
}
}
}
今天的這個例子比較復雜,要根據尋路網格的類型,來處理角色的動作是普通尋路,還是攀爬,抑或跳躍。這個例子應該是比較接近真實項目了。大家在實際項目中如果還有更加復雜的尋路,歡迎探討。ken@iamcoding.com
http://pan.baidu.com/s/1i35cVOD