如何使用 C# 在 Unity 中逼真地反映 3d 球体

How to realistically reflect a 3d sphere in Unity with C#

一段时间以来,我一直在尝试在 Unity 中真实地反映盒子墙壁上的 3d 球体。由于某种原因,反射通常是正确的,但是当球在某些方向上撞到墙壁时,反射是不正确的。 为了说明球撞到墙时会发生什么:T = 顶墙,R = 右墙,L = 左墙,B = 底墙。令 r = 球 comes/goes 向右,l = 向左,s = 球 stops/slows 明显向下。下面的说明采用以下格式:Xyz,其中 X = 球将要撞击的墙壁,y = 球的初始方向,z = 反射。游戏采用自上而下的视角,说明基于墙壁的视角。我也是 C# 的新手,所以代码可能会让人眼花缭乱。

说明:Tll,Trl; Bll, Brl; Rls 或撞到另一面墙后 Rlr, Rrl; lls 或撞到另一面墙后 Llr, Lrl

一般情况下,球停下来后,会在空中跳跃。我想知道这是否是因为角度沿着错误的轴反射,但为什么这种情况只是偶尔发生?此外,当只按住一个键时,球会来回弹跳,直到离开竞技场。我知道离散和连续命中检测,设置是离散的,但墙壁通常能很好地控制球,这种情况除外。

我尝试了什么:

  1. 弄清楚如何使用 Vector3.Reflect。我不明白这个函数应该包含哪些变量以及如何将它应用到我的代码中。我确实查看了 Unity 文档页面寻求帮助,但它没有回答我的问题。
  2. 更改负号,因为角度必须是 y 轴上的反射,这确实改变了反射的工作方式,但没有解决问题。目前的底片排序方式是我发现的最理想的方式。
  3. 给球一个物理 material 的弹性。
  4. 在 arctan 方程的分母中添加一个小数以帮助防止被零除。这根本没有帮助。
  5. 为正加速度和负加速度的不同组合创建不同的方程(基本上改变负数)。由于每次按下按钮都会产生一定的正加速度或负加速度(请参阅运动脚本),并且上述标志似乎存在问题,我想知道将每个加速度与其自己的方程组相关联是否可以解决问题。没用。
  6. 检查墙壁是否处于不同角度。
  7. 删除变量 xA 和 yA,或将它们放在不同的位置。
  8. 尝试寻找对象的速度,但不知道如何实现它。

名为 Controller 的玩家的移动脚本代码:

public class Movement : MonoBehaviour
{

    public static float xAcceleration = 0.0f;
    public static float yAcceleration = 0.0f;

    // Update is called once per frame
    void Update()
    {

        if (Input.GetKey(KeyCode.W))                            //If the key W is pressed:
        {                                                       
            Vector3 position = this.transform.position;         //Variable position is set to transform the players placement in the game.
            if (yAcceleration >= -5 && yAcceleration <= 5)      //If the y vector of the acceleration is >= -5 and <= 5:
            {
                yAcceleration = yAcceleration + 0.01f;          //The y vector of the acceleration increases by 0.01 as long as the key W is pressed.
            }
            position.z = position.z + (0.1f * yAcceleration);   //The position of the object on the z-axis (pretend it is the y-axis in the game world) is transformed by its original position plus its speed times its yAcceleration.
            this.transform.position = position;                 //The gameObject is now transformed to a position equal to the variable position by the z-axis.
        }

        else                                                    //If the key W is let go of:
        {
            Vector3 position = this.transform.position;         
            position.z = position.z + (0.1f * yAcceleration);
            this.transform.position = position;                 //The position of the gameObject continues to update, but its acceleration does not change. Basically, it continues to move forward.
        }

//The rest of the code is very similar to the above, but I included it just in case there was something wrong.

        if (Input.GetKey(KeyCode.S))
        {
            Vector3 position = this.transform.position;
            if (yAcceleration >= -5 && yAcceleration <= 5)
            {
                yAcceleration = (yAcceleration) - 0.01f;
            }
            position.z = position.z + (0.1f * yAcceleration);
            this.transform.position = position;
        }

        else
        {
            Vector3 position = this.transform.position;
            position.z = position.z + (0.1f * yAcceleration);
            this.transform.position = position;
        }

        if (Input.GetKey(KeyCode.A))
        { 
            Vector3 position = this.transform.position;
            if (xAcceleration >= -5 && xAcceleration <= 5)
            {
                xAcceleration = (xAcceleration) - 0.01f;
            }
            position.x = position.x + (0.1f * xAcceleration);
            this.transform.position = position;
        }

        else
        {
            Vector3 position = this.transform.position;
            position.x = position.x + (0.1f * xAcceleration);
            this.transform.position = position;
        }

        if (Input.GetKey(KeyCode.D))
        {
            Vector3 position = this.transform.position;
            if (xAcceleration >= -5 && xAcceleration <= 5)
            {
                xAcceleration = (xAcceleration) + 0.01f;
            }
            position.x = position.x + (0.1f * xAcceleration);
            this.transform.position = position;
        }

        else
        {
            Vector3 position = this.transform.position;
            position.x = position.x + (0.1f * xAcceleration);
            this.transform.position = position;
        }


    }   
}

这是碰撞器和反射的代码:

public class Collider : MonoBehaviour
{

    public float xA;
    public float yA;

    void OnCollisionEnter(Collision collision)          //If a gameObject enters the collision of another object, this immediately happens once.
    {
        if (gameObject.tag == "Boundary")               //If the gameObject has a tag named Boundary:
        {
            yA = -Movement.yAcceleration;                                                                       //yA stores the value of yAcceleration after being called from script Movement as a negative. Its a reflection.
            Movement.xAcceleration = (Movement.xAcceleration * -Mathf.Atan(yA / Movement.xAcceleration));       //xAcceleration is changed based on this equation: A * artan(A_y / A_x). The 0.000001 was here, adding to A_x to help prevent a 0 as the denominator.
            xA = Movement.xAcceleration;                                                                        //This is declared now...
            Movement.yAcceleration = (Movement.yAcceleration * Mathf.Atan(Movement.yAcceleration / xA));        //This uses xA because Movement.xAcceleration is changed, and the yAcceleration calculation is based on the xAcceleration prior the collision.
            
        }
    }

    void OnCollisionStay(Collision collision)
    {
        if (gameObject.tag == "Boundary")
        {         
            
            yA = Movement.yAcceleration;                                                                        //The same thing happens as before.
            Movement.xAcceleration = (Movement.xAcceleration * -Mathf.Atan(yA / Movement.xAcceleration));
            xA = Movement.xAcceleration;
            Movement.yAcceleration = (Movement.yAcceleration * Mathf.Atan(Movement.yAcceleration / xA));

            Movement.xAcceleration = -Movement.xAcceleration / 2;                                               //On collision, the ball is reflected across the x-axis at half its speed.
            Movement.yAcceleration = Movement.yAcceleration / 2;                                                //yAcceleration is half its original value.
            
        }   
    }
}

下图是游戏设置。抱歉,这是一个 link;我没有足够的声誉值得在此页面上加载图像。另外,有什么不明白的地方可以私信我。

https://i.stack.imgur.com/VREV4.png

非常感谢您的帮助。谢谢!

这里有一个非常重要的注意事项:一旦涉及任何 Rigidbody,您就不想通过 .transform 设置任何值 - 这会破坏物理和碰撞检测!

您的 Movement 应该改变 Rigidbody 的行为,例如通过简单地改变它的 Rigibody.velocity

然后我还会将碰撞检查直接放入球的组件中并检查你是否撞到墙(“边界”)

然后另一个注意事项:您的代码当前是 frame-rate 依赖的。这意味着如果您的目标设备 运行 每秒只有 30 帧,您将每秒增加 0.3 的加速度。但是,如果您 运行 在更强大的设备上以每秒 200 帧的速度达到 运行,那么您每秒添加 2

您应该定义每秒 de/increase 并将其乘以 Time.deltaTime

加起来可能是这样的

public class Movement : MonoBehaviour
{
    // Adjust these settings via the Inspector
    [SerializeField] private float _maxMoveSpeed = 5f;
    [SerializeField] private float _speedIncreasePerSecond = 1f;

    // Already reference this via the Inspector
    [SerializeField] private Rigidbody _rigidbody;

    private void Awake()
    {
        if(!_rigidbody) _rigidbody = GetComponent<Rigidbody>();
    }

    // Get User Input in Update
    private void Update()
    {
        var velocity = _rigidbody.velocity;
        velocity.y = 0;

        if (Input.GetKey(KeyCode.W) && velocity.z < _maxMoveSpeed)                            
        {                                                       
            velocity.z += _speedIncreasePerSecond * Time.deltaTime;          
        }

        if (Input.GetKey(KeyCode.S) && velocity.z > -_maxMoveSpeed)
        {
            velocity.z -= _speedIncreasePerSecond * Time.deltaTime;
        }

        if (Input.GetKey(KeyCode.A) && velocity.x > -_maxMoveSpeed)
        { 
            velocity.x -= _speedIncreasePerSecond * Time.deltaTime;
        }

        if (Input.GetKey(KeyCode.D) && velocity.x < _maxMoveSpeed)
        {
            velocity.x += _speedIncreasePerSecond * Time.deltaTime;
        }

        // clamp to the max speed in case you move diagonal
        if(velocity.magnitude > _maxMoveSpeed)
        {
            velocity = velocity.normalized * _maxMoveSpeed;
        }

        _rigidbody.velocity = velocity;
    }
}

然后最后简单地向墙壁和球添加一个具有所需设置的 PhysicsMaterial

我对球和墙使用了 Friction = 0fBounciness = 0.7f。对于慢速运动,您也可以 want/have 调整项目 Physics Settings 中的 Bounce Threshold 否则如果速度小于 2 默认情况下将不会弹跳。

这有点取决于您对“现实”的定义。我禁用了重力所以球也没有旋转和 angular 摩擦力: