计算线上的弹跳速度

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 反弹,所以我为此引入了一个变量。我正在用一些代码更新我的问题。

以上是关于计算线上的弹跳速度的主要内容,如果未能解决你的问题,请参考以下文章

fiddler修改线上的内容

时间线上的 FLV 出现故障

线上的数据技术嘉年华体验分享

线上的数据技术嘉年华体验分享

线上的zookeeper捉虫实战经验,值得读!!!

为什么PCB行业越来越多做线上推广线上下单平台呢