-2

I'm trying to make a 2D racing game here by adding a Jpanel on top of a Jpanel. This is done by using 2 classes which I have posted down below.

The problem is the car is never appears on the track... I'm really not sure what I'm missing.. any help is more than welcome!

Thank you in advance!

Car.java

public class Car extends JPanel implements Runnable 
{

   private static final long serialVersionUID = 007;
   private BufferedImage car = null;
   private float x = 100F, y = 100F;
   private Thread driveThread = new Thread(this);
   private double currentAngle = 0; // angel of the car
   private static int[] key = new int[256]; // keyboard input
   private float MAX_SPEED = 7F;
   private float speed = 0F; // speed of our racing car
   private float acceleration = 0.15F;
   private int player;
   private boolean playable = true;


   public Car(int player) 
   {

      this.player = player;

      this.setSize(super.getHeight(), super.getWidth());
      this.setFocusable(true); // enables keyboard

      try 
      {
         if (player == 1) 
         {
            //red car
            car = ImageIO.read(this.getClass().getResource(
                  "/imagesCar/first-0.png"));

            System.out.println(car.getColorModel());
         } else if(player == 2)
         {
            //blue car
            car = ImageIO.read(this.getClass().getResource(
                  "/imagesCar/second-0.png"));
            x = x +30;
         }

      } catch (IOException e) {
         System.out.println("dupi");
      }

      // starts the drive thread
      startGame();

   }

   private void startGame() {
      driveThread.start();
   }

   @Override
   protected void paintComponent(Graphics g) 
   {

      super.paintComponent(g);
      this.setOpaque(false);

      // rotation 
      Graphics2D g2d = (Graphics2D) g;
      AffineTransform rot = g2d.getTransform();
      // Rotation at the center of the car
      float xRot = x + 12.5F;
      float yRot = y + 20F;
      rot.rotate(Math.toRadians(currentAngle), xRot, yRot);
      g2d.setTransform(rot);
      //Draws the cars new position and angle
      g2d.drawImage(car, (int) x, (int) y, 50, 50, this);

   }

   protected void calculateCarPosition() {

      //calculates the new X and Y - coordinates 
      x += Math.sin(currentAngle * Math.PI / 180) * speed * 0.5;
      y += Math.cos(currentAngle * Math.PI / 180) * -speed * 0.5;

   }

   protected void carMovement() {

      // Player One Key's
      if (player == 1) {

         if (key[KeyEvent.VK_LEFT] != 0) {
            currentAngle-=2;

         } else if (key[KeyEvent.VK_RIGHT] != 0) {
            currentAngle+=2;
         }

         if (key[KeyEvent.VK_UP] != 0) {

            if (speed < MAX_SPEED) {

               speed += acceleration;
            }

         } else if (key[KeyEvent.VK_DOWN] != 0 && speed > -1) {
            speed = speed - 0.1F;
         }
         speed = speed * 0.99F;

      } else {

         //Player Two Key's

         if (key[KeyEvent.VK_A] != 0) {
            currentAngle -= 2;

         } else if (key[KeyEvent.VK_D] != 0) {
            currentAngle += 2;
         }

         if (key[KeyEvent.VK_W] != 0) {

            if (speed < MAX_SPEED) {

               speed += acceleration;
            }

         } else if (key[KeyEvent.VK_S] != 0 && speed > -1) {
            speed = speed - 0.1F;
         }
         //reduce speed when no key is pressed
         speed = speed * 0.99F;
      }

   }

   public void getUnderground() {

   }
   // get key events!
   final protected void processKeyEvent(KeyEvent e) {
      key[e.getKeyCode()] = e.getID() & 1;
   }

   @Override
   public void run() {
      while (true) {

         repaint();
         carMovement();
         calculateCarPosition();


         try {
            Thread.sleep(10);
         } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
         }

      }

   }

}

RaceTrack.java

public class RaceTrack extends JPanel
{

@Override
public void paintComponent(Graphics g)
{
    Color c1 = Color.green;

    g.setColor(c1);
    g.fillRect(150, 200, 550, 300);
    Color c2 = Color.black;
    g.setColor(c2);
    g.drawRect(50, 100, 750, 500); // outer edge
    g.drawRect(150, 200, 550, 300); // inner edge
    Color c3 = Color.yellow;
    g.setColor(c3);
    g.drawRect(100, 150, 650, 400); // mid-lane marker
    Color c4 = Color.white;
    g.setColor(c4);
    g.drawLine(425, 500, 425, 600); // start line

}
}

main

public static void main(String[] args) {

    JFrame mainFrame = new JFrame();

    mainFrame.setSize(850,650);
    mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    Container content = mainFrame.getContentPane();

    RaceTrack track = new RaceTrack();

    Car carP1 = new Car(1);

    track.add(carP1);

    content.add(track);

    mainFrame.setVisible(true);
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
Xerev
  • 1
  • 1
    1. `Car` has no defined sizing hints, so it's default size is `0x0`; 2. Adding `Car` to `RaceTrack`, which is using a `FlowLayout` will layout `Car` to it's preferred size of `0x0`; 3. Swing is not thread safe so you're probably have a bunch of thread race conditions/violations as well – MadProgrammer May 01 '18 at 02:46
  • Not sure if this is the proper way to solve this, but I tried adding `carP1.setPreferredSize(new Dimension(50,50));` before adding it to RaceTrack. Doesn't seem to solve the problem.. – Xerev May 01 '18 at 03:02
  • 1
    Don't use components for this task. Instead, you need to develop up the concept of "entities", these "entities" do "stuff", like "paint". You then define a list of these "entities" (sometimes in different lists based on there functionality). In your renderer, you loop over the "paintable" entities and paint them. You would then have a single thread whose responsibility it would be would be to update all those entities which are "movable" (or otherwise change over time) and schedule a paint pass. In simple terms, you could use Swing `Timer` has the "main loop" – MadProgrammer May 01 '18 at 03:06

1 Answers1

1
  1. Car has no defined sizing hints, so it's default size is 0x0
  2. Adding Car to RaceTrack, which is using a FlowLayout will layout Car to it's preferred size of 0x0
  3. Swing is not thread safe so you're probably have a bunch of thread race conditions/violations as well

Not sure if this is the proper way to solve this

Don't use components for this purpose, this problem just screams custom painting all the way.

There are plenty of blogs and tutorials about basic game development, so I don't want to spend a lot of time going over the same material.

Basically, what you want is to define a series of "attributes" to the objects you want to use in your game (AKA "entities"). Not all entities need to be paintable, some might trigger other actions or simply act as "markers" for other entities to use.

In this example, I'm defining two basic entities, "movable" and "paintable". A "paintable" entity may be static (ie the track) or "movable" (ie the car)

The intention is to provide a isolated concept of functionality which can easily be applied to a verity objects in order to "describe" their functionality and purpose within the game.

For example...

public interface MovableEntity extends Entity {
    public void update(Rectangle bounds);
}

public interface PaintableEntity extends Entity {
    public void paint(Graphics2D g2d, ImageObserver imageObserver, Rectangle bounds);
}

So, in your case, a Car is both Paintable and Movable.

Your "engine" would then maintain one or more lists of these "entities" and process them accordingly.

This example simply makes use of a Swing Timer to act as the "main loop"

mainLoop = new Timer(5, new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        Rectangle bounds = new Rectangle(0, 0, getWidth(), getHeight());
        // Lots of collision detection and other awesome stuff
        for (MovableEntity entity : movableEntitys) {
            entity.update(bounds);
        }
        repaint();
    }
});
mainLoop.start();

This provides a level of thread safety, as the Timer is triggered within the context of the Event Dispatching Thread, meaning that while we're updating the entities, they can't be painted.

Then we simply make use of the JPanel's paintComponent method to act as the rendering process...

protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Rectangle bounds = new Rectangle(0, 0, getWidth(), getHeight());
    for (PaintableEntity paintable : paintableEntities) {
        Graphics2D g2d = (Graphics2D) g.create();
        paintable.paint(g2d, this, bounds);
        g2d.dispose();
    }
}

This is a pretty broad example based on demonstrating basic concepts in a simple manner. There are far more complex possible solutions which would follow the same basic principles.

Personally, I'd define some kind of "path" which acts as the track, on which the cars would then calculate there positions based on different factors, but that's somewhat of a more complex solution then is required right now. But if you're really interested, it might look something like this

Runnable Example...

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.ImageObserver;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    /*
    You could have entities which can collide which have collision detection
    capabilities

    Some entities don't need to be painted and may provide things like
    visual or audio affects
    */

    public interface Entity {
    }

    public interface MovableEntity extends Entity {
        public void update(Rectangle bounds);
    }

    public interface PaintableEntity extends Entity {
        public void paint(Graphics2D g2d, ImageObserver imageObserver, Rectangle bounds);
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new GamePane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class GamePane extends JPanel {

        // Could use a single list and filter it, but hay
        private List<PaintableEntity> paintableEntities;
        private List<MovableEntity> movableEntitys;

        private Timer mainLoop;

        public GamePane() {
            paintableEntities = new ArrayList<>(25);
            movableEntitys = new ArrayList<>(25);

            paintableEntities.add(new TrackEntity());

            CarEntity car = new CarEntity();
            paintableEntities.add(car);
            movableEntitys.add(car);

            mainLoop = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    Rectangle bounds = new Rectangle(0, 0, getWidth(), getHeight());
                    // Lots of collision detection and other awesome stuff
                    for (MovableEntity entity : movableEntitys) {
                        entity.update(bounds);
                    }
                    repaint();
                }
            });
            mainLoop.start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Rectangle bounds = new Rectangle(0, 0, getWidth(), getHeight());
            for (PaintableEntity paintable : paintableEntities) {
                Graphics2D g2d = (Graphics2D) g.create();
                paintable.paint(g2d, this, bounds);
                g2d.dispose();
            }
        }

    }

    public class CarEntity implements PaintableEntity, MovableEntity {

        private int delta = 1;

        private int xDelta = 0;
        private int yDelta = delta;

        private int xPos = 2;
        private int yPos = 2;

        private int size = 4;

        @Override
        public void paint(Graphics2D g2d, ImageObserver imageObserver, Rectangle bounds) {
            g2d.translate(bounds.x, bounds.y);
            g2d.setColor(Color.RED);
            g2d.fillRect(xPos - size / 2, yPos - size / 2, size, size);
        }

        @Override
        public void update(Rectangle bounds) {
            xPos += xDelta;
            yPos += yDelta;

            if (xPos + (size / 2) > bounds.x + bounds.width) {
                xPos = bounds.x + bounds.width - (size / 2);
                xDelta = 0;
                yDelta = -delta;
            } else if (xPos - (size / 2) < bounds.x) {
                xPos = bounds.x + (size / 2);
                xDelta = 0;
                yDelta = delta;
            }

            if (yPos + (size / 2) > bounds.y + bounds.height) {
                yPos = bounds.y + bounds.height - (size / 2);
                xDelta = delta;
                yDelta = 0;
            } else if (yPos - (size / 2) < bounds.y) {
                yPos = bounds.y + (size / 2);
                xDelta = -delta;
                yDelta = 0;
            }
        }

    }

    public class TrackEntity implements PaintableEntity {

        @Override
        public void paint(Graphics2D g2d, ImageObserver imageObserver, Rectangle bounds) {
            g2d.translate(bounds.x, bounds.y);
            g2d.setColor(Color.BLUE);
            g2d.drawRect(2, 2, bounds.width - 4, bounds.height - 4);
        }

    }

}
Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366