I would to rotate JXImagePanel. It should be possible - this is about JXImagePanel:
Swing :: JXImagePanel
While JLabel and JButton allow you to easily add images to your Swing applications,
the JXImagePanel makes it trivially easy to add any BufferedImage or Icon to your Swing applications.
If editable, it also provides a way for the user to change the image. In addition, the JXImagePanel provides many built in effects out-of-the-box,
including Tiling, Scaling, Rotating, Compositing, and more.
However, I cannot figure out how to do this. Currently my code snippet is:
bufferedImage = ImageIO.read(new File("image.png"));
image = new ImageIcon(bufferedImage).getImage();
tempImage = image.getScaledInstance(100, 150, Image.SCALE_FAST);
this.deskJXImagePanel.setImage(tempImage);
Now I would like to rotate it in 0-360 degrees. How it can be done?
JXImagePanel is deprecated (actually, made package private as of 1.6.2, because it's still used internally), so better not use is, will be removed soon.
Instead, use a JXPanel with an ImagePainter and an arbitrary transformOp applied to the painter, in code snippets something like:
JXPanel panel = new JXPanel();
ImagePainter image = new ImagePainter(myImage);
image.setFilters(
new AffineTransformOp(AffineTransform.getRotateInstance(-Math.PI * 2 / 8, 100, 100), null)
);
panel.setBackgroundPainter(image);
you'll probably have to play a bit to get the exact effects you want to achieve. On problems, you might want to try posting to the Swinglabs forum.
I don't know somethimg more about SwingX's JXImagePanel but for plain vanilla Swing there exists excelent workaround (by aephyr or tjacobs or ... I hate this endless-mess from old.forums.sun.com by Sn'Oracle eerrrght)
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.Timer;
import javax.swing.filechooser.*;
public class RotatableImageComponent extends JComponent {
private static final long serialVersionUID = 1L;
private Image image;
private double angle = 0;
private MyObservable myObservable;
public RotatableImageComponent() {
myObservable = new MyObservable();
}
public RotatableImageComponent(Image image) {
this();
this.image = image;
}
public Image getImage() {
return image;
}
public void setImage(Image image) {
this.image = image;
}
public double getAngle() {
return angle;
}
public void setAngle(double angle) {
if (angle == this.angle) {
return;
}
this.angle = angle;
double circle = Math.PI * 2;
while (angle < 0) {
angle += circle;
}
while (angle > circle) {
angle -= circle;
}
if (myObservable != null) {
myObservable.setChanged();
myObservable.notifyObservers(this);
}
repaint();
}
/**
* In the rotation events sent to the listener(s), the second argument
* (the value) will be a reference to the RotatableImageComponent. One then
* should call getAngle() to get the new value.
* #param o
*/
public void addRotationListener(Observer o) {
myObservable.addObserver(o);
}
public void removeRotationListener(Observer o) {
myObservable.deleteObserver(o);
}
public void rotateClockwise(double rotation) {
setAngle(getAngle() + rotation);
}
public void rotateCounterClockwise(double rotation) {
//setAngle(getAngle() - rotation);
rotateClockwise(-rotation);
}
#Override
public void paintComponent(Graphics g) {
if (image == null) {
super.paintComponent(g);
return;
}
Graphics2D g2 = (Graphics2D) g;
AffineTransform trans = AffineTransform.getTranslateInstance(getWidth() / 2, getHeight() / 2);
trans.rotate(angle);
trans.translate(-image.getWidth(null) / 2, -image.getHeight(null) / 2);
g2.transform(trans);
g2.drawImage(image, 0, 0, null);
}
#Override
public Dimension getPreferredSize() {
if (image == null) {
return super.getPreferredSize();
}
int wid = image.getWidth(null);
int ht = image.getHeight(null);
int dist = (int) Math.ceil(Math.sqrt(wid * wid + ht * ht));
return new Dimension(dist, dist);
}
public static class TimedRotation {
private RotatableImageComponent comp;
private long totalTime, startTime;
private double toRotate, startRotation;
private int interval;
public Timer myTimer;
private myAction mAction;
public TimedRotation(RotatableImageComponent comp, double toRotate, long totalTime, int interval) {
//super(interval, new myAction());
this.comp = comp;
this.totalTime = totalTime;
this.toRotate = toRotate;
this.startRotation = comp.getAngle();
this.interval = interval;
}
public void start() {
if (mAction == null) {
mAction = new myAction();
}
if (myTimer == null) {
myTimer = new Timer(interval, new myAction());
myTimer.setRepeats(true);
} else {
myTimer.setDelay(interval);
}
myTimer.start();
startTime = System.currentTimeMillis();
}
public void stop() {
myTimer.stop();
}
private class myAction implements ActionListener {
#Override
public void actionPerformed(ActionEvent ae) {
long now = System.currentTimeMillis();
if (totalTime <= (now - startTime)) {
comp.setAngle(startRotation + toRotate);
stop();
return;
}
double percent = (double) (now - startTime) / totalTime;
double rotation = toRotate * percent;
comp.setAngle(startRotation + rotation);
}
}
}
private class MyObservable extends Observable {
#Override
protected void setChanged() {
super.setChanged();
}
}
public static class RotationKeys extends KeyAdapter {
private RotatableImageComponent comp;
private double rotationAmt;
public RotationKeys(RotatableImageComponent comp, double rotationAmt) {
this.comp = comp;
this.rotationAmt = rotationAmt;
}
public RotationKeys(RotatableImageComponent comp) {
this(comp, Math.PI / 90);
}
#Override
public void keyPressed(KeyEvent ke) {
if (ke.getKeyCode() == KeyEvent.VK_LEFT) {
comp.rotateCounterClockwise(rotationAmt);
} else if (ke.getKeyCode() == KeyEvent.VK_RIGHT) {
comp.rotateClockwise(rotationAmt);
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
FileFilter filter = new FileNameExtensionFilter("JPEG file", "jpg", "jpeg");
JFileChooser chooser = new JFileChooser();
chooser.addChoosableFileFilter(filter);
if (chooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
File f = chooser.getSelectedFile();
BufferedImage im = ImageIO.read(f);
final RotatableImageComponent c = new RotatableImageComponent(im);
c.addRotationListener(new Observer() {
#Override
public void update(Observable arg0, Object arg1) {
System.out.println("Angle changed: " + ((RotatableImageComponent) arg1).getAngle());
}
});
JPanel controls = new JPanel(new FlowLayout());
final JTextField rotation = new JTextField();
rotation.setText("30");
controls.add(new JLabel("Rotation(degrees)"));
controls.add(rotation);
final JTextField time = new JTextField();
time.setText("1000");
time.setColumns(6);
rotation.setColumns(7);
controls.add(new JLabel("Time(millis)"));
controls.add(time);
JButton go = new JButton("Go");
go.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
TimedRotation tr = new TimedRotation(c,
Double.parseDouble(rotation.getText()) / 180 * Math.PI,
Integer.parseInt(time.getText()), 50);
tr.start();
}
});
controls.add(go);
RotationKeys keys = new RotationKeys(c);
c.addKeyListener(keys);
c.setFocusable(true);
JFrame jf1 = new JFrame();
jf1.getContentPane().add(c);
JFrame jf2 = new JFrame();
jf2.getContentPane().add(controls);
jf1.pack();
jf2.pack();
jf1.setLocation(100, 100);
jf2.setLocation(400, 100);
jf1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf2.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
jf1.setVisible(true);
jf2.setVisible(true);
}
} catch (Throwable t) {
t.printStackTrace();
}
}
});
}
}
Related
I am currently trying to make a game where circles are spawned and the player must click on it to gain a score. There are a bunch of details there but I want to ask this question.
Some variables are unused there or out of place since the original code was from a snake game made by "Bro Code" on youtube. I am trying to use his code as a foundation.
How to spawn multiple circles without limit in a 2d grid? (I tested the spawnTarget() method in the always-triggering listener and it only allows a single circle to exist.)
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Random;
public class GamePanel extends JPanel implements ActionListener {
static final int SCREEN_WIDTH = 1600;
static final int SCREEN_HEIGHT = 1000;
static final int UNIT_SIZE = 25;
static final int GAME_UNITS = (SCREEN_WIDTH*SCREEN_HEIGHT)/UNIT_SIZE;
static final int DELAY = 75;
final int x[] = new int[GAME_UNITS];
final int y[] = new int[GAME_UNITS];
int bodyParts = 6;
int applesEaten = 0;
int appleX;
int appleY;
boolean running = false;
Timer timer;
Random random;
//
JPanel clockPanel;
JLabel clock;
long startTime;
long endTime;
//
long elapsedSeconds;
long elapsedTenthSeconds;
//
//
GamePanel() {
random = new Random();
this.setPreferredSize(new Dimension(SCREEN_WIDTH,SCREEN_HEIGHT));
this.setBackground(Color.black);
this.setFocusable(true);
this.addKeyListener(new MyKeyAdapter());
startGame();
}
public void startGame() {
running = true;
timer = new Timer(DELAY,this);
timer.start();
clockMethod();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
public void draw(Graphics g) {
if (running) {
// optional grid
for(int i=0; i<SCREEN_WIDTH/UNIT_SIZE; i++) {
g.drawLine(0, i*UNIT_SIZE, SCREEN_WIDTH, i*UNIT_SIZE);
g.drawLine(i*UNIT_SIZE, 0, i*UNIT_SIZE, SCREEN_HEIGHT);
}
// apple
g.setColor(Color.red);
g.fillOval(appleX, appleY, UNIT_SIZE, UNIT_SIZE);
// score
g.setColor(Color.white);
g.setFont(new Font("Courier New", Font.BOLD, 40));
FontMetrics metrics = getFontMetrics(g.getFont());
g.drawString(String.valueOf(applesEaten),(SCREEN_WIDTH - metrics.stringWidth(String.valueOf(applesEaten)))/2,2*g.getFont().getSize());
}
else {
gameOver(g);
}
}
public void newTargetCoords() {
appleX = random.nextInt((int)(SCREEN_WIDTH/UNIT_SIZE))*UNIT_SIZE;
appleY = random.nextInt((int)(SCREEN_HEIGHT/UNIT_SIZE))*UNIT_SIZE;
}
public void move() {
}
public void spawnTarget() {
newTargetCoords();
}
public void checkApple() {
if ((x[0] == appleX)&&(y[0] == appleY)) {
bodyParts++;
applesEaten++;
}
}
public void checkCollisions() {
if (!running) {
timer.stop();
}
}
public void gameOver(Graphics g) {
// score
g.setColor(Color.white);
g.setFont(new Font("Courier New", Font.BOLD, 20));
FontMetrics metrics1 = getFontMetrics(g.getFont());
g.drawString("score: " + applesEaten,(SCREEN_WIDTH - metrics1.stringWidth("score: " + applesEaten))/2,g.getFont().getSize());
// Game Over text
g.setColor(Color.green);
g.setFont(new Font("Courier New", Font.PLAIN, 40));
FontMetrics metrics2 = getFontMetrics(g.getFont());
g.drawString("game over",(SCREEN_WIDTH - metrics2.stringWidth("game over"))/2,SCREEN_HEIGHT/2);
}
public void restartGame() {
setVisible(false);
new GameFrame();
}
public void clockMethod() {
clockPanel = new JPanel();
clock = new JLabel("00:00");
clockPanel.add(clock);
startTime = System.currentTimeMillis();
add(clockPanel);
}
#Override
public void actionPerformed(ActionEvent e) {
if (running) {
move();
checkApple();
checkCollisions();
}
repaint();
if(timer.isRunning())
{
endTime = System.currentTimeMillis();
// elapsed quarter seconds for spawns
elapsedTenthSeconds = (endTime-startTime)/100;
// put elapsed seconds into variable
elapsedSeconds = (endTime-startTime)/1000;
// declare formatting
int min = (int)elapsedSeconds/60;
int sec = (int)elapsedSeconds%60;
String minStr = (min<10 ? "0" : "")+min;
String secStr = (sec<10 ? "0" : "")+sec;
// display elapsed time (minutes:seconds)
clock.setText(minStr+":"+secStr);
// spawn circle
spawnTarget();
}
}
public class MyKeyAdapter extends KeyAdapter {
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_R) {
restartGame();
}
}
}
}
since the original code was from a snake game made by "Bro Code" on youtube
As a "general" recommendation, I'd avoid YouTube for learning code, apart from going out of date really fast, SO seems to spending an lot of time correcting the code examples coming from YouTube.
First, I'd recommend you take the time to read through Painting in AWT and Swing and Performing Custom Painting to make sure you have a baseline understanding of the painting process in Swing.
As to your question, if need to keep track what is visible. Depending on how you want it to workflow, I might have a pool of "entities" which which you can randomly pick and move them to a "visible entities" pool. When a new paint cycle runs, you'd simply paint the "visible entities".
You need to take into consideration how long an entity should be displayed and each time your "game loop" runs, you'd need to check to see if any of the visible entities have "died", at which point you'd remove from the "visible entities" pool and put them back in the "entities pool".
For example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class StopWatch {
private Instant startedAt;
private Duration duration;
public void setDuration(Duration duration) {
this.duration = duration;
}
public Duration getDuration() {
return duration;
}
public void start() {
startedAt = Instant.now();
}
public Instant getStartedAt() {
return startedAt;
}
public Duration getTimeRemaining() {
Instant startedAt = getStartedAt();
Duration duration = getDuration();
if (startedAt == null || duration == null) {
return Duration.ZERO;
}
Duration runtime = Duration.between(startedAt, Instant.now());
return duration.minus(runtime);
}
public boolean hasTimeRemaining() {
Duration timeRemaining = getTimeRemaining();
return timeRemaining.toMillis() > 0;
}
}
public class Target {
private int row;
private int col;
private StopWatch stopWatch = new StopWatch();
public Target(int row, int col) {
this.row = row;
this.col = col;
}
public int getColumn() {
return col;
}
public int getRow() {
return row;
}
public void spawn(Duration lifeSpan) {
stopWatch = new StopWatch();
stopWatch.setDuration(lifeSpan);
stopWatch.start();
}
public void die() {
stopWatch = null;
}
public Instant getBirthDate() {
if (stopWatch == null) {
return null;
}
return stopWatch.getStartedAt();
}
public Duration getLifeSpan() {
if (stopWatch == null) {
return null;
}
return stopWatch.getDuration();
}
public Duration getTimeRemaining() {
if (stopWatch == null) {
return Duration.ZERO;
}
return stopWatch.getTimeRemaining();
}
public boolean isAlive() {
if (stopWatch == null) {
return false;
}
return stopWatch.hasTimeRemaining();
}
}
public class TestPane extends JPanel {
private List<Target> targets;
private List<Target> visibleTargets;
private int rows = 4;
private int cols = 4;
private Target clickedTarget;
public TestPane() {
targets = new ArrayList<>(getRows() * getColumns());
visibleTargets = new ArrayList<>(getRows() * getColumns());
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
targets.add(new Target(row, col));
}
}
Timer timer = new Timer(5, new ActionListener() {
private Random rnd = new Random();
private List<Target> deadTargets = new ArrayList<>(getRows() * getColumns());
private StopWatch respawnStopWatch;
protected void restartRespawnClock() {
// Spawn a new target every second
respawnStopWatch.setDuration(Duration.ofSeconds(rnd.nextInt(1) + 1));
respawnStopWatch.start();
}
#Override
public void actionPerformed(ActionEvent e) {
if (respawnStopWatch == null) {
respawnStopWatch = new StopWatch();
restartRespawnClock();
}
if (!respawnStopWatch.hasTimeRemaining()) {
restartRespawnClock();
if (!targets.isEmpty()) {
Collections.shuffle(targets);
Target target = targets.remove(0);
Duration lifeSpan = Duration.ofSeconds(rnd.nextInt(5) + 3);
target.spawn(lifeSpan);
visibleTargets.add(target);
}
}
deadTargets.clear();
for (Target target : visibleTargets) {
if (!target.isAlive()) {
deadTargets.add(target);
}
}
visibleTargets.removeAll(deadTargets);
targets.addAll(deadTargets);
repaint();
}
});
timer.start();
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
for (Target target : visibleTargets) {
Rectangle bounds = getBoundsFor(target);
if (bounds.contains(e.getPoint())) {
clickedTarget = target;
return;
}
}
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
public int getRows() {
return rows;
}
public int getColumns() {
return cols;
}
protected Rectangle getBoundsFor(Target target) {
int width = getWidth() / getColumns();
int height = getHeight() / getRows();
int x = target.getColumn() * width;
int y = target.getRow() * height;
return new Rectangle(x, y, width, height);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
for (Target target : visibleTargets) {
Rectangle bounds = getBoundsFor(target);
if (target == clickedTarget) {
g2d.fillOval(bounds.x, bounds.y, bounds.width, bounds.height);
} else {
g2d.drawOval(bounds.x, bounds.y, bounds.width, bounds.height);
}
}
g2d.dispose();
}
}
}
A couple of things to note...
I'm not caching the Target bounds as they are dynamically calculate based on the current size of the component. Realistically, you could use a ComponentListener and when componentResized is called, invalidate the cache, but this is an additional enhancement you could investigate yourself.
The time between spawning a new target is randomly between 1-2 seconds, this could be tweaked to use milliseconds instead of seconds, but I'm sure most users won't see the difference
A target has a random live span of between 2-7 seconds, feel free to modify it.
The example also demonstrates a simple way to detect when a target has been clicked, in the example above, it will simple cause the target to be filled in.
I would also consider using seperate components for the game and game over screens, possibly switching between them using a CardLayout. This reduces the complexity of the classes.
I'm new to java, and as a challenge I'm following some tutorials on how to create a small game. I've managed to make a nice keyboard input reader but sadly I don't know why my repaint(); isn't working... The point is to change the ints Py and Px, they are responsible in changing the position of the player!
This is the class f2, a JPanel that is added to a Jframe in another file.
import java.awt.*;
import java.awt.event.*;
public class f2 extends JPanel implements Runnable{
keys keyh = new keys();
Thread gameThreadMain;
//background color
Color bg2 = new Color(81,89,98);
Color txt1 = new Color(237,237,237);
//width control
final int w = 700, h = 700;
int wp = 64, hp = 64;
//spawn cords
//
int spawn1x = (w / 2) - (wp / 2);
int spawn1y = (h / 2) - (hp / 2);
//Player Speed
int Px = spawn1x;
int Py = spawn1y;
int Ps = 4;
//FPS
int fps = 60;
public f2(){
/* this.addKet*/
this.setBackground(bg2);
this.setPreferredSize(new Dimension(w,h));
this.setDoubleBuffered(true);
/*this.setLayout();*/
}
public void startGameThreadMain(){
gameThreadMain = new Thread(this);
gameThreadMain.start();
}
#Override
public void run(){
double drawInterval = 1000000000/fps;
double nextDrawTime = System.nanoTime() + drawInterval;
while(gameThreadMain != null){
update();
repaint();
/*System.out.print("check");*/
try{
double remainingTime = nextDrawTime - System.nanoTime();
remainingTime = remainingTime / 1000000;
if (remainingTime < 0) {
remainingTime = 0;
}
Thread.sleep((long) remainingTime);
nextDrawTime += drawInterval;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void update(){
//Keyboard
if(keyh.UpPressed == true) {
Py -= Ps;
}
else if(keyh.DownPressed == true) {
Py += Ps;
}
else if(keyh.LeftPressed == true) {
Px -= Ps;
}
else if(keyh.RightPressed == true) {
Px += Ps;
}else{
//
}
}
//player painter (this is the part where the 'repaint();' isn't working
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D player = (Graphics2D)g;
player.setColor(txt1);
player.fillRect(Px,Py,wp,hp);
System.out.println("repainted");
}
}```
//Thanks for reading!
startGameThreadMain is never called.
Swing is not thread AND is single threaded. You should never update the UI, or some state the UI depends on, from outside the context of the Event Dispatching Thread.
See Concurrency in Swing for more details and How to Use Swing Timers for one possible solution.
You should also be aware of:
How to Use Key Bindings
Painting in AWT and Swing
Performing Custom Painting
For example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.geom.Rectangle2D;
import java.util.HashSet;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class Player {
private int x;
private int y;
private Color backgroundColor = new Color(237, 237, 237);
private Shape shape;
public Player(int x, int y) {
this.x = x;
this.y = y;
this.shape = new Rectangle2D.Double(0, 0, getWidth(), getHeight());
}
public void setLocation(int x, int y) {
setX(x);
setY(y);
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getWidth() {
return 64;
}
public int getHeight() {
return 64;
}
public void paint(Graphics2D g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(backgroundColor);
g2d.translate(getX(), getY());
g2d.fill(shape);
g2d.dispose();
}
}
public enum Movement {
UP, DOWN, LEFT, RIGHT;
}
public interface MovementObserver {
public void movementDidActiviated(Object source, Movement movement);
public void movementWasDeactiviated(Object source, Movement movement);
}
public class TestPane extends JPanel {
private Timer timer;
private Player player;
private Set<Movement> movements = new HashSet<>();
public TestPane() {
player = new Player(0, 0);
int spawnY = (getPreferredSize().width - player.getWidth()) / 2;
int spawnX = (getPreferredSize().height - player.getHeight()) / 2;
player.setLocation(spawnX, spawnY);
setBackground(new Color(81, 89, 98));
timer = new Timer(15, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (movements.contains(Movement.UP)) {
player.setY(player.getY() - 1);
} else if (movements.contains(Movement.DOWN)) {
player.setY(player.getY() + 1);
}
if (movements.contains(Movement.LEFT)) {
player.setX(player.getX() - 1);
} else if (movements.contains(Movement.RIGHT)) {
player.setX(player.getX() + 1);
}
repaint();
}
});
MovementObserver observer = new MovementObserver() {
#Override
public void movementDidActiviated(Object source, Movement movement) {
movements.add(movement);
}
#Override
public void movementWasDeactiviated(Object source, Movement movement) {
movements.remove(movement);
}
};
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "Up.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "Down.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Left.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Right.pressed");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Up.released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Down.released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Left.released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Right.released");
am.put("Up.pressed", new MovementAction(Movement.UP, observer, true));
am.put("Down.pressed", new MovementAction(Movement.DOWN, observer, true));
am.put("Left.pressed", new MovementAction(Movement.LEFT, observer, true));
am.put("Right.pressed", new MovementAction(Movement.RIGHT, observer, true));
am.put("Up.released", new MovementAction(Movement.UP, observer, false));
am.put("Down.released", new MovementAction(Movement.DOWN, observer, false));
am.put("Left.released", new MovementAction(Movement.LEFT, observer, false));
am.put("Right.released", new MovementAction(Movement.RIGHT, observer, false));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(700, 700);
}
#Override
public void addNotify() {
super.addNotify();
if (timer == null) {
return;
}
timer.start();
}
#Override
public void removeNotify() {
super.removeNotify();
if (timer == null) {
return;
}
timer.stop();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (player == null) {
return;
}
Graphics2D g2d = (Graphics2D) g.create();
player.paint(g2d);
g2d.dispose();
}
}
public class MovementAction extends AbstractAction {
private Movement movement;
private MovementObserver observer;
private boolean activate;
public MovementAction(Movement movement, MovementObserver observer, boolean active) {
this.movement = movement;
this.observer = observer;
this.activate = active;
}
public Movement getMovement() {
return movement;
}
public MovementObserver getObserver() {
return observer;
}
public boolean doesActivate() {
return activate;
}
#Override
public void actionPerformed(ActionEvent e) {
if (doesActivate()) {
getObserver().movementDidActiviated(this, getMovement());
} else {
getObserver().movementWasDeactiviated(this, getMovement());
}
}
}
}
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();
}
}
}
}
I have a large program that I will post some classes of and hopefully you guys can find the problem. Basically, sometimes when I start it, it creates the game just fine, and others the background is up a few pixels to the north and west directions leaving very unsightly whitespace. I cannot seem to find the missing piece of code that decides whether not it does this. It honestly feel like some kind of rendering glitch on my machine. At any rate, I have put a background getX and getY method in for debugging and have noticed that whether the background is fully stretched to the screen(its a custom background so the pixel height and width match perfectly), or its up and to the left, the background still reads that it is displaying at (0,0). I will post all the methods from the main thread to the creating of the background in the menu. I will leave notes indicating the path it takes through this code that gets it to creating the background. Thank you for your help and I will check in regularly for edits and more information.
EDIT: added background.java
EDIT2: added pictures explaining problem
Menu.java *ignore the FileIO code, the main point is the creation of a new GamePanel()
public class Menu {
private static File file;
public static void main(String[] args) throws IOException {
file = new File("saves.txt");
if(file.exists()){
FileIO.run();
FileIO.profileChoose();
}
else{
FileIO.profileCreate();
FileIO.run();
}
JFrame window = new JFrame("Jolly Jackpot Land");
window.setContentPane(new GamePanel());
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setResizable(false);
window.pack();
window.setLocationRelativeTo(null);
window.setVisible(true);
}
}
Next is the GamePanel.java
public class GamePanel extends JPanel implements Runnable, KeyListener {
// ID
private static final long serialVersionUID = 1L;
// Dimensions
public static final int WIDTH = 320;
public static final int HEIGHT = 240;
public static final int SCALE = 2;
// Thread
private Thread thread;
private boolean running;
private int FPS = 30;
private long targetTime = 1000 / FPS;
// Image
private BufferedImage image;
private Graphics2D g;
// Game State Manager
private GameStateManager gsm;
public GamePanel() {
super();
setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
setFocusable(true);
requestFocus();
}
public void addNotify() {
super.addNotify();
if (thread == null) {
thread = new Thread(this);
addKeyListener(this);
thread.start();
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
}
private void init() {
image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
g = (Graphics2D) image.getGraphics();
running = true;
gsm = new GameStateManager();
}
#Override
public void run() {
init();
long start;
long elapsed;
long wait;
// Game Loop
while (running) {
start = System.nanoTime();
update();
draw();
drawToScreen();
elapsed = System.nanoTime() - start;
wait = targetTime - (elapsed / 1000000);
if (wait < 0) {
wait = 5;
}
try {
Thread.sleep(wait);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void update() {
gsm.update();
}
private void draw() {
gsm.draw(g);
}
private void drawToScreen() {
Graphics g2 = getGraphics();
g2.drawImage(image, 0, 0, WIDTH * SCALE, HEIGHT * SCALE, null);
g2.dispose();
}
#Override
public void keyPressed(KeyEvent k) {
gsm.keyPressed(k.getKeyCode());
}
#Override
public void keyReleased(KeyEvent k) {
}
#Override
public void keyTyped(KeyEvent arg0) {
}
}
This calls for the creation of a new GameStateManager object in its init() method and the class for that is here.
GameStateManager.java
public class GameStateManager {
private ArrayList<GameState> gameStates;
private int currentState;
public static final int MENUSTATE = 0;
public static final int SLOTGAMESTATE = 1;
public static final int DICEGAMESTATE = 2;
public static final int ROULETTEGAMESTATE = 3;
public static final int LEADERBOARDSTATE = 4;
public static final int SETTINGSSTATE = 5;
public static final int HELPSTATE = 6;
public GameStateManager() {
gameStates = new ArrayList<GameState>();
currentState = 0;
gameStates.add(new MenuState(this));
gameStates.add(new SlotGameState(this));
gameStates.add(new DiceGameState(this));
gameStates.add(new RouletteGameState(this));
gameStates.add(new LeaderboardState(this));
gameStates.add(new SettingsState(this));
gameStates.add(new HelpState(this));
}
public void setState(int state){
currentState = state;
gameStates.get(currentState).init();
currentState = 0;
}
public int getState() {
return currentState;
}
public void update() {
gameStates.get(currentState).init();
}
public void draw(java.awt.Graphics2D g){
gameStates.get(currentState).draw(g);
}
public void keyPressed(int k){
gameStates.get(currentState).keyPressed(k);
}
public void keyReleased(int k) {
gameStates.get(currentState).keyReleased(k);
}
}
GameState is an abstract class I have so its not worth posting, it only contains init(), draw(), etc. This next class is the last and final class and is called because GameStateMananger starts at MENUSTATE or 0, and when GSM is initialized it initializes its current state, thus taking us to the class MenuState
MenuState.java
public class MenuState extends GameState {
private Background bg;
public FontMetrics fontMetrics;
private int choice = 0;
private String[] options = { "Slot Machine", "Dice Toss", "Roulette Wheel", "Leaderboards", "Settings", "Help",
"Quit" };
private Color titleColor;
private Font titleFont;
private Font font;
public MenuState(GameStateManager gsm) {
this.gsm = gsm;
try {
bg = new Background("/Backgrounds/happybg.png");
titleColor = Color.WHITE;
titleFont = new Font("Georgia", Font.PLAIN, 28);
} catch (Exception e) {
e.printStackTrace();
}
font = new Font("Arial", Font.PLAIN, 12);
}
#Override
public void init() {
}
#Override
public void update() {
}
#Override
public void draw(Graphics2D g) {
Canvas c = new Canvas();
fontMetrics = c.getFontMetrics(font);
// Draw BG
bg.draw(g);
// Draw title
g.setColor(titleColor);
g.setFont(titleFont);
String title = "Jolly Jackpot Land!";
g.drawString(title, 36, 60);
g.setFont(font);
for (int i = 0; i < options.length; i++) {
if (i == choice)
g.setColor(Color.RED);
else
g.setColor(Color.WHITE);
g.drawString(options[i], 30, 120 + i * 15);
}
g.setColor(Color.WHITE);
g.setFont(new Font("Arial", Font.PLAIN, 10));
g.drawString("v1.1", 165, 235);
Object[] a = { ("Name: " + Player.getName()), ("Gil: " + Player.getGil()),
("Personal Best: " + Player.getPersonalBest()), ("Winnings: " + Player.getWinnings()),
("Wins: " + Player.getWins()), ("Losses: " + Player.getLosses()),
("Win/Loss Ratio: " + String.format("%.2f", Player.getRatio()) + "%") };
g.setFont(font);
if (Player.getName() != null) {
for (int x = 0; x < a.length; x++) {
g.drawString(a[x].toString(), GamePanel.WIDTH - fontMetrics.stringWidth(a[x].toString()) - 30,
120 + x * 15);
}
}
}
private void select() {
if (choice == 0) {
// Slots
gsm.setState(GameStateManager.SLOTGAMESTATE);
}
if (choice == 1) {
// Dice
gsm.setState(GameStateManager.DICEGAMESTATE);
}
if (choice == 2) {
// Roulette
gsm.setState(GameStateManager.ROULETTEGAMESTATE);
}
if (choice == 3) {
// Leaderboards
gsm.setState(GameStateManager.LEADERBOARDSTATE);
}
if (choice == 4) {
// Settings
gsm.setState(GameStateManager.SETTINGSSTATE);
}
if (choice == 5) {
// Help
gsm.setState(GameStateManager.HELPSTATE);
}
if (choice == 6) {
// Quit
System.exit(0);
}
}
#Override
public void keyPressed(int k) {
if (k == KeyEvent.VK_ENTER) {
select();
}
if (k == KeyEvent.VK_UP) {
choice--;
if (choice == -1) {
choice = options.length - 1;
}
}
if (k == KeyEvent.VK_DOWN) {
choice++;
if (choice == options.length) {
choice = 0;
}
}
}
#Override
public void keyReleased(int k) {
}
}
Background.java
public class Background {
private BufferedImage image;
private double x;
private double y;
public Background(String s) {
try {
image = ImageIO.read(getClass().getResourceAsStream(s));
} catch (Exception e) {
e.printStackTrace();
}
}
public void setPosition(double x, double y) {
this.setX(x);
this.setY(y);
}
public void draw(Graphics2D g) {
g.drawImage(image, 0, 0, null);
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
}
This is where it waits for input in the game loop basically. I know this is a lot of code, but a lot of it is skimming till a method call takes you to the next class. I just can't figure out why it only happens sometimes, if it was consistent I could debug it. Any help would be extremely appreciated.
These are both from clicking the .jar of the above program, exact same .jar, exact same source code, different result. I am bewildered.
I am trying to make a health bar, and as what might be original, it will start out green, and after losing health you find that it will turn yellow, then orange, then red.. or something relative to that.
I tried using the method provided in this link: https://stackoverflow.com/questions/19841477/java-smooth-color-transition
The result from that link was this code, just a test from value 100 to 0, but it ended in an IllegalArgumentException at normally Red and Green, and my guess for reason is it being over the value of 255.
Color to = Color.red;
Color base = Color.green;
int red = (int)Math.abs((100 * to.getRed()) + ((1 - 100) * base.getRed()));
int green = (int)Math.abs((100 * to.getGreen()) + ((1 - 100) * base.getGreen()));
int blue = (int)Math.abs((100 * to.getBlue()) + ((1 - 100) * base.getBlue()));
setForeground(new Color(red, green, blue));
It didn't really work, and I have absolutely no idea how I can get it to transition the way I wish it to.
So within my HealthBar class, I have an update() method
public void update() {
if (getValue() < 10) setForeground(Color.red);
else if (getValue() < 25) setForeground(Color.orange);
else if (getValue() < 60) setForeground(Color.yellow);
else setForeground(Color.green);
}
This code does a basic transition at certain points.
I need to create fields to use certain colors at certain values of the health bar, so now I have this..
if (getValue() < 10) {
Color to = Color.black;
// Color current = getForeground() ?
Color from = Color.red;
// ?
}
I am just going to use an example for the last bit.
So I know that I am going to have a color that I am going to, and a color that is a base. I am not so sure if I need a color for current. The problem that I see now is for the steps of the transition because each transition has a different amount of steps.
Summary and Question
I don't know how to achieve what I am attempting, all I know for sure is I need a to and base color, and I provided a link to an answer that I saw, but I couldn't really figure it out. With the information given, how could I get it to transition colors
?
I spent a lot of time trying to find/create a blending algorithm that worked for me, this is basically what I was able to hobble together.
I've used this approach to generate a gradient transition mixing multiple colors, as demonstrated here
Basically, this approach allows you to set up a series of colors and percentage marks so that you gain much greater control over which points the colors transition between.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.text.NumberFormat;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class ColorFading {
public static void main(String[] args) {
new ColorFading();
}
public ColorFading() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new FadePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class FadePane extends JPanel {
private final float[] fractions = new float[]{0f, 0.5f, 1f};
private final Color[] colors = new Color[]{Color.RED, Color.YELLOW, Color.GREEN};
private float progress = 1f;
private JSlider slider;
public FadePane() {
slider = new JSlider(0, 100);
setLayout(new BorderLayout());
add(slider, BorderLayout.SOUTH);
slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
progress = ((float)slider.getValue() / 100f);
repaint();
}
});
slider.setValue(100);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int width = getWidth();
int height = getHeight();
Color startColor = blendColors(fractions, colors, progress);
g2d.setColor(startColor);
g2d.fillRect(0, 0, width, height);
g2d.dispose();
}
}
public static Color blendColors(float[] fractions, Color[] colors, float progress) {
Color color = null;
if (fractions != null) {
if (colors != null) {
if (fractions.length == colors.length) {
int[] indicies = getFractionIndicies(fractions, progress);
float[] range = new float[]{fractions[indicies[0]], fractions[indicies[1]]};
Color[] colorRange = new Color[]{colors[indicies[0]], colors[indicies[1]]};
float max = range[1] - range[0];
float value = progress - range[0];
float weight = value / max;
color = blend(colorRange[0], colorRange[1], 1f - weight);
} else {
throw new IllegalArgumentException("Fractions and colours must have equal number of elements");
}
} else {
throw new IllegalArgumentException("Colours can't be null");
}
} else {
throw new IllegalArgumentException("Fractions can't be null");
}
return color;
}
public static int[] getFractionIndicies(float[] fractions, float progress) {
int[] range = new int[2];
int startPoint = 0;
while (startPoint < fractions.length && fractions[startPoint] <= progress) {
startPoint++;
}
if (startPoint >= fractions.length) {
startPoint = fractions.length - 1;
}
range[0] = startPoint - 1;
range[1] = startPoint;
return range;
}
public static Color blend(Color color1, Color color2, double ratio) {
float r = (float) ratio;
float ir = (float) 1.0 - r;
float rgb1[] = new float[3];
float rgb2[] = new float[3];
color1.getColorComponents(rgb1);
color2.getColorComponents(rgb2);
float red = rgb1[0] * r + rgb2[0] * ir;
float green = rgb1[1] * r + rgb2[1] * ir;
float blue = rgb1[2] * r + rgb2[2] * ir;
if (red < 0) {
red = 0;
} else if (red > 255) {
red = 255;
}
if (green < 0) {
green = 0;
} else if (green > 255) {
green = 255;
}
if (blue < 0) {
blue = 0;
} else if (blue > 255) {
blue = 255;
}
Color color = null;
try {
color = new Color(red, green, blue);
} catch (IllegalArgumentException exp) {
NumberFormat nf = NumberFormat.getNumberInstance();
System.out.println(nf.format(red) + "; " + nf.format(green) + "; " + nf.format(blue));
exp.printStackTrace();
}
return color;
}
}
OK, before Mad posted his answer (and 1+ to it), I was working on this too, so I might as well post what I came up with....
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.EnumMap;
import java.util.Map;
import javax.swing.*;
import javax.swing.event.*;
#SuppressWarnings("serial")
public class ColorTransition extends JPanel {
private static final int TRANSITION_DELAY = 30;
private static final int PREF_W = 800;
private static final int PREF_H = 600;
private RgbSliderPanel rgbSliderPanel1 = new RgbSliderPanel("Color 1");
private RgbSliderPanel rgbSliderPanel2 = new RgbSliderPanel("Color 2");
private Color background1;
private Color background2;
private JButton button = new JButton(new ButtonAction("Push Me"));
public ColorTransition() {
setBackground(Color.black);
add(rgbSliderPanel1.getMainPanel());
add(rgbSliderPanel2.getMainPanel());
add(button);
rgbSliderPanel1.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (RgbSliderPanel.COLOR.equals(evt.getPropertyName())) {
setBackground(rgbSliderPanel1.calculateColor());
}
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
#Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
button.setEnabled(enabled);
rgbSliderPanel1.setEnabled(enabled);
rgbSliderPanel2.setEnabled(enabled);
}
private class ButtonAction extends AbstractAction {
public ButtonAction(String name) {
super(name);
}
#Override
public void actionPerformed(ActionEvent e) {
ColorTransition.this.setEnabled(false);
background1 = rgbSliderPanel1.calculateColor();
background2 = rgbSliderPanel2.calculateColor();
setBackground(background1);
Timer timer = new Timer(TRANSITION_DELAY, new TransitionListener());
timer.start();
}
private class TransitionListener implements ActionListener {
private int index = 0;
#Override
public void actionPerformed(ActionEvent e) {
if (index > 100) {
((Timer) e.getSource()).stop();
ColorTransition.this.setEnabled(true);
} else {
int r = (int) (background2.getRed() * index / 100.0 + background1
.getRed() * (100 - index) / 100.0);
int g = (int) (background2.getGreen() * index / 100.0 + background1
.getGreen() * (100 - index) / 100.0);
int b = (int) (background2.getBlue() * index / 100.0 + background1
.getBlue() * (100 - index) / 100.0);
setBackground(new Color(r, g, b));
}
index++;
}
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("ColorTransition");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new ColorTransition());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
enum Rgb {
RED("Red"), GREEN("Green"), BLUE("Blue");
private String name;
private Rgb(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class RgbSliderPanel {
public static final String COLOR = "color";
private JPanel mainPanel = new JPanel();
private SwingPropertyChangeSupport propertyChangeSupport = new SwingPropertyChangeSupport(
this);
private Map<Rgb, JSlider> colorSliderMap = new EnumMap<>(Rgb.class);
private String name;
protected Color color;
public RgbSliderPanel(String name) {
this.name = name;
mainPanel.setBorder(BorderFactory.createTitledBorder(name));
//mainPanel.setOpaque(false);
mainPanel.setLayout(new GridLayout(0, 1));
for (Rgb rgb : Rgb.values()) {
JSlider colorSlider = new JSlider(0, 255, 0);
colorSliderMap.put(rgb, colorSlider);
mainPanel.add(colorSlider);
colorSlider.setBorder(BorderFactory.createTitledBorder(rgb.getName()));
colorSlider.setPaintTicks(true);
colorSlider.setPaintTrack(true);
colorSlider.setMajorTickSpacing(50);
colorSlider.setMinorTickSpacing(10);
colorSlider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
Color oldValue = color;
Color newValue = calculateColor();
color = newValue;
propertyChangeSupport.firePropertyChange(COLOR, oldValue,
newValue);
}
});
}
}
public JComponent getMainPanel() {
return mainPanel;
}
public void setEnabled(boolean enabled) {
for (JSlider slider : colorSliderMap.values()) {
slider.setEnabled(enabled);
}
}
public Color calculateColor() {
int r = colorSliderMap.get(Rgb.RED).getValue();
int g = colorSliderMap.get(Rgb.GREEN).getValue();
int b = colorSliderMap.get(Rgb.BLUE).getValue();
return new Color(r, g, b);
}
public String getName() {
return name;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(listener);
}
}