Search code examples
javapong

Trying to create a Pong game


I'm trying to create a pong game in Java, and I'm having a little trouble with the ball physics. It all works pretty much perfectly, until the ball gets too slow. I made it so the ball decreases in speed by 0.02 pixels per frame per frame, but I don't want the it to get too slow, so I have a check that prevents it from decelerating once the x and y velocities get below 2. The problem is once this happens, it doesn't keep the current trajectory if the x or y velocity is significantly smaller than the other. It seems to smooth out so that it is almost as straight line, except for moving up or down 1 pixel every few seconds. Here is my ball class:

import java.awt.Graphics2D;
import java.awt.Graphics;
import java.awt.Rectangle;

public class Ball {

    private static int DIAMETER = 30;
    double x = 0;
    double y = 0;
    double xVel = 5;
    double yVel = 5;
    double prevX = 0;
    double prevY = 0;
    double minSpeed = 2;
    double maxSpeed = 10;
    int drawX = 0;
    int drawY = 0;
    boolean tooSlow = false;
    public game game;




    public Ball(game game) {
        this.game = game;
    }



    public void move()
    {
        prevX = xVel;
        prevY = yVel;

        if(xVel > maxSpeed)
            xVel = maxSpeed;
        if(yVel > maxSpeed)
            yVel = maxSpeed;


        if(Math.abs(xVel) < minSpeed  && Math.abs(yVel) < minSpeed)
            tooSlow = true;
        else
            tooSlow = false;



        if(x + xVel < 0){
            xVel *= -1;
        }
        if(x + xVel > 484 - DIAMETER){
            xVel *= -1;

        }
        if(y + yVel < 0){
            yVel *= -1;
        }
        if(y + yVel > 261 - DIAMETER){
            yVel *= -1;

        }

        if(collision()){
            if(game.paddle.paddleTop().intersects(getBounds())){
                y += game.paddle.y - game.paddle.prevY;
                yVel = -1 * Math.abs(yVel) + (game.paddle.y - game.paddle.prevY);
            }

            if(game.paddle.paddleBottom().intersects(getBounds())){
                y += game.paddle.y - game.paddle.prevY;
                yVel = Math.abs(yVel) + (game.paddle.y - game.paddle.prevY);
            }
            if(game.paddle.paddleRight().intersects(getBounds())){
                x += game.paddle.x - game.paddle.prevX;
                xVel = Math.abs(xVel) + (game.paddle.x - game.paddle.prevX);
                yVel += (game.paddle.y - game.paddle.prevY) / 2;
            }
            if(game.paddle.paddleLeft().intersects(getBounds())){
                x += game.paddle.x - game.paddle.prevX;
                xVel = -1 * Math.abs(xVel) + (game.paddle.x - game.paddle.prevX);
                yVel += (game.paddle.y - game.paddle.prevY) / 2;
            }

        } 


        x += xVel;
        y += yVel;

        if(!tooSlow)
            slow();

        drawX = (int)x;
        drawY = (int)y;

    }

    public void slow()
    {
        xVel = (Math.abs(xVel) - 0.02) * Math.signum(xVel);
        yVel = (Math.abs(yVel) - 0.02) * Math.signum(yVel);
    }


    public void paint(Graphics2D g) {
        g.fillOval(drawX, drawY, 30, 30);

    }

    public Rectangle getBounds()
    {
        return new Rectangle(drawX, drawY, DIAMETER, DIAMETER);

    }

    public boolean collision()
    {
        return game.paddle.getBounds().intersects(getBounds());
    }
}

These are the rest that I use to run the game (in case it helps):

import java.awt.Rectangle;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionListener;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import java.awt.event.ActionEvent;

public class Paddle {

    int x = 50;
    int y = 10;
    int xVel = 0;
    int yVel = 0;
    int prevX = 50;
    int prevY = 10;
    int speed = 4;
    private static final int WIDTH = 10;
    private static final int HEIGHT = 80;
    private game game;



    public Paddle(game game)
    {
        this.game = game;
    }

    public void move()
    {
        prevX = x;
        prevY = y;
        if(y + yVel > 0 && y + yVel < 261 - HEIGHT){
            y += yVel;
        }
        if(x + xVel > 0 && x + xVel < 484 - WIDTH){
            x += xVel;
        }


    }

    public void keyPressed(KeyEvent e)
    {
        if(e.getKeyCode() == KeyEvent.VK_UP){
            yVel = -speed;
        }
        if(e.getKeyCode() == KeyEvent.VK_DOWN){
            yVel = speed;
        }
        if(e.getKeyCode() == KeyEvent.VK_LEFT){
            xVel = -speed;
        }
        if(e.getKeyCode() == KeyEvent.VK_RIGHT){
            xVel = speed;
        }
    }

    public void keyReleased(KeyEvent e)
    {
        yVel = 0;
        xVel = 0;
    }

    public void paint(Graphics2D g)
    {
        g.fillRect(x, y, WIDTH, HEIGHT);
    }

    public Rectangle getBounds()
    {
        return new Rectangle(x, y, WIDTH, HEIGHT);
    }

    public Rectangle paddleTop()
    {
        return new Rectangle(x + WIDTH / 2, y, 1, 1);
    }

    public Rectangle paddleLeft()
    {
        return new Rectangle(x, y, 1, HEIGHT);
    }

    public Rectangle paddleRight()
    {
        return new Rectangle(x + WIDTH, y, 1, HEIGHT);
    }

    public Rectangle paddleBottom()
    {
        return new Rectangle(x + WIDTH / 2, y + HEIGHT, 1, 1);
    }

    public Rectangle test()
    {
        return new Rectangle(x, y, WIDTH, HEIGHT);
    }
}

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Graphics;
import java.awt.event.ActionListener;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import java.awt.event.ActionEvent;




import javax.swing.JPanel;
import javax.swing.Timer;



public class game extends JPanel implements ActionListener, KeyListener{
    private Timer t;
    Ball ball = new Ball(this);
    Paddle paddle = new Paddle(this);




    public game() 
    {
        t = new Timer(15,this);
        t.start();
        addKeyListener(this);
        setFocusable(true);
    }

    public void actionPerformed(ActionEvent e)
    {
        ball.move();
        paddle.move();
        repaint();
    }

    public void keyPressed(KeyEvent e)
    {
        paddle.keyPressed(e);
    }

    public void keyReleased(KeyEvent e)
    {
        paddle.keyReleased(e);
    }

    public void keyTyped(KeyEvent e) {}

    public void paint(Graphics g)
    {
        super.paint(g);
        Graphics2D g2d = (Graphics2D) g;
        ball.paint(g2d);
        paddle.paint(g2d);
    }




}

import javax.swing.JFrame;


public class main {
    public static void main(String[]args)
    {
      JFrame frame = new JFrame("MLG Pong");
      game display = new game();
      frame.add(display);
      frame.setSize(500,300);
      frame.setVisible(true);
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setResizable(false);
      System.out.println(""+display.getHeight()+" "+display.getWidth());

    }
}

Sorry for my messy coding, I only learned Java a few months ago.


Solution

  • The problem is in the slow() method:

    public void slow()
    {
        xVel = (Math.abs(xVel) - 0.02) * Math.signum(xVel);
        yVel = (Math.abs(yVel) - 0.02) * Math.signum(yVel);
    }
    

    You are in fact substracting a vector with another direction from the velocity vector. At the beginning, the change in direction is small, but later it becomes bigger. You always substract (+-0.02, +-0.02) from the velocity vector.

    An easy fix would be:

    public void slow()
    {
        xVel = xVel*0.99;
        yVel = yVel*0.99;
    }
    

    but this slows down big velocities faster than small ones.

    If you don't want this behaviour, you have to calculate a small vector of constant length that has the same direction as the velocity vector, and substract this.

    For example:

    public void slow()
    {
        double len = Math.sqrt(xVel*xVel + yVel*yVel);
        double sx = xVel/len*0.02;
        double sy = yVel/len*0.02;
        xVel = xVel - sx;
        yVel = yVel - sy;
    }