How to draw Rectangle loop to draw a wall in a game? - java

How I could create Rectangle loop, to look like a wall in a game?
I don't have to create and draw each rectangle separately. In Rectangle class I haven't found setter for setting coordinates.

I created something you might be able to use for a Tank game I'm working on.
Here's the GUI
To use this GUI, you left-click on the location where you want a wall. If you left-click on a wall, the wall section disappears.
In the SaveGameGridListener class, actionPerformed method, I hardcoded a specific location for the text file output. You'll want to change that.
The output of this GUI is a 44 character by a 22 line text file made up of 0 and 1 values. Zero indicates a playing field, and one indicates a wall. You'll probably want to adjust the dimensions of the playing field for your Packman game.
Here's the text file for this particular game board.
00000000000000000000011000000000000000000000
00000000000000000000011000000000000000000000
00000000000000000000000000000000000000000000
00000111000000000000000000000000000011100000
00000000000000000000000000000000000000000000
00000000000001111000000000011110000000000000
00000000000001000000000000000010000000000000
00001100000000000000000000000000000000110000
00000100000000000000000000000000000000100000
00000100000000000000000000000000000000100000
00000100011000000000011000000000011000100000
00000100011000000000011000000000011000100000
00000100000000000000000000000000000000100000
00000100000000000000000000000000000000100000
00001100000000000000000000000000000000110000
00000000000001000000000000000010000000000000
00000000000001111000000000011110000000000000
00000000000000000000000000000000000000000000
00000111000000000000000000000000000011100000
00000000000000000000000000000000000000000000
00000000000000000000011000000000000000000000
00000000000000000000011000000000000000000000
Now, my game board is sparse. Your game board would have more walls, but you can use the same principle that I did.
Create a text file containing zeroes and ones to generate your game board.
Use the text file to create your game board.
Here's the complete runnable code I used. I made all the classes inner classes so I could post the code as one block. The code is expecting a grid.txt file in the Resources folder of your project.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileFilter;
public class GenerateGameGrid implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new GenerateGameGrid());
}
#Override
public void run() {
new GenerateGameGridFrame(new GameGrid());
}
public class GenerateGameGridFrame {
private final GameGrid gameGrid;
private final GenerateGameGridPanel generateGameGridPanel;
private final JFrame frame;
public GenerateGameGridFrame(GameGrid gameGrid) {
this.gameGrid = gameGrid;
this.generateGameGridPanel = new GenerateGameGridPanel(this, gameGrid);
this.frame = createAndShowGUI();
}
private JFrame createAndShowGUI() {
JFrame frame = new JFrame("Generate Game Grid");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setJMenuBar(createJMenuBar());
frame.add(generateGameGridPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
System.out.println(frame.getSize());
return frame;
}
private JMenuBar createJMenuBar() {
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
JMenuItem saveItem = new JMenuItem("Save Game Grid... ");
saveItem.setAccelerator(KeyStroke.getKeyStroke("control S"));
saveItem.addActionListener(new SaveGameGridListener(this, gameGrid));
fileMenu.add(saveItem);
fileMenu.addSeparator();
JMenuItem exitItem = new JMenuItem("Exit");
exitItem.setAccelerator(KeyStroke.getKeyStroke("alt X"));
exitItem.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent event) {
frame.dispose();
System.exit(0);
}
});
fileMenu.add(exitItem);
menuBar.add(fileMenu);
return menuBar;
}
public JFrame getFrame() {
return frame;
}
public void repaint() {
generateGameGridPanel.repaint();
}
}
public class GameGrid {
private final int blockwidth;
private final int margin;
private final int[][] grid;
private final Color backgroundColor;
private final Color foregroundColor;
public GameGrid() {
this.blockwidth = 24;
this.margin = 10;
this.backgroundColor = new Color(187, 71, 14);
this.foregroundColor = new Color(244, 192, 83);
this.grid = readGrid();
}
private int[][] readGrid() {
int[][] grid = new int[22][44];
try {
readGrid(grid, "grid.txt");
} catch (IOException e) {
e.printStackTrace();
}
return grid;
}
private void readGrid(int[][] grid, String filename) throws IOException {
InputStream is = getClass().getResourceAsStream("/" + filename);
InputStreamReader streamReader = new InputStreamReader(is, StandardCharsets.UTF_8);
BufferedReader reader = new BufferedReader(streamReader);
int row = 0;
String line = reader.readLine();
while (line != null) {
char[] chars = line.toCharArray();
for (int column = 0; column < chars.length; column++) {
grid[row][column] = Integer.valueOf(Character.toString(chars[column]));
}
row++;
line = reader.readLine();
}
reader.close();
}
public int[][] getGrid() {
return grid;
}
public Color getBackgroundColor() {
return backgroundColor;
}
public Color getForegroundColor() {
return foregroundColor;
}
public int getBlockwidth() {
return blockwidth;
}
public int getMargin() {
return margin;
}
}
public class GenerateGameGridPanel extends JPanel {
private static final long serialVersionUID = 1L;
private final GameGrid gameGrid;
// private final GenerateGameGridFrame frame;
public GenerateGameGridPanel(GenerateGameGridFrame frame, GameGrid gameGrid) {
// this.frame = frame;
this.gameGrid = gameGrid;
int[][] grid = gameGrid.getGrid();
int blockwidth = gameGrid.getBlockwidth();
int margin = gameGrid.getMargin();
int height = blockwidth * (grid.length + 2) + margin + margin;
int width = blockwidth * (grid[0].length + 2) + margin + margin;
this.setBackground(gameGrid.getBackgroundColor());
this.setPreferredSize(new Dimension(width, height));
this.addMouseListener(new BlockListener(frame, gameGrid));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
drawBorder(g2d);
drawGrid(g2d, gameGrid.getGrid());
}
private void drawBorder(Graphics2D g2d) {
int blockwidth = gameGrid.getBlockwidth();
int margin = gameGrid.getMargin();
int x = margin;
int y = margin;
int width = getWidth() - margin - margin;
int height = blockwidth;
g2d.setColor(gameGrid.getForegroundColor());
g2d.fillRect(x, y, width, height);
x = margin;
y = margin;
width = blockwidth;
height = getHeight() - margin - margin;
g2d.fillRect(x, y, width, height);
x = margin;
y = getHeight() - margin - blockwidth;
width = getWidth() - margin - margin;
height = blockwidth;
g2d.fillRect(x, y, width, height);
x = getWidth() - blockwidth - margin;
y = margin;
width = blockwidth;
height = getHeight() - margin - margin;
g2d.fillRect(x, y, width, height);
}
private void drawGrid(Graphics2D g2d, int[][] grid) {
int blockwidth = gameGrid.getBlockwidth();
int margin = gameGrid.getMargin();
g2d.setColor(gameGrid.getForegroundColor());
for (int row = 0; row < grid.length; row++) {
for (int column = 0; column < grid[row].length; column++) {
if (grid[row][column] == 1) {
int x = (column + 1) * blockwidth + margin;
int y = (row + 1) * blockwidth + margin;
g2d.fillRect(x, y, blockwidth, blockwidth);
}
}
}
}
}
public class BlockListener extends MouseAdapter {
private final GameGrid gameGrid;
private final GenerateGameGridFrame frame;
public BlockListener(GenerateGameGridFrame frame, GameGrid gameGrid) {
this.frame = frame;
this.gameGrid = gameGrid;
}
#Override
public void mouseReleased(MouseEvent event) {
int[][] grid = gameGrid.getGrid();
int blockwidth = gameGrid.getBlockwidth();
int margin = gameGrid.getMargin();
Point point = event.getPoint();
int row = (point.y - margin - blockwidth) / blockwidth;
int column = (point.x - margin - blockwidth) / blockwidth;
grid[row][column] = grid[row][column] ^ 1;
frame.repaint();
}
}
public class SaveGameGridListener implements ActionListener {
private final GameGrid gameGrid;
private final GenerateGameGridFrame frame;
public SaveGameGridListener(GenerateGameGridFrame frame, GameGrid gameGrid) {
this.frame = frame;
this.gameGrid = gameGrid;
}
#Override
public void actionPerformed(ActionEvent event) {
OSFileChooser fc = new OSFileChooser();
File dir = new File("D:\\Eclipse\\Eclipse-2020-workspace\\com.ggl.combat\\resources");
fc.setCurrentDirectory(dir);
fc.addChoosableFileFilter(new FileTypeFilter(
"Text", "txt"));
fc.setAcceptAllFileFilterUsed(false);
int returnVal = fc.showSaveDialog(frame.getFrame());
if (returnVal == OSFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
try {
writeFile(file);
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void writeFile(File file) throws IOException {
int[][] grid = gameGrid.getGrid();
BufferedWriter writer = new BufferedWriter(new FileWriter(file));
for (int row = 0; row < grid.length; row++) {
StringBuilder builder = new StringBuilder();
for (int column = 0; column < grid[row].length; column++) {
builder.append(grid[row][column]);
}
builder.append(System.lineSeparator());
writer.write(builder.toString());
}
writer.close();
}
}
public class OSFileChooser extends JFileChooser {
private static final long serialVersionUID = 1L;
#Override
public void approveSelection() {
File f = getSelectedFile();
if (f.exists() && getDialogType() == SAVE_DIALOG) {
int result = JOptionPane.showConfirmDialog(this, f.getName() +
" exists, overwrite?",
"Existing file", JOptionPane.YES_NO_CANCEL_OPTION);
switch (result) {
case JOptionPane.YES_OPTION:
super.approveSelection();
return;
case JOptionPane.NO_OPTION:
return;
case JOptionPane.CLOSED_OPTION:
return;
case JOptionPane.CANCEL_OPTION:
cancelSelection();
return;
}
}
super.approveSelection();
}
#Override
public File getSelectedFile() {
File file = super.getSelectedFile();
if (file != null && getDialogType() == SAVE_DIALOG) {
String extension = getExtension(file);
if (extension.isEmpty()) {
FileTypeFilter filter = (FileTypeFilter) getFileFilter();
if (filter != null) {
extension = filter.getExtension();
String fileName = file.getPath();
fileName += "." + extension;
file = new File(fileName);
}
}
}
return file;
}
public String getExtension(File file) {
String extension = "";
String s = file.getName();
int i = s.lastIndexOf('.');
if (i > 0 && i < (s.length() - 1)) {
extension = s.substring(i + 1).toLowerCase();
}
return extension;
}
}
public class FileTypeFilter extends FileFilter {
private String extension;
private String description;
public FileTypeFilter(String description, String extension) {
this.extension = extension;
this.description = description;
}
#Override
public boolean accept(File file) {
if (file.isDirectory()) {
return true;
}
return file.getName().endsWith("." + extension);
}
#Override
public String getDescription() {
return description + String.format(" (*.%s)", extension);
}
public String getExtension() {
return extension;
}
}
}

Related

How to make an animated number counter using Java Swing?

I want to make an animated numeric counter, like this one:
I want to be able to input the value and have the counter update with animation.
I can find how to do this on Android from Google, but I cannot find any information on how to make it in Java Swing. How would I make something like this in Swing?
This isn't a complete answer, but this is a working example of a sliding JPanel. This code could be modified to create the display in the question.
Here's the complete runnable example.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SlidingDigitGUI implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new SlidingDigitGUI());
}
private SlidingPanel secondPanel;
#Override
public void run() {
JFrame frame = new JFrame("Sliding Digit");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createSlidingPanel(), BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
Animation animation = new Animation();
new Thread(animation).start();
}
public JPanel createSlidingPanel() {
String[] digitValues = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
JPanel panel = new JPanel();
panel.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 4, false));
panel.setPreferredSize(new Dimension(300, 100));
Font font = panel.getFont();
Font derivedFont = font.deriveFont(Font.BOLD, 48F);
secondPanel = new SlidingPanel(digitValues, derivedFont);
secondPanel.setPanelValue("0");
panel.add(secondPanel);
return panel;
}
public class SlidingPanel extends JPanel {
private static final long serialVersionUID = 661553022861652947L;
private static final int MARGIN = 4;
private int imageY;
private BufferedImage slidingImage;
private Dimension characterDimension;
private final Font font;
private String currentValue;
private final String[] panelValues;
public SlidingPanel(String[] panelValues, Font font) {
this.panelValues = panelValues;
this.font = font;
this.characterDimension = calculateFontSize();
this.slidingImage = generateSlidingImage();
this.setPreferredSize(characterDimension);
}
private Dimension calculateFontSize() {
int maxWidth = 0;
int maxHeight = 0;
FontRenderContext frc = new FontRenderContext(null, true, true);
for (String s : panelValues) {
Rectangle2D r2D = font.getStringBounds(s, frc);
int rWidth = (int) Math.round(r2D.getWidth());
int rHeight = (int) Math.round(r2D.getHeight());
maxWidth = Math.max(maxWidth, rWidth);
maxHeight = Math.max(maxHeight, rHeight);
}
return new Dimension(maxWidth, maxHeight);
}
private BufferedImage generateSlidingImage() {
int height = calculateStringHeight() * (panelValues.length + 1);
BufferedImage slidingImage = new BufferedImage(characterDimension.width,
height, BufferedImage.TYPE_INT_RGB);
Graphics g = slidingImage.getGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, characterDimension.width, height);
g.setColor(Color.BLACK);
g.setFont(font);
int y = characterDimension.height - MARGIN;
for (String s : panelValues) {
g.drawString(s, 0, y);
y += calculateStringHeight();
}
g.drawString(panelValues[0], 0, y);
g.dispose();
return slidingImage;
}
public void setPanelValue(String value) {
int index = getValueIndex(value);
this.currentValue = value;
this.imageY = calculateStringHeight() * index;
repaint();
}
public void updatePanelValue(String value) {
if (!currentValue.equals(value)) {
int index = getValueIndex(value);
int finalY = calculateStringHeight() * index;
SliderAnimation sliderAnimation = new SliderAnimation(imageY, finalY);
new Thread(sliderAnimation).start();
this.currentValue = value;
}
}
private int getValueIndex(String value) {
for (int index = 0; index < panelValues.length; index++) {
if (value.equals(panelValues[index])) {
return index;
}
}
return -1;
}
private int calculateStringHeight() {
return characterDimension.height + MARGIN;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
BufferedImage subImage = slidingImage.getSubimage(0, imageY,
characterDimension.width,
characterDimension.height);
g.drawImage(subImage, 0, 0, this);
}
public class SliderAnimation implements Runnable {
private int originalY;
private int finalY;
public SliderAnimation(int originalY, int finalY) {
this.originalY = originalY;
this.finalY = finalY;
}
#Override
public void run() {
int differenceY = finalY - originalY;
if (finalY == 0) {
differenceY = characterDimension.height + MARGIN;
}
int steps = 10;
double difference = (double) differenceY / steps;
for (int index = 1; index <= steps; index++) {
imageY = (int) Math.round(difference * index + originalY);
update();
sleep(120L);
}
if (finalY == 0) {
imageY = 0;
update();
} else {
imageY = finalY;
}
}
private void update() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
SlidingPanel.this.repaint();
}
});
}
private void sleep(long duration) {
try {
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Animation implements Runnable {
#Override
public void run() {
while (true) {
update("3");
sleep(2000L);
update("8");
sleep(2000L);
}
}
private void update(final String value) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
secondPanel.updatePanelValue(value);
}
});
}
private void sleep(long duration) {
try {
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Point;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Animation {
boolean loop=true;//true //loop
static int start=1;//1 //start
static int end=10+1;//20 // end + 1
int delay1=1000;//1s // delay
int delay2=1;//1ms // delay
Font font=new Font("Helvetica",Font.BOLD,25); //Font
Timer timer=new Timer(delay1, e -> move());JPanel panel = new JPanel();int[] size = {50,100};Point point = new Point(210,size[1]);
Timer timer1=null;int value=start-1;int i;
public static void main(String[] args) {if (start!=end) {new Animation();}}
public void move() {
timer.stop();value ++;
if (!loop && value==end) {timer.stop();}i=0;
timer1 = new Timer(delay2, e -> {
if (i==100) {
timer1.stop();
timer.start();
}
point.setLocation(point.getX(), point.getY()-1);
panel.setLocation(point);
panel.revalidate();
i++;
});timer1.start();
if (loop && value==end-1) {point.setLocation(point.getX(),size[1]+size[1]);value = start-1;}else if (value==end-1 && !loop){System.exit(0);}
}
public Animation() {
JFrame frame = new JFrame();frame.setSize(500,300);frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);frame.add(panel);frame.setLocationRelativeTo(null);
panel.setLocation(point);frame.setLayout(null);frame.revalidate();panel.setSize(new Dimension(size[0],size[1]*Math.abs(end-(start-1))));
panel.setLayout(new GridLayout(Math.abs(end-start)+start,1));if (!(end-start<0)) {for (int i=start;i!=(end-start)+start;i++) {JLabel la = new JLabel(String.valueOf(i));la.setFont(font);panel.add(la);
}}timer.start();frame.revalidate();frame.setVisible(true);
}
}

marquee (ticker,text scroll) with java2d or javafx stutters

I'm trying to write a smooth ticker (text running from rigth to left on the screen).
It is almost as I want it, but there are still some stutters. I would like it to be as smooth as cloud moving in the sky. 30 years ago I managed with a few lines of assembler code, but in Java I fail.
It get's worse if I increase the speed (number of pixels I move the text at once).
Is there some kind of synchronization to the screen refresh missing?
EDIT
I updated my code according to #camickr remark to launch the window in an exclusive fullscreen windows, which lead to a sligth improvement.
Other things I tried:
Added ExtendedBufferCapabilities which is supposed to consider vsync
Toolkit.getDefaultToolkit().sync();
enabled opengl
tried a gaming loop
added some debugging info
When I use 30 fps and move the text only one pixel, on a 4k display it looks quite good but is also very slow. As soon as I increase to speed to 2 pixels it's starts to stutter.
I'm starting to think that it is simply not possible to achieve my goal with java2d and I have to move to some opengl-library.
Here is my code:
package scrolling;
import java.awt.AWTException;
import java.awt.BufferCapabilities;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.ImageCapabilities;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.Timer;
import sun.java2d.pipe.hw.ExtendedBufferCapabilities;
/**
* A smooth scroll with green background to be used with a video cutter.
*
* sun.java2d.pipe.hw.ExtendedBufferCapabilities is restricted https://stackoverflow.com/questions/25222811/access-restriction-the-type-application-is-not-api-restriction-on-required-l
*
*/
public class MyScroll extends JFrame implements ActionListener {
private int targetFps = 30;
private boolean isOpenGl = false;
private boolean isVsync = true;
private boolean useGamingLoop = false;
private int speed = 1;
private String message;
private int fontSize = 120;
private Font theFont;
private transient int leftEdge; // Offset from window's right edge to left edge
private Color bgColor;
private Color fgColor;
private int winWidth;
private int winHeight;
private double position = 0.77;
private FontMetrics fontMetrics;
private int yPositionScroll;
private boolean isFullScreen;
private long lastTimerStart = 0;
private BufferedImage img;
private Graphics2D graphicsScroll;
private GraphicsDevice currentScreenDevice = null;
private int msgWidth = 0;
private Timer scrollTimer;
private boolean isRunning;
/* gaming loop variables */
private static final long NANO_IN_MILLI = 1000000L;
// num of iterations with a sleep delay of 0ms before
// game loop yields to other threads.
private static final int NO_DELAYS_PER_YIELD = 16;
// max num of renderings that can be skipped in one game loop,
// game's internal state is updated but not rendered on screen.
private static int MAX_RENDER_SKIPS = 5;
// private long prevStatsTime;
private long gameStartTime;
private long curRenderTime;
private long rendersSkipped = 0L;
private long period; // period between rendering in nanosecs
private long fps;
private long frameCounter;
private long lastFpsTime;
public void init() {
fontSize = getWidth() / 17;
if (getGraphicsConfiguration().getBufferCapabilities().isPageFlipping()) {
try { // no pageflipping available with opengl
BufferCapabilities cap = new BufferCapabilities(new ImageCapabilities(true), new ImageCapabilities(true), BufferCapabilities.FlipContents.BACKGROUND);
// ExtendedBufferCapabilities is supposed to do a vsync
ExtendedBufferCapabilities ebc = new ExtendedBufferCapabilities(cap, ExtendedBufferCapabilities.VSyncType.VSYNC_ON);
createBufferStrategy(2, ebc);
} catch (AWTException e) {
e.printStackTrace();
}
} else {
createBufferStrategy(2);
}
System.out.println(getDeviceConfigurationString(getGraphicsConfiguration()));
message = "This is a test. ";
leftEdge = 0;
theFont = new Font("Helvetica", Font.PLAIN, fontSize);
bgColor = getBackground();
fgColor = getForeground();
winWidth = getSize().width - 1;
winHeight = getSize().height;
yPositionScroll = (int) (winHeight * position);
initScrollImage();
}
/**
* Draw the entire text to a buffered image to copy it to the screen later.
*/
private void initScrollImage() {
Graphics2D og = (Graphics2D) getBufferStrategy().getDrawGraphics();
fontMetrics = og.getFontMetrics(theFont);
Rectangle2D rect = fontMetrics.getStringBounds(message, og);
img = new BufferedImage((int) rect.getWidth(), (int) rect.getHeight(), BufferedImage.TYPE_INT_ARGB);
// At each frame, we get a reference on the rendering buffer graphics2d.
// To handle concurrency, we 'cut' it into graphics context for each cube.
graphicsScroll = img.createGraphics();
graphicsScroll.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphicsScroll.setBackground(Color.BLACK);
graphicsScroll.setFont(theFont);
graphicsScroll.setColor(bgColor);
graphicsScroll.fillRect(0, 0, img.getWidth(), img.getHeight()); // clear offScreen Image.
graphicsScroll.setColor(fgColor);
msgWidth = fontMetrics.stringWidth(message);
graphicsScroll.setColor(Color.white);
graphicsScroll.drawString(message, 1, img.getHeight() - 10);
// for better readability in front of an image draw an outline arround the text
graphicsScroll.setColor(Color.black);
graphicsScroll.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Font font = new Font("Helvetica", Font.PLAIN, fontSize);
graphicsScroll.translate(1, img.getHeight() - 10);
FontRenderContext frc = graphicsScroll.getFontRenderContext();
GlyphVector gv = font.createGlyphVector(frc, message);
graphicsScroll.draw(gv.getOutline());
}
public void start() {
scrollTimer = new Timer(1000 / targetFps, this);
scrollTimer.setRepeats(true);
scrollTimer.setCoalesce(true);
scrollTimer.start();
}
public void startGamingloop() {
// loop initialization
long beforeTime, afterTime, timeDiff, sleepTime;
long overSleepTime = 0L;
int noDelays = 0;
long excess = 0L;
gameStartTime = System.nanoTime();
// prevStatsTime = gameStartTime;
beforeTime = gameStartTime;
period = (1000L * NANO_IN_MILLI) / targetFps; // rendering FPS (nanosecs/targetFPS)
System.out.println("FPS: " + targetFps + ", vsync=");
System.out.println("FPS period: " + period);
// gaming loop http://www.javagaming.org/index.php/topic,19971.0.html
while (true) {
// **2) execute physics
updateLeftEdge();
// **1) execute drawing
drawScroll();
// Synchronise with the display hardware.
// Flip the buffer
if (!getBufferStrategy().contentsLost()) {
getBufferStrategy().show();
}
if (isVsync) {
Toolkit.getDefaultToolkit().sync();
}
afterTime = System.nanoTime();
curRenderTime = afterTime;
calculateFramesPerSecond();
timeDiff = afterTime - beforeTime;
sleepTime = (period - timeDiff) - overSleepTime;
if (sleepTime > 0) { // time left in cycle
// System.out.println("sleepTime: " + (sleepTime/NANO_IN_MILLI));
try {
Thread.sleep(sleepTime / NANO_IN_MILLI);// nano->ms
} catch (InterruptedException ex) {
}
overSleepTime = (System.nanoTime() - afterTime) - sleepTime;
} else { // sleepTime <= 0;
System.out.println("Rendering too slow");
// this cycle took longer than period
excess -= sleepTime;
// store excess time value
overSleepTime = 0L;
if (++noDelays >= NO_DELAYS_PER_YIELD) {
Thread.yield();
// give another thread a chance to run
noDelays = 0;
}
}
beforeTime = System.nanoTime();
/*
* If the rendering is taking too long, then update the game state without rendering it, to get the UPS nearer to the required frame rate.
*/
int skips = 0;
while ((excess > period) && (skips < MAX_RENDER_SKIPS)) {
// update state but don’t render
System.out.println("Skip renderFPS, run updateFPS");
excess -= period;
updateLeftEdge();
skips++;
}
rendersSkipped += skips;
}
}
private void calculateFramesPerSecond() {
if (curRenderTime - lastFpsTime >= NANO_IN_MILLI * 1000) {
fps = frameCounter;
frameCounter = 0;
lastFpsTime = curRenderTime;
}
frameCounter++;
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
#Override
public void actionPerformed(ActionEvent e) {
render();
}
private void render() {
if (!isFullScreen) {
repaint(0, yPositionScroll, winWidth, yPositionScroll + fontMetrics.getAscent());
} else {
getBufferStrategy().show();
}
if (isVsync) {
Toolkit.getDefaultToolkit().sync();
}
updateLeftEdge();
drawScroll();
}
/**
* Draws (part) of the prerendered image with text to a position in the screen defined by an increasing
* variable "leftEdge".
*
* #return time drawing took.
*/
private long drawScroll() {
long beforeDrawText = System.nanoTime();
if (winWidth - leftEdge + msgWidth < 0) { // Current scroll entirely off-screen.
leftEdge = 0;
}
int x = winWidth - leftEdge;
int sourceWidth = Math.min(leftEdge, img.getWidth());
Graphics2D og = (Graphics2D) getBufferStrategy().getDrawGraphics();
try { // copy the pre drawn scroll to the screen
og.drawImage(img.getSubimage(0, 0, sourceWidth, img.getHeight()), x, yPositionScroll, null);
} catch (Exception e) {
System.out.println(e.getMessage() + " " + x + " " + sourceWidth);
}
long afterDrawText = System.nanoTime();
return afterDrawText - beforeDrawText;
}
public static void main(String[] args) {
MyScroll scroll = new MyScroll();
System.setProperty("sun.java2d.opengl", String.valueOf(scroll.isOpenGl())); // enable opengl
System.setProperty("sun.java2d.renderer.verbose", "true");
String renderer = "undefined";
try {
renderer = sun.java2d.pipe.RenderingEngine.getInstance().getClass().getName();
System.out.println("Renderer " + renderer);
} catch (Throwable th) {
// may fail with JDK9 jigsaw (jake)
if (false) {
System.err.println("Unable to get RenderingEngine.getInstance()");
th.printStackTrace();
}
}
scroll.setBackground(Color.green);
scroll.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] screens = env.getScreenDevices();
// I want the external monitor attached to my notebook
GraphicsDevice device = screens[screens.length - 1];
boolean isFullScreenSupported = device.isFullScreenSupported();
scroll.setFullScreen(isFullScreenSupported);
scroll.setUndecorated(isFullScreenSupported);
scroll.setResizable(!isFullScreenSupported);
if (isFullScreenSupported) {
device.setFullScreenWindow(scroll);
scroll.setIgnoreRepaint(true);
scroll.validate();
} else {
// Windowed mode
Rectangle r = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
scroll.setSize(r.width, r.height);
scroll.pack();
scroll.setExtendedState(JFrame.MAXIMIZED_BOTH);
scroll.setVisible(true);
}
// exit on pressing escape
scroll.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
scroll.setRunning(false);
if(scroll.getScrollTimer() != null) {
scroll.getScrollTimer().stop();
}
System.exit(0);
}
}
});
scroll.setVisible(true);
scroll.init();
if (scroll.isUseGamingLoop()) {
scroll.startGamingloop();
} else {
scroll.start();
}
}
private void updateLeftEdge() {
leftEdge += speed;
}
public Timer getScrollTimer() {
return scrollTimer;
}
public void setFullScreen(boolean isFullScreen) {
this.isFullScreen = isFullScreen;
}
public void setTargetFps(int targetFps) {
this.targetFps = targetFps;
}
public void setOpenGl(boolean isOpenGl) {
this.isOpenGl = isOpenGl;
}
public void setVsync(boolean isVsync) {
this.isVsync = isVsync;
}
public void setUseGamingLoop(boolean useGamingLoop) {
this.useGamingLoop = useGamingLoop;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public void setMessage(String message) {
this.message = message;
}
private String getDeviceConfigurationString(GraphicsConfiguration gc){
return "Bounds: " + gc.getBounds() + "\n" +
"Buffer Capabilities: " + gc.getBufferCapabilities() + "\n" +
" Back Buffer Capabilities: " + gc.getBufferCapabilities().getBackBufferCapabilities() + "\n" +
" Accelerated: " + gc.getBufferCapabilities().getBackBufferCapabilities().isAccelerated() + "\n" +
" True Volatile: " + gc.getBufferCapabilities().getBackBufferCapabilities().isTrueVolatile() + "\n" +
" Flip Contents: " + gc.getBufferCapabilities().getFlipContents() + "\n" +
" Front Buffer Capabilities: " + gc.getBufferCapabilities().getFrontBufferCapabilities() + "\n" +
" Accelerated: " + gc.getBufferCapabilities().getFrontBufferCapabilities().isAccelerated() + "\n" +
" True Volatile: " + gc.getBufferCapabilities().getFrontBufferCapabilities().isTrueVolatile() + "\n" +
" Is Full Screen Required: " + gc.getBufferCapabilities().isFullScreenRequired() + "\n" +
" Is MultiBuffer Available: " + gc.getBufferCapabilities().isMultiBufferAvailable() + "\n" +
" Is Page Flipping: " + gc.getBufferCapabilities().isPageFlipping() + "\n" +
"Device: " + gc.getDevice() + "\n" +
" Available Accelerated Memory: " + gc.getDevice().getAvailableAcceleratedMemory() + "\n" +
" ID String: " + gc.getDevice().getIDstring() + "\n" +
" Type: " + gc.getDevice().getType() + "\n" +
" Display Mode: " + gc.getDevice().getDisplayMode() + "\n" +
"Image Capabilities: " + gc.getImageCapabilities() + "\n" +
" Accelerated: " + gc.getImageCapabilities().isAccelerated() + "\n" +
" True Volatile: " + gc.getImageCapabilities().isTrueVolatile() + "\n";
}
public boolean isOpenGl() {
return isOpenGl;
}
public boolean isUseGamingLoop() {
return useGamingLoop;
}
}
Output of graphics capabilities:
Renderer sun.java2d.pisces.PiscesRenderingEngine
Bounds: java.awt.Rectangle[x=3839,y=0,width=3840,height=2160]
Buffer Capabilities: sun.awt.X11GraphicsConfig$XDBECapabilities#68de145
Back Buffer Capabilities: java.awt.ImageCapabilities#27fa135a
Accelerated: false
True Volatile: false
Flip Contents: undefined
Front Buffer Capabilities: java.awt.ImageCapabilities#27fa135a
Accelerated: false
True Volatile: false
Is Full Screen Required: false
Is MultiBuffer Available: false
Is Page Flipping: true
Device: X11GraphicsDevice[screen=1]
Available Accelerated Memory: -1
ID String: :0.1
Type: 0
Display Mode: java.awt.DisplayMode#1769
Image Capabilities: java.awt.ImageCapabilities#27fa135a
Accelerated: false
True Volatile: false
EDIT 2:
I wrote the same in javafx now, same result, it is stuttering as soon as I move more than 3 pixels at once.
I'm running on an Intel i9 9900K, Nvidia GeForce RTX 2060 Mobile, Ubuntu, OpenJdk 14.
package scrolling;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import javax.swing.Timer;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.Screen;
import javafx.stage.Stage;
/**
* A smooth scroll with green background to be used with a video cutter.
*
* https://stackoverflow.com/questions/51478675/error-javafx-runtime-components-are-missing-and-are-required-to-run-this-appli
* https://stackoverflow.com/questions/18547362/javafx-and-openjdk
*
*/
public class MyScroll extends Application {
private boolean useGamingLoop = false;
private int speed = 3;
private String message;
private transient double leftEdge; // Offset from window's right edge to left edge
private Color bgColor;
private Color fgColor;
private double winWidth;
private int winHeight;
private double position = 0.77;
private int yPositionScroll;
private Image img;
private int msgWidth = 0;
private Timer scrollTimer;
private boolean isRunning;
private ImageView imageView;
long lastUpdateTime;
long lastIntervall;
long nextIntervall;
String ADAPTIVE_PULSE_PROP = "com.sun.scenario.animation.adaptivepulse";
int frame = 0;
long timeOfLastFrameSwitch = 0;
#Override
public void start(final Stage stage) {
message = "This is a test. ";
leftEdge = 0;
bgColor = Color.green;
fgColor = Color.white;
winWidth = (int)Screen.getPrimary().getBounds().getWidth();
winHeight = (int)Screen.getPrimary().getBounds().getHeight();
yPositionScroll = (int) (winHeight * position);
initScrollImage(stage);
stage.setFullScreenExitHint("");
stage.setAlwaysOnTop(true);
new AnimationTimer() {
#Override
public void handle(long now) {
nextIntervall = now - lastUpdateTime;
System.out.println(lastIntervall - nextIntervall);
lastUpdateTime = System.nanoTime();
drawScroll(stage);
lastIntervall = nextIntervall;
}
}.start();
//Creating a Group object
Group root = new Group(imageView);
//Creating a scene object
Scene scene = new Scene(root);
//Adding scene to the stage
stage.setScene(scene);
stage.show();
stage.setFullScreen(true);
}
/**
* Draw the entire text to an imageview and add to the scene.
*/
private void initScrollImage(Stage stage) {
int fontSize = (int)winWidth / 17;
Font theFont = new Font("Helvetica", Font.PLAIN, fontSize);
BufferedImage tempImg = new BufferedImage((int)winWidth, winHeight, BufferedImage.TYPE_INT_ARGB);
FontMetrics fontMetrics = tempImg.getGraphics().getFontMetrics(theFont);
Rectangle2D rect = fontMetrics.getStringBounds(message, tempImg.getGraphics());
msgWidth = fontMetrics.stringWidth(message);
BufferedImage bufferedImage = new BufferedImage((int) rect.getWidth(), (int) rect.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D graphicsScroll = bufferedImage.createGraphics();
graphicsScroll.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphicsScroll.setBackground(Color.BLACK);
graphicsScroll.setFont(theFont);
graphicsScroll.setColor(bgColor);
graphicsScroll.fillRect(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight()); // set background color
graphicsScroll.setColor(fgColor);
graphicsScroll.drawString(message, 1, bufferedImage.getHeight() - 10);
// for better readability in front of an image draw an outline arround the text
graphicsScroll.setColor(Color.black);
graphicsScroll.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Font font = new Font("Helvetica", Font.PLAIN, fontSize);
graphicsScroll.translate(1, bufferedImage.getHeight() - 10);
FontRenderContext frc = graphicsScroll.getFontRenderContext();
GlyphVector gv = font.createGlyphVector(frc, message);
graphicsScroll.draw(gv.getOutline());
img = SwingFXUtils.toFXImage(bufferedImage, null);
imageView = new ImageView(img);
imageView.setSmooth(false);
imageView.setCache(true);
//Setting the preserve ratio of the image view
imageView.setPreserveRatio(true);
tempImg.flush();
}
/**
* Draws (part) of the prerendered image with text to a position in the screen defined by an increasing
* variable "leftEdge".
*
* #return time drawing took.
*/
private void drawScroll(Stage stage) {
leftEdge += speed;
if (winWidth - leftEdge + msgWidth < 0) { // Current scroll entirely off-screen.
leftEdge = 0;
}
// imageView.relocate(winWidth - leftEdge, yPositionScroll);
imageView.setX(winWidth - leftEdge);
}
public static void main(String[] args) {
// System.setProperty("sun.java2d.opengl", "true");
System.setProperty("prism.vsync", "true");
// System.setProperty("com.sun.scenario.animation.adaptivepulse", "true");
System.setProperty("com.sun.scenario.animation.vsync", "true");
launch(args);
}
public Timer getScrollTimer() {
return scrollTimer;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public void setMessage(String message) {
this.message = message;
}
public boolean isUseGamingLoop() {
return useGamingLoop;
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
}
I'm running this with Windows 10. I don't think it will work well on a Unix system.
...
The code consists of 9 classes in 5 packages. The package name is in the code.
Marquee Class
package com.ggl.marquee;
import javax.swing.SwingUtilities;
import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;
public class Marquee implements Runnable {
#Override
public void run() {
new MarqueeFrame(new MarqueeModel());
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Marquee());
}
}
CreateMarqueeActionListener Class
package com.ggl.marquee.controller;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JTextField;
import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;
public class CreateMarqueeActionListener implements ActionListener {
private JTextField field;
private MarqueeFrame frame;
private MarqueeModel model;
public CreateMarqueeActionListener(MarqueeFrame frame, MarqueeModel model,
JTextField field) {
this.frame = frame;
this.model = model;
this.field = field;
}
#Override
public void actionPerformed(ActionEvent event) {
model.stopDtpRunnable();
model.resetPixels();
String s = field.getText().trim();
if (s.equals("")) {
frame.repaintMarqueePanel();
return;
}
s = " " + s + " ";
model.setTextPixels(model.getDefaultFont().getTextPixels(s));
frame.repaintMarqueePanel();
}
}
FontSelectionListener Class
package com.ggl.marquee.controller;
import javax.swing.DefaultListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import com.ggl.marquee.model.MarqueeFont;
import com.ggl.marquee.model.MarqueeModel;
public class FontSelectionListener implements ListSelectionListener {
private MarqueeModel model;
public FontSelectionListener(MarqueeModel model) {
this.model = model;
}
#Override
public void valueChanged(ListSelectionEvent event) {
DefaultListSelectionModel selectionModel = (DefaultListSelectionModel) event
.getSource();
if (!event.getValueIsAdjusting()) {
int index = selectionModel.getMinSelectionIndex();
if (index >= 0) {
MarqueeFont font = model.getDefaultListModel().get(index);
model.setDefaultFont(font);
}
}
}
}
FontGenerator Class
package com.ggl.marquee.model;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FontGenerator {
private static final boolean DEBUG = false;
private Font font;
private FontHeights fontHeights;
private Map<Character, MarqueeCharacter> characterMap;
public FontGenerator(Font font) {
this.font = font;
this.characterMap = new HashMap<Character, MarqueeCharacter>();
}
public void execute() {
int width = 50;
BufferedImage bi = generateCharacterImage(width, "B");
int[] result1 = getCharacterHeight(bi);
bi = generateCharacterImage(width, "g");
int[] result2 = getCharacterHeight(bi);
fontHeights = new FontHeights(result1[0], result1[1], result2[1]);
if (DEBUG) System.out.println(fontHeights.getAscender() + ", "
+ fontHeights.getBaseline() + ", "
+ fontHeights.getDescender());
for (int x = 32; x < 127; x++) {
char c = (char) x;
StringBuilder builder = new StringBuilder(3);
builder.append('H');
builder.append(c);
builder.append('H');
bi = generateCharacterImage(width, builder.toString());
int[][] pixels = convertTo2D(bi);
MarqueeCharacter mc = getCharacterPixels(pixels);
if (DEBUG) {
System.out.println(builder.toString() + " " +
mc.getWidth() + "x" + mc.getHeight());
}
characterMap.put(c, mc);
}
}
private BufferedImage generateCharacterImage(int width, String string) {
BufferedImage bi = new BufferedImage(
width, width, BufferedImage.TYPE_INT_RGB);
Graphics g = bi.getGraphics();
g.setFont(font);
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, width);
g.setColor(Color.BLACK);
g.drawString(string, 0, width / 2);
return bi;
}
private int[] getCharacterHeight(BufferedImage bi) {
int[][] pixels = convertTo2D(bi);
int minHeight = bi.getHeight();
int maxHeight = 0;
for (int i = 0; i < pixels.length; i++) {
for (int j = 0; j < pixels[i].length; j++) {
if (pixels[i][j] < -1) {
minHeight = Math.min(i, minHeight);
maxHeight = Math.max(i, maxHeight);
}
}
}
int[] result = new int[2];
result[0] = minHeight;
result[1] = maxHeight;
return result;
}
private MarqueeCharacter getCharacterPixels(int[][] pixels) {
List<Boolean[]> list = new ArrayList<Boolean[]>();
int startRow = fontHeights.getAscender();
int endRow = fontHeights.getDescender();
int height = fontHeights.getCharacterHeight();
int startColumn = getCharacterColumnStart(pixels);
int endColumn = getCharacterColumnEnd(pixels);
for (int i = startColumn; i <= endColumn; i++) {
Boolean[] characterColumn = new Boolean[height];
int k = 0;
for (int j = startRow; j <= endRow; j++) {
if (pixels[j][i] < -1) characterColumn[k] = true;
else characterColumn[k] = false;
k++;
}
list.add(characterColumn);
}
MarqueeCharacter mc = new MarqueeCharacter(list.size(), height);
for (int i = 0; i < list.size(); i++) {
Boolean[] characterColumn = list.get(i);
mc.setColumn(characterColumn);
}
return mc;
}
private int getCharacterColumnStart(int[][] pixels) {
int start = fontHeights.getAscender();
int end = fontHeights.getBaseline();
int letterEndFlag = 0;
int column = 1;
while (letterEndFlag < 1) {
boolean pixelDetected = false;
for (int i = start; i <= end; i++) {
if (pixels[i][column] < -1) {
pixelDetected = true;
}
}
column++;
// End of first letter
if ((letterEndFlag == 0) && !pixelDetected) letterEndFlag = 1;
}
return column;
}
private int getCharacterColumnEnd(int[][] pixels) {
int start = fontHeights.getAscender();
int end = fontHeights.getBaseline();
int height = fontHeights.getCharacterHeight2();
int letterEndFlag = 0;
int column = pixels.length - 1;
while (letterEndFlag < 4) {
int pixelCount = 0;
for (int i = start; i <= end; i++) {
if (pixels[i][column] < -1) {
pixelCount++;
}
}
column--;
// End of first letter
if (pixelCount >= height) letterEndFlag++;
// Start of first letter
// if ((letterEndFlag == 0) && (pixelCount > 0)) letterEndFlag = 1;
}
return column;
}
private int[][] convertTo2D(BufferedImage image) {
final int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer())
.getData();
final int width = image.getWidth();
final int height = image.getHeight();
int[][] result = new int[height][width];
for (int pixel = 0, row = 0, col = 0; pixel < pixels.length; pixel++) {
result[row][col] = pixels[pixel];
col++;
if (col == width) {
col = 0;
row++;
}
}
return result;
}
public MarqueeCharacter getCharacter(Character c) {
MarqueeCharacter mc = characterMap.get(c);
return (mc == null) ? characterMap.get('?') : mc;
}
public int getCharacterHeight() {
return fontHeights.getCharacterHeight();
}
}
FontHeights Class
package com.ggl.marquee.model;
public class FontHeights {
private final int ascender;
private final int baseline;
private final int descender;
public FontHeights(int ascender, int baseline, int descender) {
this.ascender = ascender;
this.baseline = baseline;
this.descender = descender;
}
public int getCharacterHeight() {
return descender - ascender + 1;
}
public int getCharacterHeight2() {
return baseline - ascender + 1;
}
public int getAscender() {
return ascender;
}
public int getBaseline() {
return baseline;
}
public int getDescender() {
return descender;
}
}
MarqueeCharacter Class
package com.ggl.marquee.model;
import java.security.InvalidParameterException;
public class MarqueeCharacter {
private static int columnCount;
private int height;
private int width;
private boolean[][] pixels;
public MarqueeCharacter(int width, int height) {
this.width = width;
this.height = height;
this.pixels = new boolean[width][height];
columnCount = 0;
}
public void setColumn(Boolean[] value) {
int height = value.length;
if (this.height != height) {
String s = "The number of values must equal the column height - "
+ this.height;
throw new InvalidParameterException(s);
}
for (int i = 0; i < height; i++) {
pixels[columnCount][i] = value[i];
}
columnCount++;
}
public boolean[][] getPixels() {
return pixels;
}
public boolean isComplete() {
return (width == columnCount);
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
}
MarqueeFont Class
package com.ggl.marquee.model;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
public class MarqueeFont {
private static final boolean DEBUG = false;
private int fontHeight;
private Font font;
public MarqueeFont(Font font) {
this.font = font;
FontRenderContext frc = new FontRenderContext(null, true, true);
Rectangle2D r2D = font.getStringBounds("HgH", frc);
this.fontHeight = (int) Math.round(r2D.getHeight());
if (DEBUG) {
System.out.println(font.getFamily() + " " + fontHeight + " pixels");
}
}
public boolean[][] getTextPixels(String s) {
FontRenderContext frc = new FontRenderContext(null, true, true);
Rectangle2D r2D = font.getStringBounds(s, frc);
int rWidth = (int) Math.round(r2D.getWidth());
int rHeight = (int) Math.round(r2D.getHeight());
int rX = (int) Math.round(r2D.getX());
int rY = (int) Math.round(r2D.getY());
if (DEBUG) {
System.out.print(s);
System.out.print(", rWidth = " + rWidth);
System.out.print(", rHeight = " + rHeight);
System.out.println(", rX = " + rX + ", rY = " + rY);
}
BufferedImage bi = generateCharacterImage(rX, -rY, rWidth, rHeight, s);
int[][] pixels = convertTo2D(bi);
if (DEBUG) {
displayPixels(pixels);
}
return createTextPixels(pixels);
}
private BufferedImage generateCharacterImage(int x, int y, int width,
int height, String string) {
BufferedImage bi = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
Graphics g = bi.getGraphics();
g.setFont(font);
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
g.setColor(Color.BLACK);
g.drawString(string, x, y);
return bi;
}
private int[][] convertTo2D(BufferedImage image) {
final int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer())
.getData();
final int width = image.getWidth();
final int height = image.getHeight();
int[][] result = new int[height][width];
int row = 0;
int col = 0;
for (int pixel = 0; pixel < pixels.length; pixel++) {
result[row][col] = pixels[pixel];
col++;
if (col == width) {
col = 0;
row++;
}
}
return result;
}
private void displayPixels(int[][] pixels) {
for (int i = 0; i < pixels.length; i++) {
String s = String.format("%03d", (i + 1));
System.out.print(s + ". ");
for (int j = 0; j < pixels[i].length; j++) {
if (pixels[i][j] == -1) {
System.out.print(" ");
} else {
System.out.print("X ");
}
}
System.out.println("");
}
}
private boolean[][] createTextPixels(int[][] pixels) {
// The int array pixels is in column, row order.
// We have to flip the array and produce the output
// in row, column order.
if (DEBUG) {
System.out.println(pixels[0].length + "x" + pixels.length);
}
boolean[][] textPixels = new boolean[pixels[0].length][pixels.length];
for (int i = 0; i < pixels.length; i++) {
for (int j = 0; j < pixels[i].length; j++) {
if (pixels[i][j] == -1) {
textPixels[j][i] = false;
} else {
textPixels[j][i] = true;
}
}
}
return textPixels;
}
public Font getFont() {
return font;
}
public int getFontHeight() {
return fontHeight;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(font.getFamily());
builder.append(", ");
builder.append(getStyleText());
builder.append(", ");
builder.append(font.getSize());
builder.append(" pixels");
return builder.toString();
}
private StringBuilder getStyleText() {
StringBuilder builder = new StringBuilder();
int style = font.getStyle();
if (style == Font.PLAIN) {
builder.append("normal");
} else if (style == Font.BOLD) {
builder.append("bold");
} else if (style == Font.ITALIC) {
builder.append("italic");
} else if (style == (Font.BOLD + Font.ITALIC)) {
builder.append("bold italic");
} else {
builder.append("unknown style");
}
return builder;
}
}
MarqueeFontFactory Class
package com.ggl.marquee.model;
import java.awt.Font;
import javax.swing.DefaultListModel;
public class MarqueeFontFactory {
private DefaultListModel<MarqueeFont> fontList;
private MarqueeFont defaultFont;
public MarqueeFontFactory() {
this.fontList = new DefaultListModel<MarqueeFont>();
addElements();
}
private void addElements() {
this.defaultFont = new MarqueeFont(new Font("Arial", Font.BOLD, 16));
fontList.addElement(defaultFont);
fontList.addElement(new MarqueeFont(new Font("Cambria", Font.BOLD, 16)));
fontList.addElement(new MarqueeFont(new Font("Courier New", Font.BOLD,
16)));
fontList.addElement(new MarqueeFont(new Font("Georgia", Font.BOLD, 16)));
fontList.addElement(new MarqueeFont(new Font("Lucida Calligraphy",
Font.BOLD, 16)));
fontList.addElement(new MarqueeFont(new Font("Times New Roman",
Font.BOLD, 16)));
fontList.addElement(new MarqueeFont(new Font("Verdana", Font.BOLD, 16)));
}
public DefaultListModel<MarqueeFont> getFontList() {
return fontList;
}
public void setDefaultFont(MarqueeFont defaultFont) {
this.defaultFont = defaultFont;
}
public MarqueeFont getDefaultFont() {
return defaultFont;
}
public int getCharacterHeight() {
int maxHeight = 0;
for (int i = 0; i < fontList.getSize(); i++) {
MarqueeFont font = fontList.get(i);
int height = font.getFontHeight();
maxHeight = Math.max(height, maxHeight);
}
return maxHeight;
}
}
MarqueeModel Class
package com.ggl.marquee.model;
import javax.swing.DefaultListModel;
import com.ggl.marquee.runnable.DisplayTextPixelsRunnable;
import com.ggl.marquee.view.MarqueeFrame;
public class MarqueeModel {
private static final int marqueeWidth = 120;
private boolean[][] marqueePixels;
private boolean[][] textPixels;
private DisplayTextPixelsRunnable dtpRunnable;
private MarqueeFontFactory fonts;
private MarqueeFrame frame;
public MarqueeModel() {
this.fonts = new MarqueeFontFactory();
this.marqueePixels = new boolean[marqueeWidth][getMarqueeHeight()];
}
public void setFrame(MarqueeFrame frame) {
this.frame = frame;
}
public MarqueeFontFactory getFonts() {
return fonts;
}
public DefaultListModel<MarqueeFont> getDefaultListModel() {
return fonts.getFontList();
}
public MarqueeFont getDefaultFont() {
return fonts.getDefaultFont();
}
public void setDefaultFont(MarqueeFont defaultFont) {
fonts.setDefaultFont(defaultFont);
}
public boolean[][] getMarqueePixels() {
return marqueePixels;
}
public boolean getMarqueePixel(int width, int height) {
return marqueePixels[width][height];
}
public int getMarqueeWidth() {
return marqueeWidth;
}
public int getMarqueeHeight() {
return fonts.getCharacterHeight();
}
public boolean[][] getTextPixels() {
return textPixels;
}
public int getTextPixelWidth() {
return textPixels.length;
}
private void startDtpRunnable() {
dtpRunnable = new DisplayTextPixelsRunnable(frame, this);
new Thread(dtpRunnable).start();
}
public void stopDtpRunnable() {
if (dtpRunnable != null) {
dtpRunnable.stopDisplayTextPixelsRunnable();
dtpRunnable = null;
}
}
public void setTextPixels(boolean[][] textPixels) {
this.textPixels = textPixels;
if (textPixels.length < getMarqueeWidth()) {
this.marqueePixels = copyCharacterPixels(0, textPixels,
marqueePixels);
} else {
startDtpRunnable();
}
}
public void resetPixels() {
for (int i = 0; i < getMarqueeWidth(); i++) {
for (int j = 0; j < getMarqueeHeight(); j++) {
marqueePixels[i][j] = false;
}
}
}
public void setAllPixels() {
for (int i = 0; i < getMarqueeWidth(); i++) {
for (int j = 0; j < getMarqueeHeight(); j++) {
marqueePixels[i][j] = true;
}
}
}
public boolean[][] copyCharacterPixels(int position,
boolean[][] characterPixels, boolean[][] textPixels) {
for (int i = 0; i < characterPixels.length; i++) {
for (int j = 0; j < characterPixels[i].length; j++) {
textPixels[i + position][j] = characterPixels[i][j];
}
}
return textPixels;
}
public void copyTextPixels(int position) {
for (int i = 0; i < marqueePixels.length; i++) {
int k = i + position;
k %= textPixels.length;
for (int j = 0; j < textPixels[i].length; j++) {
marqueePixels[i][j] = textPixels[k][j];
}
}
}
}
DisplayAllPixelsRunnable Class
package com.ggl.marquee.runnable;
import javax.swing.SwingUtilities;
import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;
public class DisplayAllPixelsRunnable implements Runnable {
private MarqueeFrame frame;
private MarqueeModel model;
public DisplayAllPixelsRunnable(MarqueeFrame frame, MarqueeModel model) {
this.frame = frame;
this.model = model;
}
#Override
public void run() {
model.setAllPixels();
repaint();
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
}
model.resetPixels();
repaint();
}
private void repaint() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
frame.repaintMarqueePanel();
}
});
}
}
DisplayTextPixelsRunnable Class
package com.ggl.marquee.runnable;
import javax.swing.SwingUtilities;
import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;
public class DisplayTextPixelsRunnable implements Runnable {
private static int textPixelPosition;
private volatile boolean running;
private MarqueeFrame frame;
private MarqueeModel model;
public DisplayTextPixelsRunnable(MarqueeFrame frame, MarqueeModel model) {
this.frame = frame;
this.model = model;
textPixelPosition = 0;
}
#Override
public void run() {
this.running = true;
while (running) {
model.copyTextPixels(textPixelPosition);
repaint();
sleep();
textPixelPosition++;
textPixelPosition %= model.getTextPixelWidth();
}
}
private void repaint() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
frame.repaintMarqueePanel();
}
});
}
private void sleep() {
try {
Thread.sleep(50L);
} catch (InterruptedException e) {
}
}
public synchronized void stopDisplayTextPixelsRunnable() {
this.running = false;
}
}
ControlPanel Class
package com.ggl.marquee.view;
import java.awt.BorderLayout;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import com.ggl.marquee.controller.CreateMarqueeActionListener;
import com.ggl.marquee.controller.FontSelectionListener;
import com.ggl.marquee.model.MarqueeFont;
import com.ggl.marquee.model.MarqueeModel;
public class ControlPanel {
private JButton submitButton;
private JPanel panel;
private MarqueeFrame frame;
private MarqueeModel model;
public ControlPanel(MarqueeFrame frame, MarqueeModel model) {
this.frame = frame;
this.model = model;
createPartControl();
}
private void createPartControl() {
panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
JPanel fontPanel = new JPanel();
fontPanel.setLayout(new BorderLayout());
JLabel fontLabel = new JLabel("Fonts");
fontPanel.add(fontLabel, BorderLayout.NORTH);
JList<MarqueeFont> fontList = new JList<MarqueeFont>(
model.getDefaultListModel());
fontList.setSelectedValue(model.getDefaultFont(), true);
fontList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
fontList.setVisibleRowCount(3);
ListSelectionModel listSelectionModel = fontList.getSelectionModel();
listSelectionModel.addListSelectionListener(new FontSelectionListener(
model));
JScrollPane fontScrollPane = new JScrollPane(fontList);
fontPanel.add(fontScrollPane, BorderLayout.CENTER);
panel.add(fontPanel);
JPanel fieldPanel = new JPanel();
JLabel fieldLabel = new JLabel("Marquee Text: ");
fieldPanel.add(fieldLabel);
JTextField field = new JTextField(30);
fieldPanel.add(field);
panel.add(fieldPanel);
JPanel buttonPanel = new JPanel();
submitButton = new JButton("Submit");
submitButton.addActionListener(new CreateMarqueeActionListener(frame,
model, field));
buttonPanel.add(submitButton);
panel.add(buttonPanel);
}
public JPanel getPanel() {
return panel;
}
public JButton getSubmitButton() {
return submitButton;
}
}
MarqueeFrame Class
package com.ggl.marquee.view;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.runnable.DisplayAllPixelsRunnable;
public class MarqueeFrame {
private ControlPanel controlPanel;
private DisplayAllPixelsRunnable dapRunnable;
private JFrame frame;
private MarqueeModel model;
private MarqueePanel marqueePanel;
public MarqueeFrame(MarqueeModel model) {
this.model = model;
model.setFrame(this);
createPartControl();
}
private void createPartControl() {
frame = new JFrame();
// frame.setIconImage(getFrameImage());
frame.setTitle("Marquee");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent event) {
exitProcedure();
}
});
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS));
marqueePanel = new MarqueePanel(model);
mainPanel.add(marqueePanel);
controlPanel = new ControlPanel(this, model);
mainPanel.add(controlPanel.getPanel());
frame.add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.getRootPane().setDefaultButton(controlPanel.getSubmitButton());
frame.setVisible(true);
dapRunnable = new DisplayAllPixelsRunnable(this, model);
new Thread(dapRunnable).start();
}
private void exitProcedure() {
frame.dispose();
System.exit(0);
}
public void repaintMarqueePanel() {
marqueePanel.repaint();
}
}
MarqueePanel Class
package com.ggl.marquee.view;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;
import com.ggl.marquee.model.MarqueeModel;
public class MarqueePanel extends JPanel {
private static final long serialVersionUID = -1677343084333836763L;
private static final int pixelWidth = 4;
private static final int gapWidth = 2;
private static final int totalWidth = pixelWidth + gapWidth;
private static final int yStart = gapWidth + totalWidth + totalWidth;
private MarqueeModel model;
public MarqueePanel(MarqueeModel model) {
this.model = model;
int width = model.getMarqueeWidth() * totalWidth + gapWidth;
int height = model.getMarqueeHeight() * totalWidth + yStart + yStart;
setPreferredSize(new Dimension(width, height));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillRect(0, 0, getWidth(), getHeight());
int x = gapWidth;
int y = yStart;
for (int i = 0; i < model.getMarqueeWidth(); i++) {
for (int j = 0; j < model.getMarqueeHeight(); j++) {
if (model.getMarqueePixel(i, j)) {
g.setColor(Color.PINK);
} else {
g.setColor(Color.BLACK);
}
g.fillRect(x, y, pixelWidth, pixelWidth);
y += totalWidth;
}
y = yStart;
x += totalWidth;
}
}
}
I did a few changes upon the same example (Marquee) posted by #Gilbert Le Blanc.
First of all, in the Marquee example the Thread.sleep() method was being used instead of a more modern approach with a TimerTask. I cannot stress enough about how much difference there is between Thread.sleep() and TimerTask.
Thread.sleep() is inaccurate. How inaccurate depends on the underlying operating system and its timers and schedulers. I've experienced that garbage collection going on in parallel can lead to excessive sleep.
Source: src
This led to an important problem
The painting function is called inconsistently instead of the expected fixed time for 30fps which is roughly 40ms, this generates some of the shuttering problem.
Source: me
which is solved partially by the TimerTask approach
On at least one major operating system (Windows), blocking on a timer has a much better precision and reliability than sleeping.
Source: GameDev.net
I have noticed a huge improvement when i rewrote the same example but using timerTask and it's surely more fluid than using the example you provided (2D Swing);
I compared it as following, the speed of the Marquee example is more or less the same speed of your 2d swing test put at 10, so try running your test with int speed = 10; and the one provided above and see if shutter more or less.
Alternatively, you can try to run the native Marquee Example and the new one with TimerTask and you should see an important major difference.
I think implementing a buffer strategy with this one is the way to go for achieving a truly fluid scroll experience...otherwise there is always OpenGL
The classes i changed:
DisplayTextPixelsRunnable is now DisplayTextPixelsTimerTask
package com.ggl.marquee.runnable;
import javax.swing.SwingUtilities;
import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;
import java.util.TimerTask;
public class DisplayTextPixelsTimerTask extends TimerTask {
private static int textPixelPosition;
private final MarqueeFrame frame;
private final MarqueeModel model;
public DisplayTextPixelsTimerTask(MarqueeFrame frame, MarqueeModel model) {
this.frame = frame;
this.model = model;
textPixelPosition = 0;
}
#Override
public void run() {
model.copyTextPixels(textPixelPosition);
repaint();
textPixelPosition++;
textPixelPosition %= model.getTextPixelWidth();
}
private void repaint() {
SwingUtilities.invokeLater(frame::repaintMarqueePanel);
}
}
MarqueeModel
package com.ggl.marquee.model;
import javax.swing.DefaultListModel;
import com.ggl.marquee.runnable.DisplayTextPixelsTimerTask;
import com.ggl.marquee.view.MarqueeFrame;
import java.util.Timer;
import java.util.TimerTask;
public class MarqueeModel {
private static final int marqueeWidth = 120;
private static final long FPS_TARGET = 30;
private static final long DELAY_TIME = (long) (1000.0d / FPS_TARGET);
private boolean[][] marqueePixels;
private boolean[][] textPixels;
private TimerTask dtpRunnable;
private MarqueeFontFactory fonts;
private MarqueeFrame frame;
public MarqueeModel() {
this.fonts = new MarqueeFontFactory();
this.marqueePixels = new boolean[marqueeWidth][getMarqueeHeight()];
}
public void setFrame(MarqueeFrame frame) {
this.frame = frame;
}
public MarqueeFontFactory getFonts() {
return fonts;
}
public DefaultListModel<MarqueeFont> getDefaultListModel() {
return fonts.getFontList();
}
public MarqueeFont getDefaultFont() {
return fonts.getDefaultFont();
}
public void setDefaultFont(MarqueeFont defaultFont) {
fonts.setDefaultFont(defaultFont);
}
public boolean[][] getMarqueePixels() {
return marqueePixels;
}
public boolean getMarqueePixel(int width, int height) {
return marqueePixels[width][height];
}
public int getMarqueeWidth() {
return marqueeWidth;
}
public int getMarqueeHeight() {
return fonts.getCharacterHeight();
}
public boolean[][] getTextPixels() {
return textPixels;
}
public int getTextPixelWidth() {
return textPixels.length;
}
private void startDtpRunnable() {
dtpRunnable = new DisplayTextPixelsTimerTask(frame, this);
//running timer task as daemon thread
Timer timer = new Timer(true);
timer.scheduleAtFixedRate(dtpRunnable, 0, DELAY_TIME);
}
public void stopDtpRunnable() {
if (dtpRunnable != null) {
dtpRunnable.cancel();
dtpRunnable = null;
}
}
public void setTextPixels(boolean[][] textPixels) {
this.textPixels = textPixels;
if (textPixels.length < getMarqueeWidth()) {
this.marqueePixels = copyCharacterPixels(0, textPixels,
marqueePixels);
} else {
startDtpRunnable();
}
}
public void resetPixels() {
for (int i = 0; i < getMarqueeWidth(); i++) {
for (int j = 0; j < getMarqueeHeight(); j++) {
marqueePixels[i][j] = false;
}
}
}
public void setAllPixels() {
for (int i = 0; i < getMarqueeWidth(); i++) {
for (int j = 0; j < getMarqueeHeight(); j++) {
marqueePixels[i][j] = true;
}
}
}
public boolean[][] copyCharacterPixels(int position,
boolean[][] characterPixels, boolean[][] textPixels) {
for (int i = 0; i < characterPixels.length; i++) {
for (int j = 0; j < characterPixels[i].length; j++) {
textPixels[i + position][j] = characterPixels[i][j];
}
}
return textPixels;
}
public void copyTextPixels(int position) {
for (int i = 0; i < marqueePixels.length; i++) {
int k = i + position;
k %= textPixels.length;
for (int j = 0; j < textPixels[i].length; j++) {
marqueePixels[i][j] = textPixels[k][j];
}
}
}
}
I don't think it's possible to do more than that using only Java.
To be honest, the Marquee example is the smoothest i saw.

KeyAdapter input in java not working

I just wrote some code to make my player move in my little maze game, but nothing happens. Also my maze is not drawn correct as in the matrix input. I don't figure out why is wrong this code...any help is well appeciated.
Thank you!
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import javax.swing.*;
public class Main extends JPanel {
private static Image white;
private static Image black;
private static Image finish;
private static Image player;
private static int x = 1;
private static int y = 1;
private String matrix[][];
public Main() {
addKeyListener(new Keys());
setFocusable(true);
}
public static String[][] load(String input) {
List<String[]> rows = new ArrayList<>();
try (Scanner scanner = new Scanner(input)) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
String[] cols = new String[line.length()];
for (int i = 0; i < cols.length; i++) {
cols[i] = line.substring(i, i + 1);
}
rows.add(cols);
}
}
return rows.toArray(new String[rows.size()][]);
}
public static JFrame buildFrame() {
JFrame frame = new JFrame("Labyrinth Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(900, 950);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
return frame;
}
public void moveUp() {
x += 0;
y += -1;
}
public void moveLeft() {
x += -1;
y += 0;
}
public void moveDown() {
x += 0;
y += 1;
}
public void moveRight() {
x += 1;
y += 0;
}
public class Keys extends KeyAdapter {
#Override
public void keyPressed(KeyEvent e) {
int keycode = e.getKeyCode();
// repaint();
if (keycode == KeyEvent.VK_W) {
if (!matrix[getX()][getY() - 1].equals("1")) {
moveUp();
}
}
if (keycode == KeyEvent.VK_A) {
if (!matrix[getX() - 1][getY()].equals("1")) {
moveLeft();
}
}
if (keycode == KeyEvent.VK_S) {
if (!matrix[getX()][getY() + 1].equals("1")) {
moveDown();
}
}
if (keycode == KeyEvent.VK_D) {
if (!matrix[getX() + 1][getY()].equals("1")) {
moveRight();
}
}
}
#Override
public void keyReleased(KeyEvent event) {
}
}
public static void main(String[] args) {
String input = "1111111111111111111111111111111111111111111\n"
+ "1000000010001000001000000010000000100000001\n"
+ "1010111010101010101111101011111010111111101\n"
+ "1010001010100010100000001010000010000010001\n"
+ "1011101010111110101111111010111111111010111\n"
+ "1000101010100000101000001000100010000010001\n"
+ "1011101011101011111011101111111010111110101\n"
+ "1010001000001010000010100000001010000010101\n"
+ "1010111111111010111110111111101011111011101\n"
+ "1010100000100010100000000000101000000000101\n"
+ "1110101111101110111110111011101011111110101\n"
+ "1000100000000010000010100010001000100010001\n"
+ "1011111111111111111011101010111111101011101\n"
+ "1000000000000000100010001010000000001010001\n"
+ "1011111111111011101110111011111111111010111\n"
+ "1000100010001000001010001000100000001010101\n"
+ "1110101011101111111010101110111110111010101\n"
+ "1000101010001000100000101000100000100010001\n"
+ "1011101010111010101111101011101110101111111\n"
+ "1000001010000010000000101000001000100010001\n"
+ "1111111011111110111111101111111011111010101\n"
+ "1000001010000010100010001000000010000010101\n"
+ "1011111010111011101010111011111110101110101\n"
+ "1010000010001010001010001000100000101010101\n"
+ "1010111111101010111011101111101111101011101\n"
+ "1000100000001010101010001000100010101000101\n"
+ "1011111011111010101010111010111010101011101\n"
+ "1010000010001000101010000010001010001000001\n"
+ "1010101110101111101011101111101011111010101\n"
+ "1010101000101000001000101000001000000010101\n"
+ "1011101011111010111110111011101111111110111\n"
+ "1000001000000010000000000010000000000010021\n"
+ "1111111111111111111111111111111111111111111\n";
String[][] matrix = load(input);
JFrame frame = buildFrame();
ImageIcon img = new ImageIcon("C:/Users/Desktop/black20.png");
black = img.getImage();
img = new ImageIcon("C:/Users/Desktop/gri20.png");
white = img.getImage();
img = new ImageIcon("C:/Users/Desktop/finish20.png");
finish = img.getImage();
img = new ImageIcon("C:/Users/Desktop/smiley20.png");
player = img.getImage();
// frame.add(new Player());
JPanel pane = new JPanel() {
#Override
public void paint(Graphics g) {
super.paint(g);
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length; j++) {
if (matrix[i][j].equals("1")) {
g.drawImage(black, i * 20, j * 20, null);
}
if (matrix[i][j].equals("0")) {
g.drawImage(white, i * 20, j * 20, null);
}
if (matrix[i][j].equals("2")) {
g.drawImage(finish, i * 20, j * 20, null);
}
}
}
g.drawImage(player, x * 20, y * 20, null);
}
};
frame.add(pane);
frame.add(new Main());
}
}
It should look like:
Problems/Suggestions:
You're adding more than one component to the JFrame in a default fashion. Since the JFrame's contentPane uses BorderLayout, only one component will display, the last one added, and the other will be completely covered and will remain invisible.
Your Main JPanel is not being used as a true JPanel. Nothing is being added to it, and in fact it looks like it should be a logical class and not a component class.
Rather than using KeyListeners, which are very fidgety when it comes to focus problems, use Key Bindings which will allow you to get around focus issues in a clean and higher level way.
Don't override the JPanel's paint method but rather its paintComponent method as this will give you several advantages, including default use of double buffering for your animation.
Don't give component classes a public int getX() and public int getY() method without care since these override key methods that place the component within its container. Since the Main class shouldn't even extend JPanel, this will end up to be a non-issue for your code, but in the future, it would mess your program up.
When you run into similar problems, such as a KeyListener not working, remove the issue from your big program and try to reproduce it in a small separate program. This will give you a much cleaner environment for helping you to isolate and understand your problem and thereby help you fix it.
Your program is mis-using the static modifier. Your x and y fields should not be static and should not be accessed in a static way.
You've got way too much code within your main method, which is one of the reasons why you likely made x and y static, because you were forced to do so since you're trying to access them within main. The solution is not to make the fields static but to get all that code out of the static world and into the instance world.
Edit
For some reason the question intrigued me, and so I decided to try to M-V-C or Model-View-Controller it if possible and see what I could come up with. So here goes a bunch of classes that sort of work, beginning with a text file that holds the data.
The GUI looks like:
It must be in the same location as the class files since it is obtained as a resource and must have the file name "input.txt"
1111111111111111111111111111111111111111111
1000000010001000001000000010000000100000001
1010111010101010101111101011111010111111101
1010001010100010100000001010000010000010001
1011101010111110101111111010111111111010111
1000101010100000101000001000100010000010001
1011101011101011111011101111111010111110101
1010001000001010000010100000001010000010101
1010111111111010111110111111101011111011101
1010100000100010100000000000101000000000101
1110101111101110111110111011101011111110101
1000100000000010000010100010001000100010001
1011111111111111111011101010111111101011101
1000000000000000100010001010000000001010001
1011111111111011101110111011111111111010111
1000100010001000001010001000100000001010101
1110101011101111111010101110111110111010101
1000101010001000100000101000100000100010001
1011101010111010101111101011101110101111111
1000001010000010000000101000001000100010001
1111111011111110111111101111111011111010101
1000001010000010100010001000000010000010101
1011111010111011101010111011111110101110101
1010000010001010001010001000100000101010101
1010111111101010111011101111101111101011101
1000100000001010101010001000100010101000101
1011111011111010101010111010111010101011101
1010000010001000101010000010001010001000001
1010101110101111101011101111101011111010101
1010101000101000001000101000001000000010101
1011101011111010111110111011101111111110111
1000001000000010000000000010000000000010021
1111111111111111111111111111111111111111111
Next the main program, the one that creates the model, the view, and the controller, hooks them all together, and then displays the GUI:
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/**
* link: http://stackoverflow.com/a/41418250/522444
*
* #author Pete
*
*/
#SuppressWarnings("serial")
public class Main2 extends JPanel {
private View mainPanel;
public Main2(MatrixModel matrixModel) {
mainPanel = new View(matrixModel);
new Controller(matrixModel, mainPanel);
setLayout(new BorderLayout());
add(mainPanel, BorderLayout.CENTER);
}
private static void createAndShowGui(MatrixModel model) {
Main2 mainPanel = new Main2(model);
JFrame frame = new JFrame("Main2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
final MatrixModel model = MatrixUtil.getInput(MatrixUtil.PATH_TO_RSC);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui(model);
}
});
}
}
Next a utility class with static methods for reading in the text file as a resource and converting it into a Model object:
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class MatrixUtil {
// again this text file must be in the jar file or the code base
// at the same location as the class / java files
public static final String PATH_TO_RSC = "input.txt";
public static MatrixModel getInput(String resourcePath) {
InputStream is = MatrixUtil.class.getResourceAsStream(resourcePath);
if (is == null) {
String text = "resourcePath is not found and not loading text: " + resourcePath;
throw new IllegalArgumentException(text);
}
return getInput(is);
}
public static MatrixModel getInput(InputStream is) {
MatrixModel model = null;
try (Scanner scan = new Scanner(is)) {
List<List<MatrixPosition>> listOfLists = new ArrayList<>();
while (scan.hasNextLine()) {
String line = scan.nextLine();
if (line.trim().isEmpty()) {
continue;
}
List<MatrixPosition> list = new ArrayList<>();
for (char c : line.toCharArray()) {
list.add(MatrixPosition.getMatrixPosition(String.valueOf(c)));
}
listOfLists.add(list);
}
MatrixPosition[][] grid = new MatrixPosition[listOfLists.size()][];
for (int i = 0; i < grid.length; i++) {
List<MatrixPosition> list = listOfLists.get(i);
grid[i] = list.toArray(new MatrixPosition[] {});
}
model = new MatrixModel(grid, new SpritePosition(1, 1));
}
return model;
}
}
Basic enum to represent direction:
public enum Direction {
UP, DOWN, LEFT, RIGHT
}
Another enum to represent a location point in the grid, and whether it is a wall, a coridor or the end as well as static methods to convert from number to MatrixPosition:
public enum MatrixPosition {
WALL(1), CORRIDOR(0), END(2);
private int value;
private MatrixPosition(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static MatrixPosition getMatrixPosition(int value) {
for (MatrixPosition position : MatrixPosition.values()) {
if (value == position.getValue()) {
return position;
}
}
String text = "value of " + value;
throw new IllegalArgumentException(text);
}
public static MatrixPosition getMatrixPosition(String strValue) {
int value = -1;
try {
value = Integer.parseInt(strValue);
} catch (NumberFormatException e) {
String text = "NumberFormatException for strValue " + strValue;
throw new IllegalAccessError(text);
}
return getMatrixPosition(value);
}
}
A class to represent a position of our sprite, similar to the java.awt.Point class but with row and column fields instead of x and y:
public class SpritePosition {
int row;
int column;
public SpritePosition(int row, int column) {
this.row = row;
this.column = column;
}
public int getRow() {
return row;
}
public void setRow(int row) {
this.row = row;
}
public int getColumn() {
return column;
}
public void setColumn(int column) {
this.column = column;
}
public void setRowColumn(int row, int column) {
this.row = row;
this.column = column;
}
}
The model has property change support code so that it can notify any classes listening to it of any changes in its state. The controller will be the class listening to the model
import java.beans.PropertyChangeListener;
import javax.swing.event.SwingPropertyChangeSupport;
public class MatrixModel {
public static final String SPRITE_POINT = "sprite point";
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
private MatrixPosition[][] grid;
private SpritePosition spritePosition;
public MatrixModel(MatrixPosition[][] grid, SpritePosition spritePosition) {
this.grid = grid;
this.spritePosition = spritePosition;
}
public int getRows() {
return grid.length;
}
public int getColumns() {
return grid[0].length;
}
public MatrixPosition getPosition(SpritePosition p) {
return getPosition(p.row, p.column);
}
public MatrixPosition getPosition(int row, int col) {
return grid[row][col];
}
public void setSpritePoint(SpritePosition spritePosition) {
SpritePosition oldValue = this.spritePosition;
SpritePosition newValue = spritePosition;
this.spritePosition = spritePosition;
pcSupport.firePropertyChange(SPRITE_POINT, oldValue, newValue);
}
public boolean isPointValid(SpritePosition p) {
if (p.column < 0 || p.row < 0) {
return false;
}
if (p.column >= grid[0].length || p.row >= grid.length) {
return false;
}
return grid[p.row][p.column] == MatrixPosition.CORRIDOR;
}
public boolean isMoveValid(Direction direction) {
int row = spritePosition.row;
int column = spritePosition.column;
switch (direction) {
case UP:
return isPointValid(new SpritePosition(row - 1, column));
case DOWN:
return isPointValid(new SpritePosition(row + 1, column));
case LEFT:
return isPointValid(new SpritePosition(row, column - 1));
case RIGHT:
return isPointValid(new SpritePosition(row, column + 1));
default:
return false;
}
}
public void move(Direction direction) {
if (!isMoveValid(direction)) {
String text = "For move to " + direction + "spritePosition: " + spritePosition;
throw new IllegalArgumentException(text);
}
int row = spritePosition.row;
int column = spritePosition.column;
switch (direction) {
case UP:
setSpritePoint(new SpritePosition(row - 1, column));
break;
case DOWN:
setSpritePoint(new SpritePosition(row + 1, column));
break;
case LEFT:
setSpritePoint(new SpritePosition(row, column - 1));
break;
case RIGHT:
setSpritePoint(new SpritePosition(row, column + 1));
break;
default:
break;
}
}
public SpritePosition getSpritePosition() {
return spritePosition;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(listener);
}
public void addPropertyChangeListener(String name, PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(name, listener);
}
public void removePropertyChangeListener(String name, PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(name, listener);
}
}
Controller class, one that sets up key bindings on the view so that it can listen for key presses, checks if they represent a valid move, and if so then tells the model to make the move. It also adds a listener to the model so that when its state changes, it will tell the view to move the sprite
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.EnumMap;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
public class Controller {
private MatrixModel model;
private View view;
private Map<Direction, KeyStroke> dirKeyMap = new EnumMap<>(Direction.class);
public Controller(MatrixModel model, View view) {
this.model = model;
this.view = view;
dirKeyMap.put(Direction.DOWN, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0));
dirKeyMap.put(Direction.UP, KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0));
dirKeyMap.put(Direction.LEFT, KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0));
dirKeyMap.put(Direction.RIGHT, KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0));
model.addPropertyChangeListener(new ModelListener());
setUpKeyBindings(view);
}
private void setUpKeyBindings(View view) {
int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = view.getInputMap(condition);
ActionMap actionMap = view.getActionMap();
for (Direction dir : Direction.values()) {
KeyStroke keyStroke = dirKeyMap.get(dir);
hookUp(inputMap, actionMap, dir, keyStroke);
}
}
private void hookUp(InputMap inputMap, ActionMap actionMap, Direction dir, KeyStroke key) {
inputMap.put(key, key.toString());
actionMap.put(key.toString(), new MoveAction(dir, model));
}
public MatrixModel getModel() {
return model;
}
public View getView() {
return view;
}
class ModelListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (MatrixModel.SPRITE_POINT.equals(evt.getPropertyName())) {
SpritePosition p = model.getSpritePosition();
view.setSpritePoint(p);
}
}
}
}
#SuppressWarnings("serial")
class MoveAction extends AbstractAction {
private Direction dir;
private MatrixModel model;
public MoveAction(Direction dir, MatrixModel model) {
super(dir.toString());
this.dir = dir;
this.model = model;
}
public void actionPerformed(ActionEvent e) {
if (model.isMoveValid(dir)) {
model.move(dir);
}
}
}
Finally the View class, that extends JPanel, that displays the maze and the sprite:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class View extends JPanel {
private static final int CELL_WIDTH = 20;
private static final Color CORRIDOR_COLOR = Color.LIGHT_GRAY;
private static final Color WALL_COLOR = Color.DARK_GRAY;
private static final Color END_COLOR = Color.ORANGE;
private static final Color SPRITE_COLOR = Color.RED;
private static final int GAP = 1;
private BufferedImage gridImg = null;
private SpritePosition spritePosition;
private JPanel mainPanel = new JPanel();
public View(MatrixModel matrixModel) {
gridImg = createImg(matrixModel);
spritePosition = matrixModel.getSpritePosition();
}
public JPanel getMainPanel() {
return mainPanel;
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet() || gridImg == null) {
return super.getPreferredSize();
}
int prefW = gridImg.getWidth();
int prefH = gridImg.getHeight();
return new Dimension(prefW, prefH);
}
public void setSpritePoint(SpritePosition spritePosition) {
this.spritePosition = spritePosition;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (gridImg != null) {
g.drawImage(gridImg, 0, 0, this);
}
g.setColor(SPRITE_COLOR);
int y = spritePosition.row * CELL_WIDTH + GAP;
int x = spritePosition.column * CELL_WIDTH + GAP;
g.fillRect(x, y, CELL_WIDTH - 2 * GAP, CELL_WIDTH - 2 * GAP);
}
private BufferedImage createImg(MatrixModel matrixModel) {
BufferedImage img = null;
if (matrixModel != null && matrixModel.getRows() > 0) {
int w = matrixModel.getColumns() * CELL_WIDTH;
int h = matrixModel.getRows() * CELL_WIDTH;
img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
for (int row = 0; row < matrixModel.getRows(); row++) {
for (int col = 0; col < matrixModel.getColumns(); col++) {
MatrixPosition position = matrixModel.getPosition(row, col);
Color c = null;
switch (position) {
case CORRIDOR:
c = CORRIDOR_COLOR;
break;
case WALL:
c = WALL_COLOR;
break;
case END:
c = END_COLOR;
break;
}
g2.setColor(c);
int x = col * CELL_WIDTH;
int y = row * CELL_WIDTH;
g2.fillRect(x, y, CELL_WIDTH, CELL_WIDTH);
}
}
g2.dispose();
}
return img;
}
}

How to implement a physics engine to jump in game [duplicate]

This question already has an answer here:
Implementing gravity in simple 2d game
(1 answer)
Closed 7 years ago.
I need to use a physics engine or something similar to achieve a downward parabolic jump in the game that I have. Right now, the up arrow will just cause the character to float, but I need him to jump.
Here is my code:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.EnumMap;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.*;
#SuppressWarnings("serial")
public class Level extends JPanel {
public static String chicken = "chicken.gif";
public static String chicken2 = "chicken2.gif";
private static final int PREF_W = 1440;
private static final int PREF_H = 500;
private static final int TIMER_DELAY = 20;
private static final String KEY_DOWN = "key down";
private static final String KEY_RELEASE = "key release";
public static final int TRANSLATE_SCALE = 3;
public static final String back = "back.gif";
public static final String corn = "corn.gif";
private EnumMap<Direction, Boolean> dirMap = new EnumMap<Level.Direction, Boolean>(
Direction.class);
private BufferedImage image = null;
private BufferedImage image2 = null;
private BufferedImage image3 = null;
private static int imgX = 20;
private static int imgY = 320;
private static Random rand = new Random();
private static int cImgX = rand.nextInt(1420) + 10;
private static int cImgY = 335;
private static int cornCounter = 0;
private static String Counter = Integer.toString(cornCounter);
private static final Font BG_STRING_FONT = new Font(Font.SANS_SERIF,
Font.BOLD, 32);
private int bgStringX;
public Level() {
for (Direction dir : Direction.values()) {
dirMap.put(dir, Boolean.FALSE);
}
try {
File bback = new File(back);
image2 = ImageIO.read(bback);
File ccorn = new File(corn);
image3 = ImageIO.read(ccorn);
File img = new File(chicken);
image = ImageIO.read(img);
} catch (IOException e) {
e.printStackTrace();
}
new Timer(TIMER_DELAY, new TimerListener()).start();
// here we set up our key bindings
int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = getInputMap(condition);
ActionMap actionMap = getActionMap();
for (final Direction dir : Direction.values()) {
// for the key down key stroke
KeyStroke keyStroke = KeyStroke.getKeyStroke(dir.getKeyCode(), 0,
false);
inputMap.put(keyStroke, dir.name() + KEY_DOWN);
actionMap.put(dir.name() + KEY_DOWN, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent arg0) {
dirMap.put(dir, true);
}
});
// for the key release key stroke
keyStroke = KeyStroke.getKeyStroke(dir.getKeyCode(), 0, true);
inputMap.put(keyStroke, dir.name() + KEY_RELEASE);
actionMap.put(dir.name() + KEY_RELEASE, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent arg0) {
dirMap.put(dir, false);
}
});
}
FontMetrics fontMetrics = getFontMetrics(BG_STRING_FONT);
int w = fontMetrics.stringWidth(Counter);
bgStringX = (PREF_W - w) / 2;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
if (image != null) {
g.drawImage(image2, 0, 0, this);
validate();
g.drawImage(image3, cImgX, cImgY, this);
validate();
g.drawImage(image, imgX, imgY, this);
validate();
}
g.setFont(BG_STRING_FONT);
g.setColor(Color.BLACK);
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.drawString(Counter, bgStringX, 190);
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
for (Direction dir : Direction.values()) {
if (dirMap.get(dir)) {
imgX += dir.getX() * TRANSLATE_SCALE;
imgY += dir.getY() * TRANSLATE_SCALE;
//System.out.println(imgY);
if(imgX >= 188 && imgX <= 380) {
if(imgY <= 190 && imgY >= 199) {
imgY = 194;
}
}
if (imgX >= (cImgX - 10) && imgX <= (cImgX + 10)) {
cImgX = rand.nextInt(1420) + 10;
repaint();
validate();
cornCounter += 1;
Counter = Integer.toString(cornCounter);
//System.out.println(cornCounter);
repaint();
validate();
}
if(imgX <= -60) {
imgX = 1373;
repaint();
validate();
}
else if(imgX >= 1442) {
imgX = 1;
repaint();
validate();
}
if(imgY >= 320) {
imgY = 320;
}
else if(imgY <= 1) {
imgY = 1;
}
}
}
repaint();
};
}
enum Direction {
Up(KeyEvent.VK_UP, 0, -1),
Left(KeyEvent.VK_A, -1, 0), Right(KeyEvent.VK_D, 1, 0),
LeftA(KeyEvent.VK_LEFT, -1, 0), RightA(KeyEvent.VK_RIGHT, 1, 0),
Down(KeyEvent.VK_DOWN, 0, 1);
private int keyCode;
private int x;
private int y;
private Direction(int keyCode, int x, int y) {
this.keyCode = keyCode;
this.x = x;
this.y = y;
}
public int getKeyCode() {
return keyCode;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
private static void createAndShowGui() {
Level mainPanel = new Level();
JFrame frame = new JFrame("Cockadoodle Duty: Awakening");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGui();
}
});
}
}
That would be an overkill if you just want to achieve a simple jumping motion.
int vy = 0; //velocity of y
//.... heres your update code:
y += vy; //y is the yposition of your image or character
vy += GRAVITY; //GRAVITY is some arbitrary positive value
//...here's your onJump method:
vy = jumpSpeed; //jumpSpeed is some arbitrary negative value

How to make an image move while listening to a keypress in Java.

I'm starting to learn java programming and I think it's cool to learn java through game development. I know how to draw image and listen to a keypress then move that image. But is it possible to make the image move back and forth to the window while the window is listening to a keypress? Like for example, while the image or object(like spaceship) is moving left to right in the window, then if I press space key, a laser will fire at the bottom of the screen( cool huh :D ). But basically I just want to know how to make the image move left to right while the window is listening to a keypress.
I'm thinking that I will add a key listener to my window then fire an infinite loop to move the image. Or do I need to learn about threading so that another thread will move the object?
Please advise.
Many thanks.
Yep, a Swing Timer and Key Bindings would work well. Here's another example (mine) :)
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class AnimationWithKeyBinding {
private static void createAndShowUI() {
AnimationPanel panel = new AnimationPanel(); // the drawing JPanel
JFrame frame = new JFrame("Animation With Key Binding");
frame.getContentPane().add(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}
#SuppressWarnings("serial")
class AnimationPanel extends JPanel {
public static final int SPRITE_WIDTH = 20;
public static final int PANEL_WIDTH = 400;
public static final int PANEL_HEIGHT = 400;
private static final int MAX_MSTATE = 25;
private static final int SPIN_TIMER_PERIOD = 16;
private static final int SPRITE_STEP = 3;
private int mState = 0;
private int mX = (PANEL_WIDTH - SPRITE_WIDTH) / 2;
private int mY = (PANEL_HEIGHT - SPRITE_WIDTH) / 2;
private int oldMX = mX;
private int oldMY = mY;
private boolean moved = false;
// an array of sprite images that are drawn sequentially
private BufferedImage[] spriteImages = new BufferedImage[MAX_MSTATE];
public AnimationPanel() {
// create and start the main animation timer
new Timer(SPIN_TIMER_PERIOD, new SpinTimerListener()).start();
setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT));
setBackground(Color.white);
createSprites(); // create the images
setupKeyBinding();
}
private void setupKeyBinding() {
int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
InputMap inMap = getInputMap(condition);
ActionMap actMap = getActionMap();
// this uses an enum of Direction that holds ints for the arrow keys
for (Direction direction : Direction.values()) {
int key = direction.getKey();
String name = direction.name();
// add the key bindings for arrow key and shift-arrow key
inMap.put(KeyStroke.getKeyStroke(key, 0), name);
inMap.put(KeyStroke.getKeyStroke(key, InputEvent.SHIFT_DOWN_MASK), name);
actMap.put(name, new MyKeyAction(this, direction));
}
}
// create a bunch of buffered images and place into an array,
// to be displayed sequentially
private void createSprites() {
for (int i = 0; i < spriteImages.length; i++) {
spriteImages[i] = new BufferedImage(SPRITE_WIDTH, SPRITE_WIDTH,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = spriteImages[i].createGraphics();
g2.setColor(Color.red);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
double theta = i * Math.PI / (2 * spriteImages.length);
double x = SPRITE_WIDTH * Math.abs(Math.cos(theta)) / 2.0;
double y = SPRITE_WIDTH * Math.abs(Math.sin(theta)) / 2.0;
int x1 = (int) ((SPRITE_WIDTH / 2.0) - x);
int y1 = (int) ((SPRITE_WIDTH / 2.0) - y);
int x2 = (int) ((SPRITE_WIDTH / 2.0) + x);
int y2 = (int) ((SPRITE_WIDTH / 2.0) + y);
g2.drawLine(x1, y1, x2, y2);
g2.drawLine(y1, x2, y2, x1);
g2.dispose();
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(spriteImages[mState], mX, mY, null);
}
public void incrementX(boolean right) {
oldMX = mX;
if (right) {
mX = Math.min(getWidth() - SPRITE_WIDTH, mX + SPRITE_STEP);
} else {
mX = Math.max(0, mX - SPRITE_STEP);
}
moved = true;
}
public void incrementY(boolean down) {
oldMY = mY;
if (down) {
mY = Math.min(getHeight() - SPRITE_WIDTH, mY + SPRITE_STEP);
} else {
mY = Math.max(0, mY - SPRITE_STEP);
}
moved = true;
}
public void tick() {
mState = (mState + 1) % MAX_MSTATE;
}
private class SpinTimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
tick();
int delta = 20;
int width = SPRITE_WIDTH + 2 * delta;
int height = width;
// make sure to erase the old image
if (moved) {
int x = oldMX - delta;
int y = oldMY - delta;
repaint(x, y, width, height);
}
int x = mX - delta;
int y = mY - delta;
// draw the new image
repaint(x, y, width, height);
moved = false;
}
}
}
enum Direction {
UP(KeyEvent.VK_UP), DOWN(KeyEvent.VK_DOWN), LEFT(KeyEvent.VK_LEFT), RIGHT(KeyEvent.VK_RIGHT);
private int key;
private Direction(int key) {
this.key = key;
}
public int getKey() {
return key;
}
}
// Actions for the key binding
#SuppressWarnings("serial")
class MyKeyAction extends AbstractAction {
private AnimationPanel draw;
private Direction direction;
public MyKeyAction(AnimationPanel draw, Direction direction) {
this.draw = draw;
this.direction = direction;
}
#Override
public void actionPerformed(ActionEvent e) {
switch (direction) {
case UP:
draw.incrementY(false);
break;
case DOWN:
draw.incrementY(true);
break;
case LEFT:
draw.incrementX(false);
break;
case RIGHT:
draw.incrementX(true);
break;
default:
break;
}
}
}
Here is another example that uses this sprite sheet:
obtained from this site.
Again it's an example of drawing within a JPanel's paintComponent method and using Key Bindings to tell which direction to move.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.swing.*;
#SuppressWarnings("serial")
public class Mcve3 extends JPanel {
private static final int PREF_W = 800;
private static final int PREF_H = 640;
private static final int TIMER_DELAY = 50;
private int spriteX = 400;
private int spriteY = 320;
private SpriteDirection spriteDirection = SpriteDirection.RIGHT;
private MySprite sprite = null;
private Timer timer = null;
public Mcve3() {
try {
sprite = new MySprite(spriteDirection, spriteX, spriteY);
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
setBackground(Color.WHITE);
setKeyBindings(SpriteDirection.LEFT, KeyEvent.VK_LEFT);
setKeyBindings(SpriteDirection.RIGHT, KeyEvent.VK_RIGHT);
setKeyBindings(SpriteDirection.FORWARD, KeyEvent.VK_DOWN);
setKeyBindings(SpriteDirection.AWAY, KeyEvent.VK_UP);
timer = new Timer(TIMER_DELAY, new TimerListener());
timer.start();
}
private void setKeyBindings(SpriteDirection dir, int keyCode) {
int condition = WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = getInputMap(condition);
ActionMap actionMap = getActionMap();
KeyStroke keyPressed = KeyStroke.getKeyStroke(keyCode, 0, false);
KeyStroke keyReleased = KeyStroke.getKeyStroke(keyCode, 0, true);
inputMap.put(keyPressed, keyPressed.toString());
inputMap.put(keyReleased, keyReleased.toString());
actionMap.put(keyPressed.toString(), new MoveAction(dir, false));
actionMap.put(keyReleased.toString(), new MoveAction(dir, true));
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
sprite.draw(g);
}
private class MoveAction extends AbstractAction {
private SpriteDirection dir;
private boolean released;
public MoveAction(SpriteDirection dir, boolean released) {
this.dir = dir;
this.released = released;
}
#Override
public void actionPerformed(ActionEvent e) {
if (released) {
sprite.setMoving(false);
} else {
sprite.setMoving(true);
sprite.setDirection(dir);
}
}
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
if (sprite.isMoving()) {
sprite.tick();
}
repaint();
}
}
private static void createAndShowGui() {
Mcve3 mainPanel = new Mcve3();
JFrame frame = new JFrame("MCVE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class MySprite {
private static final String SPRITE_SHEET_PATH = "http://"
+ "orig12.deviantart.net/7db3/f/2010/338/3/3/"
+ "animated_sprite_sheet_32x32_by_digibody-d3479l2.gif";
private static final int MAX_MOVING_INDEX = 4;
private static final int DELTA = 4;
private SpriteDirection direction;
private Map<SpriteDirection, Image> standingImgMap = new EnumMap<>(SpriteDirection.class);
private Map<SpriteDirection, List<Image>> movingImgMap = new EnumMap<>(SpriteDirection.class);
private int x;
private int y;
private boolean moving = false;
private int movingIndex = 0;
public MySprite(SpriteDirection direction, int x, int y) throws IOException {
this.direction = direction;
this.x = x;
this.y = y;
createSprites();
}
public void draw(Graphics g) {
Image img = null;
if (!moving) {
img = standingImgMap.get(direction);
} else {
img = movingImgMap.get(direction).get(movingIndex);
}
g.drawImage(img, x, y, null);
}
private void createSprites() throws IOException {
URL spriteSheetUrl = new URL(SPRITE_SHEET_PATH);
BufferedImage img = ImageIO.read(spriteSheetUrl);
// get sub-images (sprites) from the sprite sheet
// magic numbers for getting sprites from sheet, all obtained by trial and error
int x0 = 0;
int y0 = 64;
int rW = 32;
int rH = 32;
for (int row = 0; row < 4; row++) {
SpriteDirection dir = SpriteDirection.values()[row];
List<Image> imgList = new ArrayList<>();
movingImgMap.put(dir, imgList);
int rY = y0 + row * rH;
for (int col = 0; col < 5; col++) {
int rX = x0 + col * rW;
BufferedImage subImg = img.getSubimage(rX, rY, rW, rH);
if (col == 0) {
// first image is standing
standingImgMap.put(dir, subImg);
} else {
// all others are moving
imgList.add(subImg);
}
}
}
}
public SpriteDirection getDirection() {
return direction;
}
public void setDirection(SpriteDirection direction) {
if (this.direction != direction) {
setMoving(false);
}
this.direction = direction;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public boolean isMoving() {
return moving;
}
public void setMoving(boolean moving) {
this.moving = moving;
if (!moving) {
movingIndex = 0;
}
}
public void tick() {
if (moving) {
switch (direction) {
case RIGHT:
x += DELTA;
break;
case LEFT:
x -= DELTA;
break;
case FORWARD:
y += DELTA;
break;
case AWAY:
y -= DELTA;
}
movingIndex++;
movingIndex %= MAX_MOVING_INDEX;
}
}
public int getMovingIndex() {
return movingIndex;
}
public void setMovingIndex(int movingIndex) {
this.movingIndex = movingIndex;
}
}
enum SpriteDirection {
FORWARD, LEFT, AWAY, RIGHT
}
As an alternative to KeyListener, consider using actions and key bindings, discussed here. Derived from this example, the program below moves a line left, down, up or right using either buttons or keys.
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
/**
* #see https://stackoverflow.com/questions/6991648
* #see https://stackoverflow.com/questions/6887296
* #see https://stackoverflow.com/questions/5797965
*/
public class LinePanel extends JPanel {
private MouseHandler mouseHandler = new MouseHandler();
private Point p1 = new Point(100, 100);
private Point p2 = new Point(540, 380);
private boolean drawing;
public LinePanel() {
this.setPreferredSize(new Dimension(640, 480));
this.addMouseListener(mouseHandler);
this.addMouseMotionListener(mouseHandler);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.blue);
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(8,
BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
g.drawLine(p1.x, p1.y, p2.x, p2.y);
}
private class MouseHandler extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
drawing = true;
p1 = e.getPoint();
p2 = p1;
repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
drawing = false;
p2 = e.getPoint();
repaint();
}
#Override
public void mouseDragged(MouseEvent e) {
if (drawing) {
p2 = e.getPoint();
repaint();
}
}
}
private class ControlPanel extends JPanel {
private static final int DELTA = 10;
public ControlPanel() {
this.add(new MoveButton("\u2190", KeyEvent.VK_LEFT, -DELTA, 0));
this.add(new MoveButton("\u2191", KeyEvent.VK_UP, 0, -DELTA));
this.add(new MoveButton("\u2192", KeyEvent.VK_RIGHT, DELTA, 0));
this.add(new MoveButton("\u2193", KeyEvent.VK_DOWN, 0, DELTA));
}
private class MoveButton extends JButton {
KeyStroke k;
int dx, dy;
public MoveButton(String name, int code, final int dx, final int dy) {
super(name);
this.k = KeyStroke.getKeyStroke(code, 0);
this.dx = dx;
this.dy = dy;
this.setAction(new AbstractAction(this.getText()) {
#Override
public void actionPerformed(ActionEvent e) {
LinePanel.this.p1.translate(dx, dy);
LinePanel.this.p2.translate(dx, dy);
LinePanel.this.repaint();
}
});
ControlPanel.this.getInputMap(
WHEN_IN_FOCUSED_WINDOW).put(k, k.toString());
ControlPanel.this.getActionMap().put(k.toString(), new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
MoveButton.this.doClick();
}
});
}
}
}
private void display() {
JFrame f = new JFrame("LinePanel");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.add(new ControlPanel(), BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new LinePanel().display();
}
});
}
}
But basically I just want to know how to make the image move left to right while the window is listening to a keypress
You can use a Swing Timer to animate an image:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TimerAnimation extends JLabel implements ActionListener
{
int deltaX = 2;
int deltaY = 3;
int directionX = 1;
int directionY = 1;
public TimerAnimation(
int startX, int startY,
int deltaX, int deltaY,
int directionX, int directionY,
int delay)
{
this.deltaX = deltaX;
this.deltaY = deltaY;
this.directionX = directionX;
this.directionY = directionY;
setIcon( new ImageIcon("dukewavered.gif") );
// setIcon( new ImageIcon("copy16.gif") );
setSize( getPreferredSize() );
setLocation(startX, startY);
new javax.swing.Timer(delay, this).start();
}
public void actionPerformed(ActionEvent e)
{
Container parent = getParent();
// Determine next X position
int nextX = getLocation().x + (deltaX * directionX);
if (nextX < 0)
{
nextX = 0;
directionX *= -1;
}
if ( nextX + getSize().width > parent.getSize().width)
{
nextX = parent.getSize().width - getSize().width;
directionX *= -1;
}
// Determine next Y position
int nextY = getLocation().y + (deltaY * directionY);
if (nextY < 0)
{
nextY = 0;
directionY *= -1;
}
if ( nextY + getSize().height > parent.getSize().height)
{
nextY = parent.getSize().height - getSize().height;
directionY *= -1;
}
// Move the label
setLocation(nextX, nextY);
}
public static void main(String[] args)
{
JPanel panel = new JPanel();
JFrame frame = new JFrame();
frame.setContentPane(panel);
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.getContentPane().setLayout(null);
// frame.getContentPane().add( new TimerAnimation(10, 10, 2, 3, 1, 1, 10) );
frame.getContentPane().add( new TimerAnimation(300, 100, 3, 2, -1, 1, 20) );
// frame.getContentPane().add( new TimerAnimation(0, 000, 5, 0, 1, 1, 20) );
frame.getContentPane().add( new TimerAnimation(0, 200, 5, 0, 1, 1, 80) );
frame.setSize(400, 400);
frame.setLocationRelativeTo( null );
frame.setVisible(true);
// frame.getContentPane().add( new TimerAnimation(10, 10, 2, 3, 1, 1, 10) );
// frame.getContentPane().add( new TimerAnimation(10, 10, 3, 0, 1, 1, 10) );
}
}
You can add a KeyListener to the panel and it will operate independently of the image animation.

Categories

Resources