Saturday, April 15, 2017

Adding a Bit of Polish

Well, would you look at that! A blog update! It's been a while. Life tends to get in the way like that. But I have finally managed to get some more work done on Pongquetball; enough to write about I think. So, here's a quick little change log, complete with some code samples for once!:
  • Add a high score feature. If a new high score is set when the ball goes out of bounds, the number is updated, and it's also saved when the game exists and loaded when the game starts up. Currently it's just saved in a plain text file...this is only a practice project after all. :)
  • The ball now starts with a random color, and the color is changed every time it hits a paddle. This was a suggestion of my wife, Ashtan. I figured, why not? It's fun and it'll make it a little bit different. To select the color, I'm simply creating a new Color object with 3 random RGB values:
    private Color GetBallColor()
    {
        return new Color(random.Next(255), random.Next(255), random.Next(255));
    }
    
    There's one obvious downside to this: there's a small chance that the color will be black or a dark gray, making it hard to see. It would make for an interesting game though. :)
  • Add a slight pause after the ball goes out of bounds and before the game restarts. It's the same as the pause after the title screen.
  • Add pause/unpause functionality by pressing 'P'
  • Increase the score counting speed to be twice as fast. Originally I think I had it counting every second, and that just felt too slow. I wanted it to be more in line with something like the timer from Super Mario Bros., to give some more excitement to the game.
  • And finally, the big one: detect collisions between the ball and the top and bottom of the paddle. In other words, react differently based on where the ball hits the paddle. Before I changed this, the same collision code was being executed no matter where the ball hit the paddle.
    else if (ball.BoundingBox.Intersects(rightPaddle.BoundingBox))
    {
        ball.Velocity.X *= -1;
    
        ...
    }
    
    I'm simply reversing the ball's horizontal direction of movement. Of course, that doesn't make sense if it hits the top or bottom of the paddle. It resulted in some...interesting behavior as the ball tried to move back to the left:


    I slowed down the ball so you could clearly see what's happening.

    In those cases I want it to change directions vertically, not horizontally. The first step was figuring out how to detect where the ball hit the paddle. And I'll be honest, I had no idea how to go about doing that. I finally did some Googling and found an excellent and simple solution: if you calculate a straight line from the center of the ball to the center of the paddle, you can then find which side of the paddle that line intersects, and that's the side that the ball hit. Duh. That was one of those forehead-smacking, "why didn't I think of that?" moments. But then the next problem: how to actually go about calculating that line and where it intersects. The info I was reading made it seem like there were some somewhat complex calculations involved in this, but thankfully I eventually found this page, which was amazingly helpful. I adapted the LineSegmentsIntersect() method to fit my needs, because it was checking for more situations other than a simple intersection of two line segments at a single point, and ended up with this:
    private bool LineSegementsIntersect(Vector2 p, Vector2 p2, Vector2 q, Vector2 q2)
    {
        Vector2 r = p2 - p;
        Vector2 s = q2 - q;
        double rxs = Cross(r, s);
        double qpxr = Cross(q - p, r);
    
        // t = (q - p) x s / (r x s)
        double t = Cross(q - p, s) / rxs;
    
        // u = (q - p) x r / (r x s)
        double u = Cross(q - p, r) / rxs;
    
        // If r x s != 0 and 0 <= t <= 1 and 0 <= u <= 1
        // the two line segments meet at the point p + t r = q + u s.
        //if (!IsZero(rxs) && (0 <= t && t <= 1) && (0 <= u && u <= 1))
        if (rxs != 0 && (0 <= t && t <= 1) && (0 <= u && u <= 1))
            // An intersection was found.
            return true;
    
        // Otherwise, the two line segments are not parallel but do not intersect.
        return false;
    }
    
    p and p2 are the endpoints of the line segment between the centers of both objects, and q and q2 are the endpoints of one of the sides of the paddle. So using this, along with some of the other helper code from that web page, I was able to improve my collision detection!
    else if (ball.BoundingBox.Intersects(rightPaddle.BoundingBox) && ball.Velocity.X > 0)
    {
        bool intersect = false;
        int intersectedSide = -1;
    
        for (int i = 0; i <= 3; i++)
        {
            switch (i)
            {
                case 0: // top side
                    intersect = LineSegementsIntersect(ball.BoundingBox.Center.ToVector2(), rightPaddle.BoundingBox.Center.ToVector2(), new Vector2(rightPaddle.BoundingBox.Left, rightPaddle.BoundingBox.Top), new Vector2(rightPaddle.BoundingBox.Right, rightPaddle.BoundingBox.Top));
                    break;
                case 1: // right side
                    intersect = LineSegementsIntersect(ball.BoundingBox.Center.ToVector2(), rightPaddle.BoundingBox.Center.ToVector2(), new Vector2(rightPaddle.BoundingBox.Right, rightPaddle.BoundingBox.Top), new Vector2(rightPaddle.BoundingBox.Right, rightPaddle.BoundingBox.Bottom));
                    break;
                case 2: // bottom side
                    intersect = LineSegementsIntersect(ball.BoundingBox.Center.ToVector2(), rightPaddle.BoundingBox.Center.ToVector2(), new Vector2(rightPaddle.BoundingBox.Left, rightPaddle.BoundingBox.Bottom), new Vector2(rightPaddle.BoundingBox.Right, rightPaddle.BoundingBox.Bottom));
                    break;
                case 3: // left side
                    intersect = LineSegementsIntersect(ball.BoundingBox.Center.ToVector2(), rightPaddle.BoundingBox.Center.ToVector2(), new Vector2(rightPaddle.BoundingBox.Left, rightPaddle.BoundingBox.Top), new Vector2(rightPaddle.BoundingBox.Left, rightPaddle.BoundingBox.Bottom));
                    break;
            }
    
            if (intersect)
            {
                intersectedSide = i;
                break;
            }
        }
    
        if (intersectedSide == (int)PaddleSides.Left)
            ball.Velocity.X *= -1;
        else if (intersectedSide == (int)PaddleSides.Top || intersectedSide == (int)PaddleSides.Bottom)
            ball.Velocity.Y *= -1;
    
        ...
    }
    
    I'm basically just checking for intersection with each side of the paddle. Once I find it, I make note of which side it is and move on. Now, when the ball hits the top or bottom of the paddle, it bounces off vertically and continues moving in the same direction horizontally, like so:


    That's much better. Obviously, there's more I could do with it. It might make more sense for the ball to bounce more up or down rather than continuing in the same direction horizontally. But I don't think I'm going to spend any more time on this. I'm happy just knowing that I was able to handle this situation a little more properly and eliminate that wild "rapid bouncing" bug.
At this point I am very happy with how this is turning out. But I am also ready to move on from it and play around with Unity and some other ideas that I have floating around in my head. So my plan is to work on the last few bugs and feature ideas that I already have written down, and leave it at that. But that's for the next post. :)