You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity中Blender导入砖块无法反弹球方向,原生立方体正常

Unity 3D打砖块游戏:球碰撞砖块后速度不反弹的问题解决

问题背景

开发3D类打砖块游戏时,用Blender制作砖块模型并以.blend格式导入Unity,给砖块添加了未勾选Is Trigger的Box Collider;球采用带开启Is Trigger的Sphere Collider的简单球体,Rigidbody设置为连续碰撞检测。目前paddle和墙体的反弹功能正常,但存在以下问题:

  • 球第一次碰撞砖块时,碰撞能被检测到、Destroy()也能正常执行,但球的速度不会改变,直接穿过砖块
  • 球回到paddle再次飞向砖块后,反弹功能恢复正常

后续排查发现:

  • 给球的初始velocity设置z分量而非仅x时,第一次碰撞就能正常反弹,但这不是合理解决方案
  • 最终定位根源:球同时碰撞两块砖时,OnTriggerEnter会被执行两次,导致velocity.x先取反再取反,最终恢复原值

球的控制代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Ball : MonoBehaviour
{
    public float maxX;
    public float maxZ;
    private Vector3 velocity;

    private void OnTriggerEnter(Collider other)
    {
        float maxDist = 0.5f * other.transform.localScale.x + 0.5f * transform.localScale.z;
        float actualDist = transform.position.z - other.transform.position.z;
        float distNorm = actualDist / maxDist;
        if (other.CompareTag("Paddle"))
        {
            velocity.z = distNorm * maxZ;
            velocity.x *= -1;
        }
        else if (other.CompareTag("Side Wall"))
        {
            velocity.z *= -1;
        }
        else if (other.CompareTag("Wall"))
        {
            velocity.x *= -1;
        }
        else if (other.CompareTag("Brick"))
        {
            velocity.x *= -1;
            Destroy(other.gameObject);
        }
    }
    
    void Start()
    {
        velocity = new Vector3(-maxX, 0, 0);
    }

    void Update()
    {
        transform.position += velocity * Time.deltaTime;
     
    }
}

砖块生成代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BrickFactory : MonoBehaviour
{
    public GameObject emptyBrick, 
                      yellowBrick,
                      orangeBrick, 
                      greenBrick, 
                      cyanBrick, 
                      pinkBrick, 
                      purpleBrick, 
                      darkBlueBrick, 
                      redBrick;
    private GameObject[] brickTypes;

    private int[,] level1 = new int[25,10] {
        {8,8,8,8,8,8,8,8,8,8},
        {8,8,8,8,8,8,8,8,8,8},
        {7,7,7,7,7,7,7,7,7,7},
        {7,7,7,7,7,7,7,7,7,7},
        {6,6,6,6,6,6,6,6,6,6},
        {6,6,6,6,6,6,6,6,6,6},
        {5,5,5,5,5,5,5,5,5,5},
        {5,5,5,5,5,5,5,5,5,5},
        {4,4,4,4,4,4,4,4,4,4},
        {4,4,4,4,4,4,4,4,4,4},
        {3,3,3,3,3,3,3,3,3,3},
        {3,3,3,3,3,3,3,3,3,3},
        {2,2,2,2,2,2,2,2,2,2},
        {2,2,2,2,2,2,2,2,2,2},
        {1,1,1,1,1,1,1,1,1,1},
        {6,6,6,6,6,6,6,6,6,6},
        {5,5,5,5,5,5,5,5,5,5},
        {5,5,5,5,5,5,5,5,5,5},
        {4,4,4,4,4,4,4,4,4,4},
        {4,4,4,4,4,4,4,4,4,4},
        {3,3,3,3,3,3,3,3,3,3},
        {3,3,3,3,3,3,3,3,3,3},
        {2,2,2,2,2,2,2,2,2,2},
        {2,2,2,2,2,2,2,2,2,2},
        {1,1,1,1,1,1,1,1,1,1}

     };

    void Start()
    {
        brickTypes = new GameObject[]{
            emptyBrick,         //empty 0
            yellowBrick,        //yellow 1
            orangeBrick,        //orange 2
            greenBrick,         //green 3
            cyanBrick,          //cyan 4
            pinkBrick,          //pink 5
            purpleBrick,        //purple 6
            darkBlueBrick,      //darkBlue 7
            redBrick            //red 8
        };
        float rowOffset = -7.877f;
        float columnOffset = -7.5f;
        for (int i = 0; i < 25; i++)
        {
            for (int j = 0; j < 10; j++)
            {
                if(level1[i, j] != 0)
                {
                    GameObject newBrick = Instantiate(brickTypes[level1[i,j]], new Vector3(columnOffset, 1.28f, rowOffset), Quaternion.identity) as GameObject;
                    newBrick.transform.Rotate(0, 0, 90);
                }
                rowOffset += 1.75f;
            }
            rowOffset = -7.877f;
            columnOffset += 0.6f;
        }
    }

    void Update()
    {
        
    }
}

解决方法

针对同时碰撞多块砖导致速度取反抵消的问题,有几种实用处理方式:

1. 单次帧内只处理一次砖块碰撞

在Ball脚本中添加标记变量,限制每帧仅处理一次砖块碰撞的速度修改:

private bool hasHitBrickThisFrame = false;
private int lastFrameChecked = -1;

private void OnTriggerEnter(Collider other)
{
    // 每帧重置碰撞标记
    if (Time.frameCount != lastFrameChecked)
    {
        hasHitBrickThisFrame = false;
        lastFrameChecked = Time.frameCount;
    }

    float maxDist = 0.5f * other.transform.localScale.x + 0.5f * transform.localScale.z;
    float actualDist = transform.position.z - other.transform.position.z;
    float distNorm = actualDist / maxDist;
    
    if (other.CompareTag("Paddle"))
    {
        velocity.z = distNorm * maxZ;
        velocity.x *= -1;
    }
    else if (other.CompareTag("Side Wall"))
    {
        velocity.z *= -1;
    }
    else if (other.CompareTag("Wall"))
    {
        velocity.x *= -1;
    }
    else if (other.CompareTag("Brick"))
    {
        // 仅在当前帧未处理过砖块碰撞时修改速度
        if (!hasHitBrickThisFrame)
        {
            velocity.x *= -1;
            hasHitBrickThisFrame = true;
        }
        Destroy(other.gameObject);
    }
}

2. 基于碰撞法线计算反弹方向(更严谨)

放弃直接取反x速度,改用物理反射公式计算反弹方向,即使同时碰撞多块砖也能得到正确结果,还能适配带旋转角度的砖块:

else if (other.CompareTag("Brick"))
{
    // 获取碰撞点的法线方向
    Vector3 collisionNormal = other.ClosestPointOnBounds(transform.position) - transform.position;
    collisionNormal.Normalize();
    
    // 用反射公式计算反弹速度
    velocity = Vector3.Reflect(velocity, collisionNormal);
    Destroy(other.gameObject);
}

3. 调整砖块布局避免同时碰撞

微调砖块生成时的偏移量,让砖块之间留出极小间隙(比如0.01f),避免球同时触发多个Trigger:

// 在BrickFactory的循环中修改增量
rowOffset += 1.75f + 0.01f;
// ...
columnOffset += 0.6f + 0.01f;

这种方式简单直接,但仅适合布局规则的场景。

Blender模型导入注意事项

虽然本次问题根源不在模型,但导入时仍需注意:

  • 导入时勾选Generate Colliders,手动添加Collider时要确保其大小、位置与模型匹配
  • 重置模型的Transform缩放(点击Inspector中Transform组件的Reset按钮),避免缩放异常导致Collider计算错误
  • 确认模型轴心点位置正确,防止Collider偏移

内容的提问来源于stack exchange,提问作者Ernest P W

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 12:37:07