Related
I am trying to make a chess game in java, by having a class of pieces and a subclass for each piece. However, When I try to draw the pieces, The position doesn't seem to register.
Here is my Piece class:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.geom.AffineTransform;
import java.net.URL;
public class Piece {
public int Id;
public int color;
public int state;
public Image Sprite;
public AffineTransform tx;
public boolean dragged;
public int x;
public int y;
public Piece(int Id, int color, int position){
dragged = false;
this.Id = Id;
this.color = color;
int x = 100*(position % 8);
int y = 100*(position / 8);
System.out.println(x);
tx = AffineTransform.getTranslateInstance(x, y);
init(x, y);
}
private void init (double a, double b) {
tx.setToTranslation(a, b);
tx.scale(0.1, 0.1);
}
private void update(){
tx.setToTranslation(x*1000, y*1000);
tx.scale(0.1, 0.1);
}
protected Image getImage(String path) {
Image tempImage = null;
try {
URL imageURL = Piece.class.getResource(path);
tempImage = Toolkit.getDefaultToolkit().getImage(imageURL);
} catch (Exception e) {e.printStackTrace();}
return tempImage;
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
update();
g2.drawImage(Sprite, tx, null);
}
}
my pawn class:
public class Pawn extends Piece {
public Pawn(int Id, int color, int position) {
super(Id, color, position);
this.state = 0;
String path = "/imgs/Pieces/";
if(color == 0){
path += "W";
} else{
path += "B";
}
path += "_Pawn.png";
Sprite = getImage(path);
}
}
my Board class:
Piece[][] board;
public Board(){
board = new Piece[8][8];
for(int i = 0; i < 8; i++){
board[1][i] = new Pawn(i, 1, 8+i);
}
for(int i = 0; i < 8; i++){
board[6][i] = new Pawn(i, 0, 8+i);
}
}
}
and my main class:
import java.io.IOException;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main extends JPanel implements ActionListener, MouseListener, KeyListener{
Color GREEN = new Color( 41, 176, 59);
Color WHITE = new Color(254, 255, 228);
Board board = new Board();
public static void main(String[] args) {
new Main();
}
public void paint(Graphics g){
super.paintComponent(g);
boolean flag = true;
for(int i = 0; i < 8; i++){
for(int j = 0; j < 8; j++){
if(flag){
g.setColor(WHITE);
} else{
g.setColor(GREEN);
}
g.fillRect((j*100), (i*100), ((j+1)*100), ((i+1)*100));
flag = !flag;
}
flag = !flag;
}
for(int i = 0; i < 8; i++){
for(int j = 0; j < 8; j++){
if(board.board[i][j] != null){
board.board[i][j].paint(g);
}
}
}
}
public Main() {
JFrame f = new JFrame("Chess");
f.setSize(new Dimension(800, 800));
f.setBackground(Color.blue);
f.add(this);
f.setResizable(false);
f.setLayout(new GridLayout(1,2));
f.addMouseListener(this);
f.addKeyListener(this);
Timer t = new Timer(16, this);
t.start();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
}
I had previously written a game that implemented this techqnique, so I'm not sure what could have gone wrong with this one
It's really important to read the documentation, especially for something that is (to my simple brain), complicated.
If you have a read of the documentation for AffineTransform#scale
Concatenates this transform with a scaling transformation
(emphis added by me)
This is important, as it seems to apply that each time you call it, it will apply ANOTHER scaling operation.
Based on your avaliable code, this means that when the Piece is created, a scale is applied and the each time it's painted, a new scale is applied, until you're basically scaled out of existence.
Sooo. I took out your init (applied the scale within the constructor directly instead) and update methods and was able to get a basic result
Scaling from 1.0, 0.75, 0.5, 0.25and0.1` (it's there but I had to reduce the size of the output)
Runnable example...
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new Main());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
}
public class Main extends JPanel implements ActionListener, MouseListener, KeyListener {
Color GREEN = new Color(41, 176, 59);
Color WHITE = new Color(254, 255, 228);
Board board;
public Main() throws IOException {
board = new Board();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 800);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
super.paintComponent(g);
boolean flag = true;
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
if (flag) {
g.setColor(WHITE);
} else {
g.setColor(GREEN);
}
g.fillRect((j * 100), (i * 100), ((j + 1) * 100), ((i + 1) * 100));
flag = !flag;
}
flag = !flag;
}
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
if (board.board[i][j] != null) {
board.board[i][j].paint(g);
}
}
}
}
#Override
public void actionPerformed(ActionEvent e) {
}
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
}
}
public class Board {
Piece[][] board;
public Board() throws IOException {
board = new Piece[8][8];
for (int i = 0; i < 8; i++) {
board[1][i] = new Pawn(i, 1, 8 + i);
}
for (int i = 0; i < 8; i++) {
board[6][i] = new Pawn(i, 0, 16 + i);
}
}
}
public class Pawn extends Piece {
public Pawn(int Id, int color, int position) throws IOException {
super(Id, color, position);
this.state = 0;
String path = "/imgs/Pieces/";
if (color == 0) {
path += "W";
} else {
path += "B";
}
path += "_Pawn.png";
Sprite = getImage(path);
}
}
public class Piece {
public int Id;
public int color;
public int state;
public Image Sprite;
public AffineTransform tx;
public boolean dragged;
public int x;
public int y;
public Piece(int Id, int color, int position) {
dragged = false;
this.Id = Id;
this.color = color;
x = 100 * (position % 8);
y = 100 * (position / 8);
System.out.println(x + "x" + y);
tx = AffineTransform.getTranslateInstance(x, y);
tx.scale(0.1, 0.1);
}
protected Image getImage(String path) throws IOException {
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.setFont(new JLabel().getFont().deriveFont(Font.PLAIN, 16));
g2d.setColor(Color.BLACK);
FontMetrics fm = g2d.getFontMetrics();
int cellX = x;
int cellY = y;
String text = x + "x" + y;
int x = (100 - fm.stringWidth(text)) / 2;
int y = ((100 - fm.getHeight()) / 2) + fm.getAscent();
g2d.drawString(text, x, y);
g2d.setStroke(new BasicStroke(16));
g2d.drawRect(0, 0, 99, 99);
g2d.dispose();
return img;
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.drawImage(Sprite, tx, null);
}
}
}
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.
I have a program that does an animation using timers switching images. When the program is on its last image I use a class to create a buffered image of that image with text over it. When the last image of the animation is displayed I want to change the image displayed to the buffered image. I can't get it to work. The code as is plays as if the bolded section isnt there. If I delete the line above it, it displays the image with text over it and nothing else. What edits should I make to my code to fix this?
The Class that does the animation
**import java.awt.event.*;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Font;
import java.awt.image.*;
import java.io.*;
import java.io.File;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.net.URL;
import javax.swing.*;
import javax.swing.*;
import javax.imageio.ImageIO;
/**
* Write a description of class Reveal here.
*
* #author (your name)
* #version (a version number or a date)
*/
public class Reveal extends JPanel
{
private JPanel panel = new JPanel(); //a panel to house the label
private JLabel label = new JLabel(); //a label to house the image
private String[] image = {"Jack in the Box 1.png","Jack in the Box 2.png","Jack in the Box 3.png","Jack in the Box 4.png","Jack in the Box 5.png","Jack in the Box 6.png","Jack in the Box 7.png"}; //an array to hold the frames of the animation
private ImageIcon[] icon = new ImageIcon[7]; //an array of icons to be the images
private JFrame f;
private TextOverlay TO;
private Timer timer;
private Timer timer2;
int x = 0;
int y = 4;
int counter = 0;
/**
* Constructor for objects of class Reveal
*/
public Reveal(String name, int number)
{
TO = new TextOverlay("Jack in the Box 7.png", name, number);
for (int h = 0; h < 7; h++){
icon[h] = new ImageIcon(image[h]);
icon[h].getImage();
}
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
//Sets the size of the window
f.setSize(800,850);
panel = new JPanel();
label = new JLabel();
label.setIcon( icon[x] );
panel.add(label);
setVisible(true);
f.add(panel);
display(name, number);
**f.add(TO);**
}
public void display(String name, int number){
timer = new Timer(150, new ActionListener(){
public void actionPerformed(ActionEvent e) {
if (counter > 27){
timer.stop();
timer2.start(); //starts the second half of the animation
}else{
if (x != 3){
x++;
}else{
x = 0;
}
label.setIcon( icon[x] );
counter++;
} //ends if-else
} //ends action method
}); //ends timer
timer2 = new Timer(250, new ActionListener(){
public void actionPerformed(ActionEvent e){
if (y > 6) {
timer2.stop();
}else{
label.setIcon( icon[y] );
y++;
} //ends if-else
} //ends action method
}); //ends timer2
timer.start();
}
}
**
The class that puts text over an image
import java.io.*;
import java.awt.*;
import javax.swing.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
/**
* #see https://stackoverflow.com/questions/2658663
*/
public class TextOverlay extends JPanel {
private BufferedImage image;
private String name;
private String fileX;
private int number;
public TextOverlay(String f, String s, int n) {
name = s;
number = n;
fileX = f;
try {
image = ImageIO.read(new File(fileX));
} catch (IOException e) {
e.printStackTrace();
}
image = process(image, name, number);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
}
private BufferedImage process(BufferedImage old, String name, int number) {
int w = old.getWidth();
int h = old.getHeight();
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.drawImage(old, 0, 0, w, h, this);
g2d.setPaint(Color.black);
g2d.setFont(new Font("Franklin Gothic Demi Cond", Font.PLAIN, 30));
String s1 = name;
String s2 = Integer.toString(number);;
FontMetrics fm = g2d.getFontMetrics();
g2d.drawString(s1, 40, 90);
g2d.drawString(s2, 40, 140);
g2d.dispose();
return img;
}
}
So, you seem to have a misunderstanding of how Swing works, you might find How to Use Swing Timers and Concurrency in Swing of some assistance.
Basically, when you start a Timer, it doesn't block at this point until the timer ends (and even if it did, your wouldn't work the way you wanted it to). Instead, a new thread is created and after the specified period a request is placed on Event Dispatching Thread to execute the supplied Runnable.
This means that when you do something like...
f.add(panel);
display(name, number);
f.add(TO);
You are actually adding the TO component onto of the JLabel (because the frame is using a BorderLayout and the CENTRE position is the default position.
Instead, in your second timer completes, you need to remove the label and add the TO component...
timer2 = new Timer(250, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (y > 6) {
timer2.stop();
Container parent = label.getParent();
parent.remove(label);
parent.add(TO);
parent.revalidate();
} else {
label.setIcon(icon[y]);
y++;
} //ends if-else
} //ends action method
}); //ends timer2
Runnable Example...
import java.awt.event.*;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Font;
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.swing.border.LineBorder;
public class Reveal extends JPanel {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
new Reveal("Test", 5);
}
});
}
private JPanel panel = new JPanel(); //a panel to house the label
private JLabel label = new JLabel(); //a label to house the image
private ImageIcon[] icon = new ImageIcon[7]; //an array of icons to be the images
private JFrame f;
private TextOverlay TO;
private Timer timer;
private Timer timer2;
int x = 0;
int y = 4;
int counter = 0;
/**
* Constructor for objects of class Reveal
*/
public Reveal(String name, int number) {
TO = new TextOverlay("Jack in the Box 7.png", name, number);
for (int h = 0; h < 7; h++) {
icon[h] = new ImageIcon(makeImage(h));
icon[h].getImage();
}
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
//Sets the size of the window
f.setSize(800, 850);
panel = new JPanel(new GridBagLayout());
label = new JLabel();
label.setIcon(icon[x]);
label.setBorder(new LineBorder(Color.RED));
panel.add(label);
f.add(panel);
display(name, number);
// f.add(TO);
setVisible(true);
}
public void display(String name, int number) {
timer = new Timer(150, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (counter > 27) {
timer.stop();
timer2.start(); //starts the second half of the animation
} else {
if (x != 3) {
x++;
} else {
x = 0;
}
label.setIcon(icon[x]);
counter++;
} //ends if-else
} //ends action method
}); //ends timer
timer2 = new Timer(250, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (y > 6) {
timer2.stop();
Container parent = label.getParent();
parent.remove(label);
parent.add(TO);
parent.revalidate();
} else {
label.setIcon(icon[y]);
y++;
} //ends if-else
} //ends action method
}); //ends timer2
timer.start();
}
protected BufferedImage makeImage(int h) {
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
FontMetrics fm = g2d.getFontMetrics();
String text = Integer.toString(h);
int x = (100 - fm.stringWidth(text)) / 2;
int y = ((100 - fm.getHeight()) / 2) + fm.getAscent();
g2d.setColor(Color.BLUE);
g2d.fillRect(0, 0, 100, 100);
g2d.setColor(Color.BLACK);
g2d.drawString(text, x, y);
g2d.dispose();
return img;
}
public class TextOverlay extends JPanel {
private BufferedImage image;
private String name;
private String fileX;
private int number;
public TextOverlay(String f, String s, int n) {
name = s;
number = n;
fileX = f;
image = makeImage(n);
image = process(image, name, number);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, this);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 100);
}
private BufferedImage process(BufferedImage old, String name, int number) {
int w = old.getWidth();
int h = old.getHeight();
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.drawImage(old, 0, 0, w, h, this);
g2d.setPaint(Color.black);
g2d.setFont(new Font("Franklin Gothic Demi Cond", Font.PLAIN, 30));
String s1 = name;
String s2 = Integer.toString(number);;
FontMetrics fm = g2d.getFontMetrics();
g2d.drawString(s1, 40, 90);
g2d.drawString(s2, 40, 140);
g2d.dispose();
return img;
}
}
}
A "slightly" different approach...
Animation is actually a really complex subject which is not easy to implement well.
This is why, when faced with problems like these, I prefer to look at libraries which have already been implemented to help solve them. I'd recommend having a look at:
The Timing Framework
Trident
universal-tween-engine
as some starting points.
While I prefer to use libraries, sometimes it's not possible or the libraries don't fit my overall needs ... that and I like to dabble ... it's kind of a hobby.
Based on what I can understand from your code, you're trying to start out with a fast animation and then slow it down till you get to the last frame. In animation theory, this is commonly known as easement, more specifically, "slow/ease out".
The following borrows from a bunch of snippets I've been playing with (to devise a more reusable library) that will basically (randomly) display the images over a period of 4 seconds, with the animation slowing down and finally, presenting the "lucky" number
nb The gif animation is actually really slow, you'll need to run it to see the difference
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.swing.*;
public class Reveal extends JPanel {
public static void main(String[] args) {
new Reveal();
}
public Reveal() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private IntAnimatable animatable;
private List<ImageIcon> icons = new ArrayList<>(25);
private JLabel label = new JLabel();
public TestPane() {
setLayout(new GridBagLayout());
IntRange range = new IntRange(0, 111);
animatable = new IntAnimatable(range, Duration.ofSeconds(4), Easement.SLOWOUT, new AnimatableListener<Integer>() {
#Override
public void animationChanged(Animatable<Integer> animator) {
int value = animator.getValue();
int index = value % 7;
ImageIcon icon = icons.get(index);
if (label.getIcon() != icon) {
label.setIcon(icon);
}
}
}, new AnimatableLifeCycleAdapter<Integer>() {
#Override
public void animationCompleted(Animatable<Integer> animator) {
BufferedImage img = makeImage(3);
writeTextOverImage("Lucky number", img);
ImageIcon luckNumber = new ImageIcon(img);
label.setIcon(luckNumber);
}
});
for (int index = 0; index < 7; index++) {
icons.add(new ImageIcon(makeImage(index)));
}
Collections.shuffle(icons);
add(label);
Animator.INSTANCE.add(animatable);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
protected void writeTextOverImage(String text, BufferedImage img) {
Graphics2D g2d = img.createGraphics();
Font font = g2d.getFont();
font = font.deriveFont(Font.BOLD, font.getSize2D() + 2);
g2d.setFont(font);
FontMetrics fm = g2d.getFontMetrics();
int width = img.getWidth();
int height = img.getWidth();
int x = (width - fm.stringWidth(text)) / 2;
int y = fm.getAscent();
g2d.setColor(Color.YELLOW);
g2d.drawString(text, x, y);
g2d.dispose();
}
protected BufferedImage makeImage(int h) {
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
FontMetrics fm = g2d.getFontMetrics();
String text = Integer.toString(h);
int x = (100 - fm.stringWidth(text)) / 2;
int y = ((100 - fm.getHeight()) / 2) + fm.getAscent();
g2d.setColor(Color.BLUE);
g2d.fillRect(0, 0, 100, 100);
g2d.setColor(Color.WHITE);
g2d.drawString(text, x, y);
g2d.dispose();
return img;
}
/**** Range ****/
/*
A lot of animation is done from one point to another, this just
provides a self contained concept of a range which can be used to
calculate the value based on the current progression over time
*/
public abstract class Range<T> {
private T from;
private T to;
public Range(T from, T to) {
this.from = from;
this.to = to;
}
public T getFrom() {
return from;
}
public T getTo() {
return to;
}
#Override
public String toString() {
return "From " + getFrom() + " to " + getTo();
}
public abstract T valueAt(double progress);
}
public class IntRange extends Range<Integer> {
public IntRange(Integer from, Integer to) {
super(from, to);
}
public Integer getDistance() {
return getTo() - getFrom();
}
#Override
public Integer valueAt(double progress) {
int distance = getDistance();
int value = (int) Math.round((double) distance * progress);
value += getFrom();
return value;
}
}
/**** Animatable ****/
/*
The core concept of something that is animatable. This basic wraps up the
logic for calculating the progression of the animation over a period of time
and then use that to calculate the value of the range and then the observers
are notified so they can do stuff
*/
public class IntAnimatable extends AbstractAnimatableRange<Integer> {
public IntAnimatable(IntRange animationRange, Duration duration, Easement easement, AnimatableListener<Integer> listener, AnimatableLifeCycleListener<Integer> lifeCycleListener) {
super(animationRange, duration, easement, listener, lifeCycleListener);
}
}
public interface AnimatableListener<T> {
public void animationChanged(Animatable<T> animator);
}
public interface AnimatableLifeCycleListener<T> {
public void animationStopped(Animatable<T> animator);
public void animationCompleted(Animatable<T> animator);
public void animationStarted(Animatable<T> animator);
public void animationPaused(Animatable<T> animator);
}
public interface Animatable<T> {
public T getValue();
public void tick();
public Duration getDuration();
public Easement getEasement();
// Wondering if these should be part of a secondary interface
// Provide a "self managed" unit of work
public void start();
public void stop();
public void pause();
}
public class AnimatableLifeCycleAdapter<T> implements AnimatableLifeCycleListener<T> {
#Override
public void animationStopped(Animatable<T> animator) {
}
#Override
public void animationCompleted(Animatable<T> animator) {
}
#Override
public void animationStarted(Animatable<T> animator) {
}
#Override
public void animationPaused(Animatable<T> animator) {
}
}
public abstract class AbstractAnimatable<T> implements Animatable<T> {
private LocalDateTime startTime;
private Duration duration = Duration.ofSeconds(5);
private AnimatableListener<T> animatableListener;
private AnimatableLifeCycleListener<T> lifeCycleListener;
private Easement easement;
private double rawOffset;
public AbstractAnimatable(Duration duration, AnimatableListener<T> listener) {
this.animatableListener = listener;
this.duration = duration;
}
public AbstractAnimatable(Duration duration, AnimatableListener<T> listener, AnimatableLifeCycleListener<T> lifeCycleListener) {
this(duration, listener);
this.lifeCycleListener = lifeCycleListener;
}
public AbstractAnimatable(Duration duration, Easement easement, AnimatableListener<T> listener) {
this(duration, listener);
this.easement = easement;
}
public AbstractAnimatable(Duration duration, Easement easement, AnimatableListener<T> listener, AnimatableLifeCycleListener<T> lifeCycleListener) {
this(duration, easement, listener);
this.lifeCycleListener = lifeCycleListener;
}
public void setEasement(Easement easement) {
this.easement = easement;
}
#Override
public Easement getEasement() {
return easement;
}
public Duration getDuration() {
return duration;
}
protected void setDuration(Duration duration) {
this.duration = duration;
}
public double getCurrentProgress(double rawProgress) {
Easement easement = getEasement();
double progress = Math.min(1.0, Math.max(0.0, getRawProgress()));
if (easement != null) {
progress = easement.interpolate(progress);
}
return Math.min(1.0, Math.max(0.0, progress));
}
public double getRawProgress() {
if (startTime == null) {
return 0.0;
}
Duration duration = getDuration();
Duration runningTime = Duration.between(startTime, LocalDateTime.now());
double progress = rawOffset + (runningTime.toMillis() / (double) duration.toMillis());
return Math.min(1.0, Math.max(0.0, progress));
}
#Override
public void tick() {
if (startTime == null) {
startTime = LocalDateTime.now();
fireAnimationStarted();
}
double rawProgress = getRawProgress();
double progress = getCurrentProgress(rawProgress);
if (rawProgress >= 1.0) {
progress = 1.0;
}
tick(progress);
fireAnimationChanged();
if (rawProgress >= 1.0) {
fireAnimationCompleted();
}
}
protected abstract void tick(double progress);
#Override
public void start() {
if (startTime != null) {
// Restart?
return;
}
Animator.INSTANCE.add(this);
}
#Override
public void stop() {
stopWithNotitifcation(true);
}
#Override
public void pause() {
rawOffset += getRawProgress();
stopWithNotitifcation(false);
double remainingProgress = 1.0 - rawOffset;
Duration remainingTime = getDuration().minusMillis((long) remainingProgress);
setDuration(remainingTime);
lifeCycleListener.animationStopped(this);
}
protected void fireAnimationChanged() {
if (animatableListener == null) {
return;
}
animatableListener.animationChanged(this);
}
protected void fireAnimationCompleted() {
stopWithNotitifcation(false);
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationCompleted(this);
}
protected void fireAnimationStarted() {
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationStarted(this);
}
protected void fireAnimationPaused() {
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationPaused(this);
}
protected void stopWithNotitifcation(boolean notify) {
Animator.INSTANCE.remove(this);
startTime = null;
if (notify) {
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationStopped(this);
}
}
}
public abstract class AbstractAnimatableRange<T> extends AbstractAnimatable<T> {
private Range<T> range;
private T value;
public AbstractAnimatableRange(Range<T> range, Duration duration, AnimatableListener<T> listener) {
super(duration, listener);
this.range = range;
}
public AbstractAnimatableRange(Range<T> range, Duration duration, AnimatableListener<T> listener, AnimatableLifeCycleListener<T> lifeCycleListener) {
super(duration, listener, lifeCycleListener);
this.range = range;
}
public AbstractAnimatableRange(Range<T> range, Duration duration, Easement easement, AnimatableListener<T> listener) {
super(duration, easement, listener);
this.range = range;
}
public AbstractAnimatableRange(Range<T> range, Duration duration, Easement easement, AnimatableListener<T> listener, AnimatableLifeCycleListener<T> lifeCycleListener) {
super(duration, easement, listener, lifeCycleListener);
this.range = range;
}
protected void tick(double progress) {
setValue(range.valueAt(progress));
}
protected void setValue(T value) {
this.value = value;
}
#Override
public T getValue() {
return value;
}
}
/*
Easement, complicated, but fun
*/
public enum Easement {
SLOWINSLOWOUT(1d, 0d, 0d, 1d), FASTINSLOWOUT(0d, 0d, 1d, 1d), SLOWINFASTOUT(0d, 1d, 0d, 0d), SLOWIN(1d, 0d, 1d, 1d), SLOWOUT(0d, 0d, 0d, 1d);
private final double[] points;
private final List<PointUnit> normalisedCurve;
private Easement(double x1, double y1, double x2, double y2) {
points = new double[]{x1, y1, x2, y2};
final List<Double> baseLengths = new ArrayList<>();
double prevX = 0;
double prevY = 0;
double cumulativeLength = 0;
for (double t = 0; t <= 1; t += 0.01) {
Point2D xy = getXY(t);
double length = cumulativeLength + Math.sqrt((xy.getX() - prevX) * (xy.getX() - prevX) + (xy.getY() - prevY) * (xy.getY() - prevY));
baseLengths.add(length);
cumulativeLength = length;
prevX = xy.getX();
prevY = xy.getY();
}
normalisedCurve = new ArrayList<>(baseLengths.size());
int index = 0;
for (double t = 0; t <= 1; t += 0.01) {
double length = baseLengths.get(index++);
double normalLength = length / cumulativeLength;
normalisedCurve.add(new PointUnit(t, normalLength));
}
}
public double interpolate(double fraction) {
int low = 1;
int high = normalisedCurve.size() - 1;
int mid = 0;
while (low <= high) {
mid = (low + high) / 2;
if (fraction > normalisedCurve.get(mid).getPoint()) {
low = mid + 1;
} else if (mid > 0 && fraction < normalisedCurve.get(mid - 1).getPoint()) {
high = mid - 1;
} else {
break;
}
}
/*
* The answer lies between the "mid" item and its predecessor.
*/
final PointUnit prevItem = normalisedCurve.get(mid - 1);
final double prevFraction = prevItem.getPoint();
final double prevT = prevItem.getDistance();
final PointUnit item = normalisedCurve.get(mid);
final double proportion = (fraction - prevFraction) / (item.getPoint() - prevFraction);
final double interpolatedT = prevT + (proportion * (item.getDistance() - prevT));
return getY(interpolatedT);
}
protected Point2D getXY(double t) {
final double invT = 1 - t;
final double b1 = 3 * t * invT * invT;
final double b2 = 3 * t * t * invT;
final double b3 = t * t * t;
final Point2D xy = new Point2D.Double((b1 * points[0]) + (b2 * points[2]) + b3, (b1 * points[1]) + (b2 * points[3]) + b3);
return xy;
}
protected double getY(double t) {
final double invT = 1 - t;
final double b1 = 3 * t * invT * invT;
final double b2 = 3 * t * t * invT;
final double b3 = t * t * t;
return (b1 * points[2]) + (b2 * points[3]) + b3;
}
protected class PointUnit {
private final double distance;
private final double point;
public PointUnit(double distance, double point) {
this.distance = distance;
this.point = point;
}
public double getDistance() {
return distance;
}
public double getPoint() {
return point;
}
}
}
/**** Core Animation Engine ****/
public enum Animator {
INSTANCE;
private Timer timer;
private List<Animatable> properies;
private Animator() {
properies = new ArrayList<>(5);
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
List<Animatable> copy = new ArrayList<>(properies);
Iterator<Animatable> it = copy.iterator();
while (it.hasNext()) {
Animatable ap = it.next();
ap.tick();
}
if (properies.isEmpty()) {
timer.stop();
}
}
});
}
public void add(Animatable ap) {
properies.add(ap);
timer.start();
}
protected void removeAll(List<Animatable> completed) {
properies.removeAll(completed);
}
public void remove(Animatable ap) {
properies.remove(ap);
if (properies.isEmpty()) {
timer.stop();
}
}
}
}
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
I'm learning Java and would really appreciate some help.
I'm trying to get this effect http://tinypic.com/r/339p0ud/8
I using stack overflow 'MOVE CUBE' example:
How Do I Use KeyEventDispatcher
and
stack overflow 'Click coordinate' example:
Using the coordinate plane in the JFrame
I wish to join them together, so that I have a cube I can move around with my keys and
as I do the coordinates show up next to the cube (so the coordinates move with the cube) and change as the position changes.
I would be fascinated to see how this is done as I have tried my best the past week with no success.
thanks in advance for any help or tips you provide here,
Joe
MOVE.java (move the block around with keys)
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
public class Move extends JPanel {
public static final long serialVersionUID = 1L;
public static final String IMAGE_PATH = "http://mathforum.org/alejandre/magic.square/4x4grid.gif";
public static final String IMAGE_PATH_PLAYER = "http://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/Square-symbol.svg/50px-Square-symbol.svg.png";
public static final int STEP = 3;
public static final int TIMER_DELAY = STEP * 8;
public BufferedImage bkgrndImage = null;
public BufferedImage playerImage = null;
public Map<Direction, Boolean> directionMap = new HashMap<Direction, Boolean>();
public int playerX = 0;
public int playerY = 0;
enum Direction {
UP(KeyEvent.VK_UP, 0, -1), DOWN(KeyEvent.VK_DOWN, 0, 1),
LEFT(KeyEvent.VK_LEFT, -1, 0), RIGHT(KeyEvent.VK_RIGHT, 1, 0);
public int keyCode;
public int xDirection;
public int yDirection;
private Direction(int keyCode, int xDirection, int yDirection) {
this.keyCode = keyCode;
this.xDirection = xDirection;
this.yDirection = yDirection;
}
public int getKeyCode() {
return keyCode;
}
public int getXDirection() {
return xDirection;
}
public int getYDirection() {
return yDirection;
}
}
public Move() {
try {
URL bkgrdImageURL = new URL(IMAGE_PATH);
URL playerImageURL = new URL(IMAGE_PATH_PLAYER);
bkgrndImage = ImageIO.read(bkgrdImageURL);
playerImage = ImageIO.read(playerImageURL);
setPreferredSize(new Dimension(bkgrndImage.getWidth(), bkgrndImage.getHeight()));
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
for (Direction direction : Direction.values()) {
directionMap.put(direction, false);
}
setKeyBindings();
Timer timer = new Timer(TIMER_DELAY, new TimerListener());
timer.start();
}
public void setKeyBindings() {
InputMap inMap = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap actMap = getActionMap();
for (final Direction direction : Direction.values()) {
KeyStroke pressed = KeyStroke.getKeyStroke(direction.getKeyCode(), 0, false);
KeyStroke released = KeyStroke.getKeyStroke(direction.getKeyCode(), 0, true);
inMap.put(pressed, direction.toString() + "pressed");
inMap.put(released, direction.toString() + "released");
actMap.put(direction.toString() + "pressed", new AbstractAction() {
public static final long serialVersionUID = 1L;
#Override
public void actionPerformed(ActionEvent e) {
directionMap.put(direction, true);
}
});
actMap.put(direction.toString() + "released", new AbstractAction() {
public static final long serialVersionUID = 1L;
#Override
public void actionPerformed(ActionEvent e) {
directionMap.put(direction, false);
}
});
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (bkgrndImage != null) {
g.drawImage(bkgrndImage, 0, 0, null);
}
if (playerImage != null) {
g.drawImage(playerImage, playerX, playerY, null);
}
}
public class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
boolean moved = false;
for (Direction direction : Direction.values()) {
if (directionMap.get(direction)) {
playerX += STEP * direction.getXDirection();
playerY += STEP * direction.getYDirection();
moved = true;
}
}
if (moved) {
int x = playerX - 2 * STEP;
int y = playerY - 2 * STEP;
int w = playerImage.getWidth() + 4 * STEP;
int h = playerImage.getHeight() + 4 * STEP;
Move.this.repaint(x, y, w, h); // !! repaint just the player
}
}
}
public static void createAndShowUI() {
JFrame frame = new JFrame("MoveIcon");
frame.getContentPane().add(new Move());
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() {
#Override
public void run() {
createAndShowUI();
}
});
}
COORDINATE.java (get x and y position in JFrame)
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Coordinate
{
private int x;
private int y;
private String text;
private DrawingBase canvas;
private void displayGUI()
{
JFrame frame = new JFrame("Drawing Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
canvas = new DrawingBase();
canvas.addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent me)
{
text = "X : " + me.getX() + " Y : " + me.getY();
x = me.getX();
y = me.getY();
canvas.setValues(text, x, y);
}
});
frame.setContentPane(canvas);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String... args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
new Coordinate().displayGUI();
}
});
}
}
class DrawingBase extends JPanel
{
private String clickedAt = "";
private int x = 0;
private int y = 0;
public void setValues(String text, int x, int y)
{
clickedAt = text;
this.x = x;
this.y = y;
repaint();
}
public Dimension getPreferredSize()
{
return (new Dimension(500, 400));
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawString(clickedAt, x, y);
}
}
No sure if this is what you want, I removed Coordinate and did everything in one class
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.awt.image.TileObserver;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import org.w3c.dom.events.EventTarget;
import org.w3c.dom.events.MouseEvent;
import org.w3c.dom.views.AbstractView;
public class Move extends JPanel{
public static final long serialVersionUID = 1L;
public static final String IMAGE_PATH = "http://mathforum.org/alejandre/magic.square/4x4grid.gif";
public static final String IMAGE_PATH_PLAYER = "http://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/Square-symbol.svg/50px-Square-symbol.svg.png";
public static final int STEP = 3;
public static final int TIMER_DELAY = STEP * 8;
public BufferedImage bkgrndImage = null;
public BufferedImage playerImage = null;
public Map<Direction, Boolean> directionMap = new HashMap<Direction, Boolean>();
public int playerX = 0;
public int playerY = 0;
private static int xPosition = 0;
private static int yPosition = 0;
private String s = "";
private JLabel jlbl1 = new JLabel("");
enum Direction {
UP(KeyEvent.VK_UP, 0, -1), DOWN(KeyEvent.VK_DOWN, 0, 1),
LEFT(KeyEvent.VK_LEFT, -1, 0), RIGHT(KeyEvent.VK_RIGHT, 1, 0);
public int keyCode;
public int xDirection;
public int yDirection;
private Direction(int keyCode, int xDirection, int yDirection) {
this.keyCode = keyCode;
this.xDirection = xDirection;
this.yDirection = yDirection;
}
public int getKeyCode() {
return keyCode;
}
public int getXDirection() {
return xDirection;
}
public int getYDirection() {
return yDirection;
}
}
public Move() {
this.add(jlbl1,new Dimension(100,100));
try {
URL bkgrdImageURL = new URL(IMAGE_PATH);
URL playerImageURL = new URL(IMAGE_PATH_PLAYER);
bkgrndImage = ImageIO.read(bkgrdImageURL);
playerImage = ImageIO.read(playerImageURL);
setPreferredSize(new Dimension(bkgrndImage.getWidth(), bkgrndImage.getHeight()));
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
for (Direction direction : Direction.values()) {
directionMap.put(direction, false);
}
setKeyBindings();
Timer timer = new Timer(TIMER_DELAY, new TimerListener());
timer.start();
}
public void setKeyBindings() {
InputMap inMap = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap actMap = getActionMap();
for (final Direction direction : Direction.values()) {
KeyStroke pressed = KeyStroke.getKeyStroke(direction.getKeyCode(), 0, false);
KeyStroke released = KeyStroke.getKeyStroke(direction.getKeyCode(), 0, true);
inMap.put(pressed, direction.toString() + "pressed");
inMap.put(released, direction.toString() + "released");
actMap.put(direction.toString() + "pressed", new AbstractAction() {
public static final long serialVersionUID = 1L;
#Override
public void actionPerformed(ActionEvent e) {
directionMap.put(direction, true);
}
});
actMap.put(direction.toString() + "released", new AbstractAction() {
public static final long serialVersionUID = 1L;
#Override
public void actionPerformed(ActionEvent e) {
directionMap.put(direction, false);
}
});
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (bkgrndImage != null) {
g.drawImage(bkgrndImage, 0, 0, null);
}
if (playerImage != null) {
g.drawImage(playerImage, playerX, playerY, null);
}
}
public class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
boolean moved = false;
for (Direction direction : Direction.values()) {
if (directionMap.get(direction)) {
playerX += STEP * direction.getXDirection();
playerY += STEP * direction.getYDirection();
moved = true;
}
}
if (moved) {
int x = playerX - 2 * STEP;
int y = playerY - 2 * STEP;
int w = playerImage.getWidth() + 4 * STEP;
int h = playerImage.getHeight() + 4 * STEP;
Move.this.repaint(x, y, w, h); // !! repaint just the player
}
s = playerX+", "+playerY;
jlbl1.setText(s);
}
}
public static void createAndShowUI() {
JFrame frame = new JFrame("MoveIcon");
frame.getContentPane().add(new Move());
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() {
#Override
public void run() {
createAndShowUI();
}
});
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (bkgrndImage != null) {
g.drawImage(bkgrndImage, 0, 0, null);
}
if (playerImage != null) {
g.drawImage(playerImage, playerX, playerY, null);
//offsetX and offsetY are the location relative to the cube to draw the string
//also note that the y coordinate is the location where the BOTTOM of the string begins
g.drawString("Clicked at: " + playerX + "," + playerY + ".", playerX + offsetX, playerY + offsetY)
}
}
This will draw to the location of the player. I can't imagine that it requires much effort to adjust it to draw to the clicked position.