我一直在通过制作基本的球弹跳模拟来学习 C#,有点像带有气泡的 Windows 屏幕保护程序。
我有两个球在屏幕上弹跳,但当它们碰撞时它们就消失了,我不知道为什么。
我已经使用“Console.WriteLine(value)”进行了调试,发现大多数值在碰撞后都等于无穷大。
我最终放弃了那个代码,但需要一个更好的球碰撞解决方案。
** 注意 ** 这不会总是只有两个球在屏幕上弹跳,这只是我在尝试学习碰撞 ** 注意 **
任何了解 Verlet Integration 的人都将不胜感激,因为我很困惑。
这是我的一些代码和我使用的 C# 版本:
//+++ = 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 或任何其他可以帮助我的技术吗?
我通过添加一个
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);
}
}