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.
Related
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
I'm making a game/simulator and I'm using the following method to load an Image into my current Java project:
public static Image getImage(String name) {
URL url = ImageLoader.class.getClassLoader().getResource("resources/" + name);
Image img = Toolkit.getDefaultToolkit().createImage(url);
ImageLoader.tracker.addImage(img, 1);
try {
ImageLoader.tracker.waitForID(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return img;
}
This loads the images into my class that is drawing all items including the player (who has 4 different images for each direction he can face: north, south, east, west):
private Image player = ImageLoader.getImage("playerEast.png");
private Image playerEast = ImageLoader.getImage("playerEast.png");
private Image playerWest = ImageLoader.getImage("playerWest.png");
private Image playerSouth = ImageLoader.getImage("playerSouth.png");
private Image playerNorth = ImageLoader.getImage("playerNorth.png");
The class that loads above images paints them in the area like such:
public class TerritoryPanel extends JPanel implements Observer {
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
//Draw player
g.drawImage(player, (x, y, this);
}
I'm trying to update the pics with this method in the same class:
public void rotateEast(){
player = playerEast;
repaint();
revalidate();
}
..but just calling this rotateEast() does not update the picture immediately, it only gets updated afterwards when my regular Observer update() cycle calls repaint(), this happens every second so the missing repaint of rotateEast is visible:
#Override
public void update(Observable o, Object arg) {
if (EventQueue.isDispatchThread()) {
this.repaint();
} else { // Simulation-Thread
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
repaint();
}
});
}
}
Why does the repaint() from rotateEast seem to have no effect? Thank you very much in forward
but just calling this rotateEast() does not update the picture immediately, it only gets updated afterwards when my regular Observer update() cycle calls repaint(),
Don't know if it will make a difference but the normal logic is:
revalidate(); // to invoke the layout manager
repaint(); // to repaint all the components
Even if it doesn't make a difference in this case this order you should always use when dynamically changing the property of a component including adding/remove components from a panel.
I'm trying to call repaint from another class. But it does not work. I have to draw on a frame.
public class Tester extends JFrame{
public static dtest d ;
public static void main(String[] args) {
Tester t = new Tester();
d = new dtest();
test tnew = new test();
}
public static class dtest extends JFrame implements MouseMotionListener{
public static int x,y;
dtest()
{
super("title");
setSize(500,500);
setVisible(true);
addMouseMotionListener(this);
}
#Override
public void mouseDragged(MouseEvent e) {
x = e.getX();
y = e.getY();
repaint();
}
#Override
public void mouseMoved(MouseEvent e) {
// TODO Auto-generated method stub
}
public void paint(Graphics g)
{
System.out.println("I am called");
}
}
public static class test {
public test()
{
for(int i = 0 ; i < 5 ; i++)
{
System.out.println("I am called from run");
d.repaint();
}
}
}
}
this prints
I am called from run
I am called from run
I am called from run
I am called from run
I am called from run
so it does not executing the paint() portion. d.repaint() is not working. why?
Take a look at this page and look at the first answer. It's a similar if not exact question to yours.
JFrame's paint() method has been deprecated. The compiler, or your IDE, should be complaining a bit, especially if you place the #Override tag directly above the method (use this to test if this method can be rewritten... aka what you're trying to do).
This means that its use has been discouraged and some functionality may have been removed. When using javax.swing, you'll want to learn the system completely about JPanels and JComponents. To paint something on a screen, you'll want to add a custom class that extends JPanel with the add(Component c) method. Then, override the paintComponent(Graphics g) method in that class. Make sure to have the first line in that method be super.paintComponent(g); so that the window can refresh itself.
For completeness:
public class MyWindow extends JFrame {
MyPanel thePanel;
public MyWindow(int x, int y) {
setSize(x, y);
thePanel = new MyPanel(x, y);
this.add(thePanel);
}
}
public class MyPanel extends JPanel {
public MyPanel(int x, int y)
setSize(x, y);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(ImageManager.getImage("Cute Puppy"), 40, 40, null); // Or whatever
}
}
So, when the repaint() or revalidate() method is called on the MyWindow, the Panel will recieve a paintComponent call.
Please let me know in the comments if you need any additional help.
Edited:
Since you need to use MouseMotionListener, and I'm still not quite understanding the context and trouble of "I need to call repaint from another class"... I will try my best.
Firstly, check out this tutorial on the Oracle pages. Also, check out the others on GUI's. You'll learn a lot about organization and displaying that will make you realize how their system can work with yours.
Now, for your questions:
i have to use MouseMotionListener.
Not quite... it is a good way for set up but you can run a Thread (something that constantly runs methods over and over) to check the Mouse coordinates. You'll want to start doing this when you get into games and other miscellaneous applications.
new Thread() {
public void run() {
Point mouse;
int mousex;
int mousey;
while (true) {
mouse = MouseInfo.getPointerInfo().getLocation();
mousex = mouse.x - theWindow.getLocationOnScreen().x - 3; // You'll need to get the
// x coordinate, subtract the window's x coordinate, and subtract 3 because of
// the blue border around a standard pc window.
mousey = mouse.y - theWindow.getLocationOnScreen().y - 29; // 29 is top bar height
SomeOtherClass.processMove(mousex, mousey);
}
}
}.start();
Next: I tried that with JPanel but i could not do that. If you read the tutorial at the top of my edit, you see they implement MouseMotionListener with ease.
Next: I prefer to do it with JFrame. If you wish to process the mouse in the JFrame, do the following: Have your JFrame the listener, but the JPanel be where the mouse data comes from. As follows:
public class MyWindow extends JFrame implements MouseMotionListener {
public MyPanel thePanel;
public int x;
public int y;
public MyWindow() {
thePanel = new MyPanel();
thePanel.addMouseMotionListener(this);
// Make this JFrame get called when the mouse
// moves across the panel.
}
#Override
public void mouseDragged(MouseEvent e) {
x = e.getX();
y = e.getY();
thePanel.repaint();
}
#Override
public void mouseMoved(MouseEvent e) {
// TODO Auto-generated method stub
}
}
public class MyPanel extends JPanel {
public void paintComponent(Graphics g) {
super.paintComponent(g);
// Other painting stuff
}
}
Next: Now i have to update the frame from another class. I could not find a way to update the GUI(the frame) from another class.
Simple. Since the JPanel is what needs to be updated, add the following method to the MyWindow class:
public void repaintWindow() {
thePanel.repaint();
}
And add this to whenever you need to update it:
MyWindow theWindow = new MyWindow();
theWindow.repaintWindow();
Next: all the answers here extended JPanel. So i could not find my answer.
I apologize, but you NEED a panel. It is possible to do with JFrames, but if you want to start doing things raw and low-level, you need to learn how these things work by learning to read the oracle tutorials and the oracle documentation. For now, use JPanels in any ways I've shown you.
Next: from another class I have to draw something on JFrame.Is that possible?
Yes, indeed! Whenever you want to draw something:
MyWindow theWindow = new MyWindow();
Graphics g = theWindow.thePanel.getGraphics();
BufferedImage someRandomImage = SomeRandomClass.getRandomImage();
g.drawImage(someRandomImage, 200, 481, null);
theWindow.repaintWindow();
I really hope I've helped but to program in java you need to use the tools they give you, especially when it comes to high level things like Swing. There are tutorials everywhere for this stuff. Please read them before asking for specific help in the future.
I am having trouble with a custom JPanel class I am using. I have a networked camera which I am receiving Images from using an HttpURLConnection and a JPEGDecoder. These images are then displayed using Graphic.drawImage. The camera is set to run at 1 fps for debugging purposes.
This JPanel class is include inside one JFrame, I also have another JFrame which contains a NASA WorldWind. When displaying the pictures from the Camera, my WorldWind map is unresponsive and will not repaint when resized. I believe it is because my paintComponent in the custom JPanel is being spammed.
I do not understand what is calling my JPanel's paintComponent so much, and preventing my WorldWind Frame to update.
A blurb of the custom JPanel class follows:
public class ActiCamera extends JPanel implements Runnable
{
private String mjpgURL;
private DataInputStream dis;
public ActiCamera(String ip)
{
mjpgURL = "http://" + ip + "/cgi-bin/cmd/encoder?GET_STREAM";
}
public void connect()
{
URL u = new URL(mjpgURL);
...
dis = new DataInputStream(from buffered input stream from HttpURLConnection);
}
public void start()
{
appletThread = new Thread(this);
appletThread.start();
}
public void run()
{
connect();
GetImages();
}
public void GetImages()
{
while(true)
{
//This blocks, executes at 1Hz
JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(dis);
image = decoder.decodeAsBufferedImage();
}
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if(image != null)
g.drawImage(image.getScaledInstance(getWidth(), getHeight(), Image.SCALE_DEFAULT), 0, 0, this);
}
public static void main(String [] args)
{
JFrame jframe = new JFrame();
ActiCamera my_panel = new ActiCamera("1.1.1.1");
my_panel.start();
jframe.getContentPane().add(my_panel);
jframe.setVisible(true);
}
}
Note, I do not call repaint() or force a paint anywhere. However, if I put a print out in my paint component class, it gets spammed at a much greater speed than 1 Hz. I am completely lost as to whats going on.
P.S. - I do realize I need a mutex between the paintComponent and the GetImages, they're being called from different threads, but I do not imagine that would cause the problem?
I found my answer, I had to change my paint component
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if(image != null)
g.drawImage(image, 0, 0, this);
}
The paintComponent in my earlier code snippet seems to have an implicit paintComponent call in it somewhere... perhaps in (getWidth() and getHeight() or getScaledInstance())
I'm learning Java by making a small game in a JApplet.
I got a little problem with my sprite's animation.
Here is the code :
this.sprite.setBounds(0,0,20,17);
this.sprite.setIcon(this.rangerDown);
for(int i = 0; i< 16;i++)
{
this.sprite.setBounds(this.sprite.getX(), this.sprite.getY()+1, 20, 17);
this.sprite.update(this.sprite.getGraphics());
try{
Thread.currentThread().sleep(100);
}catch(InterruptedException e){
}
}
It left some flicker during the animation. Once the animation end, the flicker disappears, but it's kind of ugly... I guess there is some step I missed.
I use this method because it gives the better result for now, but I would like to stay without AWT if possible, using Swing instead.
Any ideas how to get rid of the flicker?
Thanks for reading.
Screenshoot (Can't post images, sorry).
This is not a shadow. Its the border of your sprite. It just happens to be black and appears as a shadow. If you change the amount you shift your sprite (lets say by 50 pixels, not just 1) you will see what i mean.
To fix it what you need to do is to draw the background as well each time you update the location of your sprite. Although this will probably produce flickering.
The correct way to do it is to change the way you draw your objects. You need to override the paintComponent method of your panel and then simply call repaint each time you have updated the locations of your sprites.
EDIT:
See this code sample for basic usage. NOTE: This is NOT how you should write animation using Threads. I wrote that to show you what goes in the paintComponent method and wrote the animation Thread to show you that the "shadow" you mentioned is gone. NEVER have a non ending run loop in a thread :)
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Test {
public static void main(String[] args) {
JFrame f = new JFrame("Test");
MyPanel c = new MyPanel();
f.getContentPane().add(c);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(350, 100);
f.setVisible(true);
}
}
class MyPanel extends JPanel {
int x = 0;
boolean toTheRight = true;
public MyPanel() {
new Thread(new Runnable() {
#Override
public void run() {
while (true) {
x = (toTheRight)?x+5:x-5;
if (x>300)
toTheRight = false;
if (x<0)
toTheRight = true;
repaint();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D)g.create();
g2.setPaint(Color.white);
g2.fillRect(0, 0, getWidth(), getHeight());
g2.setPaint(Color.red);
g2.fillOval(x-2, 50, 4, 4);
}
}
The problem is double buffering.
In Applets:
Double buffering is done almost automatically. Call repaint() instead of paint in your method.
In Swing, there are many ways to do it. I usually go for the BufferStrategy route. When you're initializing your frame, do this:
JFrame frame;
... code to init frame here
frame.createBufferStrategy(2);
Then in your draw methods:
Graphics g = getBufferStrategy().getDrawGraphics();
..code to do drawing here...
g.dispose();
getBufferStrategy().show();