在C#模拟中添加球体碰撞。

huangapple go评论143阅读模式
英文:

Adding ball collisions in c# simulation

问题

我一直在通过制作一个基本的弹跳球模拟来学习C#,有点像Windows屏幕保护程序中的气泡。我有两个球在屏幕上弹跳,但当它们碰撞时它们消失了,我不确定为什么。

我使用'Console.WriteLine(value)'进行了调试,发现大多数值在碰撞后都等于无穷大。

我最终放弃了那段代码,但需要一个更好的解决方案来处理球的碰撞。

** 注意 ** 这不会始终只有两个球在屏幕上弹跳,这只是我试图学习碰撞的一个例子 ** 注意 **

任何了解Verlet积分的人将不胜感激,因为我非常困惑。

这是我的一些代码和我使用的C#版本:

Screenshot from replit showing c# version

//+++ = 我不知道这是什么,一个YouTube教程告诉我要使用它
using System; 
using System.Collections.Generic; //+++
using System.ComponentModel; //+++
using System.Data; //+++
using System.Drawing;
using System.Linq; //+++
using System.Text; //+++
using System.Threading.Tasks; //+++
using System.Windows.Forms; //这在标准C#中不起作用,只在Mono中起作用。

public class Form1 : Form
{
    float screenWidth;
    float screenHeight;
    float xpa = 0;
    float ypa = 0;
    float xva = 2;
    float yva = 2;
    float xpb; //稍后设置为窗体宽度减去标记位置的椭圆的宽度
    float ypb; //稍后设置为窗体高度减去标记位置的椭圆的高度
    float xvb = -2;
    float yvb = -2;
//...这里的代码不重要...\\
        var refreshTimer = new Timer();
        refreshTimer.Interval = 1;
        refreshTimer.Tick += new EventHandler(refreshTimer_Tick);
        refreshTimer.Start();
    }
//...这里的代码不重要...\\

    private void refreshTimer_Tick(object sender, EventArgs e)
    {
        this.Invalidate();
    }

    private void Form1_Paint(object sender, PaintEventArgs e)
    {
        Graphics g = e.Graphics;
//...这里的代码不重要...\\
//当与墙壁接触时,两个椭圆都会弹跳
//椭圆A位于坐标(xpa,ypa),宽度和高度为50
//椭圆A位于坐标(xpb,ypb),宽度和高度为50
		
		//A和B之间的碰撞
		
		float dx = (xpb + 25) - (xpa + 25);
		float dy = (ypb + 25) - (ypa + 25);
		float distance = (float)Math.Sqrt(dx * dx + dy * dy);
		
		if (distance <= 50) 
		{
		    float angle = (float)Math.Atan2(dy, dx);
		    float sin = (float)Math.Sin(angle);
		    float cos = (float)Math.Cos(angle);
		
		}
	}
//...代码的其余部分...\\

有人了解Verlet积分或其他可以帮助我的技术吗?

英文:

I have been learning c# by making a basic ball bouncing simulation, kind of like the windows screensaver with the bubbles.
I have two balls bouncing around the screen, but when they collide they disappear, and I am not sure why.

I have done debugging using 'Console.WriteLine(value)', and found most values are equating to infinity after the collision.

I ended up scrapping that code, but need a better solution for ball collisions.

** NOTE ** This won't always be just two balls bouncing around a screen, this is just me trying to learn collisions ** NOTE **

Anyone with knowledge on Verlet Integration would be greatly appreciated, because I am very confused.

Here is some of my code & the version of C# I am using:

Screenshot from replit showing c# version

//+++ = I don't know what this is, a yt tutoriaol told me to use it
using System; 
using System.Collections.Generic; //+++
using System.ComponentModel; //+++
using System.Data; //+++
using System.Drawing;
using System.Linq; //+++
using System.Text; //+++
using System.Threading.Tasks; //+++
using System.Windows.Forms; // This doesn't work in standard c#, only in mono for some reason.

public class Form1 : Form
{
    float screenWidth;
    float screenHeight;
    float xpa = 0;
    float ypa = 0;
    float xva = 2;
    float yva = 2;
    float xpb; //later this is set to the width of the form minus the width of the ellipse this is marking the position of
    float ypb; //later this is set to the height of the form, minus the height of the ellipse this is marking the position of
    float xvb = -2;
    float yvb = -2;
//...Unimportant code here...\\
        var refreshTimer = new Timer();
        refreshTimer.Interval = 1;
        refreshTimer.Tick += new EventHandler(refreshTimer_Tick);
        refreshTimer.Start();
    }
//...Unimportant code here...\\

    private void refreshTimer_Tick(object sender, EventArgs e)
    {
        this.Invalidate();
    }

    private void Form1_Paint(object sender, PaintEventArgs e)
    {
        Graphics g = e.Graphics;
//...Unimportant code here...\\		
//Both ellipses bounce when in contact with the wall
//Ellipse A is located at coords (xpa, ypa) with width and height 50
//Ellipse A is located at coords (xpb, ypb) with width and height 50
		
		//Collisions between A & B
		
		float dx = (xpb + 25) - (xpa + 25);
		float dy = (ypb + 25) - (ypa + 25);
		float distance = (float)Math.Sqrt(dx * dx + dy * dy);
		
		if (distance <= 50) 
		{
		    float angle = (float)Math.Atan2(dy, dx);
		    float sin = (float)Math.Sin(angle);
		    float cos = (float)Math.Cos(angle);
		
		}
	}
//...Rest of Code...\\

Does anyone know about Verlet Integration or any other techniques that could help me?

答案1

得分: 2

我简化了代码,通过添加一个Ball类和使用System.Numerics Namespace中的Vector2 Struct(我已经在下面提供了一个针对Mono的最小实现)。Vector2包含了用于向量数学的有用方法和运算符。例如,您可以使用Vector2 result = v1 + v2来添加两个向量。

Ball类封装了球的所有状态以及一些方法,比如CollideWithWall。其优点是我们只需为所有球编写这段代码一次。Ball现在存储球的中心坐标,而不是左上角位置。这使得对其进行推理更容易。它还存储半径,允许我们拥有不同半径的球。

对于碰撞,我找到了一个来自用户mmcdole解决方案,我将其适应了C#和您的模拟。但是,您的模拟的核心部分,即将速度积分以获得运动,保持不变。

下面是Ball类的代码:

public class Ball
{
    public Brush Brush { get; set; }
    public Vector2 Center { get; set; }
    public Vector2 Velocity { get; set; }
    public float Radius { get; set; }

    // 使质量与圆的面积成正比
    public float Mass => Radius * Radius;

    public void Move()
    {
        Center += Velocity;
    }

    public void CollideWithWall(Rectangle wall)
    {
        // 只有在朝着墙壁移动时才反转速度
        if ((Center.X + Radius >= wall.Right && Velocity.X > 0) || (Center.X - Radius < 0 && Velocity.X < 0))
        {
            Velocity = new Vector2(-Velocity.X, Velocity.Y);
        }
        if ((Center.Y + Radius >= wall.Bottom && Velocity.Y > 0) || (Center.Y - Radius < 0 && Velocity.Y < 0))
        {
            Velocity = new Vector2(Velocity.X, -Velocity.Y);
        }
    }

    public void CollideWith(Ball other)
    {
        Vector2 delta = Center - other.Center;
        float d = delta.Length();
        if (d <= Radius + other.Radius && d > 1e-5)
        {
            Vector2 mtd = delta * ((Radius + other.Radius - d) / d);
            float im1 = 1 / Mass;
            float im2 = 1 / other.Mass;
            Center += mtd * (im1 / (im1 + im2));
            other.Center -= mtd * (im2 / (im1 + im2));
            Vector2 v = Velocity - other.Velocity;
            Vector2 mtdNormalized = Vector2.Normalize(mtd);
            float vn = Vector2.Dot(v, mtdNormalized);
            if (vn > 0.0f) return;
            const float Restitution = 1.0f;
            float i = -(1.0f + Restitution) * vn / (im1 + im2);
            Vector2 impulse = mtdNormalized * i;
            Velocity += impulse * im1;
            other.Velocity -= impulse * im2;
        }
    }

    public void Draw(Graphics g)
    {
        g.FillEllipse(Brush, Center.X - Radius, Center.Y - Radius, 2 * Radius, 2 * Radius);
    }
}

然后,我们可以在Form1中初始化窗体:

Ball a = new Ball()
{
    Brush = Brushes.Red,
    Center = new Vector2(),
    Velocity = new Vector2(2, 2),
    Radius = 25
};
Ball b = new Ball()
{
    Brush = Brushes.Blue,
    Center = new Vector2(),
    Velocity = new Vector2(-2, -2),
    Radius = 40
};

public Form1()
{
    InitializeComponent();
    DoubleBuffered = true;
    Load += Form1_Load; ;
    Paint += Form1_Paint;

    var refreshTimer = new System.Windows.Forms.Timer
    {
        Interval = 1
    };
    refreshTimer.Tick += RefreshTimer_Tick;
    refreshTimer.Start();
}

void Form1_Load(object sender, EventArgs e)
{
    WindowState = FormWindowState.Normal;
    System.Diagnostics.Debug.WriteLine(Width);
    b.Center = new Vector2(Width - 60, Height - 60);
}

private void RefreshTimer_Tick(object sender, EventArgs e)
{
    Invalidate();
}

现在,我们的Paint方法如下:

private void Form1_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    g.FillRectangle(Brushes.LightBlue, ClientRectangle);

    a.Draw(g);
    b.Draw(g);

    a.Move();
    b.Move();

    a.CollideWithWall(ClientRectangle);
    b.CollideWithWall(ClientRectangle);

    a.CollideWith(b);
}

如果要在窗体设计器中更改窗体属性,那么在窗体的构造函数中也必须调用InitializeComponent();

因为您使用的是不具备Vector2结构的Mono,所以以下是它的最小版本,只实现了代码上述部分所需的内容:

public struct Vector2
{
    public float X;
    public float Y;

    public Vector2(float x, float y)
    {
        X = x;
        Y = y;
    }

    public static Vector2 operator +(Vector2 left, Vector2 right)
    {
        return new Vector2(left.X + right.X, left.Y + right.Y);
    }

    public static Vector2 operator -(Vector2 left, Vector2 right)
    {
        return new Vector2(left.X - right.X, left.Y - right.Y);
    }

    public static Vector2 operator *(Vector2 left, Vector2 right)
    {
        return new Vector2(left.X * right.X, left.Y * right.Y);
    }

    public static Vector2 operator *(float left, Vector2 right)
    {
        return new Vector2(left * right.X, left * right.Y);
    }

    public static Vector2 operator *(Vector2 left, float right)
    {
        return new Vector2(left.X * right, left.Y * right);
    }

    public static float Dot(Vector2 value1, Vector2 value2)
    {
        return value1.X * value2.X + value1.Y * value2.Y;
    }

    public static Vector2 Normalize(Vector2 value)
   

<details>
<summary>英文:</summary>

I simplified the code a lot by adding a `Ball` class and using the [Vector2 Struct][1] from the [System.Numerics Namespace][2] (I have included a minimal implementation for mono below). `Vector2` contains  useful methods and operators for vector math. E.g., you can add two vectors with `Vector2 result = v1 + v2`.

The `Ball` class wraps all the state of a ball and some methods like `CollideWithWall`. The advantage is that we have to write this code only once for all the balls. `Ball` now stores the center coordinates of the ball, not the top left position. This makes it easier to reason about it. It also stores the radius, allowing us to have balls of different radii.

For the collision I found a working [solution][3] from the user [mmcdole][4]. I adapted it to C# and your simulation. But the core of your simulation, the integration of the speeds to get the motion, remains the same.

``` c#
public class Ball
{
    public Brush Brush { get; set; }
    public Vector2 Center { get; set; }
    public Vector2 Velocity { get; set; }
    public float Radius { get; set; }

    // Make mass proportional to the area of the circle
    public float Mass =&gt; Radius * Radius;

    public void Move()
    {
        Center += Velocity;
    }

    public void CollideWithWall(Rectangle wall)
    {
        // Only reverse velocity if moving towards the walls

        if (Center.X + Radius &gt;= wall.Right &amp;&amp; Velocity.X &gt; 0 || Center.X - Radius &lt; 0 &amp;&amp; Velocity.X &lt; 0) {
            Velocity = new Vector2(-Velocity.X, Velocity.Y);
        }
        if (Center.Y + Radius &gt;= wall.Bottom &amp;&amp; Velocity.Y &gt; 0 || Center.Y - Radius &lt; 0 &amp;&amp; Velocity.Y &lt; 0) {
            Velocity = new Vector2(Velocity.X, -Velocity.Y);
        }
    }

    public void CollideWith(Ball other)
    {
        // From: https://stackoverflow.com/q/345838/880990, author: mmcdole
        Vector2 delta = Center - other.Center;
        float d = delta.Length();
        if (d &lt;= Radius + other.Radius &amp;&amp; d &gt; 1e-5) {
            // Minimum translation distance to push balls apart after intersecting
            Vector2 mtd = delta * ((Radius + other.Radius - d) / d);

            // Resolve intersection - inverse mass quantities
            float im1 = 1 / Mass;
            float im2 = 1 / other.Mass;

            // Push-pull them apart based off their mass
            Center += mtd * (im1 / (im1 + im2));
            other.Center -= mtd * (im2 / (im1 + im2));

            // Impact speed
            Vector2 v = Velocity - other.Velocity;
            Vector2 mtdNormalized = Vector2.Normalize(mtd);
            float vn = Vector2.Dot(v, mtdNormalized);

            // Sphere intersecting but moving away from each other already
            if (vn &gt; 0.0f) return;

            // Collision impulse
            const float Restitution = 1.0f; //  perfectly elastic collision

            float i = -(1.0f + Restitution) * vn / (im1 + im2);
            Vector2 impulse = mtdNormalized * i;

            // Change in momentum
            Velocity += impulse * im1;
            other.Velocity -= impulse * im2;
        }
    }

    public void Draw(Graphics g)
    {
        g.FillEllipse(Brush, Center.X - Radius, Center.Y - Radius, 2 * Radius, 2 * Radius);
    }
}

then we can initialize the form with (in Form1)

Ball a = new Ball() {
    Brush = Brushes.Red,
    Center = new Vector2(),
    Velocity = new Vector2(2, 2),
    Radius = 25
};
Ball b = new Ball() {
    Brush = Brushes.Blue,
    Center = new Vector2(),
    Velocity = new Vector2(-2, -2),
    Radius = 40
};

public Form1()
{
    InitializeComponent();
    DoubleBuffered = true;
    Load += Form1_Load; ;
    Paint += Form1_Paint;

    var refreshTimer = new System.Windows.Forms.Timer {
        Interval = 1
    };
    refreshTimer.Tick += RefreshTimer_Tick;
    refreshTimer.Start();
}

void Form1_Load(object sender, EventArgs e)
{
    WindowState = FormWindowState.Normal;
    System.Diagnostics.Debug.WriteLine(Width);
    b.Center = new Vector2(Width - 60, Height - 60);
}

private void RefreshTimer_Tick(object sender, EventArgs e)
{
    Invalidate();
}

Our Paint method now looks like this:

private void Form1_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    g.FillRectangle(Brushes.LightBlue, ClientRectangle);

    a.Draw(g);
    b.Draw(g);

    a.Move();
    b.Move();

    a.CollideWithWall(ClientRectangle);
    b.CollideWithWall(ClientRectangle);

    a.CollideWith(b);
}

If you want to change the form properties in the forms designer, then you must also call InitializeComponent(); in the form's constructor.


EDIT:

Since you are using mono that does not have the Vextor2 struct, here is a minimal version of it, implementing only the required stuff for the code above:

public struct Vector2
{
    public float X;
    public float Y;

    public Vector2(float x, float y)
    {
        X = x;
        Y = y;
    }

    public static Vector2 operator +(Vector2 left, Vector2 right)
    {
        return new Vector2(left.X + right.X, left.Y + right.Y);
    }

    public static Vector2 operator -(Vector2 left, Vector2 right)
    {
        return new Vector2(left.X - right.X, left.Y - right.Y);
    }

    public static Vector2 operator *(Vector2 left, Vector2 right)
    {
        return new Vector2(left.X * right.X, left.Y * right.Y);
    }

    public static Vector2 operator *(float left, Vector2 right)
    {
        return new Vector2(left * right.X, left * right.Y);
    }

    public static Vector2 operator *(Vector2 left, float right)
    {
        return new Vector2(left.X * right, left.Y * right);
    }

    public static float Dot(Vector2 value1, Vector2 value2)
    {
        return value1.X * value2.X + value1.Y * value2.Y;
    }

    public static Vector2 Normalize(Vector2 value)
    {
        float d = MathF.Sqrt(value.X * value.X + value.Y * value.Y);
        if (d &lt; 1e-10) {
            return value;
        }
        float invNorm = 1.0f / d;
        return new Vector2(value.X * invNorm, value.Y * invNorm);
    }

    public float Length()
    {
        return MathF.Sqrt(X * X + Y * Y);
    }
}

Explanation

I am not going to explain the collision itself. Follow the link to mmcdole's code for this.

You are using a lot of variables like xpa, ypa, xva, yva, xpb, ypb, xvb, yvb. Most of the changes I've made are to reduce the number of variables and avoid code duplication.

For instance we have float xpa and float ypa storing the position of object a. The Vector2 type stores both coordinates in its X and Y fields and requires only one variable. It also contains methods and operator overloads that allow performing arithmetic operations on them.

Example:

// Old code with individual floats
float xpa = 0;
float ypa = 0;
float xva = 2;
float yva = 2;
...
xpa += xva;
ypa += yva;
// New code with Vector2
Vector2 pa = new Vector2(0, 0);
Vector2 va = new Vector2(2, 2);
...
pa += va;

Another problem is that a lot of code is duplicated because it must be applied to the &lt;whatever&gt;a variables and the &lt;whatever&gt;b variables. Especially in the Form1_Paint method.

The idea is to wrap all the variables belonging to a ball in a Ball object (declared as class). Inside this object the variables (or properties with { get; set; }) have the same name, no matter whether the object represents the a ball or the b ball.

Methods inside this Ball class now work with the object's properties.

Example:

public void Draw(Graphics g)
{
    g.FillEllipse(Brush, Center.X - Radius, Center.Y - Radius, 2 * Radius, 2 * Radius);
}

It uses the Brush, Center and Radius properties of the object. I decided to store the color of the ball as Brush, since FillEllipse requires a brush.

From outside, if we have two balls called a and b, we can draw them with the calls:

a.Draw(g);
b.Draw(g);

One code duplication eliminated! The same applies to Move, CollideWithWall and CollideWith (colliding with another ball).

This code works like yours, except for the ball-ball collision.

See also:

答案2

得分: 1

这是一种通用的解决方案,它对我的第一个答案进行了一些补充。

它向Vector2添加了两个新方法:

public float LengthSquared()
{
    return X * X + Y * Y;
}

public override string ToString() => $"Vector2({X:n3}, {Y:n3})";

LengthSquared将用于计算空气阻力。覆盖ToString简化了调试。

我用一个球的数组替换了球的两个不同字段,这样我们可以轻松地添加更多球到游戏中。

private readonly Ball[] _balls = new Ball[]{
    new Ball() {
        Brush = Brushes.Red,
        Center = new Vector2(100, 135),
        Velocity = new Vector2(10, 0),
        Radius = 40
    },
    new Ball() {
        Brush = Brushes.Blue,
        Center = new Vector2(300, 135.1f),
        Velocity = a new Vector2(-4, 0),
        Radius = 15
    },
    new Ball() {
        Brush = Brushes Green,
        Center = new Vector2(30, 20),
        Velocity = new Vector2(5, 3),
        Radius = 20
    }
};

Form1_Paint现在可以在循环中处理所有的球。这比第一个解决方案简化了,因为我们不必单独处理每个球。我们还在窗体中声明了额外的字段和常量来处理地面上的摩擦、空气阻力和重力:

private const float C = 0.1f; // 空气阻力系数(0 = 无阻力)
private const float Friction = 0.95f; // 摩擦系数(1 = 无摩擦)

private readonly Vector2 _gravity = new Vector2(0, 0.1f);

绘图方法:

private void Form1_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    g.Clear(Color.LightBlue);

    for (int i = 0; i < _balls.Length; i++) {
        Ball ball = _balls[i];
        ball.Draw(g);

        if (ball.Center.Y + ball.Radius >= ClientRectangle.Bottom) {
            // 在地面上。与地面摩擦
            ball.Velocity *= Friction;
        } else {
            // 仅在不在地面上时加速向下。
            ball.Velocity += _gravity;
        }

        // 空气阻力
        float v2 = ball.Velocity.LengthSquared();
        float vAbs = MathF.Sqrt(v2);
        float fDrag = C * v2;
        Vector2 f = fDrag / vAbs * ball.Velocity;
        ball.Velocity -= 1 / ball.Mass * f;

        ball.CollideWithWall(ClientRectangle);

        // 从i + 1开始,只处理每对球的碰撞一次。
        for (int j = i + 1; j < _balls.Length; j++) {
            ball.CollideWith(_balls[j]);
        }

        ball.Move();
    }
}

当与墙壁碰撞时,我还添加了一点阻尼(在Ball类中):

public void CollideWithWall(Rectangle wall)
{
    const float damping = 0.99f; // 1 = 无阻尼

    // 仅在向墙壁移动时才反转速度
    if (Center.X + Radius >= wall.Right && Velocity.X > 0 || Center.X - Radius < 0 && Velocity.X < 0) {
        Velocity = new Vector2(-damping * Velocity.X, Velocity.Y);
    }
    if (Center.Y + Radius >= wall.Bottom && Velocity.Y > 0 || Center.Y - Radius < 0 && Velocity.Y < 0) {
        Velocity = new Vector2(Velocity.X, -damping * Velocity.Y);
    }
}

现在,由于空气阻力、地面摩擦和与墙壁弹跳时的阻尼,球会在一段时间后停止。

英文:

Here is a generalized solution which adds some missing things to my first answer.

It adds two new methods to Vector2:

public float LengthSquared()
{
    return X * X + Y * Y;
}

public override string ToString() =&gt; $&quot;Vector2({X:n3}, {Y:n3})&quot;;

LengthSquared will be used to calculate air drag. Overriding ToString simplifies debugging.

I replaced the two distinct fields for the balls by one array of balls, allowing us to easily add more balls to the game.

private readonly Ball[] _balls = new Ball[]{
    new Ball() {
        Brush = Brushes.Red,
        Center = new Vector2(100, 135),
        Velocity = new Vector2(10, 0),
        Radius = 40
    },
    new Ball() {
        Brush = Brushes.Blue,
        Center = new Vector2(300, 135.1f),
        Velocity = new Vector2(-4, 0),
        Radius = 15
    },
    new Ball() {
        Brush = Brushes.Green,
        Center = new Vector2(30, 20),
        Velocity = new Vector2(5, 3),
        Radius = 20
    }
};

Form1_Paint can now handle all the balls in a loop. This is a simplification over the first solution where we have to handle each ball individually. We also add friction on the ground, air drag and gravity. For this we declare additional fields and constants in the form:

private const float C = 0.1f; // Air drag coefficient (0 = no drag)
private const float Friction = 0.95f; // Friction coefficient (1 = no friction)

private readonly Vector2 _gravity = new Vector2(0, 0.1f);

The paint method:

private void Form1_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    g.Clear(Color.LightBlue);

    for (int i = 0; i &lt; _balls.Length; i++) {
        Ball ball = _balls[i];
        ball.Draw(g);

        if (ball.Center.Y + ball.Radius &gt;= ClientRectangle.Bottom) {
            // On ground. Friction with ground
            ball.Velocity *= Friction;
        } else {
            // Accelerate downwards only when not on ground.
            ball.Velocity += _gravity;
        }

        // Air Drag
        float v2 = ball.Velocity.LengthSquared();
        float vAbs = MathF.Sqrt(v2);
        float fDrag = C * v2;
        Vector2 f = fDrag / vAbs * ball.Velocity;
        ball.Velocity -= 1 / ball.Mass * f;

        ball.CollideWithWall(ClientRectangle);

        // Start at i + 1 to handle collision of each pair of balls only once.
        for (int j = i + 1; j &lt; _balls.Length; j++) {
            ball.CollideWith(_balls[j]);
        }

        ball.Move();
    }
}

I also added a bit of damping when colliding with walls (in class Ball):

public void CollideWithWall(Rectangle wall)
{
    const float damping = 0.99f; // 1 = no damping

    // Only reverse velocity if moving towards the walls

    if (Center.X + Radius &gt;= wall.Right &amp;&amp; Velocity.X &gt; 0 || Center.X - Radius &lt; 0 &amp;&amp; Velocity.X &lt; 0) {
        Velocity = new Vector2(-damping * Velocity.X, Velocity.Y);
    }
    if (Center.Y + Radius &gt;= wall.Bottom &amp;&amp; Velocity.Y &gt; 0 || Center.Y - Radius &lt; 0 &amp;&amp; Velocity.Y &lt; 0) {
        Velocity = new Vector2(Velocity.X, -damping * Velocity.Y);
    }
}

Now, because of air drag, friction with the ground and damping when bouncing off a wall, the balls will come to a still after a while.

huangapple
  • 本文由 发表于 2023年3月9日 20:57:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/75684945.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定