I am trying to write a 2d graphical game. In this game I have keyboard inputs to move a square block on a black plane. I order to ensure smooth key motions I want to use Buffered key input. In order to do this I use a boolean array that saves the key strokes.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Timer;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main extends JFrame{
/**
* #param args
*/
class Entity
{
int x = 150,y = 150;
int Speed = 5;
}
Entity user = new Entity();
boolean[] keys = new boolean[KeyEvent.KEY_TYPED];
public Main()
{
setSize(800,600);
setLocationRelativeTo(null);
final JPanel display = new JPanel()
{
protected void paintComponent(Graphics g)
{
g.setColor(Color.black);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.RED);
g.fillRect(user.x, user.y, 30, 30);
}
};
addKeyListener(new KeyAdapter()
{
public void keyPressed(KeyEvent arg0)
{
keys[arg0.getKeyCode()] = true;
if(keys[KeyEvent.VK_UP])
{
user.y -= user.Speed;
}
if(keys[KeyEvent.VK_DOWN])
{
user.y += user.Speed;
}
if(keys[KeyEvent.VK_LEFT])
{
user.x -= user.Speed;
}
if(keys[KeyEvent.VK_RIGHT])
{
user.x += user.Speed;
}
setFocusable(true);
repaint();
}
});
//add a action listener
//remember to set the focusable
add(display);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread t = new Thread();
try {
t.sleep(100);
Main m = new Main();
m.setVisible(true);
m.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
In this input the block moves in the correct direction but just twice, then It moves in a different motion and it even Stops moving.
I have searched in all my Java books that I have and it does not help much. How can I get this error fixed?
Make the boolean[] keys a local variable so that it does not contain previously pressed keys, which will impact the x,y coordinates.
public void keyPressed(KeyEvent arg0)
{
boolean[] keys = new boolean[KeyEvent.KEY_TYPED];
keys[arg0.getKeyCode()] = true;
if(keys[KeyEvent.VK_UP])
{
user.y -= user.Speed;
}
else if(keys[KeyEvent.VK_DOWN])
{
user.y += user.Speed;
}
else if(keys[KeyEvent.VK_LEFT])
{
user.x -= user.Speed;
}
else if(keys[KeyEvent.VK_RIGHT])
{
user.x += user.Speed;
}
setFocusable(true);
repaint();
}
If you just want to clear the keys
public void keyPressed(KeyEvent arg0)
{
keys = new boolean[KeyEvent.KEY_TYPED];
keys[arg0.getKeyCode()] = true;
/* Rest of code */
Related
I am programming a Mario game for my project for my computer science class, and so far the Mario character can run left and right, crouch, and I have built the background.
Two questions that I have are
when making the background move, should I make a separate class that has all the background images? This way I can switch out the various backgrounds I intend to create.
My current program has the issue of when I try and jump (press the up arrow key), Mario does not jump normally, he lags a bit and then rises a lot. I am trying to make it so Mario can rise lets say, one pixel every 100th of a second, but that is not working, instead, it waits around 1 or 2 seconds then rises 100 pixels and stops.
Frame Class:
package FirstPlatformer;
import javax.swing.JFrame;
public class PlatformerFrame
{
public static void main(String[] args)
{
//change to match your values for width/height
//these can be changed
int w = 1525;
int h = 830;
//sets up a JFrame object with title "Template"
JFrame frame = new JFrame("Template");
//make sure the jframe closes when you hit the 'x'
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//adds the drawing panel to the frame
frame.getContentPane().add(new Platformer(w,h));
//resizes the frame to fit the panel
frame.pack();
//makes it visible
frame.setVisible(true);
}
}
Platformer Class:
package FirstPlatformer;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
//change to be your packagename
//all imports are necessary
//must 'extend' JPanel
public class Platformer extends JPanel
{
Random test = new Random();
Color aboveground = new Color (99, 158, 169);
private ImageIcon marioStart, ground, mysteryBlock, smallMarioRight, smallMarioLeft, smallMarioCrouchedLeft, smallMarioCrouchedRight;
//variables for the overall width and height
private int w, h;
boolean start = false;
//crouching mechanism
boolean crouchPressed = false;
boolean crouchReleased = false;
boolean youAreCrouching = false;
//facing direction mechanisms
boolean facingRight = true;
boolean facingLeft = false;
//jumping mechanisms
boolean jumpStart = false;
int jumpCountEqual = 0;
private int number = 0;
private int count = 0;
int baseHeight = 770;
int smallMarioGround = 725;
//variable that establishes marios x coord
int marioX = 0;
//sets up the initial panel for drawing with proper size
public Platformer(int w, int h)
{
setFocusable(true);
this.w = w;
this.h = h;
this.setPreferredSize(new Dimension(w,h));
//creating the images
marioStart = new ImageIcon("src/FirstPlatformer/marioStart.JPG");
//building blocks
ground = new ImageIcon("src/FirstPlatformer/ground.JPG");
mysteryBlock = new ImageIcon("src/FirstPlatformer/mysteryBlock.png");
//small mario actions
smallMarioLeft = new ImageIcon("src/FirstPlatformer/smallMarioRight.png");
smallMarioRight = new ImageIcon("src/FirstPlatformer/smallMarioLeft.png");
smallMarioCrouchedLeft = new ImageIcon("src/FirstPlatformer/marioCrouched.png");
smallMarioCrouchedRight= new ImageIcon("src/FirstPlatformer/marioCrouchedRight.png");
this.addMouseListener(new MouseTracker());
this.addKeyListener(new Keyboard());
}
//all graphical components go here
//this.setBackground(Color c) for example will change background color
public void paintComponent(Graphics g)
{
//this line sets up the graphics - always needed
super.paintComponent(g);
g.setColor(Color.RED);
//all drawings below here:
//creating the starting screen
if(start != true) {
setBackground(Color.BLACK);
marioStart.paintIcon(this,g,240,100);
g.setFont(new Font("Arial", Font.BOLD, 50));
g.drawString("Created by Zane Lanski", 515, 625);
g.drawString("Press Space to Start", 525, 725);
}
else {
//setting the overall background for when mario is aboveground
setBackground(aboveground);
//variable for building the bottom of the level
int groundCount = 0;
//for loop creates the base of the level
for(int groundBase = 0; groundBase <30; groundBase++)
{
ground.paintIcon(this, g, groundCount, 770);
groundCount = groundCount + 52;
}
mysteryBlock.paintIcon(this, g, 500, 500);
if(crouchPressed != true && facingRight == true) {
smallMarioRight.paintIcon(this,g,marioX,smallMarioGround);
}
if(crouchPressed != true && facingLeft == true) {
smallMarioLeft.paintIcon(this,g,marioX,smallMarioGround);
}
if(crouchPressed != false) {
smallMarioCrouchedRight.paintIcon(this,g, marioX, smallMarioGround+18);
}
if(jumpStart == true) {
for(int jumpCount = jumpCountEqual;jumpCount < 100; jumpCount++) {
smallMarioGround = smallMarioGround- 1;
// smallMarioRight.paintIcon(this, g, marioX, smallMarioGround);
try
{
Thread.sleep(10);
}
catch(InterruptedException e)
{
System.out.println(e);
}
repaint();
System.out.println(jumpCount);
jumpCountEqual++;
}
}
}
}
private class MouseTracker implements MouseListener, MouseMotionListener
{
#Override
public void mouseDragged(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseMoved(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mousePressed(MouseEvent e) {
// TODO Auto-generated method stub
number++;
count++;
number %= 5;
repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
}
private class Keyboard implements KeyListener
{
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
int key = e.getKeyCode();
repaint();
}
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
int key = e.getKeyCode();
if(key == KeyEvent.VK_RIGHT) {
facingLeft = false;
facingRight = true;
marioX = marioX + 5;
facingRight = true;
facingLeft = false;
}
if(key == KeyEvent.VK_LEFT) {
crouchPressed = false;
marioX = marioX - 5;
facingLeft = true;
facingRight = false;
}
if(key == KeyEvent.VK_DOWN) {
crouchPressed = true;
youAreCrouching = true;
}
if(key == KeyEvent.VK_UP) {
jumpStart = true;
}
if(key == KeyEvent.VK_SPACE) {
start = true;
}
repaint();
}
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
int key = e.getKeyCode();
if(key == KeyEvent.VK_DOWN) {
crouchPressed = false;
}
repaint();
}
}
}
I am attempting to make minesweeper. I understand that there is probably a better way to do this but this is how I am trying it.
My problem comes in the gridSquare class. This class is a JLabel and I need the frame that the gridSquare is contained within. I attempt to get the frame with the line minesweeperFrame frame = (minesweeperFrame) SwingUtilities.getWindowAncestor(this);. The problem I run into is that frame is null and never set to the gridSquares Frame. Is there any way to fix this.
Main.java
public class Main {
public static void main(String[] args) {
minesweeperFrame frame = new minesweeperFrame();
}
}
gridSquare.java
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Objects;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.border.Border;
public class gridSquare extends JLabel implements MouseListener{
Boolean isBomb = false;
int gridNum;
int n1,n2,n3,n4,n6,n7,n8,n9;
int bombNumber;
minesweeperFrame frame = (minesweeperFrame) SwingUtilities.getWindowAncestor(this);
gridSquare(int gridNum){
this.gridNum = gridNum;
n1 = gridNum - 10 - 1; //Top left
n2 = gridNum - 10 ; //Top center
n3 = gridNum - 10 + 1; //Top Left
n4 = gridNum - 1;//center left
n6 = gridNum + 1;//center right
n7 = gridNum + 10 -1;//Bottom Left
n8 = gridNum + 10; //Bottom Center
n9 = gridNum + 10 + 1; //Bottom Right
int neighbors[] = {n1,n2,n3,n4,n6,n7,n8,n9};
try {
//get the bomb number
for(int i:neighbors) {
if(frame.getSquares().get(i).getIsBomb()) { // if any neighbor is a bomb increase bombNumber ---------- frame.getSquares().get(i).getIsBomb()
bombNumber++;
}
System.out.println("WORKS");
}
}
catch(Exception e){
System.out.println(e);
}
setText("");
setHorizontalAlignment(JLabel.CENTER);
setVerticalAlignment(JLabel.CENTER);
setFont(new Font("Copperplate Gothic Bold",Font.PLAIN,20));
setForeground(new Color(0x000000)); //set font color CHANGE FOR EACHER NUMBER
setSize(50,50);
Border border = BorderFactory.createLineBorder(Color.darkGray,5);
setBorder(border);
setBackground(new Color(0xd3d3d3)); //set background color ----- change to #424242 after clicked
setOpaque(true);//display background color
addMouseListener(this);
}
public Boolean getIsBomb() {
return isBomb;
}
public void setIsBomb(Boolean isBomb) {
this.isBomb = isBomb;
}
#Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mousePressed(MouseEvent e) {
// TODO Auto-generated method stub
if(e.getButton() == MouseEvent.BUTTON1) {
setText("Left Click!");
if(isBomb) {
setText("bomb");
}
}
if(e.getButton() == MouseEvent.BUTTON3) {
setText("Flag");
}
}
#Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
}
mineSeeperFrame.java
import java.awt.GridLayout;
import java.awt.color.*;
import java.util.ArrayList;
import javax.swing.*;
public class minesweeperFrame extends JFrame{
private final int numColumns = 10;
private int numRows = 8;
int numBombs = 0;
ArrayList<gridSquare> squares = new ArrayList<gridSquare>();
minesweeperFrame(){
setTitle("Minesweeper");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(1000,850);
setResizable(false);
setLayout(new GridLayout(numRows,numColumns,1,1));
setVisible(true);
//Fill Arraylist and add to frame
for(int i = 0; i < 80;i++) {
squares.add(new gridSquare(i));
add(squares.get(i));
}
//AddBombs
while(numBombs < 10) {
squares.get((int) (Math.random()*80)).setIsBomb(true);
numBombs++;
}
}
public ArrayList<gridSquare> getSquares() {
return squares;
}
public int getGridRows() {
return numRows;
}
public int getGridColumns() {
return numColumns;
}
}
You cannot do this during initialization:
minesweeperFrame frame = (minesweeperFrame) SwingUtilities.getWindowAncestor(this);
the component is being created and not added to the frame yet, so you get null.
Moreover you add the gridsquare to the frame directly, this is wrong, you must add it to the contentPane, please read:
https://docs.oracle.com/javase/tutorial/uiswing/components/toplevel.html
https://docs.oracle.com/javase/tutorial/uiswing/components/rootpane.html
I have been working on this snake project, and dont really understand why the keylistener isnt actually changing the variable char key. I have some other examples of keylisteners, and they all work properly, but for some reason mine isnt working. Some help would be appreciated. Thanks a lot for the help.
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.awt.event.KeyListener;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.RepaintManager;
public class Main extends JPanel implements Runnable {
/*
*
* SIZE OF BOARD
*
*/
static GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
static GraphicsDevice[] gs = ge.getScreenDevices();
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
private static final int DIM_WIDTH = Toolkit.getDefaultToolkit().getScreenSize().width;
private static final int DIM_HEIGHT = Toolkit.getDefaultToolkit().getScreenSize().height;
static JFrame frame = new JFrame();
static JPanel panel = new JPanel();
static Snake s = new Snake();
static Main main = new Main();
KeyListener listener = new Snake();
boolean black = true;
public Main() {
addKeyListener(listener);
}
#SuppressWarnings("deprecation")
public static void main(String[] args) {
//gs[0].setFullScreenWindow(frame);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setCursor(Cursor.CROSSHAIR_CURSOR);
frame.setSize(DIM_WIDTH, DIM_HEIGHT);
frame.add(main);
frame.setVisible(true);
(new Thread(new Main())).start();
}
// paints the panel
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
/*
* Snake
*/
//Redraws Background
g2d.setColor(Color.black);
g2d.fillRect(0, 0, (int) screenSize.getWidth(), (int) screenSize.getHeight());
//Draws Border
g2d.setColor(Color.white);
g2d.fillRect(0,0, (int)screenSize.getWidth(), 1);
g2d.fillRect(0,0, 1, (int)screenSize.getHeight());
g2d.fillRect((int)screenSize.getWidth()-1, 1, 1, (int)screenSize.getHeight());
g2d.fillRect(0, (int)screenSize.getHeight()-86, (int)screenSize.getWidth(), 10);
//Draws Snake head
g2d.setColor(s.getColor());
g2d.fillRect(s.getX(), s.getY(), 30, 30);
}
// Creates Frame, and starts the game
#Override
public void run() {
while (!s.getIsDead()) {
move();
}
}
public void move() {
s.move();
s.death();
main.repaint();
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (Thread.interrupted()) {
return;
}
}
}
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Snake implements KeyListener {
Color c = Color.green;
//Starting position of Snake
int x = 50;
int y = 50;
char key;
boolean dead = false;
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
public void move() {
x++;
}
public void death() {
if (x + 30 >= screenSize.getWidth() || y + 115 >= screenSize.getHeight() || y<=0 || x<=0) {
c = Color.red;
dead = true;
}
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public Color getColor() {
return c;
}
public boolean getIsDead() {
return dead;
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyPressed(KeyEvent e) {
key = 'w';
}
#Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
}
Change your constructor to
public Main() {
addKeyListener(listener);
setFocusable(true);
requestFocus();
}
But take a look at this question, you should not use KeyListeners.
java keylistener not called
As #azurefrog mentioned, your keyPressed method is setting key to 'w' every time. You need to use the KeyEvent passed in as a parameter to that method to get the key that was pressed. Your keyPressed method should look something like this:
#Override
public void keyPressed(KeyEvent e) {
key = e.getKeyChar();
}
Hi I'm using the Timer class and the repaint method for the JPanel, but whenever I hit DOWN and the space ship moves halfway down the screen, it shrinks and stays in place. It never reaches the bottom of the window. It is the same when I move it to the RIGHT.
Also when I try to place it in a differnt y position, the background isn't black anymore. Can someone help me with this please? Here is a sample of my code:
package javapaint;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class SpaceShipFlight
{
private JFrame windowFrame;
private BufferedImage shipSprite;
private BufferedImage spaceBackground;
/** MAIN METHOD **/
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
try
{
SpaceShipFlight window = new SpaceShipFlight();
window.windowFrame.setVisible(true);
}
catch (Exception e)
{
e.printStackTrace();
}
}
});
}
/** CONSTRUCTOR **/
public SpaceShipFlight() throws IOException
{
/*************************************************************************
SPACE SHIP FLIGHT JFRAME
*************************************************************************/
windowFrame = new JFrame("Space Ship Flight");
windowFrame.setBounds(0, 0, 950, 700);
windowFrame.setBackground(Color.BLACK);
/*************************************************************************
SPACE SHIP FLIGHT SPRITES (FOR DRAWING)
*************************************************************************/
spaceBackground = ImageIO.read(new File("Space (Medium).png"));
// 450 x 374
shipSprite = ImageIO.read(new File("Ship Sprite.png"));
// 64 x 64 png of space ship
/*************************************************************************
SPACE SHIP FLIGHT JPANEL (FOR DRAWING)
*************************************************************************/
SpacePanel spacePanel = new SpacePanel();
windowFrame.add(spacePanel);
/*************************************************************************
SPACE SHIP FLIGHT TIMER
*************************************************************************/
Timer timer = new Timer(10, new AnimationListener(spacePanel));
timer.start();
/*************************************************************************
EVENT HANDLER TO MOVE SPACE SHIP
*************************************************************************/
/*
EventHandler <KeyEvent> moveShip = new EventHandler<KeyEvent> ()
{
#Override
public void handle(KeyEvent event)
{
if (event.getCharacter().equalsIgnoreCase("X"))
{
spacePanel.setX( spacePanel.getX() + 1 );
}
}
};
*/
KeyListener moveShip = new KeyListener()
{
#Override
public void keyTyped(KeyEvent e)
{
}
#Override
public void keyPressed(KeyEvent e)
{
if (e.getKeyCode() == KeyEvent.VK_RIGHT && spacePanel.getX() < 375)
{
spacePanel.setX( spacePanel.getX() + 5 );
spacePanel.repaint();
}
if (e.getKeyCode() == KeyEvent.VK_LEFT && spacePanel.getX() > 0)
{
spacePanel.setX( spacePanel.getX() - 5);
spacePanel.repaint();
}
if (e.getKeyCode() == KeyEvent.VK_UP && spacePanel.getY() > 0)
{
spacePanel.setY( spacePanel.getY() - 5);
spacePanel.repaint();
}
if (e.getKeyCode() == KeyEvent.VK_DOWN && spacePanel.getY() < 700)
{
spacePanel.setY( spacePanel.getY() + 5 );
spacePanel.repaint();
}
if (e.getKeyCode() == KeyEvent.VK_X)
{
System.out.printf("X: %d, Y: %d\n", spacePanel.getX(),
spacePanel.getY());
}
}
#Override
public void keyReleased(KeyEvent e)
{
}
};
windowFrame.addKeyListener(moveShip);
}
/** ANIMATION LISTENER CLASS **/
public static class AnimationListener implements ActionListener
{
private SpacePanel panel;
private Graphics graphics;
public AnimationListener(SpacePanel panel)
{
this.panel = panel;
this.graphics = panel.getGraphics();
}
#Override
public void actionPerformed(ActionEvent e)
{
panel.repaint();
}
}
/** SPACE PANEL SUBCLASS **/
public class SpacePanel extends JPanel
{
// Variables for the space ship's position temporarily placed here
private int x;
private int y;
// Constructor
public SpacePanel()
{
super();
x = 0;
y = 0;
}
// Getters
public int getX()
{
return x;
}
public int getY()
{
return y;
}
// Setters
public void setX(int newX)
{
x = newX;
}
public void setY(int newY)
{
y = newY;
}
#Override
public void paint(Graphics g)
{
g.drawImage(spaceBackground, 0, 0, this);
g.drawImage(shipSprite, x, y, this);
}
}
}
I have some grids that is painted to screen one by one. I use arrow keys to move grids around as a group. Swing is said to be doubleBuffered by default so I believe frame.createBufferStrategy(2) is a bad practice but the problem is when I don't use manual double buffering, the grids are misaligned and some holes are appearing between them. Using manual double buffering fixes it.
I'm also experiencing some graphical problems(such as a dialog's buttons not displaying properly) in the actual program(not in SSCCE) so I thought it might be caused by the incorrect implementation of the double buffering.
Here is the SSCCE of the program, that causes grids to misalign when not manually double buffered:
package SSCCE;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LayoutManager;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferStrategy;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
boolean manuallyDoubleBuffered = false; //change this
static Main main;
public final JFrame frame = new JFrame();
public final Keys keys = new Keys();
private JPanel panel;
private BufferStrategy bufferStrategy;
public static void main(String[] args) {
main = new Main();
main.initiate();
// --START LOOP--
Thread loop = new Thread(main.new Looper());
loop.start();
}
public void initiate() {
frameInit();
keys.start();
}
private void frameInit() {
frame.setSize(1200, 750);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
setUpGUI();
if (manuallyDoubleBuffered)
frame.createBufferStrategy(2); // manual double buffering
bufferStrategy = frame.getBufferStrategy();
}
private void setUpGUI() {
panel = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Main.main.rendering(g2d);
super.paintComponent(g);
}
};
LayoutManager layout = new FlowLayout();
frame.getContentPane().setBackground(Color.black);
panel.setLayout(layout);
panel.setOpaque(false);//
JButton but1 = new JButton("but1");
panel.add(but1);
frame.add(panel);
}
class Looper implements Runnable {
#Override
public void run() {
Main.main.gameLoop();
}
}
private void gameLoop() {
// variables are declared at start
while (true) {
if (manuallyDoubleBuffered)
paint(); // MANUAL double buffering
else
frame.repaint();// no manual double buffering
update();
try {
Thread.sleep(1000 / 60);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}// loop end
private void update() {
move();
}
private void rendering(Graphics2D g2d) {
// // testing
paintGrids(g2d);
}
private void move() {
x += sx;
y += sy;
}
int sx = 0; //speedX
int sy = 0; //speedY
//top left corner of the grid
int x = 0;
int y = 0;
private void paintGrids(Graphics2D g) {
for (int i = 0; i < 100; i++) {
for (int t = 0; t < 100; t++) {
g.setColor(Color.GRAY);
g.fillRect(i * 50 + x, t * 50 + y, 50, 50);
g.setColor(Color.BLACK);
g.drawString(i + "," + t, i * 50 + x, t * 50 + y + 10);
}
}
}
public void paint() {
// uses double buffering system.
do {
do {
Graphics2D g2d = (Graphics2D) bufferStrategy.getDrawGraphics();
g2d.fillRect(0, 0, frame.getWidth(), frame.getHeight());
try {
frame.paint(g2d);
} catch (NullPointerException e) {
e.printStackTrace();
}
g2d.dispose();
} while (bufferStrategy.contentsRestored());
bufferStrategy.show();
} while (bufferStrategy.contentsLost());
}
}
class Keys implements KeyListener {// Trimmed down to shorten SSCCE
private final int leftKey = 37; // left b.
private final int rightKey = 39; // Right b.
private final int upKey = 38;// up k.
private final int downKey = 40;// down k.
public void start() {
Main.main.frame.addKeyListener(this);
Main.main.frame.setFocusable(true);
}
private void left() {
Main.main.sx -= 10;
}
private void right() {
Main.main.sx += 10;
}
private void up() {
Main.main.sy -= 10;
}
private void down() {
Main.main.sy += 10;
}
#Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
System.out.println(e.getKeyCode());
switch (e.getKeyCode()) {
case leftKey:
left();
break;
case rightKey:
right();
break;
case downKey:
down();
break;
case upKey:
up();
break;
}
}
#Override
public void keyReleased(KeyEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void keyTyped(KeyEvent arg0) {
// TODO Auto-generated method stub
}
}// END OF THE KEYS CLASS
Oracle tutorials of swing does not explain the usage with a game loop. What is the best way to do it? Am I doing anything wrong?
In case the visual error is not reproduced on other computers, I'm uploading a screenshot:
Black lines are caused by the misalinging of the rectangles. They don't exist when manual double buffering is set to true.
Thanks in advance.
Edit: I've forgot to mention that the black lines occur when grids are moving.
I' have also found out, manual double buffering drastically reduces performance.
Edit 2 : I've fixed the problem and posted it as an answer but feel free to comment on my code. Main class(except the gameLoop) is similar to the actual main class I use in my program.
I couldn't see any change in the background. Here's the code change I made.
public static void main(String[] args) {
main = new Main();
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
main.initiate();
}
});
// --START LOOP--
Thread loop = new Thread(main.new Looper());
loop.start();
}
You must always start a Swing application with a call to SwingUtilities.invokeLater.
I've found the problem and writing here in case something like that ever happens to anyone else.
The problem was caused due to program being multi-threaded. Top left coordinates of the grids(x and y) were updated by the other thread in the middle of the paintGrids() method. Manual double buffering was slowing the program down (by hundreds of times) and that was allowing the paintGrids method to finish painting before x and y was updated by the keys.
To fix it I've added the following to the start of the paintGrids method:
int x = this.x;
int y = this.y;