I am working on a 2d game using Swing. Before, I used to render my objects and player on a jPanel over the
panel.repaint();
method and would override the paint methode in the panel class. Then I learned about the concept of moving the render code to a Render class which looks like this:
public class Renderer{
public void render(Graphics g, Game game){
game.getObjects.forEach(gameObject -> g.drawImage(....);
}
}
With that code there is always a drawing on a drawing, ....
The problem with this is that I can't (or don't know how to) call the super method repaint() of the panel.
I would like to keep the Render class because the code is much more structured. Any advice on how to reset a jPanel?
I tried using panel.repaint() before calling the render method but I just got a blank screen.
public class Renderer {
public void render(Game game, Graphics graphics) {
Player player = game.getPlayer();
graphics.drawImage(player.getImage(), (int)player.getPosition().getX(), (int)player.getPosition().getY(), null);
}
}
public class Game{
private static Game instance;
private GamePanel gamePanel;
private Player player;
private Renderer renderer;
private boolean isRunning = true;
private final int MAX_FPS = 60;
private Game() {
initialize();
startGameLoop();
}
private void initialize() {
renderer = new Renderer();
player = Player.getInstance();
gamePanel = GamePanel.getInstance(this);
GameWindow.getInstance(gamePanel);
}
private void startGameLoop() {
double timePerCycle = 1_000_000_000 / MAX_FPS;
int updates = 0;
long lastInfo = System.currentTimeMillis();
long timeBefore = System.nanoTime();
while(isRunning) {
if(System.nanoTime() - timeBefore >= timePerCycle) {
timeBefore = System.nanoTime();
update();
render();
updates++;
}
if(System.currentTimeMillis() - lastInfo >= 1000) {
System.out.printf("UPS: %d\n", (updates / (( System.currentTimeMillis() - lastInfo) / 1000)));
lastInfo = System.currentTimeMillis();
updates = 0;
}
}
}
private void render() {
Graphics graphics = gamePanel.getGraphics();
renderer.render(this, graphics);
graphics.dispose();
}
To clear the panel you can employ a boolean in paintComponent and fill in the rectangle via g.fillRect(x,y, width, height).
Her is one possible example. Where boolean clearScreen is an instance field.
public void clear() {
clearScreen = true; // tested in paintComponent
repaint();
clearScreen = false;
}
Here are some other suggestions.
don't override paint for JPanel. Use paintComponent.
first statement should be super.paintComponent(g). This is what allows panel.setBackground() to work, among other things as it calls the overridden method to perform additional functionality.
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// your stuff here
}
Use a Swing Timer for controlling repaint cycles.
painting and event handling are done in the EventDispatch thread. So processing should be kept to a minimum. Any computations required for your game should be done outside that thread and when possible, only the actual invocation of the graphics methods should be done in the paintComponent method.
If done properly, subsequent calls to repaint() will not add to what is there. Each call must redraw everything including changes.
For more information check out How to paint
There are many examples of painting on this site. Search for them using [Swing] and [Graphics] tags. Here is one that employs some of the above. Also note that Swing components enable double buffering by default
Related
I am trying to make a custom loading screen in Java (not using the JProgressBar). Right now I have just tried making a loading bar that slowly fills the screen left to right with a black rectangle. To test this, I made a simple program that factors random numbers. I run the factoring code on a separate thread and I have the graphics get the progress of the factoring once every 100 milliseconds. However, sometimes the application becomes not responsive for like 10 seconds, then continues to run. How can I properly implement a custom loading screen in Java or what is wrong with the code I am using now?
Here is the code:
public class Factorer implements Runnable, Loadable {
private static Random r = new Random();
private volatile int numOn;
private volatile int numsTotal;
public static void main(String[] args){
Factorer fact = new Factorer(25);
LoadingFrame f = new LoadingFrame(fact);
fact.run();
}
public Factorer(int nNums){
numsTotal = nNums;
}
public void factorRandomNumber(){
int randomNumber = r.nextInt(Integer.MAX_VALUE);
int n = randomNumber;
List<Integer> factors = new ArrayList<Integer>();
for (int i = 2; i <= n; i++) {
while (n % i == 0) {
factors.add(i);
n /= i;
}
}
}
public void factorRandomNumbers(){
for(numOn = 0; numOn < numsTotal; numOn++){
factorRandomNumber();
}
}
public float getProgress(){
return ((float) numOn) / ((float) numsTotal);
}
#Override
public void run() {
factorRandomNumbers();
}
}
Here is the loadable interface:
public interface Loadable {
public float getProgress();
}
Here is the graphics class:
public class LoadingFrame extends JFrame {
private static final long serialVersionUID = 8290713730328501888L;
private static final int WIDTH = 400;
private static final int HEIGHT = 400;
private Loadable loadable;
public LoadingFrame(Loadable l){
super("Loading Test");
this.loadable = l;
this.setSize(WIDTH, HEIGHT);
this.setResizable(false);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
Timer t = new Timer(100, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
});
t.start();
}
#Override
public void paint(Graphics g){
g.setColor(Color.BLACK);
g.fillRect(0, 0, (int) (loadable.getProgress() * (float) WIDTH), HEIGHT);
}
}
I run the factoring code on a separate thread
Well, yes and no. Your Factorer class implements runnable, but you never treat the class as a separate Thread.
Instead you just use:
fact.run();
If you are trying to use this class as a reusable separate Thread then somewhere in your code you would have:
new Thread( new Factorer() ).start();
to actually start the Thread.
However, because the JVM starts in a separate Thread from the Event Dispatch Thread (EDT) you code is running on a separate Thread but more by chance than design.
Also the logic in your Factorer class won't work because when you invoke the getProgress() method you will always get the same value returned. That is the factoroRandomNumbers() number is executed once at the start of the programs and the numOn and numsTotal will then be initialized to their final values.
public void paint(Graphics g){
You should NOT be overriding paint(). Custom painting is done by overriding paintComponent(...) on a JPanel and then you add the panel to the frame.
Your Timer logic is incorrect. All you do is invoke repaint(), but as I stated earlier the returned value will never change. What you really need to do is invoke a method like getNextValue(). This method would get a value from your List containing the random numbers.
To better understand how Swing works you need to read the Swing Tutorial. You may want to start with section on:
Concurrency in Swing
Performing Custom Painting
It would probably be easier to use a SwingWorker (as discussed in the Concurrency in Swing section). This will create a separate Thread for you. Then you have looping code that generates a random value and "publishes" this value. When this value is published you then repaint the frame with the value.
I have a class that creates a JFrame on which a simple game of Tetris will be played, I also have a class DrawSquare, which does exactly what you think it does, however when I initialise a new instance of the DrawSquare class and then try to draw that one and all the others to my JFrame things start to go wrong, the code is intended for one square to be drawn in the top left hand corner and then drop down a line at a time until it reaches the bottom of the frame (it does this), then a new square should be drawn in the second column at the top of the frame, as well as our first square in the bottom left hand corner, however once it starts dropping down the second column I get a series of squares drawn in a diagonal towards the top right hand corner. At the moment all I plan for the code to do is have a square drop from the top row of each column and stop when it reaches the bottom of the frame, am I storing the instance of the class at the wrong point in the code? Edit: In fact I'm pretty sure it's that, I'd want to store that instance when it reaches the bottom. Does every instance of the class need its own timer?
public class Tetris extends JFrame {
public static final int height = 20; //height of a square
public static final int width = 20; //width of a square
public int xPos = 0; //column number of the square
public int yPos = 0; //row number of the square
public static void main(String[] args){
Tetris tet = new Tetris();
}
public Tetris() {
DrawSquare square = new DrawSquare(xPos, yPos, width, height, false);
add(square);
DrawSquare.squares.add(square);
setSize(220,440);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
}
public class DrawSquare extends JPanel {
public static List<DrawSquare> squares = new ArrayList<>();
protected int xPos;
protected int yPos;
protected int width;
protected int height;
protected Timer timer = new Timer(200, new TimerListener());
protected boolean endFall = false;
public DrawSquare(int xPos, int yPos, int width, int height, boolean endFall) {
this.xPos = xPos;
this.yPos = yPos;
this.width = width;
this.height = height;
this.endFall = endFall;
this.timer.start();
}
class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
yPos++;
if (yPos > 19) {
yPos = 19;
endFall = true;
}
if (endFall == true) {
timer.stop();
if (xPos > 8) {
xPos = 8;
}
xPos++;
endFall = false;
yPos = 0;
DrawSquare newSqr = new DrawSquare(xPos, yPos, width, height, true);
squares.add(newSqr);
add(newSqr);
}
timer.start();
repaint();
}
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Iterator<DrawSquare> it = squares.iterator();
while (it.hasNext()) {
DrawSquare square = it.next();
g.fillRect(square.xPos * square.width, square.yPos * square.height, square.width, square.height);
}
}
}
You are giving a great example of the fundamental misunderstanding beginners have of how the swing (and many other graphics toolkits) render stuff to the screen. I will give an overview of that, as it pertains to you, then answer your immediate questions and explain how to fix your code.
It took me a (very long) while to figure out how this stuff works my self, so please bear with me. I hope that reading through this answer will help you in a much more general way than answering this one question.
Asynchronous Drawing
Swing draws windows in a totally different sequence (the event dispatching thread) than the ones that modifies the state of your program (the main thread, as well as timer and other threads). You can modify the coordinates of things you want to draw as many times as you like in the main thread, but the changes will not show up until you request them to by calling JComponent.repaint() on one of your components. This will generally trigger a nearly-immediate repaint of the component, displaying your latest state.
If you change the coordinates of a widget like a JPanel in your main thread, it will likely show up immediately. This is because the methods you use to set the position will trigger repaint requests internally.
A repaint request gets queued and eventually processed by the event dispatching thread. This is where the paintComponent method gets called. The paintComponent method should therefore only draw. It should not do any other logic. If it needs to know how to draw some specialized stuff, the information for that should be stashed somewhere accessible by one of the other threads.
In short, you make calculations and update state as you need in the main thread or the timer. Then you access that state in the event dispatching thread via the paintComponent method.
Timers
There are a bunch of ways you can use timers to run your GUI, but you only really need one for the current application. In your case, the timer only needs to do two things:
Check if a block has fallen all the way down and doesn't need to move any more.
Trigger a repaint of your panel.
You do not need to compute the updated position of the blocks in the timer if the block's position is a simple equation with respect to time. If you know the time at which a block appears on the screen and the current time, you know how far the block has moved, so you can paint it in the correct spot based purely on the elapsed time.
If you had a more complicated system with paths that you could not predict purely on the time, I would recommend sticking the movement logic into the timer events as well. In that case, you might consider having multiple timers, or switching to java.util.timer. But again, this does not apply to your current case (even with multiple blocks).
Model and View
The model of your program is the thing that holds the abstract state. In this case, the positions and other meta-data about all your blocks. The view is the part that does the rendering. It is usually a good idea to separate these two things. There is often a third component to GUIs, called the controller, which connects the model and view to the user. We will ignore it here since you are not asking about controlling the blocks yet.
In your current code, you have attempted to represent your blocks with an extension to JPanel and a static list of existing blocks. While a JPanel may be a convenient way to display rectangular blocks with some custom graphics in them (like icons), I would recommend that you start by drawing the blocks directly using the Graphics object passed to paintComponent. At least initially, it will help you to think of the drawing code and the game logic as separate entities.
Final Rant Before Code Dump
I have made rewrites to your code to encapsulate all the ranting I did before into code. Here are some additional minor points about what I did that may help explain my reasoning:
When you call JFrame.add(...) to add a component to a JFrame, you are really calling JFrame.getContentPane().add(...). The content pane is where 90% of normal swing components go in a window. Therefore, we can either set the JPanel that will do the rendering as your content pane or we can add it to the current content pane. I have chosen to do the latter so that you can add other widgets, like a score board, at a later time.
Class names should generally be nouns, while methods are often verbs. This is not an absolute rule (nothing really is), but naming things this way will often help you visualize the interactions between objects in a more meaningful way. I have renamed DrawSquare to GamePiece for this reason.
There is no longer any reason for GamePiece to be a JPanel. It just needs to know its own width, height, and time of appearance.
The other problem with trying to have DrawSquare draw itself is that a component can only really draw within its own bounding box. So you really want to override the paintComponent of whatever holds the rectangles.
The rendering class maintains a reference to two lists of GamePieces. One is for the moving objects and one is for the ones that have fallen. The logic for moving them between the lists is in the timer. This is better than say adding a flag to GamePiece because it facilitates incremental repaint. I will only partially illustrate this here, but there is a version of repaint that only requests a small region to be painted. This would be useful to speed up the movement.
Code
public class Tetris extends JFrame
{
public static final int height = 20; //height of a square
public static final int width = 20; //width of a square
public static final int x = 0;
private GamePanel gamePanel;
public static void main(String[] args)
{
Tetris tet = new Tetris();
// Normally you would tie this to a button or some other user-triggered action.
tet.gamePanel.start();
tet.gamePanel.addPiece(new GamePiece(width, height, x));
}
public Tetris()
{
getContentPane().setLayout(new BorderLayout());
gamePanel = GamePanel();
add(gamePanel, BorderLayout.CENTER);
setSize(220,440);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
}
public class GamePanel extends JPanel
{
private List<GamePiece> moving;
private List<GamePiece> still;
private Timer timer;
public GamePanel()
{
moving = new ArrayList<>();
still = new ArrayList<>();
timer = new Timer(100, new TimerListener());
}
public addPiece(int width, int height, int x)
{
moving.add(new GamePiece(width, height, x));
}
public void start()
{
timer.start();
}
#Override
public void paintComponent(Graphics g)
{
Rectangle clip = g.getClipBounds(null);
Rectangle rectToDraw = new Rectangle();
// I prefer this, but you can make the call every
// time you call `GamePiece.getY()`
long time = System.currentTimeMillis();
for(GamePiece piece : this.moving) {
rectToDraw.setSize(piece.width, piece.height)
rectToDraw.setLocation(piece.x, piece.getY(time))
if(rectangleToDraw.intersects(clip))
((Graphics2D)g).fill(rectToDraw)
}
for(GamePiece piece : this.still) {
rectToDraw.setSize(piece.width, piece.height)
rectToDraw.setLocation(piece.x, piece.getY(time))
if(rectangleToDraw.intersects(clip))
((Graphics2D)g).fill(rectToDraw)
}
}
private class TimerListener implements ActionListener
{
#Override
public void actionPerformed(ActionEvent e)
{
long time = System.currentTimeMillis();
// Using non-iterator loop to move the pieces that
// stopped safely. Iterator would crash on in-loop move.
for(int i = 0; i < moving.size(); i++) {
piece = moving.get(i);
if(piece.getY(time) > 440 - piece.height) {
moving.remove(i);
still.add(piece);
i--;
}
}
repaint();
}
}
}
public class GamePiece
{
public final int width;
public final int height;
public final long startTime;
public int x;
public GamePiece(int width, int height, int x)
{
this.width = width;
this.height = height;
this.startTime = System.currentTimeMillis();
this.x = x;
}
public int getY(long time)
{
// This hard-codes a velocity of 10px/sec. You could
// implement a more complex relationship with time here.
return (int)((time - this.startTime) / 100.0);
}
}
Your main problem in a nutshell: you need to separate the JPanel component class from the square logical class. Right now, they are one and the same, and every time you create a new DrawSqaure, you're creating a new JPanel, starting a new Swing Timer, and thus calling code that doesn't need to be called. This is also forcing you to make the List static else you'd have a stack overflow error. Solution: separate the two out, make your List non-static, and use only one Swing Timer.
I am making a simple game project and I am having a problem when trying to create a rectangle that moves across the screen.
Here is the main class:
`public class Main extends Canvas implements Runnable {
private static final long serialVersionUID = 1L;
private JFrame frame;
boolean running = false;
Graphics g;
static int HEIGHT = 500;
static int WIDTH = HEIGHT * 16 / 9;
SoundHandler sh = new SoundHandler();
//Game state manager
private GameStateManager gsm;
public Main()
{
//window
frame = new JFrame("Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(this, BorderLayout.CENTER);
frame.pack();
frame.setSize(WIDTH, HEIGHT);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
init();
}
void init()
{
gsm = new GameStateManager();
sh.playMusic("Undertale.wav", 1);
}
public synchronized void start(){
running = true;
new Thread(this).start();
}
public synchronized void stop(){
running = false;
}
//game loop
public void run()
{
//init time loop variables
long lastLoopTime = System.nanoTime();
final int TARGET_FPS = 60;
final long OPTIMAL_TIME = 1000000000 / TARGET_FPS;
double lastFpsTime = 0;
int fps = 0;
while(running)
{
//work out how long its been since last update
//will be used to calculate how entities should
//move this loop
long now = System.nanoTime();
long updateLength = now - lastLoopTime;
lastLoopTime = now;
double delta = updateLength / ((double)OPTIMAL_TIME);
//update frame counter
lastFpsTime += updateLength;
fps++;
//update FPS counter
if(lastFpsTime >= 1000000000)
{
System.out.println("FPS " + fps);
lastFpsTime = 0;
fps = 0;
}
//game updates
update(delta);
//draw
draw(g);
try{
Thread.sleep((lastLoopTime - System.nanoTime() + OPTIMAL_TIME)/1000000 );
}catch(InterruptedException e){
System.out.println("Error in sleep");
}
}
}
private void update(double delta)
{
//updates game state code
gsm.update(delta);
}
public void draw(Graphics g)
{
gsm.draw(g);
}`
here is the class I want to draw the rectangle with
package me.mangodragon.gamestate;
import java.awt.Graphics;
public class MainState extends GameState{
int x;
public MainState(GameStateManager gsm){
this.gsm = gsm;
}
public void init() {
}
public void update(double delta) {
x += 2 * delta;
}
public void draw(Graphics g) {
g.drawRect(x, 0, 50, 50);
g.dispose();
}
public void keyPressed(int k) {
}
public void keyReleased(int k) {
}
}
I keep getting this error:
Exception in thread "Thread-4" java.lang.NullPointerException
at me.mangodragon.gamestate.MainState.draw(MainState.java:22)
at me.mangodragon.gamestate.GameStateManager.draw(GameStateManager.java:37)
at me.mangodragon.main.Main.draw(Main.java:118)
at me.mangodragon.main.Main.run(Main.java:100)
at java.lang.Thread.run(Unknown Source)
I tried to fix it, but I could not locate the problem.
Thanks!
You never assign anything to g (Graphics). Now, before you run off and try and figure out how you might do that, I'd highly, highly recommend you get rid of this variable, it's going to cause you too many issues.
Normally, when the system wants your component to painted, it calls your paint method and passes you the Graphics context which it wants you to paint to. This approach is known as passive painting, as the paint requests come at random times, which isn't really what you want. Another issue is java.awt.Canvas isn't double buffered, which will cause flickering to occur as your component is updated.
You might want to take a look at Painting in AWT and Swing and Performing Custom Painting for more details
You could use a JPanel instead, which is double buffered, but the main reason for using java.awt.Canvas is so you can make use the BufferStrategy API. This not only provides double buffering, but also provides you with a means by which you can take direct control over the painting process (or active painting).
See BufferStrategy and BufferStrategy and BufferCapabilities for more details
You defined g as such:
Graphics g;
But never gave it a value.
This is not how you should be drawing shapes, anyways. Instead, override the paint method (inherited from Canvas) in class Main:
#Override
public void paint(Graphics2D g) {
//Drawing code goes in here. This runs whenever the Canvas is rendered.
}
Then, when you want to update it, such as in your while loop, run
this.repaint(); //note that this doesn't take arguments
If you want to use the draw(Graphics g) method in the other class, call it in paint().
public void paint(Graphics2D g) {
gsm.draw(g);
}
The problem is that you have not defined g, so it is null. And for the most part, you are never supposed to create a new Graphics object, but instead get it from somewhere.
Since you are inheriting a Canvas, this can be very easily done.
First, you should change your draw method to be like this.
private void draw() {
BufferStrategy bs = this.getBufferStrategy();
if (bs == null) {
this.createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
// Draw your game here, using the g declared above
g.dispose();
bs.show();
}
The first few lines create something called a BufferStrategy which you can read more about here but it essentially lets Java render the next couple frames ahead of schedule so that you don't see any flickering.
From the BufferStrategy, you can get the Graphics object to draw on.
And, finally, you have to dispose of the Graphics object, and then show the Buffer so that everything you did shows on the screen.
I have a small problem. In carrying out the method paintComponent() during the animation I have to constantly update the variable bgImage. But it takes a lot of time, so that the animation slows down.
A block of code with the problem:
public class ProblemClass extends JComponent {
private static final int FRAME_FREQUENCY = 30;
private final Timer animationTimer;
public ProblemClass() {
this.animationTimer = new Timer(1000 / FRAME_FREQUENCY, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
repaint(); // When the animation started is often invoked repaint()
}
});
}
// Other code...
/**
* Start animation from another class
*/
public void startAnimation() {
this.animationTimer.start();
}
#Override
protected void paintComponent(Graphics g) {
// GraphicsUtils.gaussianBlur(...) it's a long-time operation
BufferedImage bgImage = GraphicsUtils.gaussianBlur(AnotherClass.getBgImage());
g2.drawImage(bgImage, 0, 0, null);
// Other code...
}
}
I read on the Internet that I need run long task in parallel thread (SwingWorker), but I have no idea how to do it in my case. How can I solve this problem?
P.S. Sorry for my bad English, it's not my first language.
The best you're going to do is having the image update outside of the paint method, and only redraw whenever a new image is ready. Take your existing code, and add a persistent reference to the image, which gets drawn onto the JComponent each paint method. Then have your animation timer do the Gaussian blur and update your image.
public class ProblemClass extends JComponent {
private static final int FRAME_FREQUENCY = 30;
private final Timer animationTimer;
//persistent reference to the image
private BufferedImage bgImage;
public ProblemClass() {
this.animationTimer = new Timer(1000 / FRAME_FREQUENCY, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
//Update the image on each call before repainting
bgImage = GraphicsUtils.gaussianBlur(AnotherClass.getBgImage());
repaint();
}
});
}
/**
* Start animation from another class
*/
public void startAnimation() {
this.animationTimer.start();
}
#Override
protected void paintComponent(Graphics g2) {
g2.drawImage(bgImage, 0, 0, null);
// Other code...
}
}
There is no generic way to solve this when you generate a new background image every time and then blur it. GaussianBlur is a slow operation, period.
If AnotherClass.getBgImage() delivers images from a predefined set of images, then apply the blur once to each image in the set, problem goes away.
If you create the image in AnotherClass.getBgImage() dynamically, then you may be able to speed it up by changing the image creation to create a blurred image from the start. Depending on what is done to create the image this may or may not be feasible.
If neither of the above works out, you need to investigate other options to produce the blurred image which are faster; there are simpler blurring methods that are generally faster but look somewhat similar to a gaussian.
You see it all boils down to getting rid of calling GaussianBlur repeatedly on the performance critical path.
You should extract logic from painter. Painters are called constrantly and should be executed very fast.
BufferedImage bgImage = GraphicsUtils.gaussianBlur(AnotherClass.getBgImage());
This line has to be executed every time? maybe you could use a field to store the image and the painter could just paitn the image, not applying each time a gaussianBlur.
Try this:
public class ProblemClass extends JComponent {
private static final int FRAME_FREQUENCY = 30;
private final Timer animationTimer;
private final BufferedImage bgImage;
public ProblemClass() {
bgImage = GraphicsUtils.gaussianBlur(AnotherClass.getBgImage());
this.animationTimer = new Timer(1000 / FRAME_FREQUENCY, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
repaint(); // When the animation started is often invoked repaint()
}
});
}
// Other code...
/**
* Start animation from another class
*/
public void startAnimation() {
this.animationTimer.start();
}
#Override
protected void paintComponent(Graphics g2) {
// GraphicsUtils.gaussianBlur(...) it's a long-time operation
g2.drawImage(bgImage, 0, 0, null);
// Other code...
}
}
I have a custom, abstract class 'Panel' which extends JPanel. There aren't many differences with the two when painting. I have a Panel and I'm simulating an animation by updating the x value of an image. I have two animations right now, one that properly repaints and another than does not. This is for the one that does not. The one that works will be labelled A, the one that doesn't will be B.
A and B follow the same format. Update some variable on the Panel, calls update (a method in Panel which calls PaintComponent) and then calls repaint. It calls repaint after because this issue was with A before and was solved that way.
A: Updates an image variable.
B: Updates the x variable of an image.
The Problem: The repaint doesn't clear the old image location and so it's a choppy mess across the screen.
What I've tried:
I've seen the super.PaintComponent(g) mentioned a lot, but this
hasn't solved the problem.
I've tried changing the order for when the repaint/update methods are
called.
Repaint does not update the Panel at all. (Probably because the
painting is done in PaintComponent)
Any help would be appreciated.
Code:
Panel:
public Panel (boolean visible){
super();
this.setLayout(new BorderLayout(640, 416));//sets the Layout type of the panel
this.setOpaque(false);//Makes it so that the panel underneath can be seen where images aren't drawn
this.setVisible(visible);
ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
gs = ge.getDefaultScreenDevice();
gc = gs.getDefaultConfiguration();
}
public void paintComponent (Graphics g){
setUp();
drawOff();
setDown(g);
}
private void setUp(){
off_screen = gc.createCompatibleImage(getSize().width, getSize().height, Transparency.TRANSLUCENT);
buffer = off_screen.createGraphics();
}
protected abstract void drawOff();
private void setDown(Graphics g){
g.drawImage(off_screen,0,0,this);
off_screen.flush();
}
public void update(){
paintComponent(this.getGraphics());
}
Animation Methods (mg is the panel in question):
private void battleStart(User user) {
for (int i = 0; i < user.battle.length; i++) {
mg.battleStart(user.battleStart(i));
mg.update();
try {
Thread.sleep(150);
} catch (Exception e) {
}
mg.repaint();
}
}
private void animateStart(User user){
for (int i = 0; i < 10; i++){
mg.x = mg.x + 10;
mg.update();
try {
Thread.sleep(100);
} catch (Exception e) {
}
mg.repaint();
}
}
I think your design is way off and that is why things are not working. I'm not quite sure how your non-abstract JPanels work, but consider making your parent JPanel something more along these lines:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class MyPanel extends JPanel {
private GraphicsEnvironment ge;
private GraphicsDevice gs;
private GraphicsConfiguration gc;
private BufferedImage offScreen;
public MyPanel(boolean visible) {
super();
this.setLayout(new BorderLayout(640, 416)); // strange constants for this layout.
this.setOpaque(false);
this.setVisible(visible);
ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
gs = ge.getDefaultScreenDevice();
gc = gs.getDefaultConfiguration();
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
setUp();
}
});
}
#Override
// don't make this public. Keep it protected like the super's
// just draw in this method. Don't call other methods that create buffers
// or draw to buffers.
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (offScreen != null) {
g.drawImage(offScreen, 0, 0, null);
}
}
private void setUp() {
offScreen = gc.createCompatibleImage(getSize().width, getSize().height,
Transparency.TRANSLUCENT);
}
// draw to the buffer outside of the paintComponent
// and then call repaint() when done
public void upDateOffScreen() {
// ?? offScreen.flush(); // I've never used this before,
// so am not sure if you need this here
Graphics2D osGraphics = offScreen.createGraphics();
// TODO: do drawing with osGraphics object here
osGraphics.dispose();
repaint();
}
}
Also and again,
Do all long processing methods off of the EDT (Event Dispatch Thread).
Never call Thread.sleep(...) on the EDT.
Consider using Swing Timers instead of using Thread.sleep for the animations.
It's OK to call repaint on your JPanel off of the EDT, but for the most part that's about it.
All other Swing methods should be called on the EDT.
Read, re-read, and study the 2D and Swing graphics tutorials.