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.
Related
I am trying to create spinning image Animation but something seems to be not working in the code. I am rotating image at various angles and drawing it but at the end I only end up single rotated image than animation. Is this possible to do in Java or do I need switch to C# Unity where I found multiple examples on doing so nothing so far in Java. I am new to Swing so I would really appreciate simplified answer.
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.util.concurrent.TimeUnit;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Rotate extends JPanel {
public static void main(String[] args) {
new Rotate().go();
}
public void go() {
JFrame frame = new JFrame("Rotate");
JButton b = new JButton("click");
MyDrawPanel p = new MyDrawPanel();
frame.add(p);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1000, 1000);
frame.setVisible(true);
for(int i = 0; i < 300; i++) {
try {
TimeUnit.MILLISECONDS.sleep(50);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
repaint();
}
}
class MyDrawPanel extends JPanel{
Image image = new ImageIcon(
getClass()
.getResource("wheel.png"))
.getImage();
public void animateCircle(Graphics2D g2d ) {
//g2d = (Graphics2D) g2d.create();
g2d.rotate(Math.toRadians(25), 250, 250);
g2d.drawImage(image, 0, 0, 500, 500, this);
}
#Override
protected void paintComponent(Graphics g) {
//super.paintComponent(g);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
Graphics2D g2d = (Graphics2D) g;
animateCircle(g2d);
}
}
}
I tried moving for loop in the paintComponent() method but it didn't help either.
Here
public void animateCircle(Graphics2D g2d ) {
//g2d = (Graphics2D) g2d.create();
g2d.rotate(Math.toRadians(25), 250, 250);
g2d.drawImage(image, 0, 0, 500, 500, this);
}
You rotation is fixed, so you aren't seeing your image spinning
By changing your value in Math.toRadians(...), you can make it appear to spin
This
for(int i = 0; i < 300; i++) {
rotationStep ++;
try {
TimeUnit.MILLISECONDS.sleep(50);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
repaint();
}
}
Is the wrong way to do animation in swing. The right way is to use a javax.swing.Timer
public class MyTimer implements ActionListener {
int rotationStep = 0;
public void actionPerformed(ActionEvent ae) {
// for(int i = 0; i < 300; i++) {
rotationStep ++;
repaint();
}
}
You can do it like this. I reorganized some of your code which you can of course change at your leisure. Most of the critical elements are documented within the code. Major changes include:
eliminating magic numbers - allows alterations to happen in one place
using Rendering hints to eliminate rotating images.
overridding getPreferredSize() in the panel class
computing panel size to allow full rotation within panel.
using a swing timer to control repaint and angle updates
public class Rotate extends JPanel {
BufferedImage image = null;
public static void main(String[] args) {
new Rotate().go();
}
public void go() {
JFrame frame = new JFrame("Rotate");
JButton b = new JButton("click");
File file = new File("wheel.png");
try {
image = ImageIO
.read(new FileInputStream(new File("H:/Bench.jpg")));
} catch (IOException ioe) {
ioe.printStackTrace();
}
// invoke an instance of the panel with the image
MyDrawPanel p = new MyDrawPanel();
frame.add(p);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
// center the frame on the screen
frame.setLocationRelativeTo(null);
frame.setVisible(true);
// start the animation
p.animateCircle();
}
class MyDrawPanel extends JPanel {
double angle = 0;
//increment of the angle of rotation
double inc = Math.toRadians(.1);
int imageWidth;
int imageHeight;
int panelWidth;
int panelHeight;
double ctrX;
double ctrY;
int startX;
int startY;
#Override
public Dimension getPreferredSize() {
return new Dimension(panelWidth, panelHeight);
}
public MyDrawPanel() {
double imageWidth = image.getWidth();
double imageHeight = image.getHeight();
setBackground(Color.WHITE);
// compute panel size to allow full rotation within by computing
//the image's diagonal length
panelWidth = (int)Math.hypot(imageWidth, imageHeight);
panelHeight = panelWidth;
//target location for writing object (upper left corner)
startX = (int)(panelWidth-imageWidth)/2;
startY = (int)(panelHeight-imageHeight)/2;
// center of rotation
ctrX = panelWidth/2;
ctrY = panelHeight/2;
}
// This starts the animation using a swing timer to update the angle and repaint the image
public void animateCircle() {
Timer timer = new Timer(0, (ae)-> {
angle += inc; repaint();
});
timer.setDelay(10);
timer.start();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// setting the rendering hints allows smooth rotation of images with minimal distortion
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
// rotate the graphics context about the center
g2d.rotate(angle, ctrX, ctrY);
// draw the image. Top left at startX and startY
g2d.drawImage(image, startX, startY, this);
}
}
}
Note: If the timer is set to a low value (faster rotation) and the image is too large, the image may not finish the current rotation cycle before the timer re-fires.
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());
}
}
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 hope my first post isn't too basic for y'all.
I'm trying to do some per-pixel drawing on a JCanvas using a BufferedImage (using setRGB()). I thought I would test all was working with a single diagonal line from the origin to the width/height of the JCanvas. The trouble is that I get a strange offset in the x axis that I can't seem to fix!
Here's a link to the problem:
http://i811.photobucket.com/albums/zz31/bohngy/problemMandel_zpsae20713a.jpeg
Here's the code:
public class Mandelbrot extends JFrame {
private BufferedImage I;
public Mandelbrot() {
super("Mandelbrot Set");
setSize(600, 600);
setResizable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE);
I = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < getHeight(); x++) {
for (int y = 0; y < getWidth(); y++) {
I.setRGB(x, x, Color.GREEN.getRGB());
}
}
}
#Override
public void paint(Graphics g) {
g.drawImage(I, 0, 0, this);
}
public static void main(String[] args) {
new Mandelbrot().setVisible(true);
}
}
General issues
Don't extend JFrame (particularly, don't override the paint method of JFrame). Instead, do the painting in the paintComponent method a class that extends JPanel
Create the GUI from the Event Dispatch Thread
The main reason for the unexpected result is that you are creating an image that has the size of the frame - but the frame also has a title bar and a border, and these are "covering" parts of the image. The size of the area that is actually available for painting is smaller than the total frame size. Additionally, the getWidth() and getHeight() methods may return garbage as long as the frame is not yet visible on the screen.
One approach considering all this could look like in this snippet:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Mandelbrot
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new GridLayout(1, 1));
BufferedImage image = createImage(500, 500);
ImagePanel imagePanel = new ImagePanel(image);
frame.getContentPane().add(imagePanel);
frame.pack();
frame.setVisible(true);
}
private static BufferedImage createImage(int w, int h)
{
BufferedImage image = new BufferedImage(w, h,
BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < w; x++)
{
image.setRGB(x, x, Color.GREEN.getRGB());
}
return image;
}
static class ImagePanel extends JPanel
{
private final BufferedImage image;
ImagePanel(BufferedImage image)
{
this.image = image;
}
#Override
public Dimension getPreferredSize()
{
if (super.isPreferredSizeSet())
{
return super.getPreferredSize();
}
return new Dimension(image.getWidth(), image.getHeight());
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
}
}
}
All BufferedImage objects have an upper left corner coordinate of (0, 0). Any Raster used to construct a BufferedImage must therefore have minX=0 and minY=0.
Therein lies your problem.
JavaDoc for BufferedImage
Edit:
Also remove this from your loop:
for (int y = 0; y < getWidth(); y++) {
I.setRGB(x, x, Color.GREEN.getRGB());
}
Let say I have an image. I put the image in a JPanel and add the JPanel inside a JFrame. The image moves from the bottom part of the frame to top of the frame while its size is also decreased using AffineTransform. The variable changes using thread.
So here's the following code:
public class SplashScreen extends JFrame{
Image img1;
int w=1,h=1;
int x=0,y=0;
Thread th = new Thread(new Runnable() {
#Override
public void run() {
while(true){
w-=0.05;
h-=0.05;
y-=2;
x+=1;
if(y==-100){
new MainMenu_BlueJay().setVisible(true);
dispose();
}
repaint();
try {
Thread.sleep(10);
} catch (InterruptedException ex) {
Logger.getLogger(SplashScreen.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
});
JPanel p = new JPanel();
public SplashScreen(){
setLayout(new BorderLayout());
p.setPreferredSize(new Dimension(900,600));
p.setBackground(Color.black);
p.setLayout(new GridLayout());
add(p);
setTitle("BlueJay");
setSize(900,600);
getContentPane().setBackground(Color.black);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
th.start();
requestFocus();
setFocusable(true);
}
#Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
img1 = new ImageIcon("images/Intro/BJ Production 2013.png").getImage();
AffineTransform at = new AffineTransform();
at.scale(w,h);
g2d.setTransform(at);
g2d.drawImage(img1, x, y, p);
}
public static void main(String[] args) {
new SplashScreen();
}
However what I get from code above is only black screen. What's the matter? Anyway, If I don't use the AffineTransform function (just move it from bottom to top), the image is shown and moves BUT the frame is flickered (blinking) rapidly.
Any idea to solve this problem so I could move the image while decrease its size and also solve the flickered/rapid blinking frame?
You should not override the paint method of the JFrame. If you want to paint anything, you should paint this in a class that extends JPanel, where you override the paintComponent method.
You should NOT load the image in the painting method. This is horribly inefficient. You should load the image only ONCE, probably in a constructor.
You should not call Graphics2D#setTransform(). Have a look at the JavaDoc at http://docs.oracle.com/javase/7/docs/api/java/awt/Graphics2D.html#setTransform%28java.awt.geom.AffineTransform%29 , which explicitly states
WARNING: This method should never be used to apply a new coordinate transform on top of an existing transform
You should think about your w and h values. Should they be the size of the image that is painted, or used as scaling factors for the image? Setting them as the scaling factors of an AffineTransform will NOT have the effect of scaling an image to the desired size. At the moment, they are declared as int values, so something like w-=0.05 does not really make sense anyhow.
You should have a clear idea of how you are going to describe the animation that the image should perform.
One could possibly summarize this:
You should not write code and assume it is "correct" only because there are no compilation errors ;-)
However, the following snippet may be a first step towards your goal:
package stackoverflow;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SplashScreen extends JFrame
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
new SplashScreen();
}
});
}
private PaintPanel paintPanel;
public SplashScreen()
{
setTitle("BlueJay");
setDefaultCloseOperation(EXIT_ON_CLOSE);
getContentPane().setBackground(Color.BLACK);
getContentPane().setLayout(new BorderLayout());
paintPanel = new PaintPanel();
getContentPane().add(paintPanel, BorderLayout.CENTER);
setSize(900,600);
setLocationRelativeTo(null);
setFocusable(true);
requestFocus();
setVisible(true);
startAnimation();
}
void startAnimation()
{
Thread thread = new Thread(new Runnable()
{
int x = 100;
int y = 100;
int w = 0;
int h = 0;
#Override
public void run()
{
try
{
Thread.sleep(500);
}
catch (InterruptedException ex)
{
Thread.currentThread().interrupt();
return;
}
while (true)
{
if (y == 200)
{
// new MainMenu_BlueJay().setVisible(true);
dispose();
}
x += 2;
y += 1;
w += 1;
h += 1;
paintPanel.setImageCoordinates(x, y, w, h);
repaint();
try
{
Thread.sleep(10);
}
catch (InterruptedException ex)
{
Thread.currentThread().interrupt();
return;
}
}
}
});
thread.start();
}
}
class PaintPanel extends JPanel
{
private final Image image;
private int imageX, imageY;
private int imageW, imageH;
PaintPanel()
{
image = new ImageIcon("Clipboard02.jpg").getImage();
imageX = 0;
imageY = 0;
imageW = 0;
imageH = 0;
}
void setImageCoordinates(int imageX, int imageY, int imageW, int imageH)
{
this.imageX = imageX;
this.imageY = imageY;
this.imageW = imageW;
this.imageH = imageH;
repaint();
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D) gr;
float scalingX = (float) imageW / image.getWidth(null);
float scalingY = (float) imageH / image.getHeight(null);
g.scale(scalingX, scalingY);
int ix = (int)(imageX / scalingX);
int iy = (int)(imageY / scalingY);
g.drawImage(image, ix, iy, null);
}
}
Don't paint on top-level containers like JFrame. Instead use a JPanel and override its paintComponent method and call super.paintComponent
No need to call Thread.sleep(). Instead Use a javax.swing.Timer.
Run your program from the EDT
Don't create a new ImageIcon in the paint method. It will create new ImageIcon object every time repaint() is called. Instead, instantiate it in the constructor.
Here's a refactor of the code.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class SplashScreen extends JFrame {
Image img1;
int w = 900, h = 600;
int x = 0, y = 0;
public SplashScreen() {
setLayout(new BorderLayout());
add(new MyPanel());
setTitle("BlueJay");
setSize(900, 600);
getContentPane().setBackground(Color.black);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
requestFocus();
setFocusable(true);
}
private class MyPanel extends JPanel {
public MyPanel() {
img1 = new ImageIcon(SplashScreen.class.getResource("/resources/stackoverflow5.png")).getImage();
setBackground(Color.black);
setLayout(new GridLayout());
Timer timer = new Timer(20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
w -= 5;
h -= 5;
y -= 2;
x += 1;
if (y == -250) {
new MainMenu_BlueJay().setVisible(true);
dispose();
}
repaint();
}
});
timer.start();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
//AffineTransform at = new AffineTransform();
// at.scale(w, h);
// g2d.setTransform(at);
g2d.drawImage(img1, x, y, w, h, this);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(900, 600);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new SplashScreen();
}
});
}
}
I'm not too familiar with Graphics2D so I commented out the AffirmTransformation stuff, but fixed your other problems.