Unity 对象移动(物理)

一、准备工作

1.创建项目

2.创建示例场景

a.创建3d ——Plane(地面)(0,0,0)和Sphere(游戏对象)(0,0.5,0),Sphere添加拖尾组件TrailRenderer
b.创建脚本 MovingSphere ——该脚本挂载到Sphere上并添加Rigidboy组件


二、需求如下

  1. 控制刚体球体的速度。
    2.支持通过跳跃进行垂直移动。
    3.检测地面及其角度。
    4.沿斜坡移动。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MovingSphere : MonoBehaviour
{
    // 最大速度,通过Inspector面板调整,范围0到100
    [SerializeField, Range(0f, 100f), Tooltip("最大速度")]
    private float maxSpeed = 10f;
    // 最大加速度,通过Inspector面板调整,范围0到100
    [SerializeField, Range(0f, 100f), Tooltip("最大加速度")]
    private float maxAcceleration = 10f;
    // 最大空中加速度,通过Inspector面板调整,范围0到100
    [SerializeField, Range(0f, 100f), Tooltip("最大空气加速度")]
    private float maxAirAcceleration = 1f;

    // 跳跃高度,通过Inspector面板调整,范围0到10
    [SerializeField, Range(0f, 10f), Tooltip("跳跃高度")]
    private float jumpHeight = 2f;
    // 最大空中跳跃次数,通过Inspector面板调整,范围0到5
    [SerializeField, Range(0, 5), Tooltip("最大空中跳跃次数")]
    private int maxAirJumps = 0;
    // 最大地面角度,通过Inspector面板调整,范围0到90度
    [SerializeField, Range(0, 90), Tooltip("最大地面角度")]
    private float maxGroundAngle = 25;

    // 当前速度
    Vector3 velocity;
    // 期望速度
    Vector3 desiredVelocity;

    // Rigidbody组件,用于物理模拟
    Rigidbody body;
    // 是否需要跳起
    bool desiredJump;

    // 地面接触点计数
    int groundContactCount;
    // 是否在地面上
    bool OnGround
    {
        get { return groundContactCount > 0; }
    }

    // 跳跃阶段计数器
    int jumpPhase;

    // 地面法线与球体移动方向的最小点积,用于判断接触角度
    float minGroundDotProduct;
    // 接触点法线
    Vector3 contactNormal;

    // 当Inspector面板中的值改变时调用
    private void OnValidate()
    {
        // 计算最大地面角度的余弦值
        minGroundDotProduct = Mathf.Cos(maxGroundAngle * Mathf.Deg2Rad);
    }

    // Awake在对象实例化时调用一次
    private void Awake()
    {
        // 获取Rigidbody组件
        body = GetComponent<Rigidbody>();
        // 调用OnValidate以确保minGroundDotProduct被正确初始化
        OnValidate();
    }

    // 每帧调用
    void Update()
    {
        // 获取玩家输入
        Vector2 playerInput;
        playerInput.x = Input.GetAxis("Horizontal");
        playerInput.y = Input.GetAxis("Vertical");
        // 限制输入向量的长度为1
        playerInput = Vector2.ClampMagnitude(playerInput, 1f);

        // 根据输入和最大速度计算期望速度
        desiredVelocity = new Vector3(playerInput.x, 0f, playerInput.y) * maxSpeed;

        // 如果按下跳跃键,则设置desiredJump为true
        desiredJump |= Input.GetButtonDown("Jump");

        // 根据地面接触点数调整球体颜色(仅作为示例)
        GetComponent<Renderer>().material.SetColor("_Color", Color.white * (groundContactCount * 0.25f));
    }

    // FixedUpdate用于物理更新,每固定时间间隔调用一次
    private void FixedUpdate()
    {
        // 更新状态信息
        UpdateState();
        // 根据当前状态调整速度
        AdjustVelocity();

        // 如果需要跳跃,则执行跳跃动作
        if (desiredJump)
        {
            desiredJump = false;
            Jump();
        }
        // 应用新速度
        body.velocity = velocity;
        // 清除状态信息,为下一帧准备
        ClearState();
    }

    // 碰撞开始和持续时调用
    private void OnCollisionEnter(Collision collision)
    {
        EvaluateCollision(collision);
    }
    private void OnCollisionStay(Collision collision)
    {
        EvaluateCollision(collision);
    }

    // 清除状态信息
    void ClearState()
    {
        groundContactCount = 0;
        contactNormal = Vector3.zero;
    }

    // 根据当前状态和期望状态调整速度
    void AdjustVelocity()
    {
        // 计算接触平面的X轴和Z轴方向
        Vector3 xAxis = ProjectOnContactPlane(Vector3.right).normalized;
        Vector3 zAxis = ProjectOnContactPlane(Vector3.forward).normalized;

        // 计算当前速度在X轴和Z轴上的分量
        float currentX = Vector3.Dot(velocity, xAxis);
        float currentZ = Vector3.Dot(velocity, zAxis);

        // 根据是否在地面上选择加速度
        float acceleration = OnGround ? maxAcceleration : maxAirAcceleration;
        // 计算每帧最大速度变化量
        float maxSpeedChange = acceleration * Time.deltaTime;

        // 计算新的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);
    }

    // 更新状态信息,包括速度和接触信息
    void UpdateState()
    {
        // 获取当前速度
        velocity = body.velocity;
        // 如果在地面上,重置跳跃阶段和更新接触法线
        if (OnGround)
        {
            jumpPhase = 0;
            if (groundContactCount > 0)
            {
                contactNormal.Normalize();
            }
        }
        else
        {
            // 如果不在地面上,将接触法线设置为向上方向
            contactNormal = Vector3.up;
        }
    }

    // 根据碰撞信息更新地面接触点和法线
    void EvaluateCollision(Collision collision)
    {
        for (int i = 0; i < collision.contactCount; i++)
        {
            Vector3 normal = collision.GetContact(i).normal;
            // 如果法线与Y轴的夹角小于最大地面角度,则认为是地面接触
            if (normal.y >= minGroundDotProduct)
            {
                groundContactCount += 1;
                contactNormal += normal;
            }
        }
    }

    // 执行跳跃动作
    void Jump()
    {
        // 如果在地面上或者跳跃次数未超过限制,则执行跳跃
        if (OnGround || jumpPhase < maxAirJumps)
        {
            jumpPhase += 1;
            // 计算跳跃速度
            float jumpSpeed = Mathf.Sqrt(-2f * Physics.gravity.y * jumpHeight);
            float alignedSpeed = Vector3.Dot(velocity, contactNormal);
            // 如果球体已经向上移动,则减少跳跃速度
            if (alignedSpeed > 0f)
            {
                jumpSpeed = Mathf.Max(jumpSpeed - velocity.y, 0f);
            }
            // 添加跳跃速度到当前速度上
            velocity += contactNormal * jumpSpeed;
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容