A LinearGradientPaint object from java.awt may appear nice once painted, but there's a problem I'm having with it for painting an animated background in some kind of game model that's taking me long to build.
I want to paint an animated rainbow gradient on the background using the Graphics2D paint object, except that when I do so, I notice a lot of lag in repainting the panel. It should repaint itself at least 30 frames per second, which is only possible if the Paint object the graphics object uses is not a rainbow gradient.
Even running it as a separate thread will not do the trick. Below is the code for what I am trying to do at the end of each frame:
gamePanel.executor.execute(new Runnable(){
public void run()
{
while(true)
{
if (Background.selectedBackgroundIndex >= Background.SKY_HORIZON_GRADIENT_PAINT &&
Background.selectedBackgroundIndex < Background.SPACE_PAINT)
{
float displacementValue = 1.0f;
if (Background.backgroundShape.y < ((-2990.0f) + CannonShooterModel.gamePanel.getSize().height) && gamePanel.horizonGoingDown)
gamePanel.horizonGoingDown = false;
else if (Background.backgroundShape.y > (-10.0f) && !gamePanel.horizonGoingDown)
gamePanel.horizonGoingDown = true;
Point2D.Double startPoint = (Point2D.Double)(((LinearGradientPaint)Background.background).getStartPoint()),
endPoint = (Point2D.Double)(((LinearGradientPaint)Background.background).getEndPoint());
if (gamePanel.horizonGoingDown)
Background.backgroundShape.y -= displacementValue;
else
Background.backgroundShape.y += displacementValue;
startPoint.setLocation(0, Background.backgroundShape.y);
endPoint.setLocation(0, Background.horizonGradientPaintHeight + Background.backgroundShape.y);
// Should be done in another thread, particularly in arithmetic calculations.
Background.background = new LinearGradientPaint(startPoint, endPoint,
((LinearGradientPaint)Background.background).getFractions(),
((LinearGradientPaint)Background.background).getColors());
}
for (int a = 0; a < PlayerUnit.weapon.bullets.length; a++)
{
if (PlayerUnit.weapon.bullets[a] != null)
{
if (PlayerUnit.weapon instanceof Pistol &&
((Ellipse2D.Float)PlayerUnit.weapon.bullets[a]).x + ((Pistol)PlayerUnit.weapon).bulletWidth >= 0 &&
((Ellipse2D.Float)PlayerUnit.weapon.bullets[a]).x <= CannonShooterModel.gamePanel.getSize().width &&
((Ellipse2D.Float)PlayerUnit.weapon.bullets[a]).y + ((Pistol)PlayerUnit.weapon).bulletWidth >= 0)
{
if (PlayerUnit.weapon.weaponAngles[a] >= 0)
((Ellipse2D.Float)PlayerUnit.weapon.bullets[a]).x +=
PlayerUnit.weapon.bulletSpeed * Math.cos(PlayerUnit.weapon.weaponAngles[a]);
else
((Ellipse2D.Float)PlayerUnit.weapon.bullets[a]).x -=
PlayerUnit.weapon.bulletSpeed * Math.cos(PlayerUnit.weapon.weaponAngles[a]);
if (PlayerUnit.weapon.weaponAngles[a] >= 0)
((Ellipse2D.Float)PlayerUnit.weapon.bullets[a]).y -=
PlayerUnit.weapon.bulletSpeed * Math.sin(PlayerUnit.weapon.weaponAngles[a]);
else
((Ellipse2D.Float)PlayerUnit.weapon.bullets[a]).y +=
PlayerUnit.weapon.bulletSpeed * Math.sin(PlayerUnit.weapon.weaponAngles[a]);
}
else
PlayerUnit.weapon.bullets[a] = null;
}
}
//System.out.println(Background.backgroundShape.y);
repaint();
try
{
Thread.sleep(1000 / 60);
}
catch (InterruptedException ex)
{
}
}
}
});
The classes Background, PlayerUnit, and CannonShooterModel are important to my game model. It's an upright shooting game supposed to be designed with various weapons and enemies.
This rainbow gradient I have uses an array of eight different Color objects. For every frame passed through, I change the y-coordinate for both Point2D.Float objects required for the gradient paint as desired. In order for the animation to work, I have to actually instantiate another object of LinearGradientPaint again, with some of the previous properties from the previous object, and have it be referenced by the variable background of type Paint.
Problem is, LinearGradientPaint does not have a method to where you can do a translate on the two end points, and the get methods do not return the actual object that LinearGradientPaint object contains. (what I mean is, the get methods return a new object of Point2D with the same values as those part of the LinearGradientPaint object.)
For every frame passed, I have to change not only the y-coordinate property of the shape that's associated with the gradient, but also set the locations of the two Point2D objects that are needed to instantiate LinearGradientPaint once again.
I would love to re-explain this much simpler, because I can have trouble with some knowledge of English, even though this is my primary language. Please let me know if you need re-explanation.
There are a couple of solutions you might try.
Instead of filling the entire paintable area, you could create a BufferedImage whose width is 1 pixel and height equal to the area you want to fill (assuming you are fill vertically). You would then apply the LinearGradientPaint to this BufferedImage's Graphics2D and fill it (don't forget to dispose of the Graphics context when your done).
Then, you would simply use Graphics#drawImage(Image, x, y, width, height, ImageObserver) to actually paint the image. Generally speaking, it appears that it's faster to rescale an image then it is to fill it with a LinearGradientPaint, especially when you consider you are only stretching the image horizontally.
The other choice would be to generate a basic BufferedImage which had the LinearGradientPaint already applied and you would simply paint this offset as required. This would probably require you to paint it at least twice to allow it to "stitch" toegther...
If you are just making the background gradient move up and down, could you write it to an image at initialization and then move and wrap the image vertically?
Related
I am currently developing a 2D Java game using Swing as my primary drawing component. Every object has a shadow (BufferedImage) but every shadow overlaps other shadows. Is it possible to only have the shadows not overlap each other? Because I still want the shadows to draw over the player if the object is beneath it, and not if the object is above of the player. Here is a picture for clarity:
I have looked at alpha compositing, I guess I need Source Out? I also thought of having all the shadows (with no transparency) draw on one layer and then draw it with transparency but then it won't draw over the player and other objects like before.
I have a Draw object which is a JPanel and overrides the paintComponent method. Within this method I draw the floor of the current room and then I iterate over the list of objects that belongs to the current room and call each objects' draw method to draw everything.
The object draw method:
public void draw(Graphics g) {
if (visible && checkInScreen()) {
// The required drawing location
int drawLocationX = getX() - globalCameraX;
int drawLocationY = getY() - globalCameraY;
if (shadow) {
g.drawImage(shadowImages.get(imageIndex),
drawLocationX + shadowOffset.x + (getImageWidth()/2),
drawLocationY + shadowOffset.y, null);
}
g.drawImage(images.get(imageIndex), drawLocationX, drawLocationY, null);
//Collisionbox
if (SHOW_COLLISION_BOXES){
g.setColor(Color.WHITE);
g.drawRect(drawLocationX + getCollBoxX(), drawLocationY + getCollBoxY(), getCollBoxW() - getCollBoxX(), getCollBoxH() - getCollBoxY());
}
}
}
My apologies if this question has already been asked but I couldn't find something similar like this.
What I would do to solve this is to have a shadow-layer bitmap. By which I mean:
have your shadow textures saved as a 2D array of boolean values (representing the position of a shadow pixel).
What you can do with this is to then logically or the shadow maps together to create a single layer, which can be layered behind the tree textures to create the shadows.
You may want to change the booleans to floats to represent the colour/intensity of the shadow, then have a larger calculation to merge the shadows together.
The below ShadowMap class is used to store the data for each shadow:
class ShadowMap {
public int xPos, yPos;
public boolean[][] array;
public ShadowMap(int xPos, int yPos, boolean[][] array) {
this.xPos = xPos;
this.yPos = yPos;
this.array = array;
}
}
The ShadowLayer class creates a 2D array for the entire screen, containing if a shadow is present for each pixel:
class ShadowLayer {
public static boolean[][] array = new boolean[SCREEN_WIDTH][SCREEN_HEIGHT];
public static makeNew(ShadowMap[] shadows) {
for (int x = 0; x < SCREEN_WIDTH; x++) {
for (int y = 0; y < SCREEN_HEIGHT; y++) {
array[x][y] = false;
}
}
for (ShadowMap map : shadows) {
for (int i = 0; i < SCREEN_WIDTH; i++) {
for (int j = 0; j < SCREEN_HEIGHT; j++) {
// Logical or such that the pixel at (x, y) has a shadow
// if any shadow map also has a shadow at pixel (x, y)
array[i + map.xPos][j + map.yPos] |= map.array[i][j];
}
}
}
}
}
Using this ShadowLayer class, you just need to darken each pixel on the screen if the ShadowMap has a shadow on the same pixel:
public static Color ajustPixelForShadows(int x, int y, Color pixel) {
return ShadowMap.array[x][y] ? pixel.darken() : pixel;
}
I admit I'm not familiar with Swing so I'm not sure it is possible with that specific interface but the below solution could be used in a variety of 2D graphics engines.
You'll need an off-screen "shadow layer" to draw to that matches the screen dimensions. Initialize the shadow layer to being pure white.
For each object you draw from back to front (y-sorting), do the following, in order, with the shadow layer:
Draw the object's shadow shape in a single solid dark grey color to the shadow layer
Draw the object itself to the shadow layer as a pure white sprite (i.e. all non-transparent pixels in the object's bitmap are white)
Of course, also draw the object itself to the screen.
Then, once all objects have been drawn to both the screen and the shadow layer, draw the shadow layer to the screen using multiply blending. The multiply blend guarantees shadows will darken whatever they are drawn over (unlike alpha blend which, with very light shadows, could potentially actually lighten the colors they are drawn over). It will also make the pure white portions of the layer do nothing, which is what you want.
The above steps mean that after each object draws a shadow, it erases any shadows that would be underneath it in the final scene when it draws itself in white to the shadow layer. Therefore it won't cast a shadow on itself, and objects won't cast shadows over other objects that are technically in front of them.
Objects will still cast shadows onto other objects that are behind them as you wanted, since any parts of the shadow that haven't been erased by an overlapping object will still apply (or if they are erased, will be potentially re-drawn by a later object). And, since you are drawing the shadows as a single non-translucent color to the shadow layer, multiple shadows overlapping won't affect each other either, which was of course the main point.
You could modify this technique depending on what you have available. For example, instead of white you could use a fully transparent shadow layer initially and an "erase" blend mode [(src * 0) + (dst * (1 - srcAlpha))] to draw the objects that erase shadows underneath them. You could then use alpha instead of multiply blend if you prefer for drawing the shadow layer to the screen.
My game engine is drawing a tiled map on a Canvas inside a JFrame. I am using a Bufferstrategy to get smoother rendering and tried to boost the performance at several points inside the program and end up with ca. 120 frames per second to fill a 1920 * 1080 Window with a black color and draw some isometric 512 * 256 tiles on top. When moveing the map around there are small black lines in between the tiles and . I assume that they occur because some tiles have already been moved to the new position, but the image isn´t done yet, when put on the sreen. But I don´t really have a solution to that and I´m also not sure if I´m right.
I should probably metnion that I´m using two threads in my program, one thread calls update and render methods, the other one is currently just getting user input to move the camera position. Also in some lauches everything works smoothly and in others it flickers very bad.
Here is some code of the window creation and rendering:
private final void createWindow(){
frame = new JFrame(title);
frame.setMinimumSize(size);
frame.setMaximumSize(size);
frame.setPreferredSize(size);
display = new Canvas();
display.setMinimumSize(size);
display.setMaximumSize(size);
display.setPreferredSize(size);
image = ImageLoader.boostPerformance(new BufferedImage((int)size.getWidth(), (int)size.getHeight(), BufferedImage.TYPE_INT_RGB));
graphics = (Graphics2D) image.getGraphics();
frame.add(display);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.requestFocusInWindow();
}
public final void renderToScreen(){
BufferStrategy bs = display.getBufferStrategy();
if(bs==null){
display.createBufferStrategy(bufferCount);
return;
}
graphicsToScreen = (Graphics2D) bs.getDrawGraphics();
graphicsToScreen.drawImage(image, 0, 0, display.getWidth(), display.getHeight(), null);
graphicsToScreen.dispose();
bs.show();
}
I use the Graphics2D graphics object in other classes to draw the tiles.
public void render(Graphics2D g) {
g.setColor(Color.black);
g.fillRect(0, 0, getWidth(), getHeight());
tilemap.render(g);
g.setColor(Color.red);
g.drawString(String.valueOf(Main.actualFps), 30, 30);
}
Above is the render method in another class using the graphics object I mentioned.
And here you can see the tile map render method:
private final void renderIsometric(Graphics2D g){
for(int x = 0; x < 4; x++){
for(int y = 3; y >= 0; y--){
if(x > tilesX - 1|| y > tilesY - 1)break;
if(x < 0 || y < 0)continue;
if(map[x][y] == null)continue;
g.drawImage(map[x][y].getImage(), (int)orthoToIso(x, y).x - cameraPosX, (int)orthoToIso(x, y).y - cameraPosY, null);
}
}
}
Finally here is the main loop to ensure that the fps counter is working and the value of 120 fps is correct:
while(running){
long now = System.nanoTime();
double ns = 1000000000.0 / preferredUps;
delta += (now - lastTime) / ns;
lastTime = now;
while(delta >= 1){
//call the update method
update();
updates++;
delta--;
}
//call the render method
render();
frames++;
//refresh the actualUps and actualFps once every second
if(System.currentTimeMillis() - timer > 1000){
timer+=1000;
actualUps = updates;
actualFps = frames;
updates = 0;
frames = 0;
}
}
How can I get rid of these tiny lags and flicker problems? And additionally I have been wondering if it would give better results to use OpenGL via LWJGL instead of Java2D?
Thanks for your help!
What you see there is most likely not flickering strickly speaking. Its tearing and weaving artifacts. Its a natural consequence of updating the screen without synchronizing to the vertical synchronization of screen refresh.
Windowing systems are in general not made to honor v-sync (they favor performance, as those effects are not grossly perceptible - or important - in a normal UI). As far as I know there is no way of synchronizing with a (J)Frame (you could probably hack something with JNI, asking the OS to tell you when the raster beam is at the top of the frame, but thats beside the point).
Also rendering at 120fps may actually make the problem worse, unless the display device also refreshes at 120hz - when you render more frames than the device actually displays you force the device to display graphics from more than one frame (e.g. frame 1 in the top half of screen, frame 2 in the bottom half). This will always result in perciptible artifacts with moving objects.
There is one way around it in pure java, and thats fullscreen mode (that will wait for v-sync, as a result your rendering rate is limited to and controlled by the refresh rate of the display device).
You can try openGL, but I have no idea if it will synchronize when rendering in windowed mode.
A first step to improve things would be to not render faster than the display devices displays (e.g. max. 60 fps for a 60hz screen).
In case your application is not time critical, you can resolve flickering by using a double-buffering strategy.
Essentially, the image you are compositing is copied to a secondary off-screen image buffer, which is not rendered this frame, but next frame. There are some tricks you can use to have Swing components double buffer automatically for you, see also:
https://gamedev.stackexchange.com/questions/30921/thread-safe-double-buffering
Double buffered image example in Jpanel
The downside of double buffering is that you introduce one frame of latency, which means your input is only going to result in visible changes one frame later than it would without double buffering. At 120hz this is probably not going to be a big issue, but just for completeness sake..
I'm doing the following to a Canvas object.
graphics.setColor(BLUE);
graphics.fill(new Rectangle2D.Double(x, y, width, height));
I'd like to fade in the fill colour to create a smooth transition from the canvas background colour to the new colour (and possibly fade out whatever colour was originally there).
I've played with this kind of thing (setting the graphics object's composite to an AlphaComposite which a Timer updating the alpha value every n milliseconds) but I get flickering.
I'm wondering what general concept I'm missing.
Thanks for any pointers.
First of all, how could you be using the AWT? It is quite outdated. I reccomend you switch to swing, mainly because swing has double buffering, which would remove your flicker.
Your application does exactly what you tell it to do. If you want to make a fade-in effect, you have to determine what kind of color changes you want to make, create a function which does it, and implement the fade itself.
I'd approach it like that:
class FadeEffect{
int totalDurationMs;
int elapsedDurationMs;
Color initialColor;
Color finalColor;
Color getColor(int durationDelta) {
elapsedDurationMs += durationDelta;
if (elapsedDurationMs > totalDurationMs) {
return finalColor;
}
double progress = 1.0d*elapsedDurationMs/totalDurationMs;
return new Color( (int)(finalColor.getRed()-initialColor.getRed())*progress,
(int)(finalColor.getGreen()-initialColor.getGreen())*progress,
(int)(finalColor.getBlue()-initialColor.getBlue())*progress);
}
//getters, setters, etc
}
As for the flickering issue: make sure you are using double buffering - either in your component, or by manually drawing on a off-screen buffer (image) and only posting the image to the screen when the drawing is complete.
Here is a sample code from my Graphic2D app doing the double buffering:
private VolatileImage vImg;
#Override
public void paint(Graphics g) {
if (gc==null) gc = this.getGraphicsConfiguration();
do {
boolean sizeChanged = false;
sizeChanged = (vImg!=null&&(vImg.getWidth()!=getWidth()|| vImg.getHeight()!=getHeight()));
if (vImg == null || vImg.validate(gc) == VolatileImage.IMAGE_INCOMPATIBLE
|| sizeChanged) {
vImg = gc.createCompatibleVolatileImage(getWidth(), getHeight());
vImg.setAccelerationPriority(1);
}
final Graphics gimg = vImg.getGraphics();
if (gimg instanceof Graphics2D) {
renderContents((Graphics2D) gimg);
gimg.dispose();
g.drawImage(vImg, 0, 0, null);
} else {
throw new UnsupportedOperationException("Rendering impossible, graphics are not of Graphics2D class");
}
} while (vImg.contentsLost());
updateAnimationNo();
}
I am using slick for java since a few days and got a serious problem.
If i run a completely empty apllication (it just shows the fps) with a solution of 800x600 i get a fps count between 700 and 800.
If I now draw an array with 13300 entries as a grid of green and white rectangles, the fps drop to something around 70.
With more entries in the array it becomes really slow.
For example in a solution of 1024x768 and an array with 21760 entries the fps drop to 40.
How i draw a single entry:
public void draw(Graphics graphics){
graphics.setColor(new Color(getColor().getRed(), getColor().getGreen(), getColor().getBlue(), getColor().getAlpha()));
graphics.fillRect(getPosition().x, getPosition().y, getSize().x, getSize().y);
Color_ARGB white = new Color_ARGB(Color_ARGB.ColorNames.WHITE);
graphics.setColor(new Color(white.getRed(), white.getGreen(), white.getBlue(), white.getAlpha()));
}
And this is how I draw the complete array:
public void draw(Graphics graphics) {
for (int ix = 0; ix < getWidth(); ix++) {
for (int iy = 0; iy < getHeight(); iy++) {
getGameGridAt(ix, iy).draw(graphics);
}
}
}
In my opinion 21760 is not that much.
Is there anything wrong with my code or is slick just too slow to draw so much rectangles?
You only want to draw rectangles that are on the screen. If your screen bounds go from 0 to 1024 in the x direction and from 0 to 768 in the y direction, then you only want to loop through rectangles that are inside those bounds and then only draw those rectangles. I can't imagine you are trying to draw 21760 rectangles inside those bounds.
If you are, then try creating one static rectangle and then just try drawing that ONE in all of the different positions you need to draw it at rather than creating a new one every time. For example, in a game I am making, I might have 1000 tiles that are "grass" tiles, but all 1000 of those share the same static texture. So I only need to reference one image rather than each tile creating its own.
Each rectangle can still have a unique state. Just make your own rectangle class and have a static final Image that holds a 5*5 image. Each rectangle will use this image when it needs to be drawn. You can still have unique properties for each rectangle. For example, private Vector2f position, private boolean isAlive, etc
You're probably not going to be able to draw individual rectangles any faster than that.
Games that render millions of polygons per second do so using vertex buffer objects (VBO). For that, you'll probably need to code against the OpenGL API (LWJGL) itself, not a wrapper.
Not sure if Slick will allow it, but if this thing looks anything like a chessboard grid... you could draw just 4 rectangles, grab them and use the resulting image as a texture for your whole image. I'm not even a java programmer just trying to come up with a solution.
Since you're only repeatedly using just a few colors creating a new Color object for every single one is bound to be slow... use new only once for each different color used and store the re-usable colors somewhere in your class, than call the functions with those, constantly allocating and freeing memory is very slow.
And while this might not be as much a benefit as not using new each time but have you considered caching the results of all those function calls and rewriting code as
public void draw(Graphics graphics) {
int ixmax = getWidth();
int iymax = getHeight();
for (int ix = 0; ix < ixmax; ix++) {
for (int iy = 0; iy < iymax; iy++) {
getGameGridAt(ix, iy).draw(graphics);
}
}
}
Or if you'd prefer not to declare new variables
public void draw(Graphics graphics) {
for (int ix = getWidth() - 1; ix >= 0; ix--) {
for (int iy = getHeight() - 1; iy >= 0; iy--) {
getGameGridAt(ix, iy).draw(graphics);
}
}
}
Just noticed in another answer you have an integral size grid (5x5) ... in this case the fastest way to go about this would seem to be to draw each item a single pixel (you can do this directly in memory using a 2-dimensional array) and scale it to 500% or use it as a texture and draw a single rectangle with it the final size you desire ... should be quite fast. Sorry for all the confusion caused by previous answers, you should have said what you're doing more clearly from the start.
If scaling and textures are not available you can still draw in memory using something like this (written in c++, please translate it to java yourself)
for( int x = 0; x < grid.width(); x++ ) {
for( int y = 0; y < grid.height(); y++ ) {
image[x*5][y*5] = grid.color[x][y];
image[x*5][y*5 + 1] = grid.color[x][y];
image[x*5][y*5 + 2] = grid.color[x][y];
image[x*5][y*5 + 3] = grid.color[x][y];
image[x*5][y*5 + 4] = grid.color[x][y];
}
memcpy(image[x*5+1], image[x*5], grid.height() * sizeof(image[0][0]) );
memcpy(image[x*5+2], image[x*5], grid.height() * sizeof(image[0][0]) );
memcpy(image[x*5+3], image[x*5], grid.height() * sizeof(image[0][0]) );
memcpy(image[x*5+4], image[x*5], grid.height() * sizeof(image[0][0]) );
}
I'm not sure, but perhaps for graphics the x and y might be represented in the reversed order than used here, so change the code accordingly if it that's the case (you'll figure that out as soon as a few iterations run), also your data is probably structured a bit differently but I think the idea should be clear.
I'm fairly new to Android programming. I'm trying to create an animation of a bitmap image using Canvas in Android. I am using setAlpha() to manipulate the opacity of the bitmap. My drawFrame() method includes the following bit:
c = holder.lockCanvas();
drawScene(c, paint);
holder.unlockCanvasAndPost(c);
My drawScene() includes this bit:
Paint transparencyValue = new Paint();
transparencyValue.setAlpha(paint);
canvas.drawBitmap(boom.getImage(), logoToBoom.getX(), logoToBoom.getY(),
transparencyValue);
I imagine I have to insert a loop to modify paint from 0 to 255 and back down. So far it hasn't worked, but I am probably doing something wrong. Could anyone please recommend something?
EDIT: Here is my code for the Runnable. paint is a private double set to 255. boom_activated is a boolean that becomes true if the onTouchEvent enabled it. It should stay true until the Runnable disables it (setBoomState(false);). For some reason it's still not drawing the bitmap at the decreasing opacity. Is the code below valid, or am I missing something?
private final Runnable DrawSceneThread = new Runnable() {
public void run() {
if (boom_activated && paint <= 0) {
paint = 0;
drawFrame();
setBoomState(false);
paint = 255;
} else if (boom_activated && paint >= 0) {
drawFrame();
paint -= 0.7;
} else {
drawFrame();
}`
In my drawScene() I have this line:
scene_handler.postDelayed(DrawSceneThread, 25);
What you have to do is to animate the opacity values over time. You need to use a handler to update the alpha values and then draw the bitmap in your onDraw function. Have a look at this tutorial to get a better idea about updating the UI through handlers: http://developer.android.com/resources/articles/timed-ui-updates.html
Refer to this topic
How to change a bitmap's opacity?
Also, I would recommend to take your Paint instance and paint variable out of your drawScene and declare it in a global scope so you can reuse it. It would hurt performance when recreating it over and over.