I am trying to create a simple Java 2D physics game, and the current problem that I am running into is that I cannot think of a way to center the camera on the spaceship as it moves around.
I have the model of the ship as a component in the panel Universe which is in the frame main. Currently the panel displays the pixels from (0, 0) to (1000, 1000).
What I want to happen, is if the ship moves 10 pixels to the right, then the panel will display pixels from (10, 0) to (1010, 1000), following the ship, but remaining rigid in the frame.
Or maybe there is a better way to achieve a similar effect.
This is the relevant part I believe:
public class Main extends JFrame {
public static void main(String[] args) {
M = new Main(1000, 1000);
}
public Main(int width, int height) {
boardWidth = width;
boardHeight = height;
FRAMERATE = 60;
setSize(boardWidth, boardHeight);
setTitle("space");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
createUniverse();
createShip();
runThreads();
}
private void createUniverse() {
U = new Universe();
U.setLayout(new BorderLayout());
add(U, BorderLayout.CENTER);
}
private void createShip() {
U.createShip();
}
private void runThreads() {
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
long lengthOfFrame = (long) (1000 / FRAMERATE);
executor.scheduleAtFixedRate(new Input(U.getShip(), FRAMERATE), 0L, lengthOfFrame, TimeUnit.MILLISECONDS);
executor.scheduleAtFixedRate(new UpdatePhysics(U.getObjects(), FRAMERATE), 0L, lengthOfFrame, TimeUnit.MILLISECONDS);
executor.scheduleAtFixedRate(new RepaintFrame(this), 0L, lengthOfFrame, TimeUnit.MILLISECONDS);
this.addKeyListener(new UserInput());
}
}
That is the JFrame that holds this panel:
public class Universe extends JPanel {
public Universe() {
setSize(Main.boardWidth, Main.boardHeight);
setBackground(Color.BLACK);
setVisible(true);
}
public void createShip() {
ship = new Ship(new double[]{getWidth() / 2, getHeight() / 2});
shipM = new ShipModel(ship);
_objects = new MassiveObject[1];
_objects[0] = ship;
add(shipM, BorderLayout.CENTER);
}
}
The ShipModel is just a Java component that is represented by a few polygons.
The frame is painted by the thread in Main which basically just calls Main.repaint() repeatedly.
I think thats all the relevant bits. My bad for the link, never posted before.
My suggestion would be to go a different route, or at least a slightly different route.
You definitely do not want to retrieve pixels from the picture and draw the new pixels every frame (you will draw the pixels every frame, but getting pixels every tick will slow you down)
You could load the background image altogether (which you are likely already doing from your abstract description) and then move that background image's position according to button presses. The ship would always be rendered in the middle. (The edges of the background image is where this gets tricky -- a whole other challenge to tackle)
There is also the option of splitting the background image into tiles and moving those accordingly.
I've done both of the above, and since I wanted the background image to be extremely large, it only moved smoothly after I split it up into tiles programmatically, storing it in memory and rending each tile according to the position.
Like I said, the edges are where it gets tricky if you want your spaceship to move to the edge. If you just show infinite black behind the background image and you don't mind showing that, then you can just keep the same physics and have only half of the screen showing the background image.
So summary, just move the background image within the frame/panel accordingly.
Your question was not super clear and linking to your code is not great practice on Stack Overflow (it's better to include the relevant code snippets in your post using backticks like this:
This is code, I used backticks to surround it
That makes it scroll if it's long.
Consider placing your background image within a JScrollPane, remove the scrollbars by setting the appropriate scrolling policy:
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
You can move the image held within the JScrollPane by calling
backgroundLabel.scrollRectToVisible(rect);
and change the location of this rect based on button presses.
For example:
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;
#SuppressWarnings("serial")
public class SideScroll extends JLayeredPane {
public static final String BG_IMG_PATH = "https://upload.wikimedia.org/wikipedia/commons"
+ "/a/ad/Tomada_da_cidade_de_S%C3%A3o_Salvador_s%C3%A9culo_XVIII_%28panor%C3%A2mico%29.jpg";
public static final String CAMEL_PATH = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/PEO-bactrian_camel.svg/200px-PEO-bactrian_camel.svg.png";
private static final int PREF_W = 800;
private static final int PREF_H = 650;
protected static final int SCALE = 10;
private JLabel backgroundLabel = new JLabel();
JScrollPane scrollPane = new JScrollPane(backgroundLabel);
private JLabel camelLabel = new JLabel();
public SideScroll(Icon bgIcon, Icon camelIcon) {
camelLabel.setIcon(camelIcon);
camelLabel.setSize(camelLabel.getPreferredSize());
JPanel camelPanel = new JPanel(new GridBagLayout());
camelPanel.setOpaque(false);
camelPanel.add(camelLabel);
camelPanel.setSize(getPreferredSize());
backgroundLabel.setIcon(bgIcon);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
scrollPane.setSize(getPreferredSize());
add(scrollPane, JLayeredPane.DEFAULT_LAYER);
add(camelPanel, JLayeredPane.PALETTE_LAYER);
setFocusable(true);
addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
switch (keyCode) {
case KeyEvent.VK_LEFT:
moveImg(-1, 0);
break;
case KeyEvent.VK_RIGHT:
moveImg(1, 0);
break;
case KeyEvent.VK_UP:
moveImg(0, -1);
break;
case KeyEvent.VK_DOWN:
moveImg(0, 1);
break;
default:
break;
}
}
private void moveImg(int right, int down) {
Rectangle rect = backgroundLabel.getVisibleRect();
int x = rect.x + SCALE * right;
int y = rect.y + SCALE * down;
int width = rect.width;
int height = rect.height;
rect = new Rectangle(x, y, width, height);
backgroundLabel.scrollRectToVisible(rect);
}
});
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private static void createAndShowGui() {
Icon bgIcon = null;
BufferedImage camel = null;
Icon camelIcon = null;
BufferedImage bgImg;
try {
URL bgImageUrl = new URL(BG_IMG_PATH);
URL camelUrl = new URL(CAMEL_PATH);
bgImg = ImageIO.read(bgImageUrl);
camel = ImageIO.read(camelUrl);
// make background one quarter the size because it's too big
int imgW = bgImg.getWidth() / 4;
int imgH = bgImg.getHeight() / 4;
BufferedImage bgImage2 = new BufferedImage(imgW, imgH, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = bgImage2.createGraphics();
g2.drawImage(bgImg, 0, 0, imgW, imgH, null);
g2.dispose();
bgIcon = new ImageIcon(bgImage2);
// flip camel image so facing right
imgW = camel.getWidth();
imgH = camel.getHeight();
BufferedImage camelImg = new BufferedImage(imgW, imgH, BufferedImage.TYPE_INT_ARGB);
g2 = camelImg.createGraphics();
AffineTransform xform = AffineTransform.getTranslateInstance(imgW, 0);
xform.scale(-1, 1);
g2.drawImage(camel, xform, null);
g2.dispose();
camelIcon = new ImageIcon(camelImg);
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
JFrame frame = new JFrame("SideScroll");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new SideScroll(bgIcon, camelIcon));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
Related
So I have to create an implementation of a Sierpinski Gasket with Swing.
I can't use recursion or triangles. I have to use the following
algorithm:
Pick 3 points to define a triangle.
Select one of the vertices as current Loop 50,000 times:
Randomly choose a vertex as the target.
Draw a pixel at the mid-point between the target and current.
Make current the mid-point.
In the image below is what I sometimes get upon compilation, but other times it will pop up and disappear or it will not show up at all. If it does show up, and then I resize the window it disappears (I don't care about this, but if it helps.) I can only produce the below image sometimes when I compile (about 1/3rd of the time.) Below the image is my code, separated in two classes.
Image of when it works
import java.awt.*;
import javax.swing.JFrame;
public class SierpinskiGasket {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setTitle("SierpinskiGasket");
frame.setSize(630,580);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawSierpinski Sierpinski = new drawSierpinski();
frame.add(Sierpinski);
frame.setVisible(true);
}
}
import javax.swing.*;
import java.awt.*;
public class drawSierpinski extends JPanel{
Point point1 = new Point(10,550),
point2 = new Point(300,30),
point3 = new Point(600,555),
current = point1, target;
private int count = 0;
public void paintComponent(Graphics g){
super.paintComponent(g);
while(count<= 50000){
int choice = (int)(Math.random()*3);
switch(choice){
case 0: target = point1; break;
case 1: target = point2; break;
case 2: target = point3; break;
default: System.exit(0);
}
current = midpoint(current,target);
g.drawLine(current.x,current.y,current.x,current.y);
count++;
}
}
public Point midpoint(Point a, Point b){
return new Point((Math.round(a.x+b.x)/2),
(Math.round(a.y+b.y)/2));
}
}
I am assuming that it has something to do with how Swing does multithreading, but unfortunately I don't have too much knowledge of how to fix it. Thank you very much for any help!
This loop:
while(count<= 50000) {
// ....
}
may take a while to complete, and meanwhile it will be completely blocking the Swing event thread at its most key point -- while drawing. What's more, any trivial re-draw will trigger the loop to re-run, again freezing your GUI completely.
The solution: do your drawing outside of paintComponent. Instead create a BufferedImage the size of your JPanel, get the image's Graphics object, draw your random dots for your triangle in the BufferedImage, and then display that image within your JPanel's paintComponent method. You could draw the image at program start up, and then start up the GUI after its complete, or you can start the GUI and draw to the BufferedImage in a background thread, and display it when done, either would be fine (if this is the only thing your GUI should be doing).
For example:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class SierpTest {
public static final int BI_WIDTH = 630;
public static final int BI_HEIGHT = 580;
public static void main(String[] args) {
// do this stuff off the swing event thread
final BufferedImage sierpImg = new BufferedImage(BI_WIDTH, BI_HEIGHT, BufferedImage.TYPE_INT_ARGB);
Graphics g = sierpImg.getGraphics();
// draw triangle with g here
g.dispose(); // always dispose of any Graphics you create yourself
// do this on the Swing event thread
SwingUtilities.invokeLater(() -> {
SierpPanel sierpPanel = new SierpPanel(sierpImg); // pass in image
JFrame frame = new JFrame("Siep Frame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(sierpPanel);
frame.pack(); // size it to the size of the JPanel
frame.setLocationRelativeTo(null); // center it
frame.setVisible(true);
});
}
}
class SierpPanel extends JPanel {
private BufferedImage img = null;
public SierpPanel(BufferedImage img) {
this.img = img;
}
// so that JPanel sizes itself with the image
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet() || img == null) {
return super.getPreferredSize();
}
return new Dimension(img.getWidth(), img.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
g.drawImage(img, 0, 0, this);
}
}
}
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class SierpTest {
public static final int BI_WIDTH = 630;
public static final int BI_HEIGHT = 580;
private static final int MAX_COUNT = 100000;
public static void main(String[] args) {
// do this stuff off the swing event thread
Point point1 = new Point(10, 550);
Point point2 = new Point(300, 30);
Point point3 = new Point(600, 555);
Point current = point1;
Point target = current;
int count = 0;
final BufferedImage sierpImg = new BufferedImage(BI_WIDTH, BI_HEIGHT, BufferedImage.TYPE_INT_ARGB);
Graphics g = sierpImg.getGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, BI_WIDTH, BI_HEIGHT);
g.setColor(Color.BLACK);
while (count <= MAX_COUNT) {
int choice = (int) (Math.random() * 3);
switch (choice) {
case 0:
target = point1;
break;
case 1:
target = point2;
break;
case 2:
target = point3;
break;
default:
System.exit(0);
}
current = midpoint(current, target);
g.drawLine(current.x, current.y, current.x, current.y);
count++;
}
// draw triangle with g here
g.dispose(); // always dispose of any Graphics you create yourself
// do this on the Swing event thread
SwingUtilities.invokeLater(() -> {
SierpPanel sierpPanel = new SierpPanel(sierpImg); // pass in image
JFrame frame = new JFrame("Siep Frame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(sierpPanel);
frame.pack(); // size it to the size of the JPanel
frame.setLocationRelativeTo(null); // center it
frame.setVisible(true);
});
}
public static Point midpoint(Point a, Point b) {
return new Point((Math.round(a.x + b.x) / 2), (Math.round(a.y + b.y) / 2));
}
}
class SierpPanel extends JPanel {
private BufferedImage img = null;
public SierpPanel(BufferedImage img) {
this.img = img;
}
// so that JPanel sizes itself with the image
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet() || img == null) {
return super.getPreferredSize();
}
return new Dimension(img.getWidth(), img.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
g.drawImage(img, 0, 0, this);
}
}
}
Note that if you want to get fancy and draw the triangle as it's being created, and with a delay, then consider using either a Swing Timer or a SwingWorker.
I am trying to make a simple program where an oval follows your mouse cursor, and if you enter "r", "g", or "b" on the keyboard, the oval changes color accordingly.
However, I cannot get my KeyListener to work. Here is my issue. I have a static JPanel, because I need it to be accessible in all functions and methods. However, Java does not let you do this with a static JPanel. I need the JPanel to be static so I can set the color in the keyPressed(KeyEvent e) function.
I understand the basics of Java fairly well, and am grasping some more complicated concepts. Please try to explain if there is any complex code. Thank you!
Here is the code in Drivers.java, the main class.
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class Drivers implements KeyListener
{
// panel.red = panel.red - 3;
// panel.green = panel.green - 3;
// panel.blue = panel.blue - 3;
public static JFrame frame = new JFrame();
public static ShapesPanel panel = new ShapesPanel().addKeyListener(this);
// Notice the error we get with the addKeyListener(this);
public static void main(String[] args)
{
// Creates new pointer info
PointerInfo info;
// Creates a point (for mouse tracking)
Point point;
JLabel label = new JLabel();
panel.add(label);
// Set window size
panel.setPreferredSize(new Dimension(300, 350));
// Set panel inside frame
frame.setContentPane(panel);
// Transparent 16 x 16 pixel cursor image.
BufferedImage cursorImg = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
// Create a new blank cursor.
Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(
cursorImg, new Point(0, 0), "blank cursor");
// Set the blank cursor to the JFrame.
frame.getContentPane().setCursor(blankCursor);
// Compile everything into the frame
frame.pack();
// Set frame to close on red exit button
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Get screen size
Dimension sSize = Toolkit.getDefaultToolkit().getScreenSize();
// Position frame
frame.setLocation(sSize.width / 2 - frame.getWidth(), sSize.height / 2 - frame.getHeight());
// Make frame visible
frame.setVisible(true);
// Set name of frame
frame.setTitle("Graphical User Interface");
// While loop to draw oval
while(true)
{
// Repaint the panel
panel.repaint();
// Get mouse info (for tracking)
info = MouseInfo.getPointerInfo();
// Set mouse location data to point
point = info.getLocation();
// Create variables to store coordinates of oval from mouse point location
int x = (int) point.getX();
int y = (int) point.getY();
// Assign those coordinate variables to oval
panel.x = x;
panel.y = y;
// System.out.println("X: " + x);
// System.out.println("Y: " + y);
// System.out.println("X: " + point.getX());
// System.out.println("Y: " + point.getY());
// Try-catch to sleep, to reduce some memory
try
{
Thread.sleep(10);
}
catch(InterruptedException e)
{
}
}
}
// If key is pressed
public void keyPressed(KeyEvent e)
{
// If key is R, change color and print that key has been pressed
if (e.getKeyCode() == KeyEvent.VK_R)
{
System.out.println("R");
panel.red = 255;
panel.green = 0;
panel.blue = 0;
}
// If key is G, change color and print that key has been pressed
if (e.getKeyCode() == KeyEvent.VK_G)
{
System.out.println("G");
panel.red = 0;
panel.green = 255;
panel.blue = 0;
}
// If key is B, change color and print that key has been pressed
if (e.getKeyCode() == KeyEvent.VK_B)
{
System.out.println("B");
panel.red = 0;
panel.green = 0;
panel.blue = 255;
}
}
// Doesn't do anything.. yet
#Override
public void keyReleased(KeyEvent e)
{
}
#Override
public void keyTyped(KeyEvent e)
{
}
}
And, here is the code in ShapesPanel.java:
import java.awt.*;
import javax.swing.*;
public class ShapesPanel extends JPanel
{
// Create x and y variables, and set as default to zero
public int x = 0, y = 0;
// Create RGB variables, used for changing color
public int red = 0, green = 0, blue = 0;
private static final long serialVersionUID = 1L;
// Create new paintComponent function, using an override
#Override
public void paintComponent(Graphics g)
{
// Create new Graphics2D g2 version
Graphics2D g2 = (Graphics2D) g;
// Reset screen, so there are no trails
g2.clearRect(0, 0, getWidth(), getHeight());
// Set background, currently unfunctional
// g2.setBackground(new Color(235, 150, 30));
// Set color palette, using RGB variables
g2.setColor(new Color(red, green, blue));
// Use color palette to draw oval, using x and y variables
g2.fillOval(x, y, 100, 100);
}
}
I have a static JPanel, because I need it to be accessible in all functions and methods.
This is not a good reason for making a field static.
However, Java does not let you do this with a static JPanel.
This is not true at all. You can add KeyListeners or any other similar construct just as easily to static and non-static fields. Your problem has nothing to do with restrictions on use of static fields. It's all because you're trying to use this in a static context where this doesn't exist.
Note that your compiler error could go away with something as simple as:
public static ShapesPanel panel = new ShapesPanel().addKeyListener(new Drivers());
I need the JPanel to be static so I can set the color in the keyPressed(KeyEvent e) function.
This again is not a good reason for the field to be static. A Swing listener has direct access to the listened to component any time and at all times via the XxxEvent parameter's getSource() method. For instance, if you used a KeyListener, then its method's KeyEvent parameter has a getSource() method which will return the component (here your drawing JPanel) that is being listened to. If you need references to other components or objects, then pass them into the listener via a constructor setter parameter.
Your main problem is that you're trying to use this in a static context, and this doesn't exist within this context.
First thing is to not make your panel field static. You state that you know Java pretty well, but then give a wrong reason for making it static. Instead make it an instance field and pass the instance where needed.
You've got many other issues with this code including:
You've a huge static main method. This method should be much smaller and its job should be to create the key objects of your program, set them running and that's it.
You've a Swing program that has a while (true) and a Thread.sleep(...) within code that on later iterations (when you've structured your program better and have started all Swing code on the event thread) risk being called on the Swing event thread. You will want to get rid of these guys and consider using a Swing Timer instead.
Your paintComponent method does not call the super's method, breaking the Swing painting chain.
You would be much better off not using a KeyListener but instead using Key Bindings.
Your while (true) block isn't even needed. Don't poll for the mouse's position but instead use a MouseListener and/or MouseMotionListener for this.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.*;
import java.util.HashMap;
import java.util.Map;
import javax.swing.*;
public class KeyBindingTest {
// start gui
private static void createAndShowGui() {
KeyBindingPanel mainPanel = new KeyBindingPanel();
JFrame frame = new JFrame("Key Binding Example");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
// start all in a thread safe manner
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class KeyBindingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private static final int PREF_W = 600;
private static final int PREF_H = PREF_W;
private static final Color BACKGROUND = Color.WHITE;
private Color ovalColor = Color.blue;
private int ovalX = PREF_W / 2;
private int ovalY = PREF_H / 2;
private int ovalWidth = 100;
public KeyBindingPanel() {
setName("Key Binding Eg");
setBackground(BACKGROUND);
final Map<Color, Integer> colorKeyMap = new HashMap<>();
colorKeyMap.put(Color.BLUE, KeyEvent.VK_B);
colorKeyMap.put(Color.RED, KeyEvent.VK_R);
colorKeyMap.put(Color.GREEN, KeyEvent.VK_G);
// set Key Bindings
int condition = WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = getInputMap(condition);
ActionMap actionMap = getActionMap();
for (final Color color : colorKeyMap.keySet()) {
int keyCode = colorKeyMap.get(color);
KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, 0);
inputMap.put(keyStroke, keyStroke.toString());
actionMap.put(keyStroke.toString(), new ColorAction(color));
}
MyMouse myMouse = new MyMouse();
addMouseMotionListener(myMouse);
}
public void setOvalColor(Color color) {
ovalColor = color;
repaint();
}
public void setOvalPosition(Point p) {
ovalX = p.x;
ovalY = p.y;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(ovalColor);
int x = ovalX - ovalWidth / 2;
int y = ovalY - ovalWidth / 2;
g2.fillOval(x, y, ovalWidth, ovalWidth);
}
#Override // make panel bigger
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
}
class ColorAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private Color color;
public ColorAction(Color color) {
this.color = color;
}
#Override
public void actionPerformed(ActionEvent e) {
// get reference to bound component
KeyBindingPanel panel = (KeyBindingPanel) e.getSource();
panel.setOvalColor(color);
}
}
class MyMouse extends MouseAdapter {
#Override
public void mouseMoved(MouseEvent e) {
// get reference to listened-to component
KeyBindingPanel panel = (KeyBindingPanel) e.getSource();
panel.setOvalPosition(e.getPoint());
}
}
You may ask, why use a Map<Color, Integer> when creating the Key Bindings?
Doing this allows me to use the for loop to avoid code repetition. Often more concise code is easier to understand and to debug. It also makes it easier to enhance the program later. For instance, if later I want to add Color.CYAN and associate it with the c char, all I have to do is add another entry to my Map:
colorKeyMap.put(Color.CYAN, KeyEvent.VK_C);
and boom it's done. If I needed more than a 1-1 association, then I'd consider using an Enum or separate class to hold the related attributes together.
I am making Pac-Man and I'm having trouble with drawing graphics on a frame, when i draw my point image it looks like a game of snake, i tried putting my drawing methods for background and char both in the render method, but than my point image flickers
What it currently looks like, feel free to ignore the random face it was an inside joke.
Also this is my very first game so any tips on structure, pointers on what I am doing right (if anything) and what I'm doing wrong, and general tips would be extremely helpful!
Also I am aware that i have a couple unused methods
Code:
package game;
import graphics.map;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
public class main extends Canvas implements Runnable{
private static final long serialVersionUID = 1L; //not sure why it wanted me to do this, maybe ask bender, or just google it later
public static boolean running = false;
public static int HEIGHT = 800;
public static int WIDTH = 600;
public static int posX = 50;
public static int posY = 50;
public static final String name = "Pac Man Alpha 1.4";
private static final double speed = 1.2;
public input input;
static BufferedImage background = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage pacman = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage settingsBackground = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage level1 = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage level2 = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage points = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage point = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static JFrame frame;
private input keypress = new input();
private map map;
private static boolean charLoaded = false;
public static boolean MAIN_MENU = true;
public static boolean GAME = false;
public static boolean level1test = true;
public static boolean level2test = false;
public static boolean level3test = false;
public static boolean level4test = false;
static boolean drawn = false;
public static boolean key_down;
public static boolean key_up;
public static boolean key_right;
public static boolean key_left;
//private Screen screen;
JButton startButton = new JButton("Start"); //Start
JButton settingsButton = new JButton("Settings"); //Settings
JButton exitButton = new JButton("Exit"); //Exit
public main()
{
setMinimumSize(new Dimension(WIDTH , HEIGHT ));
setMaximumSize(new Dimension(WIDTH , HEIGHT )); // keeps the canvas same size
setPreferredSize(new Dimension(WIDTH, HEIGHT));
frame = new JFrame(name);
if(MAIN_MENU == true && GAME == false){
buttons(frame.getContentPane());
}
frame.setLayout(new BorderLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // ends program on
// close
frame.addKeyListener(new input() );
frame.add(this, BorderLayout.CENTER);
frame.pack(); // keeps size correct
frame.setResizable(false);
frame.setVisible(true);
this.addKeyListener(keypress);
}
public static void main(String[] args)
{
try {
background = ImageIO.read(new File("res\\Background.png"));
pacman = ImageIO.read(new File("res\\pacmansprites.png"));
settingsBackground = ImageIO.read(new File("res\\Background.png"));
level1 = ImageIO.read(new File("res\\level1.png"));
//level2 = ImageIO.read(new File("res\\level2.png"));
point = ImageIO.read(new File("res\\Points for pacman.png"));
} catch (IOException e) {
}
running = true;
new main().start();
}
public void run()
{
long lastTime = System.nanoTime();
double nsPerTick = 1000000000 / 60D;
long lastTimer = System.currentTimeMillis();
double delta = 0;
int frames = 0;
int ticks = 0;
while (running == true) {
long now = System.nanoTime();
delta += (now - lastTime) / nsPerTick;
lastTime = now;
boolean render = false;
while (delta >= 1) {
ticks++;
tick();
delta -= 1;
render = true;
}
try {
Thread.sleep(3); //keep the Frames from going to high
} catch (InterruptedException e) {
e.printStackTrace();
}
if(render == true){
frames++;
render();
}
if (System.currentTimeMillis() - lastTimer >= 1000) {
lastTimer +=1000;
//System.out.println("Frames: " + frames + " Ticks: " + ticks);
frames = 0;
ticks = 0;
}
}
}
public synchronized void start()
{
new Thread(this).start();
run();
}
public synchronized void stop()
{
running = false;
}
public void tick()
{
if (key_up) posY -= speed / 2;
if (key_down) posY += speed;
if (key_left) posX -= speed / 2;
if (key_right) posX += speed;
}
public void render()
{
drawn = false;
if(MAIN_MENU == false && GAME == true)
{
drawMap();
drawChar();
}
else if(MAIN_MENU == false && GAME == false) {
Graphics g = getGraphics();
{
g.drawImage(settingsBackground,0,0,getWidth(),getHeight(),null);
g.dispose();
}
} else {
Graphics g = getGraphics();{
g.drawImage(background,0,0,getWidth(), getHeight(),null);
g.dispose(); //kill it
}
}
}
public void drawMap(){
if(level1test == true){
Graphics g = getGraphics();
{
g.drawImage(level1,0,0,getWidth(),getHeight(),null);
g.dispose();
}
}
if(level2test == true && drawn == false){
Graphics g = getGraphics();
{
g.drawImage(level2,0,0,getWidth(),getHeight(),null);
}
g.dispose();
}
drawn = true;
}
public void drawChar(){
//drawMap();
Graphics g = getGraphics();{
g.drawImage(point,posX,posY,20, 20,null);
g.dispose();
revalidate();
}
}
public void begin() {
if (key_up) System.out.println("up");
if (key_down) System.out.println("down");
if (key_left) System.out.println("left");
if (key_right) System.out.println("right");
}
public void loadMap(){
if(!drawn && level1test){
}else if(!drawn && level2test){
//draw 2nd map here
}else if(!drawn && level3test){
//draw 3rd map here
}
}
public void buttons(Container pane)
{
pane.setLayout(null);
startButton.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent ae) {
MAIN_MENU = false;
GAME = true;
frame.remove(startButton);
frame.remove(settingsButton);
frame.remove(exitButton);
frame.revalidate();
drawMap();
System.out.println("Start Button Clicked");
}
} );
settingsButton.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent ae) {
MAIN_MENU = false;
GAME = false;
frame.remove(startButton);
frame.remove(settingsButton);
frame.remove(exitButton);
frame.revalidate();
frame.repaint();
System.out.println("Settings Button Clicked");
}
} );
exitButton.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent ae) {
System.out.println("Exit Button Clicked");
System.exit(0);
}
} );
pane.add(startButton);
pane.add(settingsButton);
pane.add(exitButton);
Insets insets = pane.getInsets();
Dimension size = startButton.getPreferredSize();
startButton.setBackground(new Color(0, 0, 0));
startButton.setForeground(Color.CYAN);
startButton.setFocusPainted(false);
startButton.setFont(new Font("Calabri", Font.BOLD, 16));
settingsButton.setBackground(new Color(0, 0, 0));
settingsButton.setForeground(Color.RED);
settingsButton.setFocusPainted(false);
settingsButton.setFont(new Font("Calabri", Font.BOLD, 16));
exitButton.setBackground(new Color(0, 0, 0));
exitButton.setForeground(Color.YELLOW);
exitButton.setFocusPainted(false);
exitButton.setFont(new Font("Calabri", Font.BOLD, 16));
startButton.setBounds((WIDTH - 125) + insets.left, 10 + insets.top,
size.width + 50, size.height + 10);
settingsButton.setBounds((WIDTH - 125) + insets.left, 55 + insets.top,
size.width + 50, size.height + 10);
exitButton.setBounds((WIDTH - 125) + insets.left, 100 + insets.top,
size.width + 50, size.height + 10);
}
}
getGraphics is not how custom painting is done. You should, in your case, override the paint method, and make sure you call super.paint before doing any custom painting.
getGraphics returns the Graphics context last used to paint the component, which could be discarded on the next paint cycle, be null or no longer used by the component
Remember, painting uses the "painters canvas" approach, that is, just like painting in a physical canvas, when you paint into it, you paint over what was previously there, but not erasing it.
Now, if you override paint, you will find that you will have a flickering problem. This is because Canvas
is not double buffered
To solve this, you should consider user a BufferStrategy, which allows you to not only generate multiple buffers to paint to, but also to take control of the paint process itself
Just don't forget to clear each buffer before you paint to it...
Double buffering is the trick that allows you to have flicker-free animation. Basically you have two representations of your canvas, one that's currently being displayed and one that you can draw on. If you're finished with drawing, you copy the draw-canvas over the display-canvas. Depending on system and hardware there are more elegant ways where you can just tell the hardware to switch canvases (page flipping).
Without double buffering or a similar techniques, it is almost impossible to have flicker-free animation.
With double buffering you can afford to draw the background and then the foreground sprites. It is possibly more efficient to draw only those parts of the background that have been destroyed by the foreground sprites (there are various techniques for that as well, including of taking a snapshot image of the affected areas before you paint the sprites).
You can find a simple example for Java double buffering here. Java's BufferStrategy is a more complex solution that can use hardware features for page flipping.
I think the problem is that you only draw onto the image background, never erasing the old drawing from your image. You will need to clear the area and then start drawing in order to get your desired results.
I have never attempted to make a game but when I do simple animations I usually will do them on a JFrame or JPanel. With a JFrame you can Override the paint() method and with a JPanel, the paintComponent() method. It helps to keep everything that I'm drawing centralized, which makes it much easier for me to modify my code. When you call the respective super method in your overridden method, it will start you off with a clean slate, meaning you will have to paint the (image) background and your characters all over again. Calling the super method is also necessary to paint that component's children if you decide to add anything onto the JFrame/JPanel.
If you chose to use one of the above then I would recommend a JPanel due to it offering double buffering, which will help make your animations look smooth/fluid. Also, do not forget to call repaint();.
Here is a quick example, which can be used to duplicate your issue if you comment out super.paintComponent(g);.
*Note that I am extending and using a JPanel for this example.
Code:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Trial extends JPanel{
public static void main(String[] args)
{
new Trial();
}
int x = 5; // will represent the x axis position for our crude animation.
javax.swing.Timer timer = new javax.swing.Timer( 500, new ActionListener(){
// Timer used to control the animation and
// the listener is used to update x position and tell it to paint every .5 seconds.
#Override
public void actionPerformed(ActionEvent e) {
x += 5;
if ( x > 250)
timer.stop();
repaint(); // will call the paintComponent method.
}
});
Trial()
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.add(this);
frame.setSize(300, 200);
frame.setVisible(true);
timer.start();
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g); // calling the super paintComponent method to paint children. can comment it
// out to duplicate your error, which happens when the frame isn't "refreshed".
Graphics2D g2d = (Graphics2D) g.create(); // creating a copy of the graphics object. // I do this to not alter the original
// Good practice and also Graphics2D
// offers a bit more to work with.
g2d.drawString("Graphics", x, 60); // painting a string.
g2d.drawRect(x, 80, 10, 10); // painting a rectangle.
}
}
Edit:
If you have a bunch of stuff to do and don't want to add it all into your paintComponent(); method you could create a method for various things and call them from inside your Overriden paint method, you could also pass them your graphics object. It will help you keep things relatively simple.
I am creating a GUI, and am fairly new to swing and awt. I am trying to create a gui that, upon launch, sets the background to an image, then uses a method to create a slideshow of sorts. I have attempted it, and I am not attached to the code so I am able to take both revisions and/or whole new concepts.
EDIT(9/15/13): I am having trouble with the slideshow, I cant seem to get it to work.
Here is my current code.
public class MainFrame extends JFrame{
JLabel backgroundL = null;
private JLabel bakckgroundL;
BufferedImage backimg;
Boolean busy;
double width;
double height;
public MainFrame() throws IOException {
initMainframe();
}
public void initMainframe() throws IOException {
//misc setup code, loads a default jpg as background
setTitle("Pemin's Aura");
busy = true;
String backgroundDir = "resources/frame/background.jpg";
backimg = ImageIO.read(new File(backgroundDir));
backgroundL = new JLabel(new ImageIcon(backimg));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
refreshframe();
setVisible(true);
busy = false;
}
public void adjSize() { // the attempted start of a fullscreen mode
GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().setFullScreenWindow(this);
width = this.getWidth();
height = this.getHeight();
setVisible(true);
}
public void setmastheadText() {//unfinished code
busy = true;
busy = false;
}
public void setbackground() {
add(backgroundL);
}
public void refreshframe() { //should refresh image?
setSize(2049, 2049);
setSize(2048, 2048);
}
public void loadingscreen() throws IOException, InterruptedException {
//this is the code in question that is faulty.
if (busy == false) {
busy = true;
String backgroundDir1 = "resources/frame/background.jpg";
String backgroundDir2 = "resources/frame/scr1.jpg";
String backgroundDir3 = "resources/frame/scr2.jpg";
BufferedImage backimg1 = ImageIO.read(new File(backgroundDir1));
BufferedImage backimg2 = ImageIO.read(new File(backgroundDir2));
BufferedImage backimg3 = ImageIO.read(new File(backgroundDir3));
backgroundL = new JLabel(new ImageIcon(backimg1));
Thread.sleep(2000);
setbackground();
setVisible(true);
backgroundL = new JLabel(new ImageIcon(backimg2));
setbackground();
setVisible(true);
Thread.sleep(2000);
bakckgroundL = new JLabel(new ImageIcon(backimg3));
setbackground();
setVisible(true);
if(backimg != null) {
backgroundL = new JLabel(new ImageIcon(backimg));;
}
}
busy = false;
}//end of loading screen
See ImageViewer for a working example of displaying images using a Swing based Timer.
See also How to use Swing Timers.
And while I'm here, another (prettier) example of animating an image. It uses this Mercator map of land masses. The image can be tiled horizontally, and therefore be scrolled left/right as needed.
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import javax.swing.*;
import java.net.URL;
import javax.imageio.ImageIO;
public class WorldView {
public static void main(String[] args) throws Exception {
URL url = new URL("http://i.stack.imgur.com/P59NF.png");
final BufferedImage bi = ImageIO.read(url);
Runnable r = new Runnable() {
#Override
public void run() {
int width = 640;
int height = 316;
Graphics2D g = bi.createGraphics();
float[] floats = new float[]{0f, .4f, .55f, 1f};
Color[] colors = new Color[]{
new Color(20, 20, 20, 0),
new Color(0, 10, 20, 41),
new Color(0, 10, 20, 207),
new Color(0, 10, 20, 230),};
final LinearGradientPaint gp2 = new LinearGradientPaint(
new Point2D.Double(320f, 0f),
new Point2D.Double(0f, 0f),
floats,
colors,
MultipleGradientPaint.CycleMethod.REFLECT);
final BufferedImage canvas = new BufferedImage(
bi.getWidth(), bi.getHeight() + 60,
BufferedImage.TYPE_INT_RGB);
final JLabel animationLabel = new JLabel(new ImageIcon(canvas));
ActionListener animator = new ActionListener() {
int x = 0;
int y = 30;
#Override
public void actionPerformed(ActionEvent e) {
Graphics2D g = canvas.createGraphics();
g.setColor(new Color(55, 75, 125));
g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
int offset = (x % bi.getWidth());
g.drawImage(bi, offset, y, null);
g.drawImage(bi, offset - bi.getWidth(), y, null);
g.setPaint(gp2);
g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
g.dispose();
animationLabel.repaint();
x++;
}
};
Timer timer = new Timer(40, animator);
timer.start();
JOptionPane.showMessageDialog(null, animationLabel);
timer.stop();
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}
Here is a version of that image with the equator added (it is 44 pixels 'south' of the center of the image).
You're calling Thread.sleep(...) and likely on the EDT or Swing event thread (full name is the Event Dispatch Thread). This thread is responsible for all Swing painting/drawing and user interactions, and so sleeping it will only serve to freeze your entire GUI. Instead you should use a Swing Timer to allow you to swap a JLabel's ImageIcon.
So, briefly:
Don't call Thread.sleep(...) on the Swing event thread (Event Dispatch Thread or EDT).
Do use a Swing Timer to do your repeating delayed actions.
Don't make and add many JLabels. Just make and add one.
Do Swap the ImageIcon that the JLabel displays by calling setIcon(...) on the label.
Better (cleaner) to write if (busy == false) { as if (!busy) {
e.g.,
ImageIcon[] icons = {...}; // filled up with your ImageIcons
if (!busy) {
int timerDelay = 2000;
new Timer(timerDelay, new ActionListener() {
private int i = 0;
public void actionPerfomed(ActionEvent e) {
myLabel.setIcon(icons(i));
i++;
if (i == icons.length) {
((Timer)e.getSource).stop();
}
};
}).start();
}
We know that there are a class named RadialGradientPaint in Java and we can use it to have a gradient painting for circle.
But I want to have an oval (ellipse) GradientPaint. How to implement oval GradientPaint?
Use an AffineTransform when drawing the RadialGradientPaint. This would require a scale instance of the transform. It might end up looking something like this:
import java.awt.*;
import java.awt.MultipleGradientPaint.CycleMethod;
import java.awt.geom.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
public class OvalGradientPaint {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
// the GUI as seen by the user (without frame)
JPanel gui = new JPanel(new BorderLayout());
gui.setBorder(new EmptyBorder(2, 3, 2, 3));
gui.add(new OvalGradientPaintSurface());
gui.setBackground(Color.WHITE);
JFrame f = new JFrame("Oval Gradient Paint");
f.add(gui);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// See http://stackoverflow.com/a/7143398/418556 for demo.
f.setLocationByPlatform(true);
// ensures the frame is the minimum size it needs to be
// in order display the components within it
f.pack();
// should be done last, to avoid flickering, moving,
// resizing artifacts.
f.setVisible(true);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
}
class OvalGradientPaintSurface extends JPanel {
public int yScale = 150;
public int increment = 1;
RadialGradientPaint paint;
AffineTransform moveToOrigin;
OvalGradientPaintSurface() {
Point2D center = new Point2D.Float(100, 100);
float radius = 90;
float[] dist = {0.05f, .95f};
Color[] colors = {Color.RED, Color.MAGENTA.darker()};
paint = new RadialGradientPaint(center, radius, dist, colors,CycleMethod.REFLECT);
moveToOrigin = AffineTransform.
getTranslateInstance(-100d, -100d);
ActionListener listener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
if (increment < 0) {
increment = (yScale < 50 ? -increment : increment);
} else {
increment = (yScale > 150 ? -increment : increment);
}
yScale += increment;
repaint();
}
};
Timer t = new Timer(15, listener);
t.start();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
AffineTransform moveToCenter = AffineTransform.
getTranslateInstance(getWidth()/2d, getHeight()/2d);
g2.setPaint(paint);
double y = yScale/100d;
double x = 1/y;
AffineTransform at = AffineTransform.getScaleInstance(x, y);
// We need to move it to the origin, scale, and move back.
// Counterintutitively perhaps, we concatentate 'in reverse'.
moveToCenter.concatenate(at);
moveToCenter.concatenate(moveToOrigin);
g2.setTransform(moveToCenter);
// fudge factor of 3 here, to ensure the scaling of the transform
// does not leave edges unpainted.
g2.fillRect(-getWidth(), -getHeight(), getWidth()*3, getHeight()*3);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 200);
}
}
Original image: The original static (boring) 'screen shot' of the app.
RadialGradientPaint provides two ways to paint itself as an ellipse instead of a circle:
Upon construction, you can specify a transform for the gradient. For example, if you provide the following transform: AffineTransform.getScaleInstance(0.5, 1), your gradient will be an upright oval (the x dimension will be half that of the y dimension).
Or, you can use the constructor that requires a Rectangle2D be provided. An appropriate transform will be created to make the gradient ellipse bounds match that of the provided rectangle. I found the class documentation helpful: RadialGradientPaint API. In particular, see the documentation for this constructor.