Double Buffering a JFrame - swing

[Warning, I am a beginner, I have little knowledge about JFrame and I am just getting started, but your help would be greatly appreciated!]
So, here is my Problem: I am currently working on something extremely simple, just a red rectangle moving across the screen and I know how beginner-like that must sound.
My current Code:
package movement;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
public class Movement extends JFrame {
public static void main(String[] args) {
Movement m = new Movement();
m.setSize(1000, 1000);
m.setTitle("Movement");
m.setBackground(Color.WHITE);
m.setVisible(true);
m.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void paint (Graphics g){
int width = 0;
int height = 0;
int x = 100;
int y = 900;
while(x < 1000 && y > 0){
//System.out.println("Success");
g.setColor(Color.RED);
g.fillRect(x+width, y-height, 200, 200);
//g.fillRect(x+width, y-height, 200, 200);
try{
Thread.sleep(10);
} catch(InterruptedException e){
}
g.setColor(Color.WHITE);
g.fillRect(x+width, y-height, 200, 200);
//g.fillRect(x+width, y-height, 200, 200);
width=width+1;
height=height+1;
}
}
}
So as you can see it works, but the image stutters and flicks a bit, since there is only a single Buffer. I heard that adding a JPanel would allow me to double buffer and have a way smoother experience, but since I am a real beginner I wouldn't know how to implement it here. I am not sure how a JPanel would help here and in which way to use it.

Swing is double buffered by default. You are overriding the paint() method of the frame and have not invoked super.paint(...) so you are losing the default double buffering.
I heard that adding a JPanel would allow me to double buffer and have a way smoother experience
Yes, this is the proper way to do custom painting. It is not difficult all you do is override the paintComponent() method of the JPanel and add the panel to the frame. Read the section from the Swing tutorial on Custom Painting for more information and examples.
Also, you should NEVER have a while loop with a Thread.sleep() in a painting method. To do animation you should use a Swing Timer. The tutorial link I provided above also has a section on Timers or you can search the forum/web for examples.

Related

Java libgdx 1.20 version texture bug

For 2 days I've been trying to fix bug with fickering and distored textures in my game. I was serching on the internet and i tried a few solutions like using scene2d, but it didn't work. What should i do ?
This screenshot shows the problem: as the character moves, one eye is sometimes bigger than the other:
edit:
I still got the problem widthdistored eye when i use sprite.setPosition((int) sprite.getX(), (int) sprite.getY()); every time before i render my character.
When i use custom viewport from the answer i see nothing on the game window what i do wrong?
package com.mygdx.redHoodie;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.utils.viewport.StretchViewport;
public class GameScreen implements Screen {
public static final int GAME_WIDTH = 800;
public static final int GAME_HEIGHT= 480 ;
SpriteBatch batch;
Background background;
public Hoodie hoodie;
public PixelMultipleViewport viewport;
OrthographicCamera camera;
public int gameMode; // 0 normalna gra, 1 level up, 2 end game
public GameScreen(){
camera= new OrthographicCamera(GAME_WIDTH,GAME_HEIGHT);
viewport = new PixelMultipleViewport(GAME_WIDTH, GAME_HEIGHT, camera);
viewport.update();
camera.setToOrtho(false, GAME_WIDTH, GAME_HEIGHT);
batch = new SpriteBatch();
//klasy wyswietlane
background= new Background(this);
hoodie = new Hoodie(this);
startNewGame();
}
#Override
public void render(float delta) {
// TODO Auto-generated method stu
Gdx.gl.glClearColor(1, 1, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.setProjectionMatrix(camera.projection);
batch.setTransformMatrix(camera.view);
camera.update();
//batch.setProjectionMatrix(camera.combined);
this.update(delta);
this.batch.begin();
toRender(delta);
this.batch.end();
}
public void update(float delta){
hoodie.update(delta);
}
public void toRender(float delta){
background.render();
hoodie.render();
}
public void startNewGame(){
}
public void startNevLevel(){
}
#Override
public void resize(int width, int height) {
// TODO Auto-generated method stub
viewport.update(width, height,false);
}
#Override
public void show() {
// TODO Auto-generated method stub
}
#Override
public void hide() {
// TODO Auto-generated method stub
}
#Override
public void pause() {
// TODO Auto-generated method stub
}
#Override
public void resume() {
// TODO Auto-generated method stub
}
#Override
public void dispose() {
// TODO Auto-generated method stub
}
}
When loading your texture, use linear filtering and mip-mapping. The default filter is Nearest/Nearest, which will cause the issue you're seeing.
Texture myTexture = new Texture("textureFilename", true); //must enable mip-mapping in constructor
myTexture.setFilter(TextureFilter.MipMapLinearNearest, TextureFilter.Linear);
EDIT:
I realize now, looking at your screenshot, that you are doing pixelated graphics in a larger window. In order to do this, yes you need to keep the Nearest/Nearest filtering, instead of what I suggested.
To avoid having the some of the pixels vary in size, you must round off character movement and camera movement to the nearest world unit. When your character is partway between pixels, the size of the sprite pixels varies because they don't line up with the screen pixels.
You have your world scaled so one unit equals one of your large pixels. So whenever you draw anything, you need to first round its position to the nearest integer in the x and the y, as well as the camera position. So after you move the camera or the sprites, you must do something like this:
sprite.position.set((int)sprite.position.x,(int)sprite.position.y,sprite.position.z);
As far as your Viewport goes, if you don't want any black bars, you will probably need a custom Viewport class that tries to match your desired resolution as closely as possible and then extends it outwards to avoid distortion. ExtendViewport does something similar, but the difference with pixellated graphics is that you need the world resolution to be an integer multiple of the screen's resolution so the edges of pixels look crisp rather than fuzzy.
I think this will do what you want. It takes your desired screen resolution and shrinks it to fit where the size of each of your pixels in screen pixels is an integer. Then it extends the view beyond your desired resolution to avoid distortion and black bars. This class makes the assumption that all screen dimensions are always a multiple of 4. I think that's true. If you want to get fancy, you could use OpenGL scissoring to round down the viewport size to the nearest multiples of 4, to be safe. At most you would be having 2 pixels of black bar, which I don't think would be noticeable.
public class PixelMultipleViewport extends Viewport {
private int minWorldWidth, minWorldHeight;
public PixelMultipleViewport (int minWorldWidth, int minWorldHeight, Camera camera) {
this.minWorldHeight = minWorldHeight;
this.minWorldWidth = minWorldWidth;
this.camera = camera;
}
#Override
public void update (int screenWidth, int screenHeight, boolean centerCamera) {
viewportWidth = screenWidth;
viewportHeight = screenHeight;
int maxHorizontalMultiple = screenWidth / minWorldWidth;
int maxVerticalMultiple = screenHeight / minWorldHeight;
int pixelSize = Math.min(maxHorizontalMultiple, maxVerticalMultiple);
worldWidth = (float)screenWidth/(float)pixelSize;
worldHeight = (float)screenHeight/(float)pixelSize;
super.update(screenWidth, screenHeight, centerCamera);
}
}
Here's a different option I just came across. This is a way to draw your scene at the scale you like without black bars, at any resolution.
The visual quality will be slightly worse than in my other answer (where you draw at an integer multiple of your desired scene scale), but significantly better than using straight nearest filtering like in your screenshot.
The basic idea is to draw everything to a small FrameBuffer at the scale you want, and then draw the FrameBuffer's color texture to the screen using an upscaling shader that (unlike linear filtering) interpolates pixel colors only along the edges of sprite pixels.
The explanation is here. I have not ported this to Libgdx or tested it. And I'm not sure how well this shader would run on mobile. It involves running four dependent texture look-ups per screen fragment.
I know this topic is old but as my search led me here, there may be more people following in the future. I was having the same pixel tearing issue, but only on my iOS 9 iPhone 4S. It was rendering fine on my Android 9 Pixel 2. Tried many things (especially rounding to full pixels) but even using an unzoomed fullscreen orthographic camera it suffered from the artefacts.
Forcing my texture to be POT (power of two) fixed the issue!

Canvas repaint should be called by itself

I have an application with jTabbedPane. There are two tab (JPanel) in jTabbedPane. First tab includes canvas and second one includes simple JLabel. Button draws rectangle into canvas.
Every thing is fine until then. However, when switching tabs, canvas would lose everything. It should be repainted by itself.
Rectangle should exist after changing tabs. Do you have any idea about the problem?
My button code is here:
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
Graphics g = canvas1.getGraphics();
g.drawRect(10, 10, 100, 100);
}
Thanks in advance.
First of all, you shouldn't put AWT components inside Swing components. Use JComponent or JPanel instead of Canvas.
Second, no, it shouldn't repaint itself. When the button is clicked, you should simply store what should be painted in some variable, and the paintComponent() method should be overridden in order to paint what is stored in this variable. This way, every time the component is repainted, it will repaint what has been stored last in this variable.
For example:
public class RectangleComponent extends JComponent {
private boolean shouldPaintRectangle = false;
public void setShouldPaintRectangle(boolean b) {
this.shouldPaintRectangle = b;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (shouldPaintRectangle) {
g.drawRect(10, 10, 100, 100);
}
}
}
In general, you should never ask the Graphics of a component and paint on it. Instead, you should override paintComponent() and paint the component using the Graphics passed as argument.

How to finish animation with libgdx

I am trying to implement a simple animation with libGDX and I am currently stuck on one thing. Lets say I have a bunch of sprites which take some time to finish. For example, around 30 sprites like this link: https://github.com/libgdx/libgdx/wiki/2D-Animation
However, before the animation completes some key is pressed. For smooth animation I want the 30 frames to be completed before I start the next set of animation, to prevent an abrupt stop.
So my question is how do I achieve it this in libGDX? My current idea is to extend the Animation class, which would keep track of how frames I have and how many have been rendered and then display the rest. Or use the isAnimationFinished(float stateTime) function (though I haven't had luck using that).
The examples I have seen like superjumper have very few animations and don't really change that much.
Also, is there a way to hold the list of sprites from a TextureAtlas.createSprites method and use those with the Animation class? If not, whats the purpose of providing this function?
Thanks
You can use
animation.isAnimationFinished(stateTime);
To see if your animation is finished.
For the sprites : personnaly I use TextureRegion from a TextureAtlas and I store them in an array for my animation
I create a class AnimatedImage that extends Image to automate spriting in Image. My code will be like this:
public class AnimatedImage extends Image{
private Array<Array<Sprite>> spriteCollection;
private TextureRegionDrawable drawableSprite;
private Animation _animation;
private boolean isLooping;
private float stateTime;
private float currentTime;
public AnimatedImage(Array<Array<Sprite>> _sprites, float animTime, boolean _looping){
// set the first sprite as the initial drawable
super(_sprites.first().first());
spriteCollection = _sprites;
// set first collection of sprite to be the animation
stateTime = animTime;
currentTime = 0;
_animation = new Animation(stateTime, spriteCollection.first());
// set if the anmation needs looping
isLooping = _looping;
drawableSprite = new TextureRegionDrawable(_animation.getKeyFrame(currentTime));
this.setDrawable(drawableSprite);
}
public void update(float delta){
currentTime += delta;
TextureRegion currentSprite = _animation.getKeyFrame(currentTime, isLooping);
drawableSprite.setRegion(currentSprite);
}
public void changeToSequence(int seq){
// reset current animation time
resetTime();
_animation = new Animation(stateTime, spriteCollection.get(seq));
}
public void changeToSequence(float newseqTime, int seq){
_animation = new Animation(newseqTime, spriteCollection.get(seq));
}
public void setRepeated(boolean _repeat){
isLooping = _repeat;
}
public boolean isAnimationFinished(){
return _animation.isAnimationFinished(currentTime);
}
public void resetTime(){
currentTime = 0;
}
}
changetosequence method will make new Animation that will be used to update the current TextureRegionDrawable at the update method. resetTime will reset the total time for the animation when you call changeToSequence. You could add the event listener to call changeToSequence method.
Here is the example:
private AnimatedImage _img;
then I add the InputListener like this:
_img.addListener(new InputListener(){
#Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button){
_img.changeToSequence(1);
return true;
}
});
Hope it helps.
Use tween engine for this kind of animation. Its well documented and libgdx supports it .. Google about it , and you can find bunch of examples using libgdx .. Hope it will help you !!

Zooming in JavaFx: ScrollEvent is consumed when content size exceeds ScrollPane viewport

I have an application that requires zoom inside a ScrollPane, but with my current approach I'm still facing 2 challenges. In order to replicate the problem, I have written a small application ZoomApp that you will find the code for underneath. Its' limited functionality allows zooming in and out (using Ctrl + Mouse Wheel) on some arbitrary shapes. When the zoomed content grows outside the bounds of the window, scroll bars should apperar.
Challenge 1.
When the scroll bars appears as a consequence of innerGroup scales up in size, the ScrollEvent no longer reach my ZoomHandler. Instead, we start scrolling down the window, until it reaches the bottom, when zooming again works as expect. I thought maybe
scrollPane.setPannable(false);
would make a difference, but no. How should this unwanted behaviour be avoided?
Challenge 2.
How would I go about to center innerGroup inside the scrollPane, without resorting to paint a pixel in the upper left corner of innerGroup with the desired delta to the squares?
As a sidenote, according to the JavaDoc for ScrollPane: "If an application wants the scrolling to be based on the visual bounds of the node (for scaled content etc.), they need to wrap the scroll node in a Group". This is the reason why I have an innerGroup and an outerGroup inside ScrollPane.
Any suggestions that guides me to the solution is much appreciated by this JavaFX novice.
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
/**
* Demo of a challenge I have with zooming inside a {#code ScrollPane}.
* <br>
* I am running JavaFx 2.2 on a Mac. {#code java -version} yields:
* <pre>
* java version "1.7.0_09"
* Java(TM) SE Runtime Environment (build 1.7.0_09-b05)
* Java HotSpot(TM) 64-Bit Server VM (build 23.5-b02, mixed mode)
* </pre>
* 6 rectangles are drawn, and can be zoomed in and out using either
* <pre>
* Ctrl + Mouse Wheel
* or Ctrl + 2 fingers on the pad.
* </pre>
* It reproduces a problem I experience inside an application I am writing.
* If you magnify to {#link #MAX_SCALE}, an interesting problem occurs when you try to zoom back to {#link #MIN_SCALE}. In the beginning
* you will see that the {#code scrollPane} scrolls and consumes the {#code ScrollEvent} until we have scrolled to the bottom of the window.
* Once the bottom of the window is reached, it behaves as expected (or at least as I was expecting).
*
* #author Skjalg Bjørndal
* #since 2012.11.05
*/
public class ZoomApp extends Application {
private static final int WINDOW_WIDTH = 800;
private static final int WINDOW_HEIGHT = 600;
private static final double MAX_SCALE = 2.5d;
private static final double MIN_SCALE = .5d;
private class ZoomHandler implements EventHandler<ScrollEvent> {
private Node nodeToZoom;
private ZoomHandler(Node nodeToZoom) {
this.nodeToZoom = nodeToZoom;
}
#Override
public void handle(ScrollEvent scrollEvent) {
if (scrollEvent.isControlDown()) {
final double scale = calculateScale(scrollEvent);
nodeToZoom.setScaleX(scale);
nodeToZoom.setScaleY(scale);
scrollEvent.consume();
}
}
private double calculateScale(ScrollEvent scrollEvent) {
double scale = nodeToZoom.getScaleX() + scrollEvent.getDeltaY() / 100;
if (scale <= MIN_SCALE) {
scale = MIN_SCALE;
} else if (scale >= MAX_SCALE) {
scale = MAX_SCALE;
}
return scale;
}
}
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
final Group innerGroup = createSixRectangles();
final Group outerGroup = new Group(innerGroup);
final ScrollPane scrollPane = new ScrollPane();
scrollPane.setContent(outerGroup);
scrollPane.setOnScroll(new ZoomHandler(innerGroup));
StackPane stackPane = new StackPane();
stackPane.getChildren().add(scrollPane);
Scene scene = SceneBuilder.create()
.width(WINDOW_WIDTH)
.height(WINDOW_HEIGHT)
.root(stackPane)
.build();
stage.setScene(scene);
stage.show();
}
private Group createSixRectangles() {
return new Group(
createRectangle(0, 0), createRectangle(110, 0), createRectangle(220, 0),
createRectangle(0, 110), createRectangle(110, 110), createRectangle(220, 110),
createRectangle(0, 220), createRectangle(110, 220), createRectangle(220, 220)
);
}
private Rectangle createRectangle(int x, int y) {
Rectangle rectangle = new Rectangle(x, y, 100, 100);
rectangle.setStroke(Color.ORANGERED);
rectangle.setFill(Color.ORANGE);
rectangle.setStrokeWidth(3d);
return rectangle;
}
}
OK, so I finally found a solution to my problem.
By merely substituting the line
scrollPane.setOnScroll(new ZoomHandler(innerGroup));
with
scrollPane.addEventFilter(ScrollEvent.ANY, new ZoomHandler(innerGroup));
it now works as expected. No mystic Rectangle or other hacks are needed.
So the next question is why? According to this excellent article on Processing Events,
An event filter is executed during the event capturing phase.
while
An event handler is executed during the event bubbling phase.
I assume this is what makes the difference.
The following workaround seems to be giving better results:
Set onscroll event on the outer group to steal the event from the scroll pane.
Add a opaque rectangle which covers the whole screen, so that you don't miss the scroll event. Apparently you can miss scroll event if you don't hit a shape.
Rectangle opaque = new Rectangle(0,0,WINDOW_WIDTH,WINDOW_HEIGHT);
opaque.setOpacity( 0 );
outerGroup.getChildren().add( opaque );
outerGroup.setOnScroll(new ZoomHandler(innerGroup));
In my case I have updated the following line
if (scrollEvent.isControlDown()) {
with if (!scrollEvent.isConsumed()) {.... along with the change you have posted.... :)

Graphics plain 2D objects are not rendered while an action occurred

I am designing a game in Swing. Currently I am designing the maze for this game. The maze is generated by using Depth First Search algorithm. In my main JFrame, I have some JPanel. One JPanel, named mazePanel contains the maze. There are some other JPanel also, which contains the JButton for controlling. Following is the mazePanel code.
import java.awt.Graphics;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
public class MazePanel extends JPanel {
private MazeGenerator mazeGenerator;
private boolean startNewMaze = false;
public MazePanel() {
setBorder(BorderFactory.createTitledBorder("Maze"));
setToolTipText("This is the maze");
}
public void addNewMaze() {
startNewMaze = true;
mazeGenerator = new MazeGenerator();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (startNewMaze) {
mazeGenerator.generate(g);
startNewMaze = false;
}
}
}
There is one JButton, which calls the method mazePanel.addNewMaze() and set the Boolean startNewMaze to true. After setting the startNewMaze, maze should be generated. i.e. mazeGenerator.generate(g) is inside if() condition. Method mazeGenerator.generate(g) recursively draw the random maze. That is why I don’t want to run this method not more than once.
Up to this everything is looking fine. But while I am running the main JFrame and clicks on the JButton, maze is not rendered in the mazePanel. Sometimes when I minimize and maximize the JFrame, maze rendered (might be because of repaint() occur). Even if I comment mazeGenerator.generate(g) inside if() condition and put some g.drawString(). The string is not rendered while action performed (i.e.Pressing JButton).
Where is the problem? Please help.
Thank you.
So basically you have a JPanel which contains nothing, you call a method unknown to Swing and expects the paintComponent method is magically called when you change the state of a private field.
You already discovered that minimizing and maximizing again solves your problem due to a repaint. That should be sufficient information to know you have to trigger a repaint yourself when you press that button.
If you would have followed the suggestion from #kleopatra to, and I quote,
change the state and then trigger the revalidation/painting
you would already have solved your problem