人物的運動有很多種,最簡單的可以直接用transform.translate或者直接改變?nèi)宋锏腜osition,這種方法在2D游戲可能會有采用。
另外還可以用導(dǎo)航系統(tǒng)進(jìn)行運動。但用得更多的是另外兩種方式:Rigidbody和CharacterController。
CharacterController能很快做出人物的運動方案,但是相比Rigidbody,物理特性表現(xiàn)沒有那么好,接口也相對少。
結(jié)合視角控制的內(nèi)容,采用了第三人稱固定視角,搖桿控制,并加入了一個小型事件分發(fā)系統(tǒng)。實現(xiàn)搖桿控制人物的移動,懸崖邊界檢測,地面檢測,相機第三人稱視角跟隨。
其中移動模塊代碼如下:
using UnityEngine;
public class MoveMotor : MonoBehaviour
{
public Animator animator;
public Rigidbody selfRigidbody;//自身剛體組件
public float speed = 17f;//移動速度
public float rotSpeed = 17f;//旋轉(zhuǎn)速度
public Transform[] groundPoints;//地面檢測點
public LayerMask groundLayerMask = ~0;//地面LayerMask
public LayerMask wallLayerMask = ~0;//墻壁LayerMask
int mIsMoveAnimatorHash;//移動Animator變量哈希
private Camera mainCamera;
public bool IsMoving { get; private set; }
public Vector3 MoveDirection { get; private set; }
void Start()
{
mIsMoveAnimatorHash = Animator.StringToHash("IsMove");
EventManager.AddEvent<Vector2>(EventTypeList.EventTouchDir, InputDir);
mainCamera = Camera.main;
}
void InputDir(Vector2 v2)
{
horizontal = v2.x;
vertical = v2.y;
}
float horizontal;
float vertical;
void Update()
{
const float INPUT_EPS = 0.2f;
//horizontal = Input.GetAxis("Horizontal");//橫向軸的值
//vertical = Input.GetAxis("Vertical");//縱向軸的值
var inputDirection = new Vector3(horizontal, 0f, vertical);//輸入向量
var upAxis = -Physics.gravity.normalized;//up軸向
var moveDirection = CameraDirectionProcess(inputDirection, upAxis);//相機輸入方向修正
MoveDirection = moveDirection;
if (inputDirection.magnitude > INPUT_EPS)//是否有輸入方向
{
var raycastHit = default(RaycastHit);
var groundNormal = Vector3.zero;
var groundedFlag = GroundProcess(ref raycastHit, ref moveDirection, out groundNormal, upAxis);//地面檢測
if (groundedFlag)
{
var cacheMoveDirection = moveDirection;
var wallFlag = WallProcess(ref raycastHit, ref moveDirection, groundNormal, upAxis);//墻壁檢測
var cliffFlag = false;
if (!wallFlag)
cliffFlag = CliffProcess(ref raycastHit, ref moveDirection, groundNormal, upAxis);//懸崖檢測
if (!cliffFlag)
selfRigidbody.velocity = moveDirection * speed * Time.fixedDeltaTime;//更新位置
UpdateGroundDetectPoints();//打亂地面檢測點順序
RotateProcess(cacheMoveDirection, upAxis);//更新旋轉(zhuǎn)
}
animator.SetBool(mIsMoveAnimatorHash, true);//更新Animator變量
IsMoving = true;
}
else//沒有移動
{
MoveDirection = Vector3.zero;
animator.SetBool(mIsMoveAnimatorHash, false);//更新Animator變量
IsMoving = false;
Debug.Log("沒有輸入方向");
}
}
Vector3 CameraDirectionProcess(Vector3 inputDirection, Vector3 upAxis)
{
mainCamera = Camera.main;
var quat = Quaternion.FromToRotation(mainCamera.transform.up, upAxis);//不同重力的up軸修正
var cameraForwardDirection = quat * mainCamera.transform.forward;//轉(zhuǎn)換forward方向
var moveDirection = Quaternion.LookRotation(cameraForwardDirection, upAxis) * inputDirection.normalized;//轉(zhuǎn)換輸入向量方向
return moveDirection;
}
//檢測目前是否著地
bool GroundProcess(ref RaycastHit raycastHit, ref Vector3 moveDirection, out Vector3 groundNormal, Vector3 upAxis)
{
const float GROUND_RAYCAST_LENGTH = 0.2f;//地面檢測射線長度
var result = false;
for (int i = 0; i < groundPoints.Length; i++)
{
var tempRaycastHit = default(RaycastHit);
if (Physics.Raycast(new Ray(groundPoints[i].position, -upAxis), out tempRaycastHit, GROUND_RAYCAST_LENGTH, groundLayerMask))//投射地面射線
{
if (raycastHit.transform == null || Vector3.Distance(transform.position, tempRaycastHit.point) < Vector3.Distance(transform.position, raycastHit.point))
raycastHit = tempRaycastHit;//選取最近的地面點
result = true;
break;
}
}
groundNormal = raycastHit.normal;//返回地面法線
var upQuat = Quaternion.FromToRotation(upAxis, groundNormal);
moveDirection = upQuat * moveDirection;//根據(jù)地面法線修正移動位置
return result;
}
//檢測是否墻壁
bool WallProcess(ref RaycastHit raycastHit, ref Vector3 moveDirection, Vector3 groundNormal, Vector3 upAxis)
{
const float HEIGHT = 1.2f;//玩家高度估算值
const float OBLIQUE_P0 = 0.3f, OBLIQUE_P1 = 0.6f;//斜方向射線偏移
const float RAYCAST_LEN = 0.37f;//射線長度
const float DOT_RANGE = 0.86f;//點乘范圍約束
const float ANGLE_STEP = 30f;//檢測角度間距
var result = false;
var ray = new Ray(transform.position + upAxis * HEIGHT, moveDirection);
for (float angle = -90f; angle <= 90f; angle += ANGLE_STEP)//180度內(nèi)每隔一定角度進(jìn)行射線檢測
{
var quat = Quaternion.AngleAxis(angle, upAxis);//得到當(dāng)前角度
ray = new Ray(transform.position, quat * moveDirection);
var p0 = ray.origin + ray.direction * OBLIQUE_P0;
var p1 = ray.origin + upAxis * HEIGHT + ray.direction * OBLIQUE_P1;
if (Physics.Linecast(p0, p1, out raycastHit, wallLayerMask))//是否碰到墻壁
{
var newRay = new Ray(Vector3.Project(raycastHit.point, upAxis) + Vector3.ProjectOnPlane(ray.origin, upAxis), ray.direction);
if (Physics.Raycast(newRay, out raycastHit, RAYCAST_LEN, wallLayerMask))//重新得到射線位置并投射
{
if (Vector3.Dot(moveDirection, -raycastHit.normal) < DOT_RANGE)//點乘約束
{
var cross = Vector3.Cross(raycastHit.normal, upAxis).normalized;
var cross2 = -cross;
if (Vector3.Dot(cross, moveDirection) > Vector3.Dot(cross2, moveDirection))//獲得最接近方向
moveDirection = cross;
else
moveDirection = cross2;
break;//若已確定修正方向則跳出循環(huán)
}
}
result = true;//確定碰到了墻壁
}
}
return result;
}
//檢測是否是懸崖
bool CliffProcess(ref RaycastHit raycastHit, ref Vector3 moveDirection, Vector3 groundNormal, Vector3 upAxis)
{
const float GROUND_RAYCAST_LENGTH = 0.4f;//地面檢測射線長度
var result = false;
for (int i = 0; i < groundPoints.Length; i++)//遍歷地面檢測點
{
var relative = groundPoints[i].position - transform.position;//取相對位置
var quat = Quaternion.FromToRotation(upAxis, groundNormal);//映射到地面法線方向四元數(shù)
var newPoint = transform.position + moveDirection + quat * relative;
var ray = new Ray(newPoint, -upAxis);//取移動后的位置投射射線
if (!Physics.Raycast(ray, out raycastHit, GROUND_RAYCAST_LENGTH, groundLayerMask))//只要有一個未檢測到地面則為懸崖
{
result = true;//返回true
break;
}
}
return result;
}
void RotateProcess(Vector3 moveDirection, Vector3 upAxis)
{
moveDirection = Vector3.ProjectOnPlane(moveDirection, upAxis);//投影到up平面上
var playerLookAtQuat = Quaternion.LookRotation(moveDirection, upAxis);//得到移動方向代表的旋轉(zhuǎn)
transform.rotation = Quaternion.Lerp(transform.rotation, playerLookAtQuat, rotSpeed * Time.deltaTime);//更新插值
}
void UpdateGroundDetectPoints()
{
var groupPointsIndex_n = Random.Range(0, groundPoints.Length);//隨機一個索引
var temp = groundPoints[groupPointsIndex_n];
groundPoints[groupPointsIndex_n] = groundPoints[groundPoints.Length - 1];
groundPoints[groundPoints.Length - 1] = temp;//交換地面檢測點,防止每次順序都一樣。
}
}