在 C# 模拟中添加球碰撞

问题描述 投票:0回答:1

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

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

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

** 注意 ** 这不会总是只有两个球在屏幕上弹跳,这只是我在尝试学习碰撞 ** 注意 **

任何了解 Verlet Integration 的人都将不胜感激,因为我很困惑。

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

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...\\

有人知道 Verlet Integration 或任何其他可以帮助我的技术吗?

c# mono game-physics verlet-integration
1个回答
0
投票

我通过添加一个

Ball
类并使用 System.Numerics 命名空间 中的 Vector2 结构 大大简化了代码(我在下面包含了单声道的最小实现)。
Vector2
包含矢量数学的有用方法和运算符。例如,您可以使用
Vector2 result = v1 + v2
.

添加两个向量

Ball
类包装了球的所有状态和一些方法,如
CollideWithWall
。优点是我们只需为所有球编写一次此代码。

对于碰撞,我从用户 mmcdole 那里找到了一个可行的 solution。我将其改编为 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 => Radius * Radius;

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

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

        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)
    {
        // From: https://stackoverflow.com/q/345838/880990, author: mmcdole
        Vector2 delta = Center - other.Center;
        float d = delta.Length();
        if (d <= Radius + other.Radius && d > 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 > 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);
    }
}

然后我们可以用(在

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();


编辑

由于您使用的单声道没有

Vextor2
结构,这里是它的最小版本,只实现上面代码所需的东西:

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 invNorm = 1.0f / MathF.Sqrt(value.X * value.X + value.Y * value.Y);
        return new Vector2(value.X * invNorm, value.Y * invNorm);
    }

    public float Length()
    {
        return MathF.Sqrt(X * X + Y * Y);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.