Related
I'm trying to make a racing game with the top down view on a static player in the middle of the screen, so instead of moving the player through the map, the map would move around the player. Since it's a racing game, I wanted it to also be somewhat similar to a car, but I've been having trouble with rotating the map around the player and having that work with translations.
I've tried keeping track of the center by adding or subtracting from it, which is what I did for the translations, but it doesn't work with the rotate method. The rotate function wouldn't rotate about the player and instead would rotate the player around some other point, and the translations would snap to a different location from the rotations. I'm sure my approach is flawed, and I have read about layers and such, but I'm not sure what I can do with them or how to use them. Also, any recommendations as to how to use java graphics in general would be greatly appreciated!
This is what I have in my main:
import javax.swing.JFrame;
import java.awt.BorderLayout;
public class game
{
public static void main(String []args)
{
JFrame frame = new JFrame();
final int FRAME_WIDTH = 1000;
final int FRAME_HEIGHT = 600;
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final Map b = new Map();
frame.add(b,BorderLayout.CENTER);
frame.setVisible(true);
b.startAnimation();
}
}
And this is the class that handles all the graphics
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JComponent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Map extends JComponent implements Runnable, KeyListener
{
private int speed = 5;
private int xcenter = 500; // starts on player
private int ycenter = 300;
private double angle = 0.0;
private int[] xcords = {xcenter+10, xcenter, xcenter+20};
private int[] ycords = {ycenter-10, ycenter+20, ycenter+20};
private boolean moveNorth = false;
private boolean moveEast = false;
private boolean moveSouth = false;
private boolean moveWest = false;
public Map()
{
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
}
public void startAnimation()
{
Thread t = new Thread(this);
t.start();
}
public void paintComponent(Graphics g)
{
g.fillPolygon(xcords, ycords, 3);
// move screen
if(moveNorth)
{
ycenter += speed;
g.translate(xcenter, ycenter);
}
else if(moveEast)
{
angle += ((1 * Math.PI/180) % (2 * Math.PI));
((Graphics2D) g).rotate(angle, 0, 0);
}
else if(moveSouth)
{
System.out.println(xcenter + ", " + ycenter);
ycenter -= speed;
((Graphics2D) g).rotate(angle, 0, 0);
g.translate(xcenter, ycenter);
}
else if(moveWest)
{
angle -= Math.toRadians(1) % (2 * Math.PI);
((Graphics2D) g).rotate(angle, 0, 0);
}
for(int i = -10; i < 21; i++)
{
g.drawLine(i * 50, -1000, i * 50, 1000);
g.drawLine(-1000, i * 50, 1000, i * 50);
}
g.drawOval(0, 0, 35, 35);
}
public void run()
{
while (true)
{
try
{
if(moveNorth || moveEast || moveSouth || moveWest)
{
repaint();
}
Thread.sleep(10);
}
catch (InterruptedException e)
{
}
}
}
public void keyPressed(KeyEvent e)
{
if(e.getExtendedKeyCode() == 68) // d
{
moveEast = true;
}
else if(e.getExtendedKeyCode() == 87) // w
{
moveNorth = true;
}
else if(e.getExtendedKeyCode() == 65) // a
{
moveWest = true;
}
else if(e.getExtendedKeyCode() == 83) // s
{
moveSouth = true;
}
}
public void keyReleased(KeyEvent e)
{
moveNorth = false;
moveEast = false;
moveSouth = false;
moveWest = false;
}
public void keyTyped(KeyEvent e)
{
}
}
You have to keep in mind that transformations are compounding, so if you rotate the Graphics context by 45 degrees, everything painted after it will be rotated 45 degrees (around the point of rotation), if you rotate it again by 45 degrees, everything painted after it will be rotated a total of 90 degrees.
If you want to paint additional content after a transformation, then you either need to undo the transformation, or, preferably, take a snapshot of the Graphics context and dispose of it (the snapshot) when you're done.
You also need to beware of the point of rotation, Graphics2D#rotate(double) will rotate the Graphics around the point of origin (ie 0x0), which may not be desirable. You can change this by either changing the origin point (ie translate) or using Graphics2D#rotate(double, double, double), which allows you to define the point of rotation.
For example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class TestPane extends JPanel {
enum Direction {
LEFT, RIGHT;
}
protected enum InputAction {
PRESSED_LEFT, PRESSED_RIGHT, RELEASED_LEFT, RELEASED_RIGHT
}
private BufferedImage car;
private BufferedImage road;
private Set<Direction> directions = new TreeSet<>();
private double directionOfRotation = 0;
public TestPane() throws IOException {
car = ImageIO.read(getClass().getResource("/images/Car.png"));
road = ImageIO.read(getClass().getResource("/images/Road.png"));
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), InputAction.PRESSED_LEFT);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), InputAction.RELEASED_LEFT);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), InputAction.PRESSED_RIGHT);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), InputAction.RELEASED_RIGHT);
am.put(InputAction.PRESSED_LEFT, new DirectionAction(Direction.LEFT, true));
am.put(InputAction.RELEASED_LEFT, new DirectionAction(Direction.LEFT, false));
am.put(InputAction.PRESSED_RIGHT, new DirectionAction(Direction.RIGHT, true));
am.put(InputAction.RELEASED_RIGHT, new DirectionAction(Direction.RIGHT, false));
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (directions.contains(Direction.RIGHT)) {
directionOfRotation += 1;
} else if (directions.contains(Direction.LEFT)) {
directionOfRotation -= 1;
}
// No doughnuts for you :P
if (directionOfRotation > 180) {
directionOfRotation = 180;
} else if (directionOfRotation < -180) {
directionOfRotation = -180;
}
repaint();
}
});
timer.start();
}
protected void setDirectionActive(Direction direction, boolean active) {
if (active) {
directions.add(direction);
} else {
directions.remove(direction);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(213, 216);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
drawRoadSurface(g2d);
drawCar(g2d);
g2d.dispose();
}
protected void drawCar(Graphics2D g2d) {
g2d = (Graphics2D) g2d.create();
int x = (getWidth() - car.getWidth()) / 2;
int y = (getHeight() - car.getHeight()) / 2;
g2d.drawImage(car, x, y, this);
g2d.dispose();
}
protected void drawRoadSurface(Graphics2D g2d) {
g2d = (Graphics2D) g2d.create();
// This sets the point of rotation at the center of the window
int midX = getWidth() / 2;
int midY = getHeight() / 2;
g2d.rotate(Math.toRadians(directionOfRotation), midX, midY);
// We then need to offset the top/left corner so that what
// we want draw appears to be in the center of the window,
// and thus will be rotated around it's center
int x = midX - (road.getWidth() / 2);
int y = midY - (road.getHeight() / 2);
g2d.drawImage(road, x, y, this);
g2d.dispose();
}
protected class DirectionAction extends AbstractAction {
private Direction direction;
private boolean active;
public DirectionAction(Direction direction, boolean active) {
this.direction = direction;
this.active = active;
}
#Override
public void actionPerformed(ActionEvent e) {
setDirectionActive(direction, active);
}
}
}
}
I have this code which is basically a home menu with two clickable rectangles.
Start Game
Info
Start Game works fine.
Info is what is not really working. When pressed, the info screen will appear, but the home menu buttons will still be there though not visible (can be clicked).. it seems that when the info menu is appearing, the home menu buttons are not getting cleared.
Also, any point on the info menu is clickable and will show the home menu again. (not what intended, only the back buttons should do that).
How can I fix those problems ?
package test;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.awt.image.ImageObserver;
import java.awt.image.ImageProducer;
public class HomeMenu extends JComponent implements MouseListener, MouseMotionListener {
private static final String GAME_TITLE = "BRICK DESTROY";
private static final String START_TEXT = "START";
private static final String INFO_TEXT = "INFO";
private static final String howtoPlay = """
1- Click Start\n
2- Choose the mode\n
3- Each mode has 3 levels\n
4- To play/pause press space, use 'A' and 'D' to move\n
5- To open pause menu press 'ESC'\n
6- To open DebugPanel press 'ALT-SHIFT-F1'""";
private static final String backText = "BACK";
private static final Color BORDER_COLOR = new Color(200,8,21); //Venetian Red
private static final Color DASH_BORDER_COLOR = new Color(255, 216, 0);//school bus yellow
private static final Color TEXT_COLOR = new Color(255, 255, 255);//white
private static final Color CLICKED_BUTTON_COLOR = Color.ORANGE.darker();;
private static final Color CLICKED_TEXT = Color.ORANGE.darker();
private static final int BORDER_SIZE = 5;
private static final float[] DASHES = {12,6};
private Rectangle menuFace;
private Rectangle infoFace;
private Rectangle startButton;
private Rectangle infoButton;
private Rectangle backButton;
private BasicStroke borderStoke;
private BasicStroke borderStoke_noDashes;
private Image img = Toolkit.getDefaultToolkit().createImage("1.jpeg");
private Font gameTitleFont;
private Font infoFont;
private Font buttonFont;
private Font howtoPlayFont;
private GameFrame owner;
private boolean startClicked;
private boolean infoClicked = false;
private boolean backClicked = false;
public HomeMenu(GameFrame owner,Dimension area){
this.setFocusable(true);
this.requestFocusInWindow();
this.addMouseListener(this);
this.addMouseMotionListener(this);
this.owner = owner;
menuFace = new Rectangle(new Point(0,0),area);
infoFace = new Rectangle(new Point(0,0),area);
this.setPreferredSize(area);
Dimension btnDim = new Dimension(area.width / 3, area.height / 12);
startButton = new Rectangle(btnDim);
infoButton = new Rectangle(btnDim);
backButton = new Rectangle(btnDim);
borderStoke = new BasicStroke(BORDER_SIZE,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND,0,DASHES,0);
borderStoke_noDashes = new BasicStroke(BORDER_SIZE,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND);
gameTitleFont = new Font("Calibri",Font.BOLD,28);
infoFont = new Font("Calibri",Font.BOLD,24);
buttonFont = new Font("Calibri",Font.BOLD,startButton.height-2);
howtoPlayFont = new Font("Calibri",Font.PLAIN,14);
}
public void paint(Graphics g){
drawMenu((Graphics2D)g);
}
public void drawMenu(Graphics2D g2d){
if(infoClicked) {
drawInfoMenu(g2d);
return;
}else{
drawContainer(g2d);
Color prevColor = g2d.getColor();
Font prevFont = g2d.getFont();
double x = menuFace.getX();
double y = menuFace.getY();
g2d.translate(x,y);
//methods calls
drawText(g2d);
drawButton(g2d);
//end of methods calls
g2d.translate(-x,-y);
g2d.setFont(prevFont);
g2d.setColor(prevColor);
}
Toolkit.getDefaultToolkit().sync();
}
private void drawContainer(Graphics2D g2d){
Color prev = g2d.getColor();
//g2d.setColor(BG_COLOR);
g2d.drawImage(img,0,0,menuFace.width,menuFace.height,this);
//g2d.fill(menuFace);
Stroke tmp = g2d.getStroke();
g2d.setStroke(borderStoke_noDashes);
g2d.setColor(DASH_BORDER_COLOR);
g2d.draw(menuFace);
g2d.setStroke(borderStoke);
g2d.setColor(BORDER_COLOR);
g2d.draw(menuFace);
g2d.setStroke(tmp);
g2d.setColor(prev);
}
private void drawText(Graphics2D g2d){
g2d.setColor(TEXT_COLOR);
FontRenderContext frc = g2d.getFontRenderContext();
Rectangle2D gameTitleRect = gameTitleFont.getStringBounds(GAME_TITLE,frc);
int sX,sY;
sY = (int)(menuFace.getHeight() / 4);
sX = (int)(menuFace.getWidth() - gameTitleRect.getWidth()) / 2;
sY += (int) gameTitleRect.getHeight() * 1.1;//add 10% of String height between the two strings
g2d.setFont(gameTitleFont);
g2d.drawString(GAME_TITLE,sX,sY);
}
private void drawButton(Graphics2D g2d){
FontRenderContext frc = g2d.getFontRenderContext();
Rectangle2D txtRect = buttonFont.getStringBounds(START_TEXT,frc);
Rectangle2D mTxtRect = buttonFont.getStringBounds(INFO_TEXT,frc);
g2d.setFont(buttonFont);
int x = (menuFace.width - startButton.width) / 2;
int y =(int) ((menuFace.height - startButton.height) * 0.5);
startButton.setLocation(x,y);
x = (int)(startButton.getWidth() - txtRect.getWidth()) / 2;
y = (int)(startButton.getHeight() - txtRect.getHeight()) / 2;
x += startButton.x;
y += startButton.y + (startButton.height * 0.9);
if(startClicked){
Color tmp = g2d.getColor();
g2d.setColor(CLICKED_BUTTON_COLOR);
g2d.draw(startButton);
g2d.setColor(CLICKED_TEXT);
g2d.drawString(START_TEXT,x,y);
g2d.setColor(tmp);
}
else{
g2d.draw(startButton);
g2d.drawString(START_TEXT,x,y);
}
x = startButton.x;
y = startButton.y;
y *= 1.3;
infoButton.setLocation(x,y);
x = (int)(infoButton.getWidth() - mTxtRect.getWidth()) / 2;
y = (int)(infoButton.getHeight() - mTxtRect.getHeight()) / 2;
x += infoButton.getX();
y += infoButton.getY() + (startButton.height * 0.9);
if(infoClicked){
Color tmp = g2d.getColor();
g2d.setColor(CLICKED_BUTTON_COLOR);
g2d.draw(infoButton);
g2d.setColor(CLICKED_TEXT);
g2d.drawString(INFO_TEXT,x,y);
g2d.setColor(tmp);
}
else{
g2d.draw(infoButton);
g2d.drawString(INFO_TEXT,x,y);
}
}
private void drawInfoMenu(Graphics2D g2d){
FontRenderContext frc = g2d.getFontRenderContext();
Rectangle2D infoRec = infoFont.getStringBounds(INFO_TEXT,frc);
Color prev = g2d.getColor();
Stroke tmp = g2d.getStroke();
g2d.setStroke(borderStoke_noDashes);
g2d.setColor(DASH_BORDER_COLOR);
g2d.draw(infoFace);
g2d.setStroke(borderStoke);
g2d.setColor(BORDER_COLOR);
g2d.draw(infoFace);
g2d.fillRect(0,0,infoFace.width,infoFace.height);
g2d.setStroke(tmp);
g2d.setColor(prev);
g2d.setColor(TEXT_COLOR);
int sX,sY;
sY = (int)(infoFace.getHeight() / 15);
sX = (int)(infoFace.getWidth() - infoRec.getWidth()) / 2;
sY += (int) infoRec.getHeight() * 1.1;//add 10% of String height between the two strings
g2d.setFont(infoFont);
g2d.drawString(INFO_TEXT,sX,sY);
TextLayout layout = new TextLayout(howtoPlay, howtoPlayFont, frc);
String[] outputs = howtoPlay.split("\n");
for(int i=0; i<outputs.length; i++) {
g2d.setFont(howtoPlayFont);
g2d.drawString(outputs[i], 40, (int) (80 + i * layout.getBounds().getHeight() + 0.5));
}
backButton.setLocation(getWidth()/3,getHeight()-50);
int x = (int)(backButton.getWidth() - infoRec.getWidth()) / 2;
int y = (int)(backButton.getHeight() - infoRec.getHeight()) / 2;
x += backButton.x+11;
y += backButton.y + (layout.getBounds().getHeight() * 1.35);
backButton.setLocation(getWidth()/3,getHeight()-50);
if(backClicked){
Color tmp1 = g2d.getColor();
g2d.setColor(CLICKED_BUTTON_COLOR);
g2d.draw(backButton);
g2d.setColor(CLICKED_TEXT);
g2d.drawString(backText,x,y);
g2d.setColor(tmp1);
infoClicked = false;
repaint();
}
else{
g2d.draw(backButton);
g2d.drawString(backText,x,y);
}
}
#Override
public void mouseClicked(MouseEvent mouseEvent) {
Point p = mouseEvent.getPoint();
if(startButton.contains(p)){
owner.enableGameBoard();
}
else if(infoButton.contains(p)){
infoClicked = true;
}
else if(backButton.contains(p)){
infoClicked = false;
}
repaint();
}
#Override
public void mousePressed(MouseEvent mouseEvent) {
Point p = mouseEvent.getPoint();
if(startButton.contains(p)){
startClicked = true;
repaint(startButton.x,startButton.y,startButton.width+1,startButton.height+1);
}
else if(infoButton.contains(p)){
infoClicked = true;
}
else if(backButton.contains(p)){
infoClicked = false;
}
repaint();
}
#Override
public void mouseReleased(MouseEvent mouseEvent) {
if(startClicked){
startClicked = false;
repaint(startButton.x,startButton.y,startButton.width+1,startButton.height+1);
}
else if(infoClicked){
infoClicked = false;
}
else if(backClicked){
infoClicked = true;
}
repaint();
}
#Override
public void mouseEntered(MouseEvent mouseEvent) {
}
#Override
public void mouseExited(MouseEvent mouseEvent) {
}
#Override
public void mouseDragged(MouseEvent mouseEvent) {
}
#Override
public void mouseMoved(MouseEvent mouseEvent) {
Point p = mouseEvent.getPoint();
if(startButton.contains(p) || infoButton.contains(p) || backButton.contains(p)) {
this.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
else {
this.setCursor(Cursor.getDefaultCursor());
}
}
}
Here are the images of both windows
main menu
info menu, pressing anywhere = back to home menu, pressing roughly in the middle = start game or back to main menu too
First read, Performing Custom Painting and Painting in AWT and Swing to get a better understanding how painting in Swing works and how you're suppose to work with it.
But I already have ...
public void paint(Graphics g){
drawMenu((Graphics2D)g);
}
would suggest otherwise. Seriously, go read those links so you understand all the issues that the above decision is going to create for you.
You're operating in a OO language, you need to take advantage of that and decouple your code and focus on the "single responsibility" principle.
I'm kind of tired of talking about it, so you can do some reading:
https://softwareengineering.stackexchange.com/questions/244476/what-is-decoupling-and-what-development-areas-can-it-apply-to
Cohesion and Decoupling, what do they represent?
Single Responsibility Principle
Single Responsibility Principle in Java with Examples
SOLID Design Principles Explained: The Single Responsibility Principle
These are basic concepts you really need to understand as they will make your live SOOO much easier and can be applied to just about any language.
As an example, from your code...
public HomeMenu(GameFrame owner,Dimension area){
//...
this.setPreferredSize(area);
There is no good reason (other than laziness (IMHO)) that any caller should be telling a component what size it should be, that's not their responsibility. It's the responsibility of the component to tell the parent container how big it would like to be and for the parent component to figure out how it's going to achieve that (or ignore it as the case may be).
The "basic" problem you're having is a simple one. Your "God" class is simply trying to do too much (ie it's taken on too much responsibility). Now we "could" add a dozen or more flags into the code to compensate for this, which is just going to increase the coupling and complexity, making it harder to understand and maintain, or we can take a step back, break it down into individual areas of responsibility and build the solution around those, for example...
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new HomePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class HomePane extends JPanel {
public HomePane() {
setLayout(new BorderLayout());
navigateToMenu();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected void navigateToMenu() {
removeAll();
HomeMenuPane pane = new HomeMenuPane(new HomeMenuPane.NavigationListener() {
#Override
public void navigateToInfo(HomeMenuPane source) {
HomePane.this.navigateToInfo();
}
#Override
public void navigateToStartGame(HomeMenuPane source) {
startGame();
}
});
add(pane);
revalidate();
repaint();
}
protected void navigateToInfo() {
removeAll();
HowToPlayPane pane = new HowToPlayPane(new HowToPlayPane.NavigationListener() {
#Override
public void navigateBack(HowToPlayPane source) {
navigateToMenu();
}
});
add(pane);
revalidate();
repaint();
}
protected void startGame() {
removeAll();
add(new JLabel("This is pretty awesome, isn't it!", JLabel.CENTER));
revalidate();
repaint();
}
}
public abstract class AbstractBaseMenuPane extends JPanel {
protected static final Color BORDER_COLOR = new Color(200, 8, 21); //Venetian Red
protected static final Color DASH_BORDER_COLOR = new Color(255, 216, 0);//school bus yellow
protected static final Color TEXT_COLOR = new Color(255, 255, 255);//white
protected static final Color CLICKED_BUTTON_COLOR = Color.ORANGE.darker();
protected static final Color CLICKED_TEXT = Color.ORANGE.darker();
protected static final int BORDER_SIZE = 5;
protected static final float[] DASHES = {12, 6};
private Rectangle border;
private BasicStroke borderStoke;
private BasicStroke borderStoke_noDashes;
private BufferedImage backgroundImage;
public AbstractBaseMenuPane() {
borderStoke = new BasicStroke(BORDER_SIZE, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, DASHES, 0);
borderStoke_noDashes = new BasicStroke(BORDER_SIZE, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
border = new Rectangle(new Point(0, 0), getPreferredSize());
// You are now responsible for filling the background
setOpaque(false);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
BufferedImage backgroundImage = getBackgroundImage();
if (backgroundImage != null) {
g2d.drawImage(backgroundImage, 0, 0, getWidth(), getHeight(), this);
}
Color prev = g2d.getColor();
Stroke tmp = g2d.getStroke();
g2d.setStroke(borderStoke_noDashes);
g2d.setColor(DASH_BORDER_COLOR);
g2d.draw(border);
g2d.setStroke(borderStoke);
g2d.setColor(BORDER_COLOR);
g2d.draw(border);
g2d.dispose();
}
public void setBackgroundImage(BufferedImage backgroundImage) {
this.backgroundImage = backgroundImage;
repaint();
}
public BufferedImage getBackgroundImage() {
return backgroundImage;
}
}
public class HomeMenuPane extends AbstractBaseMenuPane {
public static interface NavigationListener {
public void navigateToInfo(HomeMenuPane source);
public void navigateToStartGame(HomeMenuPane source);
}
private static final String GAME_TITLE = "BRICK DESTROY";
private static final String START_TEXT = "START";
private static final String INFO_TEXT = "INFO";
private Rectangle startButton;
private Rectangle infoButton;
private Font gameTitleFont;
private Font buttonFont;
// Don't do this, this just sucks (for so many reasons)
// Use ImageIO.read instead and save yourself a load of frustration
//private Image img = Toolkit.getDefaultToolkit().createImage("1.jpeg");
private Point lastClickPoint;
private NavigationListener navigationListener;
public HomeMenuPane(NavigationListener navigationListener) {
this.navigationListener = navigationListener;
try {
setBackgroundImage(ImageIO.read(getClass().getResource("/images/BrickWall.jpg")));
} catch (IOException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent mouseEvent) {
Point p = mouseEvent.getPoint();
lastClickPoint = p;
if (startButton.contains(p)) {
peformStartGameAction();
} else if (infoButton.contains(p)) {
performInfoAction();
}
repaint();
}
#Override
public void mouseReleased(MouseEvent mouseEvent) {
lastClickPoint = null;
repaint();
}
});
this.addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent mouseEvent) {
Point p = mouseEvent.getPoint();
if (startButton.contains(p) || infoButton.contains(p)) {
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
} else {
setCursor(Cursor.getDefaultCursor());
}
}
});
Dimension area = getPreferredSize();
Dimension btnDim = new Dimension(area.width / 3, area.height / 12);
startButton = new Rectangle(btnDim);
infoButton = new Rectangle(btnDim);
gameTitleFont = new Font("Calibri", Font.BOLD, 28);
buttonFont = new Font("Calibri", Font.BOLD, startButton.height - 2);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Color prevColor = g2d.getColor();
Font prevFont = g2d.getFont();
//methods calls
drawText(g2d);
drawButton(g2d);
//end of methods calls
g2d.setFont(prevFont);
g2d.setColor(prevColor);
g2d.dispose();
}
private void drawText(Graphics2D g2d) {
g2d.setColor(TEXT_COLOR);
FontRenderContext frc = g2d.getFontRenderContext();
Rectangle2D gameTitleRect = gameTitleFont.getStringBounds(GAME_TITLE, frc);
int sX, sY;
sY = (int) (getHeight() / 4);
sX = (int) (getWidth() - gameTitleRect.getWidth()) / 2;
sY += (int) gameTitleRect.getHeight() * 1.1;//add 10% of String height between the two strings
g2d.setFont(gameTitleFont);
g2d.drawString(GAME_TITLE, sX, sY);
}
private void drawButton(Graphics2D g2d) {
FontRenderContext frc = g2d.getFontRenderContext();
Rectangle2D txtRect = buttonFont.getStringBounds(START_TEXT, frc);
Rectangle2D mTxtRect = buttonFont.getStringBounds(INFO_TEXT, frc);
g2d.setFont(buttonFont);
int x = (getWidth() - startButton.width) / 2;
int y = (int) ((getHeight() - startButton.height) * 0.5);
startButton.setLocation(x, y);
x = (int) (startButton.getWidth() - txtRect.getWidth()) / 2;
y = (int) (startButton.getHeight() - txtRect.getHeight()) / 2;
x += startButton.x;
y += startButton.y + (startButton.height * 0.9);
if (lastClickPoint != null && startButton.contains(lastClickPoint)) {
Color tmp = g2d.getColor();
g2d.setColor(CLICKED_BUTTON_COLOR);
g2d.draw(startButton);
g2d.setColor(CLICKED_TEXT);
g2d.drawString(START_TEXT, x, y);
g2d.setColor(tmp);
} else {
g2d.draw(startButton);
g2d.drawString(START_TEXT, x, y);
}
x = startButton.x;
y = startButton.y;
y *= 1.3;
infoButton.setLocation(x, y);
x = (int) (infoButton.getWidth() - mTxtRect.getWidth()) / 2;
y = (int) (infoButton.getHeight() - mTxtRect.getHeight()) / 2;
x += infoButton.getX();
y += infoButton.getY() + (startButton.height * 0.9);
if (lastClickPoint != null && infoButton.contains(lastClickPoint)) {
Color tmp = g2d.getColor();
g2d.setColor(CLICKED_BUTTON_COLOR);
g2d.draw(infoButton);
g2d.setColor(CLICKED_TEXT);
g2d.drawString(INFO_TEXT, x, y);
g2d.setColor(tmp);
} else {
g2d.draw(infoButton);
g2d.drawString(INFO_TEXT, x, y);
}
}
protected void peformStartGameAction() {
navigationListener.navigateToStartGame(this);
}
protected void performInfoAction() {
navigationListener.navigateToInfo(this);
}
}
public class HowToPlayPane extends AbstractBaseMenuPane {
public static interface NavigationListener {
public void navigateBack(HowToPlayPane source);
}
private static final String HOW_TO_PLAY_TEXT = """
1- Click Start\n
2- Choose the mode\n
3- Each mode has 3 levels\n
4- To play/pause press space, use 'A' and 'D' to move\n
5- To open pause menu press 'ESC'\n
6- To open DebugPanel press 'ALT-SHIFT-F1'""";
private static final String BACK_TEXT = "BACK";
private static final String INFO_TEXT = "INFO";
private Rectangle backButton;
private boolean backClicked = false;
private Font infoFont;
private Font howtoPlayFont;
private NavigationListener navigationListener;
public HowToPlayPane(NavigationListener navigationListener) {
this.navigationListener = navigationListener;
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent mouseEvent) {
Point p = mouseEvent.getPoint();
if (backButton.contains(p)) {
backClicked = true;
repaint();
performBackAction();
}
}
#Override
public void mouseReleased(MouseEvent e) {
backClicked = false;
}
});
this.addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent mouseEvent) {
Point p = mouseEvent.getPoint();
if (backButton.contains(p)) {
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
} else {
setCursor(Cursor.getDefaultCursor());
}
}
});
Dimension btnDim = new Dimension(getPreferredSize().width / 3, getPreferredSize().height / 12);
backButton = new Rectangle(btnDim);
infoFont = new Font("Calibri", Font.BOLD, 24);
howtoPlayFont = new Font("Calibri", Font.PLAIN, 14);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(BORDER_COLOR);
g2d.fillRect(0, 0, getWidth(), getHeight());
FontRenderContext frc = g2d.getFontRenderContext();
Rectangle2D infoRec = infoFont.getStringBounds(INFO_TEXT, frc);
//
// Color prev = g2d.getColor();
//
// Stroke tmp = g2d.getStroke();
//
// g2d.setStroke(borderStoke_noDashes);
// g2d.setColor(DASH_BORDER_COLOR);
// g2d.draw(infoFace);
//
// g2d.setStroke(borderStoke);
// g2d.setColor(BORDER_COLOR);
// g2d.draw(infoFace);
//
// g2d.fillRect(0, 0, infoFace.width, infoFace.height);
//
// g2d.setStroke(tmp);
//
// g2d.setColor(prev);
//
g2d.setColor(TEXT_COLOR);
int sX, sY;
sY = (int) (getHeight() / 15);
sX = (int) (getWidth() - infoRec.getWidth()) / 2;
sY += (int) infoRec.getHeight() * 1.1;//add 10% of String height between the two strings
g2d.setFont(infoFont);
g2d.drawString(INFO_TEXT, sX, sY);
TextLayout layout = new TextLayout(HOW_TO_PLAY_TEXT, howtoPlayFont, frc);
String[] outputs = HOW_TO_PLAY_TEXT.split("\n");
for (int i = 0; i < outputs.length; i++) {
g2d.setFont(howtoPlayFont);
g2d.drawString(outputs[i], 40, (int) (80 + i * layout.getBounds().getHeight() + 0.5));
}
backButton.setLocation(getWidth() / 3, getHeight() - 50);
int x = (int) (backButton.getWidth() - infoRec.getWidth()) / 2;
int y = (int) (backButton.getHeight() - infoRec.getHeight()) / 2;
x += backButton.x + 11;
y += backButton.y + (layout.getBounds().getHeight() * 1.35);
backButton.setLocation(getWidth() / 3, getHeight() - 50);
if (backClicked) {
Color tmp1 = g2d.getColor();
g2d.setColor(CLICKED_BUTTON_COLOR);
g2d.draw(backButton);
g2d.setColor(CLICKED_TEXT);
g2d.drawString(BACK_TEXT, x, y);
g2d.setColor(tmp1);
repaint();
} else {
g2d.draw(backButton);
g2d.drawString(BACK_TEXT, x, y);
}
g2d.dispose();
}
protected void performBackAction() {
navigationListener.navigateBack(this);
}
}
}
Now, this example makes use of components to present different views (it even has a nice abstract implementation to allow for code re-use 😱), but it occurs to me, that, if you "really" wanted to, you could have a series of "painter" classes, which could be used to delegate the painting of the current state to, and mouse clicks/movements could be delegated to, meaning you could have a single component, which would simple delegate the painting (via the paintComponent method) to which ever painter is active.
And, wouldn't you know it, they have a design principle for that to, the Delegation Pattern
The above example also makes use of the observer pattern, so you might want to have a look into that as well
So I'm new at java and need some help with my breakout game. My JFrame is just blank and i don't know how to fix it?
So I have a ball class, paddle class, canvas class and a brick class as well as a main class. In my canvas class I set all functions the ball, paddle and bricks has etc. In brick class I draw the bricks. And in my main I do the JFrame but it's blank
Main class :
public class Main {
public static void main(String[] args){
JFrame frame = new JFrame();
Canvas c = new Canvas();
frame.add(c);
frame.pack();
frame.setResizable(false);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
I expect the JFrame to show the game instead of just blank window
package breakout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import javax.swing.JPanel;
import javax.swing.Timer;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.KeyEvent;
import breakout.Bricks.Type;
public class Canvas extends JPanel implements ActionListener, MouseMotionListener, MouseListener, KeyListener {
/**
*
*/
private static final long serialVersionUID = 1L;
private static final int HEIGHT = 600;
public static final int WIDTH = 720;
private int horizontalCount;
private BufferedImage image;
private Graphics2D bufferedGraphics;
private Timer time;
private static final Font endFont = new Font(Font.SANS_SERIF, Font.BOLD, 20);
private static final Font scoreFont = new Font(Font.SANS_SERIF, Font.BOLD, 15);
private Paddle player;
private Ball ball;
ArrayList<ArrayList<Bricks>> bricks;
public Canvas() {
super();
setPreferredSize( new Dimension(WIDTH, HEIGHT));
image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
bufferedGraphics = image.createGraphics();
time = new Timer (15, this);
player = new Paddle((WIDTH/2)-(Paddle.PADDLE_WIDTH/2));
ball = new Ball (((player.getX() + (Paddle.PADDLE_WIDTH / 2 )) - (Ball.DIAMETER / 2)), (Paddle.Y_POS - (Ball.DIAMETER + 10 )), -5, -5);
bricks = new ArrayList<ArrayList<Bricks>>();
horizontalCount = WIDTH / Bricks.BRICK_WIDTH;
for(int i = 0; i < 8; ++i) {
ArrayList<Bricks> temp = new ArrayList<Bricks>();
#SuppressWarnings("unused")
Type rowColor = null;
switch(i) {
case 0 :
case 2:
rowColor = Type.LOW;
break;
case 1 :
case 3 :
case 5 :
rowColor = Type.MEDIUM;
break;
case 4 :
case 6 :
rowColor = Type.HIGH;
break;
case 7 :
default :
rowColor = Type.ULTRA;
break;
}
for(int j = 0; j < horizontalCount; ++j) {
Bricks tempBrick = new Bricks();
temp.add(tempBrick);
}
bricks.add(temp);
addMouseMotionListener(this);
addMouseListener(this);
addKeyListener(this);
requestFocus();
}
}
public void actionPerformed(ActionEvent e) {
checkCollisions();
ball.Move();
for(int i = 0; i < bricks.size(); ++i) {
ArrayList<Bricks> al = bricks.get(i);
for(int j = 0; j < al.size(); ++j) {
Bricks b = al.get(j);
if(b.dead()) {
al.remove(b);
}
}
}
repaint();
}
private void checkCollisions() {
if(player.hitPaddle(ball)) {
ball.setDY(ball.getDY() * -1);
return;
}
if(ball.getX() >= (WIDTH - Ball.DIAMETER) || ball.getX() <= 0) {
ball.setDX(ball.getDX() * -1);
}
if(ball.getY() > (Paddle.Y_POS + Paddle.PADDLE_HEIGHT + 10)) {
resetBall();
}
if(ball.getY() <= 0) {
ball.setDY(ball.getDY() * -1);
}
int brickRowActive = 0;
for(ArrayList<Bricks> alb : bricks) {
if(alb.size() == horizontalCount) {
++brickRowActive;
}
}
for(int i = (brickRowActive==0) ? 0 : (brickRowActive - 1); i < bricks.size(); ++i) {
for(Bricks b : bricks.get(i)) {
if(b.hitBy(ball)) {
player.setScore(player.getScore() + b.getBrickType().getPoints());
b.decrementType();
}
}
}
}
private void resetBall() {
if(gameOver()) {
time.stop();
return;
}
ball.setX(WIDTH/2);
ball.setDY((HEIGHT/2) + 80);
player.setLives(player.getLives() -1);
player.setScore(player.getScore() <= 1);
}
private boolean gameOver() {
if(player.getLives() <= 1) {
return true;
}
return false;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
bufferedGraphics.clearRect(0, 0, WIDTH, HEIGHT);
player.drawPaddle(bufferedGraphics);
player.drawBall(bufferedGraphics);
for(ArrayList<Bricks> row : bricks) {
for(Bricks b : row) {
b.drawBrick(bufferedGraphics);
}
}
bufferedGraphics.setFont(scoreFont);
bufferedGraphics.drawString("Score: " + player.getScore(), 10, 25);
if(gameOver() && ball.getY() >= HEIGHT) {
bufferedGraphics.setColor(Color.black);
bufferedGraphics.setFont(endFont);
bufferedGraphics.drawString("Game Over Score: " + player.getScore(), (WIDTH /2) -85, (HEIGHT/2));
}
if(empty()) {
bufferedGraphics.setColor(Color.black);
bufferedGraphics.setFont(endFont);
bufferedGraphics.drawString("You won. Score: " + player.getScore(), (WIDTH /2) -85, (HEIGHT /2));
time.stop();
}
g.drawImage(image, 0, 0, this);
Toolkit.getDefaultToolkit().sync();
}
private boolean empty() {
for(ArrayList<Bricks> al : bricks) {
if(al.size() != 0) {
return false;
}
}
return true;
}
#Override
public void mouseMoved(MouseEvent e) {
player.setX(e.getX() - (Paddle.PADDLE_WIDTH / 2));
}
#Override
public void mouseClicked(MouseEvent e) {
if(time.isRunning()) {
return;
}
time.start();
}
#Override
public void mouseDragged(MouseEvent e) { }
#Override
public void mouseEntered(MouseEvent arg0) {}
#Override
public void mouseExited(MouseEvent arg0) {}
#Override
public void mousePressed(MouseEvent arg0) {}
#Override
public void mouseReleased(MouseEvent arg0) {}
#Override
public void keyPressed(KeyEvent arg0) {}
#Override
public void keyReleased(KeyEvent arg0) {}
#Override
public void keyTyped(KeyEvent arg0) {}
}
Preparing an MCVE, as required in SO, not only it makes helping much easier.
In many case, while preparing one, you are likely to find the problem, so it is a good debugging tool.
To answer "why is my JFrame blank ?" you could create the minimal code example like the following (copy-paste the entire code into GameBoard.java and run):
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class GameBoard extends JPanel {
static final int HEIGHT = 600, WIDTH = 720, BRICK_ROWS = 8;
private final int horizontalCount;
private static final Font scoreFont = new Font(Font.SANS_SERIF, Font.BOLD, 15);
private final Paddle player;
private final Ball ball;
ArrayList<ArrayList<Brick>> bricks;
public GameBoard() {
super();
setPreferredSize( new Dimension(WIDTH, HEIGHT));
player = new Paddle(WIDTH/2-Paddle.PADDLE_WIDTH/2);
ball = new Ball (player.getX() + Paddle.PADDLE_WIDTH / 2 - Ball.DIAMETER / 2,
Paddle.Y_POS - (Ball.DIAMETER + 10 ));
bricks = new ArrayList<>();
horizontalCount = WIDTH / Brick.BRICK_WIDTH;
for(int i = 0; i < BRICK_ROWS; ++i) {
ArrayList<Brick> temp = new ArrayList<>();
for(int j = 0; j < horizontalCount; ++j) {
Brick tempBrick = new Brick(j*Brick.BRICK_WIDTH , Brick.BRICK_YPOS + i*Brick.BRICK_HEIGHT);
temp.add(tempBrick);
}
bricks.add(temp);
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2D = (Graphics2D)g;
g2D.clearRect(0, 0, WIDTH, HEIGHT);
player.drawPaddle(g2D);
ball.drawBall(g2D);
for(ArrayList<Brick> row : bricks) {
for(Brick b : row) {
b.drawBrick(g2D);
}
}
g2D.setFont(scoreFont);
g2D.drawString("Score: " + player.getScore(), 10, 25);
}
}
class Paddle{
public final static int PADDLE_WIDTH = 100, PADDLE_HEIGHT= 30, Y_POS = GameBoard.HEIGHT - 2* PADDLE_HEIGHT;
private int xPos, score;
Paddle(int xPos) {
this.xPos = xPos;
}
void setX(int xPos) {this.xPos = xPos;}
int getX() {return xPos;}
String getScore() {
return String.valueOf(score);
}
void drawPaddle(Graphics2D g2D) {
g2D.setColor(Color.GREEN);
g2D.fillRect(xPos, Y_POS, PADDLE_WIDTH, PADDLE_HEIGHT);
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocation(400,250);
frame.add(new GameBoard());
frame.pack();
frame.setResizable(false);
frame.setVisible(true);
}
}
class Brick{
final static int BRICK_WIDTH = 80, BRICK_HEIGHT = 15, BRICK_YPOS = 50;
int xPos, yPos;
Brick(int xPos, int yPos) {
this.xPos = xPos;
this.yPos = yPos;
}
void drawBrick(Graphics2D g2D) {
g2D.setColor(Color.RED);
g2D.fillRect(xPos, yPos, BRICK_WIDTH, BRICK_HEIGHT);
g2D.setColor(Color.BLACK);
g2D.drawRect(xPos, yPos, BRICK_WIDTH, BRICK_HEIGHT);
}
}
class Ball{
final static int DIAMETER = 40;
int xPos, yPos;
Ball(int xPos, int yPos) {
this.xPos = xPos;
this.yPos = yPos;
}
void drawBall(Graphics2D g2D) {
g2D.setColor(Color.BLUE);
g2D.fillOval(xPos, yPos, DIAMETER, DIAMETER);
}
}
This produces the following result, which I believe can serve as the basis of what you wanted to achieve:
Now start adding the missing functionality and see what breaks it.
I try to implement an explosion into my game but the animation stops after the first frame. If the player loses his health the explosion should be triggered. I made 3 Classes. Animation Class, an Explosion Class and GameActivity Class. I tried to only post the relevant pieces.
I searched the internet but I could not find a solution to my problem. So I am asking you if you maybe see my mistake and could help me. If more code is needed I will edit it of course. Maybe I am just overlooking something.
Animation Class{
public void setFrames(Bitmap[] frames)
{
this.frames = frames;
currentFrame = 0;
startTime = System.nanoTime();
}
public void setDelay(long d){delay = d;}
public void setFrame(int i){currentFrame= i;}
public void work()
{
long elapsed = (System.nanoTime()-startTime)/1000000;
if(elapsed>delay)
{
currentFrame++;
startTime = System.nanoTime();
}
if(currentFrame == frames.length){
currentFrame = 0;
playedOnce = true; }}
public Bitmap getImage(){
return frames[currentFrame];
}
public int getFrame(){return currentFrame;}
public boolean playedOnce(){return playedOnce; }}
This is the relevant part of the Explosion Class:
Bitmap[] image = new Bitmap[numFrames];
//Bitmap has 4 * 4 frames
for (int i = 0; i < image.length; i++) {
if (i % 4 == 0 && i > 0) row++;
image[i] = Bitmap.createBitmap(spritesheet, (i - (4 * row)) * width,
row * height, width*4, height*4);
}
animation.setFrames(image);
animation.setDelay(10);
}
public void animating() {
if (!animation.playedOnce()) {
animation.work(); }}
If the player loses all his health I call the explosion in GameActivity
if (player.getHealth() < 0) {
// player bitmap disappears
dissapear = true;
//game ends
gameEnded=true;
explosion = new Explosion(BitmapFactory.decodeResource(getResources(),
R.drawable.images),player.getX(),player.getY()-10,9,9,25);
explosion.animating();
This is the main code I use to load the sprite sheet and grab the sprites:
package com.animation.main;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
public class Main extends JFrame{
BufferedImage sprite;
Animator player;
public Main(){
setSize(800, 600);
setVisible(true);
setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
init();
}
private void init(){
BufferedImageLoader loader = new BufferedImageLoader();
BufferedImage spriteSheet = null;
try {
spriteSheet = loader.loadImage("insert your sprite sheet path here");
} catch (IOException ex) {
// TODO Auto-generated catch block
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
SpriteSheet ss = new SpriteSheet(spriteSheet);
ArrayList<BufferedImage> sprites = new ArrayList<BufferedImage>();
sprites.add(ss.grabSprite(0, 16, 92, 109));
sprites.add(ss.grabSprite(94, 16, 92, 109));
sprites.add(ss.grabSprite(196, 12, 92, 115));
sprites.add(ss.grabSprite(387, 5, 92, 115));
sprites.add(ss.grabSprite(387, 5, 92, 115));
sprites.add(ss.grabSprite(487, 5, 92, 115));
sprites.add(ss.grabSprite(587, 5, 77, 115));
player = new Animator(sprites);
player.setSpeed(100);
player.start();
}
Image dbImage;
Graphics dbg;
public void paint(Graphics g){
dbImage = createImage(getWidth(), getHeight());
dbg = dbImage.getGraphics();
paintComponent(dbg);
g.drawImage(dbImage, 0, 0, null);
}
private void paintComponent(Graphics g) {
if(player != null) {
g.drawImage(player.sprite, 100, 100, null);
player.update(System.currentTimeMillis());
}
repaint();
}
public static void main(String[] args){
Main main = new Main();
}
}
The parameters for the 'sprites.add(ss.grabSprite(0, 16, 92, 109));' code are the x and y value of your sprites top left corner on the spriteSheet and its width and height.
Run the code in the paintComponent class when the players health is less than or equal to 0. It would look something like this:
private void paintComponent(Graphics g) {
if(player != null && playerHealth <= 0) {
g.drawImage(player.sprite, 100, 100, null);
player.update(System.currentTimeMillis());
}
repaint();
}
This is the code I use for the spritesheet:
package com.animation.main;
import java.awt.image.BufferedImage;
public class SpriteSheet {
public BufferedImage spriteSheet;
public SpriteSheet(BufferedImage ss){
this.spriteSheet = ss;
}
public BufferedImage grabSprite(int x, int y, int width, int height){
BufferedImage sprite = spriteSheet.getSubimage(x, y, width, height);
return sprite;
}
THE grab sprite function and the BufferedImage sprite gets the sprite from the spriteSheet.
This is the BufferedImageLoader:
package com.animation.main;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
public class BufferedImageLoader {
public BufferedImage loadImage(String pathRelativeToThis) throws IOException{
URL url = this.getClass().getResource(pathRelativeToThis);
BufferedImage img = ImageIO.read(url);
return img;
}
}
This is the animator class:
package com.animation.main;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
public class Animator {
ArrayList<BufferedImage> frames;
BufferedImage sprite;
private volatile boolean running = false;
private long previousTime, speed;
private int frameAtPause, currentFrame;
public Animator(ArrayList<BufferedImage> frames){
this.frames = frames;
}
public void setSpeed(long speed){
this.speed = speed;
}
public void update(long time){
if(running){
if(time - previousTime >= speed){
// Update animation
currentFrame++;
try{
sprite = frames.get(currentFrame);
} catch(IndexOutOfBoundsException e){
currentFrame = 0;
sprite = frames.get(currentFrame);
}
previousTime = time;
}
}
}
public void start(){
running = true;
previousTime = 0;
currentFrame = 0;
frameAtPause = 0;
}
public void stop(){
running = false;
previousTime = 0;
currentFrame = 0;
frameAtPause = 0;
}
public void pause(){
frameAtPause = 0;
}
public void resume(){
}
}
I solved it. The problem was my Game Activity. I had to change
if (player.getHealth() < 0) {
// player bitmap disappears
dissapear = true;
//game ends
gameEnded=true;
explosion = new Explosion(BitmapFactory.decodeResource(getResources(),
R.drawable.images),player.getX(),player.getY()-10,9,9,25);
explosion.animating();}
to
if (player.getHealth() < 0) {
// player bitmap disappears
dissapear = true;
//game ends
gameEnded=true;
explosion = new Explosion(BitmapFactory.decodeResource(getResources(),
R.drawable.images),player.getX(),player.getY()-10,9,9,25);
}
if (dissapear){explosion.animating();}
I'm trying to create a graphical component that allows me to draw one rectangle on a selected image (the rectangle must be drawn using drag-and-drop operations): the purpose of this component is to get the coordinates and the size of the rectangle drawn; the second goal is to provide a component which can be easily integrated in a graphical user interface.
The authors of this example created a subclass of JLabel in order to draw the image, then they added a MouseInputAdapter to the instance of this subclass in order to deal with the drawing of the rectangle.
I was inspired by that example, with the difference that I have created a subclass of JPanel class: I named it FigurePanel class. Then I made some changes in order to provide the following features:
if the image is larger than the instance of FigurePanel, then the scrollers must appear;
if the image is smaller than the instance of FigurePanel, then this image must be placed in the center of the panel;
while the user draws the rectangle, it should not extend beyond the limits of the image.
Here is the source code of FigurePanel class.
package imageselectionproject;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.JPanel;
import javax.swing.event.MouseInputAdapter;
public class FigurePanel extends JPanel
{
private Image backgroundImage = null;
private Rectangle imageLimits = new Rectangle(0, 0, getWidth(), getHeight());
private Rectangle currentRect = null;
private Rectangle rectToDraw = null;
private final Rectangle previousRectDrawn = new Rectangle();
public FigurePanel()
{
setOpaque(true);
SelectionListener listener = new SelectionListener();
addMouseListener(listener);
addMouseMotionListener(listener);
}
#Override
public Dimension getPreferredSize()
{
return backgroundImage == null ? super.getPreferredSize() : new Dimension(backgroundImage.getWidth(this), backgroundImage.getHeight(this));
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g); //paints the background and image
if (backgroundImage != null)
{
g.drawImage(backgroundImage, imageLimits.x, imageLimits.y, this);
}
// If currentRect exists, paint a box on top.
if (currentRect != null)
{
// Draw a rectangle on top of the image.
g.setXORMode(Color.white); // Color of line varies
// depending on image colors
g.drawRect(rectToDraw.x, rectToDraw.y,
rectToDraw.width - 1, rectToDraw.height - 1);
System.out.println(rectToDraw);
}
}
public void setImage(Image image)
{
int x = 0;
int y = 0;
if (image != null)
{
backgroundImage = image;
// The following instructions are used to center the image on the panel.
/*x = (getSize().width - image.getWidth(this)) / 2;
y = (getSize().height - image.getHeight(this)) / 2;
if (x < 0) x = 0;
if (y < 0) y = 0;*/
}
else
{
backgroundImage = null;
}
currentRect = null;
setSize(getPreferredSize());
imageLimits.setBounds(x, y, getWidth(), getHeight());
System.out.println("imageLimits = " + imageLimits);
repaint();
}
private void updateDrawableRect()
{
int x = currentRect.x;
int y = currentRect.y;
int width = currentRect.width;
int height = currentRect.height;
// Make the width and height positive, if necessary.
if (width < 0)
{
width = 0 - width;
x = x - width + 1;
if (x < 0)
{
width += x;
x = 0;
}
}
if (height < 0)
{
height = 0 - height;
y = y - height + 1;
if (y < 0)
{
height += y;
y = 0;
}
}
// The rectangle should not extend beyond the boundaries of the image.
if (x < imageLimits.x)
{
width -= (imageLimits.x - x);
x = imageLimits.x;
}
else if ((x + width) > imageLimits.x + imageLimits.width)
{
width = imageLimits.x + imageLimits.width - x;
}
if (y < imageLimits.y)
{
height -= (imageLimits.y - y);
y = imageLimits.y;
}
if ((y + height) > imageLimits.y + imageLimits.height)
{
height = imageLimits.y + imageLimits.height - y;
}
// Update rectToDraw after saving old value.
if (rectToDraw != null)
{
previousRectDrawn.setBounds(rectToDraw.x, rectToDraw.y,
rectToDraw.width, rectToDraw.height);
rectToDraw.setBounds(x, y, width, height);
}
else
{
rectToDraw = new Rectangle(x, y, width, height);
}
}
private class SelectionListener extends MouseInputAdapter
{
#Override
public void mousePressed(MouseEvent e)
{
int x = e.getX();
int y = e.getY();
currentRect = new Rectangle(x, y, 0, 0);
updateDrawableRect();
repaint();
}
#Override
public void mouseDragged(MouseEvent e)
{
updateSize(e.getX(), e.getY());
}
#Override
public void mouseReleased(MouseEvent e)
{
updateSize(e.getX(), e.getY());
}
/*
* Update the size of the current rectangle
* and call repaint. Because currentRect
* always has the same origin, translate it
* if the width or height is negative.
*
* For efficiency (though
* that isn't an issue for this program),
* specify the painting region using arguments
* to the repaint() call.
*
*/
void updateSize(int x, int y)
{
currentRect.setSize(x - currentRect.x, y - currentRect.y);
updateDrawableRect();
Rectangle totalRepaint = rectToDraw.union(previousRectDrawn);
repaint(totalRepaint.x, totalRepaint.y,
totalRepaint.width, totalRepaint.height);
}
}
}
The method setImage is used to set a new image, so it invokes the method repaint to redraw the graphical component. In the code shown above, I disabled (via comments) instructions to center the image: in this way, the component seems to work properly.
Instead, if I enable such instructions, the image is correctly positioned in the center of the panel when it is smaller than the panel itself, however, I encountered the following problem: suppose that it is currently displayed an image larger than the panel, if the new image that I decide to load is smaller than the currently displayed image, then the new image is not displayed; if I try to reload the new image, then it appears.
Why does this problem occur? How to solve it?
I also created the FigurePanelTest class in order to test the FigurePanel class.
package imageselectionproject;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
public class FigurePanelTest extends JFrame
{
public FigurePanelTest()
{
FigurePanel imagePanel = new FigurePanel();
JScrollPane imageScrollPane = new JScrollPane();
imageScrollPane.setPreferredSize(new Dimension(420, 250));
imageScrollPane.setViewportView(imagePanel);
JButton imageButton = new JButton("Load Image");
imageButton.addActionListener(
new ActionListener()
{
#Override
public void actionPerformed(ActionEvent evt)
{
JFileChooser fc = new JFileChooser();
int returnValue = fc.showOpenDialog(null);
if (returnValue == JFileChooser.APPROVE_OPTION) {
File selectedFile = fc.getSelectedFile();
System.out.println(selectedFile.getName());
try
{
Image image = ImageIO.read(selectedFile.getAbsoluteFile());
imagePanel.setImage(image);
imageScrollPane.getViewport().setViewPosition(new Point(0, 0));
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
}
);
Container container = getContentPane();
container.setLayout(new FlowLayout());
container.add(imageScrollPane);
container.add(imageButton);
setSize(600, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Here is the main.
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new FigurePanelTest().setVisible(true);
}
});
}
For Andrew, a single program:
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.event.MouseInputAdapter;
public class TestDrawPanel {
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new FigurePanelTest().setVisible(true);
}
});
}
}
class FigurePanelTest extends JFrame {
public FigurePanelTest() {
final FigurePanel imagePanel = new FigurePanel();
final JScrollPane imageScrollPane = new JScrollPane();
imageScrollPane.setPreferredSize(new Dimension(420, 250));
imageScrollPane.setViewportView(imagePanel);
JButton imageButton = new JButton("Load Image");
imageButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
JFileChooser fc = new JFileChooser();
int returnValue = fc.showOpenDialog(null);
if (returnValue == JFileChooser.APPROVE_OPTION) {
File selectedFile = fc.getSelectedFile();
System.out.println(selectedFile.getName());
try {
Image image = ImageIO.read(selectedFile.getAbsoluteFile());
imagePanel.setImage(image);
imageScrollPane.getViewport()
.setViewPosition(new Point(0, 0));
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
Container container = getContentPane();
container.setLayout(new FlowLayout());
container.add(imageScrollPane);
container.add(imageButton);
setSize(600, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
class FigurePanel extends JPanel {
private Image backgroundImage = null;
private Rectangle imageLimits = new Rectangle(0, 0, getWidth(), getHeight());
private Rectangle currentRect = null;
private Rectangle rectToDraw = null;
private final Rectangle previousRectDrawn = new Rectangle();
public FigurePanel() {
setOpaque(true);
SelectionListener listener = new SelectionListener();
addMouseListener(listener);
addMouseMotionListener(listener);
}
#Override
public Dimension getPreferredSize() {
return backgroundImage == null ? super.getPreferredSize()
: new Dimension(backgroundImage.getWidth(this),
backgroundImage.getHeight(this));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // paints the background and image
if (backgroundImage != null) {
g.drawImage(backgroundImage, imageLimits.x, imageLimits.y, this);
}
// If currentRect exists, paint a box on top.
if (currentRect != null) {
// Draw a rectangle on top of the image.
g.setXORMode(Color.white); // Color of line varies
// depending on image colors
g.drawRect(rectToDraw.x, rectToDraw.y, rectToDraw.width - 1,
rectToDraw.height - 1);
System.out.println(rectToDraw);
}
}
public void setImage(Image image) {
int x = 0;
int y = 0;
if (image != null) {
backgroundImage = image;
// The following instructions are used to center the image on the
// panel.
/*
* x = (getSize().width - image.getWidth(this)) / 2; y =
* (getSize().height - image.getHeight(this)) / 2;
*
* if (x < 0) x = 0; if (y < 0) y = 0;
*/
} else {
backgroundImage = null;
}
currentRect = null;
setSize(getPreferredSize());
imageLimits.setBounds(x, y, getWidth(), getHeight());
System.out.println("imageLimits = " + imageLimits);
repaint();
}
private void updateDrawableRect() {
int x = currentRect.x;
int y = currentRect.y;
int width = currentRect.width;
int height = currentRect.height;
// Make the width and height positive, if necessary.
if (width < 0) {
width = 0 - width;
x = x - width + 1;
if (x < 0) {
width += x;
x = 0;
}
}
if (height < 0) {
height = 0 - height;
y = y - height + 1;
if (y < 0) {
height += y;
y = 0;
}
}
// The rectangle should not extend beyond the boundaries of the image.
if (x < imageLimits.x) {
width -= (imageLimits.x - x);
x = imageLimits.x;
} else if ((x + width) > imageLimits.x + imageLimits.width) {
width = imageLimits.x + imageLimits.width - x;
}
if (y < imageLimits.y) {
height -= (imageLimits.y - y);
y = imageLimits.y;
}
if ((y + height) > imageLimits.y + imageLimits.height) {
height = imageLimits.y + imageLimits.height - y;
}
// Update rectToDraw after saving old value.
if (rectToDraw != null) {
previousRectDrawn.setBounds(rectToDraw.x, rectToDraw.y,
rectToDraw.width, rectToDraw.height);
rectToDraw.setBounds(x, y, width, height);
} else {
rectToDraw = new Rectangle(x, y, width, height);
}
}
private class SelectionListener extends MouseInputAdapter {
#Override
public void mousePressed(MouseEvent e) {
int x = e.getX();
int y = e.getY();
currentRect = new Rectangle(x, y, 0, 0);
updateDrawableRect();
repaint();
}
#Override
public void mouseDragged(MouseEvent e) {
updateSize(e.getX(), e.getY());
}
#Override
public void mouseReleased(MouseEvent e) {
updateSize(e.getX(), e.getY());
}
/*
* Update the size of the current rectangle and call repaint. Because
* currentRect always has the same origin, translate it if the width or
* height is negative.
*
* For efficiency (though that isn't an issue for this program), specify
* the painting region using arguments to the repaint() call.
*/
void updateSize(int x, int y) {
currentRect.setSize(x - currentRect.x, y - currentRect.y);
updateDrawableRect();
Rectangle totalRepaint = rectToDraw.union(previousRectDrawn);
repaint(totalRepaint.x, totalRepaint.y, totalRepaint.width,
totalRepaint.height);
}
}
}
The problem is calculating the x and y in the setImage().It's not calculating the center of panel correctly.I'm saying this by checking the values of x and y after loading small and large images many times
However I tried putting the centering the image inside paintComponent and it worked perfectly
if (backgroundImage != null) {
imageLimits.x = (this.getWidth() - backgroundImage.getWidth(this)) / 2;
imageLimits.y = (this.getHeight() - backgroundImage.getHeight(this)) / 2;
g.drawImage(backgroundImage, imageLimits.x, imageLimits.y, this);
}