JFrame duplicates drawings when resizing - java

I wrote a code in Java (using swing) which draws few polygons on a panel.
public MyClass extends JPanel
The code is very simple (but long) and basically adds few Polygons, then adds few points to each polygon and then draw them on the screen (with drawPolygon).
My problem is when I run the program, I can't see the drawings on the panel.
After a while, I figure out that when I re-size my frame, I can suddenly see the drawing but it duplicates itself many times (depends how much I re-size). If I play enough time with the resizing, I get:
java.lang.OutOfMemoryError: Java heap space
Also, myPolygon.invalidate() doesn't help.
When using setResizable(false) I can't see my drawing at all.
Does anyone have a solution?
Duplicate Image Screenshot:1

To start with, in your paintComponent method, don't call
setPreferredSize(new Dimension(500,500));
setVisible(true);
validate();
This will request a repaint, cause paintComponent to be recalled and you'll end up in a nasty loop, consuming your CPU and (as you have found out), your memory.
IF you can get away with it, you're better off to draw the polygon to a buffer and draw the buffer to the screen on each iteration of the paintComponent. This will be faster in the long run...
// Create a field
private BufferedImage buffer;
// Call this when you need to change the polygon some how...
protected void createBuffer() {
// You need to determine the width and height values ;)
buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
int xoffset=5;//Multiply in order to "zoom" the picture
int offset=0;//moves shape to the right
p.addPoint(40*xoffset-offset, 30*xoffset-offset);
p.addPoint(50*xoffset-offset,30*xoffset-offset);
p.addPoint(57*xoffset-offset,37*xoffset-offset);
p.addPoint(57*xoffset-offset,47*xoffset-offset);
p.addPoint(50*xoffset-offset,54*xoffset-offset);
p.addPoint(40*xoffset-offset,54*xoffset-offset);
p.addPoint(33*xoffset-offset,47*xoffset-offset);
p.addPoint(33*xoffset-offset, 37*xoffset-offset);
g.drawPolygon(p);
g.dispose();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (buffer != null) {
Graphics2D g2d = (Graphics2D)g;
g2d.drawImage(buffer, translateX, translateY, this);
}
}
UPDATE
So, anyway, the other fun things you're doing are...
Creating a static reference to your Polygon. Hope you weren't intending to have more the one on the screen at a time ;)
Add new points to an already existing polygon (each time paintComponent was called)
Translating the polygon each time paintComponent was called
Try something like this instead
public class RoundTop extends JPanel {
//Polygons declarations
private Polygon p = new Polygon();
//Translate variables;
private int translateX = 10;
private int translateY = 10;
public RoundTop() {
int xoffset = 5;//Multiply in order to "zoom" the picture
int offset = 0;//moves shape to the right
p.addPoint(40 * xoffset - offset, 30 * xoffset - offset);
p.addPoint(50 * xoffset - offset, 30 * xoffset - offset);
p.addPoint(57 * xoffset - offset, 37 * xoffset - offset);
p.addPoint(57 * xoffset - offset, 47 * xoffset - offset);
p.addPoint(50 * xoffset - offset, 54 * xoffset - offset);
p.addPoint(40 * xoffset - offset, 54 * xoffset - offset);
p.addPoint(33 * xoffset - offset, 47 * xoffset - offset);
p.addPoint(33 * xoffset - offset, 37 * xoffset - offset);
p.translate(translateX, translateY);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawPolygon(p);
g2d.dispose();
}
}

Related

Java Game Dev - How do I create a vertical health bar?

I need some assistance fixing my vertical health bar. It tracks health correctly and updates, but because of how Java draws (from the top left corner, i.e. (0,0)), the bar appears to be upside down. I would like to flip the health bar so it would appear correctly but I am unsure how to do so.
The division in the codes x and y is just to place the bar in the correct position on the canvas. The multiplier is to scale up the health bar to fit a graphical overlay I made for it.
private void drawHealthBar(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.red);
g2.fillRect(WIDTH - (WIDTH / 50), HEIGHT / 2, 10, ships.get(0).getHealth() * 6);
}
Setup the coordinate system how you want, using transform() and translate(). So:
you want the origin to be at (0, height); bottom left.
then you want to flip the Y axis.
Example code:
AffineTransform tform = AffineTransform.getTranslateInstance( 0, height);
tform.scale( 1, -1);
g2.setTransform( tform);
you need to shift bar depending on amount of health left, like this:
private void drawHealthBar(Graphics g) {
final Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.red);
final int w = 10;
final int h = ships.get(0).getHealth() * 6; // calculate height first
final int x = WIDTH - (WIDTH / 50);
final int y = HEIGHT / 2 + (MAX_HEIGHT - h); // shift by height of whitespace
g2.fillRect(x, y, w, h);
}

Graphics in one object is influencing another object's graphics - JAVA

First post, so I'll try to be as clear as I can.
Basically, what I'm trying to do is create a little game, where you have a ship, and you can shoot bullets from it. The ship rotates accordingly to the player's deltaX and deltaY using a tangent formula. I use a standard game loop like this one:
public void run() {
long lastTime = System.nanoTime();
double amountOfTicks = 60.0;
double ns = 1000000000 / amountOfTicks;
double delta = 0;
long timer = System.currentTimeMillis();
#SuppressWarnings("unused")
int frames = 0;
while(running){
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while(delta >=1){
tick();
delta--;
}
if(running)
repaint();
frames++;
if(System.currentTimeMillis() - timer > 1000)
{
timer += 1000;
frames = 0;
}
}
stop();
}
My "game" runs on a child of a JPanel, a class called Board. In the loop, I call the methods tick(), which updates the info, and repaint(), that works as a render method. This is my paintComponent(Graphics g) method:
public void paintComponent(Graphics g) {
//This is for background
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.black);
g2d.fill(new Rectangle(0, 0, Constants.width, Constants.height));
//This is where actual game rendering occurs
handler.render(g);
g.dispose();
}
As can be seen, I dont render everything on my Board class. I do so on my handler. This is how I deal with the handler:
public void render(Graphics g) {
for (int i = 0; i < handlerList.size(); i++) {
//handlerList is a LinkedList
handlerList.get(i).render(g);
}
}
The LinkedList handlerList contains Entities.
Entities is an abstract class, which is parent of Creature, which is parent of Player and Bullet.
This is the code for the rendering of a Player instance:
public void render(Graphics g) {
float centerX = x + (width / 2);
float centerY = y + (height / 2);
double theta = findAngle(deltaX, deltaY);
Graphics2D g2d = (Graphics2D) g;
if(!stopped) g2d.rotate(theta, centerX, centerY);
else g2d.rotate(stoppedTheta, centerX, centerY);
g2d.drawImage(shipImage, (int)x, (int)y, (int)width, (int)height, null);
}
There is a boolean "stopped" which keeps track of the objects condition. I use Graphics2D instead of Graphics due to the fact that I wanna be able to rotate my ship.
Here's the code for the bullet's rendering:
public void render(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.yellow);
g2d.fillOval((int)x, (int)y, (int)width, (int)height);
}
It looks right, as far as I'm aware. Whenever I don't have a bullet on, the game runs fine, as you can see in this GIF:
DISCLAIMER: Sorry for low quality and the watermark, I've just formatted the computer and havent had time to install proper stuff....
When I add a bullet this happens:
The x and y position of the bullet doesn't change, but the bullet rotates with the ship. I'm assuming it has something to do with the misuse of the "dispose()" method, but I'm not sure what can be done to fix it.
Thank you in advance.
Don't ever call dispose on a Graphics context you did not explicitly create (or snapshot with create), this can cause issues further down the rendering pipeline.
Graphics is a shared context, so you need to be mindful of the changes you make to it and undo any "significant" changes you make, especially transformations
If it was me, I'd create a snapshot of the Graphics context before each call to render and the dispose of it after, for example
public void render(Graphics g) {
for (int i = 0; i < handlerList.size(); i++) {
//handlerList is a LinkedList
Graphics2D g2d = (Graphics2D)g.create();
handlerList.get(i).render(g2d);
g2d.dispose();
}
}
This ensures that what ever changes that render makes to the Graphics context are undone before the next element is rendered
If the changes are compounding, then I'd make the snapshot before the start of the loop and dispose of it after it.
In either case, it means you control the changes been made and how they affect other elements down the line.
Also, remember, transformations are compounding

Java Animation Rotation

I have very little experience with Java, and I am an amateur programmer. So mind my vocabulary.
I want to be able to stick a static rectangle on top of a rotating rectangle.
So far when I try to add another object it spins with the other image. I have tried setting the rotation to zero but that doesn't seem to work. I have also tried to create another class that draws components separately and added them to the frame using frame.add. I have also tried creating another part to the Draw class that has no effect on the GUI. Here is my current Draw class. Any help is appreciated.
class DrawRectangle extends JPanel {
#Override
public void paintComponent(Graphics g) {
int h = this.getHeight();
int w = this.getWidth();
Graphics2D g2 = (Graphics2D) g;
//draw background
g2.setColor(Color.WHITE);
g2.fillRect(0, 0, w, h);
//draw roatiing rectangle
g2.setColor(Color.CYAN);
Rectangle rRec = new Rectangle(w / 4, h / 4, 2 * w / 4, 2 * h / 4);
double wr = rRec.getX() + rRec.getWidth() / 2;
double hr = rRec.getY() + rRec.getHeight() / 2;
g2.rotate(Math.toRadians(count), wr, hr);
g2.fill(rRec);
g2.fillRect(w / 3, h / 3, 2 * w / 3, 2 * h / 3);
}
public void paintComponent2(Graphics g) {
int h = this.getHeight();
int w = this.getWidth();
Graphics2D g2 = (Graphics2D) g;
}
}
So far when I try to add another object it spins with the other image.
Create a separate Graphics object to do the rotation so you don't affect the properties of the Graphics object passed into the painting method:
//Graphics2D g2 = (Graphics2D) g;
Graphics2D g2 = (Graphics2D)g.create();
// painting code
g2.dispose();
Move your g2.fill(rRec); BEFORE the rotate call, and it should work (I just tested it out).
This way, you will draw your static rectangle before the rotation, perform the rotation, THEN draw your second rectangle. Assuming your count variable is incremented somewhere, it should show the second rectangle being rotated.

How do I increase the frequency JPanel is redrawn?

I have a simple program that draws the trajectory of a particle launched from the origin at a certain speed and angle. I created a subclass of JPanel to handle the drawing of this. My everytime my subclass is redrawn it takes the difference between the current time and the initial time(both in milliseconds), converts this to seconds, then finds the x and y coordinate of where the particle should be at that point in time, and finally takes those x and y coordinates and draws them on the screen. My problem is that my subclass seems to be redrawn at interval that seem long because there are only a few dots that are shown.
My drawing method:
private void doDrawing(Graphics g) {
Dimension size = getSize();
Insets insets = getInsets();
int w = size.width - insets.left - insets.right;
int h = size.height - insets.top - insets.bottom;
Graphics2D g2d = (Graphics2D) g;
g.drawString("Acceleration: -9.8m/s i", 0, 20);
StringBuilder b = new StringBuilder();
b.append("Current Velocity: ");
b.append(String.valueOf(sim.getVector(tickSpeed
* ((System.currentTimeMillis() - initTime) / 1000)).getMagnitude()));
b.append(" m/s at ");
b.append(String.valueOf(sim.getVector(tickSpeed
* ((System.currentTimeMillis() - initTime) / 1000)).getDirection().getDirectionDeg()));
b.append(" degrees");
g.drawString(b.toString(), 0, 30);
drawPreviousPoints(g2d);
drawCurrentPointAndAppend(g2d, w, h);
repaint();
}
private void drawCurrentPointAndAppend(Graphics2D g2d, int w, int h) {
g2d.setColor(Color.red);
double height = (length / w) * h;
Vector2D c = sim.getVector(tickSpeed
* ((System.currentTimeMillis() - initTime) / 1000));
double currentX = w
* ((sim.getX(tickSpeed
* ((System.currentTimeMillis() - initTime) / 1000))) / length);
double currentY = h
* (1 - ((sim.getY(tickSpeed
* ((System.currentTimeMillis() - initTime) / 1000))) / height));
g2d.drawLine((int) currentX, (int) currentY, (int) currentX,
(int) currentY);
g2d.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE,
BasicStroke.JOIN_MITER));
g2d.drawLine((int) currentX, (int) (currentY),
(int) (currentX + w * (c.getX() / length)),
(int) (currentY + (h * -(c.getY() / height))));
xList.add(currentX);
yList.add(currentY);
}
private void drawPreviousPoints(Graphics2D g2d) {
g2d.setColor(Color.blue);
g2d.setStroke(new BasicStroke(7, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND));
if (!xList.isEmpty()) {
for (int i = 0; i < xList.size(); i++) {
g2d.drawLine(xList.get(i).intValue(), yList.get(i).intValue(),
xList.get(i).intValue(), yList.get(i).intValue());
}
}
}
tickSpeed is just a variable that I use to speed up or slow down the particle. It runs fine; however, the animation seems very choppy.
How do I fix this choppiness(make everything seem more "fluid")
Where should I call repaint()? Because I feel like calling it at the end of my drawing method isn't right.
An important rule of Swing- You don't control the paint process...
Don't perform these calculations within the paintComponent. The paintComponent is meant to paint the current state of the UI and may be called at any time for many reasons, most of which are outside your control.
Instead, consider using a javax.swing.Timer set to repeat at a regular interval (40ms is 25 ticks a second).
Set up a model which keeps track of the particles current been processed. When the timer ticks, calculate your particle positions and update them, then call repaint.
Within your paintComponent, simply paint the current state of your model.
Have a look at Concurrency in Swing and How to use Swing Timers for more details
The paint process is internally handled so you can not control the frequency of it's execution.
However, you can create separate threads or timers which can invoke processes at your desired frequency. Use the paint method only to render on your canvas, do other logic and processing in another function.

Java - Does subpixel line accuracy require an AffineTransform?

I've never worked with Java drawing methods before, so I decided to dive in and create an analog clock as a PoC. In addition to the hands, I draw a clock face that includes tick marks for minutes/hours. I use simple sin/cos calculations to determine the position of the lines around the circle.
However, I've noticed that since the minute tick-marks are very short, the angle of the lines looks wrong. I'm certain this is because both Graphics2D.drawLine() and Line2D.double() methods cannot draw with subpixel accuracy.
I know I can draw lines originating from the center and masking it out with a circle (to create longer, more accurate lines), but that seems like such an inelegant and costly solution. I've done some research on how to do this, but the best answer I've come across is to use an AffineTransform. I assume I could use an AffineTransform with rotation only, as opposed to having to perform a supersampling.
Is this the only/best method of drawing with sub-pixel accuracy? Or is there a potentially faster solution?
Edit: I am already setting a RenderingHint to the Graphics2D object.
As requested, here is a little bit of the code (not fully optimized as this was just a PoC):
diameter = Math.max(Math.min(pnlOuter.getSize().getWidth(),
pnlOuter.getSize().getHeight()) - 2, MIN_DIAMETER);
for (double radTick = 0d; radTick < 360d; radTick += 6d) {
g2d.draw(new Line2D.Double(
(diameter / 2) + (Math.cos(Math.toRadians(radTick))) * diameter / 2.1d,
(diameter / 2) + (Math.sin(Math.toRadians(radTick))) * diameter / 2.1d,
(diameter / 2) + (Math.cos(Math.toRadians(radTick))) * diameter / 2.05d,
(diameter / 2) + (Math.sin(Math.toRadians(radTick))) * diameter / 2.05d));
} // End for(radTick)
Here's a screenshot of the drawing. It may be somewhat difficult to see, but look at the tick mark for 59 minutes. It is perfectly vertical.
Line2D.double() methods cannot draw
with subpixel accuracy.
Wrong, using RenderingHints.VALUE_STROKE_PURE the Graphics2D object can draw "subpixel" accuracy with the shape Line2D.
I assume I could use an
AffineTransform with rotation only, as
opposed to having to perform a
supersampling. Is this the only/best
method of drawing with sub-pixel
accuracy? Or is there a potentially
faster solution?
I think you are missing somthing here. The Graphics2D object already holds a AffineTransform and it is using it for all drawing actions and its cheap performance wise.
But to get back to you what is missing from your code - this is missing:
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
Below is a self contained example that produces this picture:
public static void main(String[] args) throws Exception {
final JFrame frame = new JFrame("Test");
frame.add(new JComponent() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
System.out.println(g2d.getTransform());
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
double dia = Math.min(getWidth(), getHeight()) - 2;
for (int i = 0; i < 60 ; i++) {
double angle = 2 * Math.PI * i / 60;
g2d.draw(new Line2D.Double(
(dia / 2) + Math.cos(angle) * dia / 2.1d,
(dia / 2) + Math.sin(angle) * dia / 2.1d,
(dia / 2) + Math.cos(angle) * dia / 2.05d,
(dia / 2) + Math.sin(angle) * dia / 2.05d));
}
g2d.draw(new Ellipse2D.Double(1, 1, dia - 1, dia - 1));
}
});
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
frame.setVisible(true);
}

Categories

Resources