一蚯舱、準(zhǔn)備工作
1.創(chuàng)建項(xiàng)目
2.創(chuàng)建示例場(chǎng)景
a.創(chuàng)建3d ——Plane(地面)(0,0,0)和Sphere(游戲?qū)ο?(0,0.5,0)陆赋,Sphere添加拖尾組件TrailRenderer
b.創(chuàng)建腳本 MovingSphere ——該腳本掛載到Sphere上并添加Rigidboy組件
二拍柒、需求如下
- 控制剛體球體的速度灯节。
2.支持通過(guò)跳躍進(jìn)行垂直移動(dòng)汁掠。
3.檢測(cè)地面及其角度史隆。
4.沿斜坡移動(dòng)谣妻。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MovingSphere : MonoBehaviour
{
// 最大速度,通過(guò)Inspector面板調(diào)整勾拉,范圍0到100
[SerializeField, Range(0f, 100f), Tooltip("最大速度")]
private float maxSpeed = 10f;
// 最大加速度鞠苟,通過(guò)Inspector面板調(diào)整,范圍0到100
[SerializeField, Range(0f, 100f), Tooltip("最大加速度")]
private float maxAcceleration = 10f;
// 最大空中加速度熔酷,通過(guò)Inspector面板調(diào)整孤紧,范圍0到100
[SerializeField, Range(0f, 100f), Tooltip("最大空氣加速度")]
private float maxAirAcceleration = 1f;
// 跳躍高度,通過(guò)Inspector面板調(diào)整拒秘,范圍0到10
[SerializeField, Range(0f, 10f), Tooltip("跳躍高度")]
private float jumpHeight = 2f;
// 最大空中跳躍次數(shù)号显,通過(guò)Inspector面板調(diào)整臭猜,范圍0到5
[SerializeField, Range(0, 5), Tooltip("最大空中跳躍次數(shù)")]
private int maxAirJumps = 0;
// 最大地面角度,通過(guò)Inspector面板調(diào)整押蚤,范圍0到90度
[SerializeField, Range(0, 90), Tooltip("最大地面角度")]
private float maxGroundAngle = 25;
// 當(dāng)前速度
Vector3 velocity;
// 期望速度
Vector3 desiredVelocity;
// Rigidbody組件蔑歌,用于物理模擬
Rigidbody body;
// 是否需要跳起
bool desiredJump;
// 地面接觸點(diǎn)計(jì)數(shù)
int groundContactCount;
// 是否在地面上
bool OnGround
{
get { return groundContactCount > 0; }
}
// 跳躍階段計(jì)數(shù)器
int jumpPhase;
// 地面法線與球體移動(dòng)方向的最小點(diǎn)積,用于判斷接觸角度
float minGroundDotProduct;
// 接觸點(diǎn)法線
Vector3 contactNormal;
// 當(dāng)Inspector面板中的值改變時(shí)調(diào)用
private void OnValidate()
{
// 計(jì)算最大地面角度的余弦值
minGroundDotProduct = Mathf.Cos(maxGroundAngle * Mathf.Deg2Rad);
}
// Awake在對(duì)象實(shí)例化時(shí)調(diào)用一次
private void Awake()
{
// 獲取Rigidbody組件
body = GetComponent<Rigidbody>();
// 調(diào)用OnValidate以確保minGroundDotProduct被正確初始化
OnValidate();
}
// 每幀調(diào)用
void Update()
{
// 獲取玩家輸入
Vector2 playerInput;
playerInput.x = Input.GetAxis("Horizontal");
playerInput.y = Input.GetAxis("Vertical");
// 限制輸入向量的長(zhǎng)度為1
playerInput = Vector2.ClampMagnitude(playerInput, 1f);
// 根據(jù)輸入和最大速度計(jì)算期望速度
desiredVelocity = new Vector3(playerInput.x, 0f, playerInput.y) * maxSpeed;
// 如果按下跳躍鍵揽碘,則設(shè)置desiredJump為true
desiredJump |= Input.GetButtonDown("Jump");
// 根據(jù)地面接觸點(diǎn)數(shù)調(diào)整球體顏色(僅作為示例)
GetComponent<Renderer>().material.SetColor("_Color", Color.white * (groundContactCount * 0.25f));
}
// FixedUpdate用于物理更新次屠,每固定時(shí)間間隔調(diào)用一次
private void FixedUpdate()
{
// 更新?tīng)顟B(tài)信息
UpdateState();
// 根據(jù)當(dāng)前狀態(tài)調(diào)整速度
AdjustVelocity();
// 如果需要跳躍,則執(zhí)行跳躍動(dòng)作
if (desiredJump)
{
desiredJump = false;
Jump();
}
// 應(yīng)用新速度
body.velocity = velocity;
// 清除狀態(tài)信息雳刺,為下一幀準(zhǔn)備
ClearState();
}
// 碰撞開(kāi)始和持續(xù)時(shí)調(diào)用
private void OnCollisionEnter(Collision collision)
{
EvaluateCollision(collision);
}
private void OnCollisionStay(Collision collision)
{
EvaluateCollision(collision);
}
// 清除狀態(tài)信息
void ClearState()
{
groundContactCount = 0;
contactNormal = Vector3.zero;
}
// 根據(jù)當(dāng)前狀態(tài)和期望狀態(tài)調(diào)整速度
void AdjustVelocity()
{
// 計(jì)算接觸平面的X軸和Z軸方向
Vector3 xAxis = ProjectOnContactPlane(Vector3.right).normalized;
Vector3 zAxis = ProjectOnContactPlane(Vector3.forward).normalized;
// 計(jì)算當(dāng)前速度在X軸和Z軸上的分量
float currentX = Vector3.Dot(velocity, xAxis);
float currentZ = Vector3.Dot(velocity, zAxis);
// 根據(jù)是否在地面上選擇加速度
float acceleration = OnGround ? maxAcceleration : maxAirAcceleration;
// 計(jì)算每幀最大速度變化量
float maxSpeedChange = acceleration * Time.deltaTime;
// 計(jì)算新的X軸和Z軸速度分量
float newX = Mathf.MoveTowards(currentX, desiredVelocity.x, maxSpeedChange);
float newZ = Mathf.MoveTowards(currentZ, desiredVelocity.z, maxSpeedChange);
// 更新速度
velocity += xAxis * (newX - currentX) + zAxis * (newZ - currentZ);
}
// 將向量投影到接觸平面上
Vector3 ProjectOnContactPlane(Vector3 vector)
{
return vector - contactNormal * Vector3.Dot(vector, contactNormal);
}
// 更新?tīng)顟B(tài)信息劫灶,包括速度和接觸信息
void UpdateState()
{
// 獲取當(dāng)前速度
velocity = body.velocity;
// 如果在地面上,重置跳躍階段和更新接觸法線
if (OnGround)
{
jumpPhase = 0;
if (groundContactCount > 0)
{
contactNormal.Normalize();
}
}
else
{
// 如果不在地面上掖桦,將接觸法線設(shè)置為向上方向
contactNormal = Vector3.up;
}
}
// 根據(jù)碰撞信息更新地面接觸點(diǎn)和法線
void EvaluateCollision(Collision collision)
{
for (int i = 0; i < collision.contactCount; i++)
{
Vector3 normal = collision.GetContact(i).normal;
// 如果法線與Y軸的夾角小于最大地面角度本昏,則認(rèn)為是地面接觸
if (normal.y >= minGroundDotProduct)
{
groundContactCount += 1;
contactNormal += normal;
}
}
}
// 執(zhí)行跳躍動(dòng)作
void Jump()
{
// 如果在地面上或者跳躍次數(shù)未超過(guò)限制,則執(zhí)行跳躍
if (OnGround || jumpPhase < maxAirJumps)
{
jumpPhase += 1;
// 計(jì)算跳躍速度
float jumpSpeed = Mathf.Sqrt(-2f * Physics.gravity.y * jumpHeight);
float alignedSpeed = Vector3.Dot(velocity, contactNormal);
// 如果球體已經(jīng)向上移動(dòng)滞详,則減少跳躍速度
if (alignedSpeed > 0f)
{
jumpSpeed = Mathf.Max(jumpSpeed - velocity.y, 0f);
}
// 添加跳躍速度到當(dāng)前速度上
velocity += contactNormal * jumpSpeed;
}
}
}