计算线上的弹跳速度
Posted
技术标签:
【中文标题】计算线上的弹跳速度【英文标题】:Calculate bounce velocity on a line 【发布时间】:2021-11-07 08:41:41 【问题描述】:我正在尝试使用 MonoGame c# 在我的游戏中的球上实现反弹物理。我用谷歌搜索了很多,但我无法理解如何做到这一点。
圆应该能够击中任何红线并逼真地弹跳(不仅仅是反转速度)。
我正在使用这段代码来检测碰撞:
public bool IntersectCircle(Vector2 pos, float radius, out Vector2 circleWhenHit)
circleWhenHit = default;
// find the closest point on the line segment to the center of the circle
var line = End - Start;
var lineLength = line.Length();
var lineNorm = (1 / lineLength) * line;
var segmentToCircle = pos - Start;
var closestPointOnSegment = Vector2.Dot(segmentToCircle, line) / lineLength;
// Special cases where the closest point happens to be the end points
Vector2 closest;
if (closestPointOnSegment < 0) closest = Start;
else if (closestPointOnSegment > lineLength) closest = End;
else closest = Start + closestPointOnSegment * lineNorm;
// Find that distance. If it is less than the radius, then we
// are within the circle
var distanceFromClosest = pos - closest;
var distanceFromClosestLength = distanceFromClosest.Length();
if (distanceFromClosestLength > radius)
return false;
// So find the distance that places the intersection point right at
// the radius. This is the center of the circle at the time of collision
// and is different than the result from Doswa
var offset = (radius - distanceFromClosestLength) * ((1 / distanceFromClosestLength) * distanceFromClosest);
circleWhenHit = pos - offset;
return true;
当球想要改变位置时,这段代码:
private void GameBall_OnPositionChange(object sender, GameBallPositionChangedEventArgs e)
foreach(var boundary in mapBounds)
if (boundary.IntersectCircle(e.TargetPosition, gameBall.Radius, out Vector2 colVector))
var normalizedVelocity = Vector2.Normalize(e.Velocity);
var velo = e.Velocity.Length();
var surfaceNormal = Vector2.Normalize(colVector - e.CurrentPosition);
e.Velocity = Vector2.Reflect(normalizedVelocity, surfaceNormal) * velo;
e.TargetPosition = e.CurrentPosition;
break;
这段代码给出了不错的结果,但我没有使用我的边界位置来计算角度。
我如何着手考虑这些因素?
编辑:
我已删除基于事件的更新。我添加了球员和球之间的碰撞。现在这是我的地图更新方法:
foreach (var entity in circleGameEntities)
for (int i = 0; i < interpolatePos; i++)
entity.UpdatePosition(gameTime, interpolatePos);
var intersectingBoundaries = mapBounds
.Where(b =>
var intersects = b.IntersectCircle(entity.Position, entity.Radius, 0f, out _);
if (intersects)
averageNormal += b.Normal;
return intersects;
).ToList();
if (intersectingBoundaries.Count > 0)
averageNormal.Normalize();
var normalizedVelocity = Vector2.Normalize(entity.Velocity); // Normalisera hastigheten
var velo = entity.Velocity.Length();
entity.Velocity = Vector2.Reflect(normalizedVelocity, averageNormal) * velo * entity.Bounciness;
entity.UpdatePosition(gameTime, interpolatePos);
foreach (var otherEntity in circleGameEntities.Where(e => e != entity))
if (entity.CollidesWithCircle(otherEntity, out Vector2 d))
Vector2 CMVelocity = (otherEntity.Mass * otherEntity.Velocity + entity.Mass * entity.Velocity) / (otherEntity.Mass + entity.Mass);
var otherEntityNorm = otherEntity.Position - entity.Position;
otherEntityNorm.Normalize();
var entityNorm = -otherEntityNorm;
var myVelocity = entity.Velocity;
myVelocity -= CMVelocity;
myVelocity = Vector2.Reflect(myVelocity, otherEntityNorm);
myVelocity += CMVelocity;
entity.Velocity = myVelocity;
entity.UpdatePosition(gameTime, interpolatePos);
var otherEntityVelocity = otherEntity.Velocity;
otherEntityVelocity -= CMVelocity;
otherEntityVelocity = Vector2.Reflect(otherEntityVelocity, entityNorm);
otherEntityVelocity += CMVelocity;
otherEntity.Velocity = otherEntityVelocity;
otherEntity.UpdatePosition(gameTime, interpolatePos);
entity.UpdateDrag(gameTime);
entity.Update(gameTime);
此代码运行良好,但有时对象会卡在墙内并相互卡住。
CircleGameEntity 类:
class CircleGameEntity : GameEntity
internal float Drag get; set; = .9999f;
internal float Radius => Scale * (Texture.Width + Texture.Height) / 4;
internal float Bounciness get; set; = 1f;
internal float Mass => BaseMass * Scale;
internal float BaseMass get; set;
internal Vector2 Velocity get; set;
internal float MaxVelocity get; set; = 10;
internal void UpdatePosition(GameTime gameTime, int interpolate)
var velocity = Velocity;
if (velocity.X < 0 && velocity.X < -MaxVelocity)
velocity.X = -MaxVelocity;
else if (velocity.X > 0 && velocity.X > MaxVelocity)
velocity.X = MaxVelocity;
if (velocity.Y < 0 && velocity.Y < -MaxVelocity)
velocity.Y = -MaxVelocity;
else if (velocity.Y > 0 && velocity.Y > MaxVelocity)
velocity.Y = MaxVelocity;
Velocity = velocity;
Position += Velocity / interpolate;
internal void UpdateDrag(GameTime gameTime)
Velocity *= Drag;
internal bool CollidesWithCircle(CircleGameEntity otherCircle, out Vector2 depth)
var a = Position;
var b = otherCircle.Position;
depth = Vector2.Zero;
float distance = Vector2.Distance(a, b);
if (Radius + otherCircle.Radius > distance)
float result = (Radius + otherCircle.Radius) - distance;
depth.X = (float)Math.Cos(result);
depth.Y = (float)Math.Sin(result);
return depth != Vector2.Zero;
【问题讨论】:
“真实地弹跳”可以用不同的方式来理解,所以你能更具体一点吗?碰撞后可能需要改变角度,但也涉及降低移动速度和重力? 【参考方案1】:surfaceNormal
不是边界法线,而是碰撞点与圆心的夹角。这个向量考虑了球的圆度,并且是球方向的负值(如果归一化),就好像它正面撞击表面一样,除非另一个表面是弯曲的,否则不需要这样做。
在Boundary
类中计算构造函数中的角度和法线之一并将它们存储为public readonly
:
public readonly Vector2 Angle; // replaces lineNorm for disabiguity
public readonly Vector2 Normal;
public readonly Vector2 Length;
public Boundary(... , bool inside) // inside determines which normal faces the center
// ... existing constructor code
var line = End - Start;
Length = line.Length();
Angle = (1 / Length) * line;
Normal = new Vector2(-Angle.Y,Angle.X);
if (inside) Normal *= -1;
public bool IntersectCircle(Vector2 pos, float radius)
// find the closest point on the line segment to the center of the circle
var segmentToCircle = pos - Start;
var closestPointOnSegment = Vector2.Dot(segmentToCircle, End - Start) / Length;
// Special cases where the closest point happens to be the end points
Vector2 closest;
if (closestPointOnSegment < 0) closest = Start;
else if (closestPointOnSegment > Length) closest = End;
else closest = Start + closestPointOnSegment * Angle;
// Find that distance. If it is less than the radius, then we
// are within the circle
var distanceFromClosest = pos - closest;
return (distanceFromClosest.LengthSquared() > radius * radius); //the multiply is faster than square root
改变位置代码子集:
// ...
var normalizedVelocity = Vector2.Normalize(e.Velocity);
var velo = e.Velocity.Length();
e.Velocity = Vector2.Reflect(normalizedVelocity, boundary.Normal) * velo;
//Depending on timing and movement code, you may need add the next line to resolve the collision during the current step.
e.CurrentPosition += e.Velocity;
//...
此更新后的代码假定inside
变量规定的单边不移动边界线。
我不是游戏中 C# 事件的忠实拥护者,因为它增加了延迟层(在 C# 内部以及在正确使用期间发送者的演员。)
更不用说你滥用e
变量了。 e
应始终被视为值类型:即只读。 sender
变量应该被强制转换(慢)并用于写作目的。
【讨论】:
感谢您的反馈,我一直对活动选择犹豫不决,但当时似乎很简单。我会改变它。您对清理 IntersectCircle 方法有什么建议吗? 嗨@Strom,我现在正在试用您的代码。似乎 IntersectCircle 方法不起作用,它为每个 MapBoundary 返回 true。我假设 Vector2 长度也是浮点长度?我在解决碰撞定位方面遇到了一些问题。对于某些对象,我不希望 1:1 反弹,所以我为此引入了一个变量。我正在用一些代码更新我的问题。以上是关于计算线上的弹跳速度的主要内容,如果未能解决你的问题,请参考以下文章