【Unity】碰撞后缩小比例并与碰撞目标紧贴(吸附效果)

目标

玩家控制的大立方体 碰到小立方体时变成一样的大小,与小立方体分离后变为原来大小

实现过程

首先创建一个Cube,改个名称,作为玩家控制的大立方体
然后创建几个小立方体,标签设置为 Cube,可以改个颜色,方便区分
然后给玩家挂上 下文的脚本,设置一下移动和旋转的速度
请添加图片描述

直接变小,发现Bug

挂在玩家身上的脚本代码是这样的:

// 这是段有Bug的代码
using UnityEngine;

public class PlayerController_00 : MonoBehaviour
{
    private Rigidbody rb;
    private MeshRenderer meshRenderer;

    [Header("Move")]
    private Vector3 moveDir;            // 移动方向
    private Quaternion quaternionDir;   // 旋转朝向
    public float moveSpeed;             // 移动速度
    public float rotateSpeed;           // 旋转速度
                                           
    [Header("Change Scale")]               
    private Vector3 originScale;        // 初始比例

    private void Awake()
    {
        rb = GetComponent<Rigidbody>();                // 获取刚体组件
        meshRenderer = GetComponent<MeshRenderer>();   // 获取渲染器
        originScale = transform.localScale;            // 获取玩家的初始比例
    }

    private void FixedUpdate()
    {
        // 获取水平、前后方向的输入
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");

        // 获取移动方向向量
        moveDir.Set(horizontal, 0, vertical);
        // 归一化向量
        moveDir.Normalize();
        // 随移方向旋转朝向
        quaternionDir = Quaternion.LookRotation(Vector3.RotateTowards(transform.forward, moveDir, rotateSpeed * Time.deltaTime, 0));

        // 用刚体移动和旋转
        rb.MovePosition(rb.position + moveDir * moveSpeed * Time.deltaTime);
        rb.MoveRotation(quaternionDir);
    }

    private void OnCollisionEnter(Collision collision)
    {
    	// 小立方体的标签为 Cube
        if (collision.gameObject.CompareTag("Cube"))
        {
            // 将玩家的比例设为碰撞目标的比例
            transform.localScale = collision.transform.localScale;

            // 为了看的更清楚,改为红色
            meshRenderer.material.color = Color.red;
        }
    }

    private void OnCollisionExit(Collision collision)
    {
        if (collision.gameObject.CompareTag("Cube"))
        {
            // 将玩家比例设为初值
            transform.localScale = originScale;

            // 改为白色
            meshRenderer.material.color = Color.white;
        }
    }
}
//这是段有Bug的代码

像这样如果碰撞瞬间直接变小,会立刻与小立方体分离,然后瞬间恢复原来大小,然后会继续碰撞,出现如下Bug
碰撞时闪烁

变小的瞬间,改变位置

想解决这个Bug,可以让玩家控制的方块在碰到小立方体的一瞬间直接贴住小立方体,那么就要得到变小瞬间的落点
直接计算落点比较麻烦
这里提供一种比较容易理解的方法:
Unity API 提供了Collider.ClosestPoint 方法,可以帮我们在碰撞时找到小立方体上距离玩家的最近点,然后可以根据碰撞最近点计算出 玩家下一刻的落点

为了方便理解,我将计算过程拆成两步,分别是平面上的位移竖直方向的位移

平面上的位移

平面上碰撞的角度大致分为四种,这些情况全部可以用通过 .ClosestPoint() 获得碰撞最近点的位置来计算落点位置
因为碰撞时的四种情况

竖直方向上的位移

竖直方向也就是Y轴,如果你创建的小立方体并不是很小,那么靠自由下落也可以,这一步可以忽略
请添加图片描述

以下为碰撞瞬间的代码片段:

    //碰撞瞬间
    private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.CompareTag("Cube"))
        {
            // 获取目标物体比例
            float targetScale = collision.transform.localScale.x;
            // 获取目标物体与玩家的最近点
            Vector3 collisionPoint = collision.collider.ClosestPoint(rb.position);
            // 最近点投影在平面上的点
            Vector3 collisionPointOnGround = new Vector3(collisionPoint.x, 0, collisionPoint.z);
            // 先将玩家的比例设为碰撞目标的比例
            transform.localScale = collision.transform.localScale;
            // 目标相对位置的向量(与小立方体在平面上的相对位置)
            Vector3 targetPointOnGround = new Vector3(rb.position.x, 0, rb.position.z) - collisionPointOnGround;
            Debug.Log(targetPointOnGround);
            // Y轴偏移量,可以不加,让物体自由下落
            float offsetY = -(originScale.x - targetScale) * 0.5f;

            // 最后变换位置 = 平面上的最近点坐标 + 平面上的目标相对位置 - Y轴的偏移量
            rb.position = collisionPointOnGround + targetPointOnGround * targetScale + new Vector3(0, offsetY, 0);
        }
    }

撞到后保持紧贴,离开时慢慢恢复大小

要实现这个效果,需要对玩家控制的方块做一些修改
首先创建一个空物体,重置其Transform,创建一个Cube作为其子物体
将本文最后的完整代码的脚本 挂在这个空物体上
请添加图片描述
然后设置一下Cube的位置
请添加图片描述

完整代码

using UnityEngine;

public class PlayerController_02 : MonoBehaviour
{
    public GameObject cube;
    private Rigidbody rb;
    private SphereCollider sphereCollider;
    private MeshRenderer meshRenderer;

    [Header("Move")]
    private Vector3 moveDir;            // 移动方向
    private Quaternion quaternionDir;   // 旋转朝向
    public float moveSpeed;             // 移动速度
    public float rotateSpeed;           // 旋转速度

    [Header("Change Scale")]
    private Vector3 originScale;        // 初始比例
    private float targetScale;          // 目标物体比例
    private float baseDistance;         // 碰撞方块离开后的变换距离
    private float tempDistance;         // 与碰撞目标的当前距离
    private bool startChange;           // 是否开始根据距离做比例调整


    private void Awake()
    {
        rb = GetComponent<Rigidbody>();                     // 获取刚体组件
        sphereCollider = GetComponent<SphereCollider>();    // 获取触发器
        meshRenderer = cube.GetComponent<MeshRenderer>();   // 获取渲染器
        originScale = cube.transform.lossyScale;            // 获取玩家的初始比例
        baseDistance = sphereCollider.radius;               // 设置变换距离
    }

    private void FixedUpdate()
    {
        // 获取水平、前后方向的输入
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");

        // 获取移动方向向量
        moveDir.Set(horizontal, 0, vertical);
        // 归一化向量
        moveDir.Normalize();
        // 随移方向旋转朝向
        quaternionDir = Quaternion.LookRotation(Vector3.RotateTowards(transform.forward, moveDir, rotateSpeed * Time.deltaTime, 0));

        // 用刚体移动和旋转
        rb.MovePosition(rb.position + moveDir * moveSpeed * Time.deltaTime);
        rb.MoveRotation(quaternionDir);
    }

    //碰撞瞬间
    private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.CompareTag("Cube"))
        {
            // 获取目标物体比例
            targetScale = collision.transform.localScale.x;
            // 获取目标物体与玩家的最近点
            Vector3 collisionPoint = collision.collider.ClosestPoint(rb.position);
            // 最近点投影在平面上的点
            Vector3 collisionPointOnGround = new Vector3(collisionPoint.x, 0, collisionPoint.z);
            // 先将玩家的比例设为碰撞目标的比例
            cube.transform.localScale = collision.transform.localScale;
            // 目标相对位置的向量(与小立方体在平面上的相对位置)
            Vector3 targetPointOnGround = new Vector3(rb.position.x, 0, rb.position.z) - collisionPointOnGround;
            Debug.Log(targetPointOnGround);
            // Y轴偏移量
            float offsetY = -(originScale.x - targetScale) * 0.5f;

            // 最后变换位置 = 平面上的最近点坐标 + 平面上的目标相对位置 - Y轴的偏移量
            rb.position = collisionPointOnGround + targetPointOnGround * targetScale + new Vector3(0, offsetY, 0);
        }
    }

    //在贴住的过程中
    private void OnCollisionStay(Collision collision)
    {
        if (collision.gameObject.CompareTag("Cube"))
        {
            startChange = false;
            // 和Cube相撞时,玩家速度设为0,避免被挤开
            rb.velocity = Vector3.zero;
            // 将玩家的比例设为碰撞目标的比例
            cube.transform.localScale = collision.transform.localScale;

            // 为了看的更清楚,改为红色
            meshRenderer.material.color = Color.red;
        }
    }

    //分离瞬间
    private void OnCollisionExit(Collision collision)
    {
        if (collision.gameObject.CompareTag("Cube"))
        {
            // 和Cube分离时,可以开始变换比例
            startChange = true;
        }
    }

    private void OnTriggerStay(Collider other)
    {
        if (other.CompareTag("Cube") && startChange)
        {
            // 获取玩家和目标物体在平面上的位置
            Vector2 playerPos = new Vector2(rb.position.x, rb.position.z);
            Vector2 targetPos = new Vector2(other.transform.position.x, other.transform.position.z);
            // 获取在平面上的距离,- targetScale * 1.5f是为预留一部分空间,避免距离过近时出现不停碰撞的Bug
            tempDistance = Vector2.Distance(playerPos, targetPos) - targetScale * 1.5f;

            // 计算需要变换比例的部分,限制在 0 到 1 - targetScale 之间
            float tempScale = Mathf.Clamp(tempDistance / baseDistance * (1 - targetScale), 0, 1 - targetScale);
            // 玩家当前的比例 = 初始比例 * 目标缩放值 + 根据距离计算的动态值
            cube.transform.localScale = originScale * targetScale + originScale * tempScale;

            // 改为黄色
            meshRenderer.material.color = Color.yellow;
        }
    }

    private void OnTriggerExit(Collider other)
    {
        if (other.CompareTag("Cube"))
        {
            startChange = false;
            // 将玩家比例设为初值
            cube.transform.localScale = originScale;

            // 改为白色
            meshRenderer.material.color = Color.white;
        }
    }
}

关于分离时慢慢恢复大小的讲解详见视频

最终效果

最终效果1
最终效果2
最终效果3

感谢观看!

  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dugege007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值