JAVA How can I update an already drawn string? - java

I am trying to repaint a string used to keep score in a small java game I'm making but I am not sure how to have the string change on the screen. As you can see it is initially drawn, and I am trying to update it inside of the ingame if statement.
public void paint(Graphics g)
{
super.paint(g);
g.setColor(Color.white);
g.fillRect(0, 0, d.width, d.height);
//g.fillOval(x,y,r,r);
//Draw Player
g.setColor(Color.red);
g.fillRect(p.x, p.y, 20, 20);
if(p.moveUp == true) {
p.y -= p.speed;
}
moveObstacles();
for (int i = 0; i < o.length; i++ ) {
g.fillRect(o[i].x, o[i].y, 10, 5);
}
Font small = new Font("Helvetica", Font.BOLD, 14);
FontMetrics metr = this.getFontMetrics(small);
g.setColor(Color.black);
g.setFont(small);
g.drawString(message, 10, d.height-60);
g.drawString(message2, 10, d.height-80);
if (ingame) {
for (int i = 0; i < o.length; i++ ) {
if ((o[i].x < p.x + 20 && o[i].x > p.x) && (o[i].y < p.y + 20 && o[i].y > p.y)) {
p.x = BOARD_WIDTH/2;
p.y = BOARD_HEIGHT - 60;
lives = lives - 1;
g.drawString(message, 10, d.height-60);
}
}
// g.drawImage(img,0,0,200,200 ,null);
}
Toolkit.getDefaultToolkit().sync();
g.dispose();
}

You create a method like setMessage(…). This method with then save the "message" as a property in your class.
The method will then invoke repaint(), which will cause the component to repaint itself.
This is how all Swing components work. Think about a JLabel and the setText(…) method.
Also:
Custom painting is done by overriding the paintComponent() method, not the paint() method.
There is no need for the Toolkit sync() method.
You should NOT dispose of the Graphics object.

Related

Why is paintComponent not displaying correctly?

When running my program, sometimes the the triangles are drawn, sometimes they aren't, and sometimes only the last one appears. Originally I put the code in a for loop, but it didn't work, so I tried to go backwards and write it all out instead to see if it works, but to no avail. The correct behavior should be, displaying five triangles, equally spaced on the screen (Directly below the top rectangle). I tried printing out the array, but the number of times the println() method was called was sort of random, instead of constant. I heard that the paintComponent() method can be called at any time by the Swing Framework, but I'm not sure. Essentially I'm asking, why the triangles (the cyan ones) aren't being drawn correctly, and how do I fix it?
#SuppressWarnings("serial")
public class GraphicsClass extends JPanel {
private int[] xCoordinates = {20, 40, 30};
private int[] yCoordinates = {40, 40, 60};
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.DARK_GRAY);
g.fillRect(0, 0, 450, 40);
g.fillRect(0, 260, 450, 40);
g.setColor(Color.CYAN);
g.fillPolygon(xCoordinates, yCoordinates, 3);
xCoordinates[0] += 95;
xCoordinates[1] += 95;
xCoordinates[2] += 95;
g.fillPolygon(xCoordinates, yCoordinates, 3);
xCoordinates[0] += 95;
xCoordinates[1] += 95;
xCoordinates[2] += 95;
g.fillPolygon(xCoordinates, yCoordinates, 3);
xCoordinates[0] += 95;
xCoordinates[1] += 95;
xCoordinates[2] += 95;
g.fillPolygon(xCoordinates, yCoordinates, 3);
xCoordinates[0] += 95;
xCoordinates[1] += 95;
xCoordinates[2] += 95;
g.fillPolygon(xCoordinates, yCoordinates, 3);
}
}
Think about it this way. paintComponent may be called any time (upwards of four times when the component is first painted on the screen in some cases). So each time it is called, you're adding 95 to each of the xCoordinates, this would make xCoordinates[0] equal to 400 after paintComponent is called the first time, 800 after the second time, so on and so fourth...
Instead, you need to make a copy of the xCoordinates and modify it instead, for example...
public class TestPane extends JPanel {
private int[] xCoordinates = {20, 40, 30};
private int[] yCoordinates = {40, 40, 60};
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.DARK_GRAY);
g.fillRect(0, 0, 450, 40);
g.fillRect(0, 260, 450, 40);
int[] xPosy = Arrays.copyOf(xCoordinates, xCoordinates.length);
g.setColor(Color.CYAN);
for (int index = 0; index < 4; index++) {
g.fillPolygon(xPosy, yCoordinates, 3);
xPosy[0] += 95;
xPosy[1] += 95;
xPosy[2] += 95;
}
}
}
Of course, you could forgo some of the oddities and just make use of the 2D Graphics Shape API
public class TestPane extends JPanel {
private Polygon triangle;
public TestPane() {
triangle = new Polygon(new int[]{20, 40, 30}, new int[]{40, 40, 60}, 3);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.DARK_GRAY);
g2d.fillRect(0, 0, 450, 40);
g2d.fillRect(0, 260, 450, 40);
g2d.setColor(Color.CYAN);
AffineTransform at = AffineTransform.getTranslateInstance(0, 0);
for (int index = 0; index < 4; index++) {
Shape shape = at.createTransformedShape(triangle);
g2d.fill(shape);
at.translate(95, 0);
}
}
}

Swing auto repaints JPanel after button clicked

I'm having this weird issue with Swing.
I am drawing few points and lines between them, directly on JPanel.
I'm just calling:
g.drawLine(pointAX, pointAY, pointBX, pointBY);
Where g is Graphics object taken from panel.getGraphics()
And this works alright, gives me nice output.
But then I'm trying to move it and I use ActionListener on my button, which does:
panel.revalidate();
panel.repaint();
drawPanel();
moveVector(moveX, moveY);
drawPoints();
drawConnections(connections);
The other methods:
drawPanel just draws some lines, so it's easier to see:
private void drawPanel(){
Graphics g = panel.getGraphics();
zeroX = panel.getWidth() / 2;
zeroY = panel.getHeight() / 2;
g.setColor(Color.GRAY);
for(int i = zeroX + 10; i < panel.getWidth(); i += 10){
g.drawLine(i, 0, i, panel.getHeight());
}
for(int i = zeroX + 10; i > 0; i -= 10){
g.drawLine(i, 0, i, panel.getHeight());
}
for(int j = zeroY - 10; j < panel.getHeight(); j += 10){
g.drawLine(0, j, panel.getWidth(), j);
}
for(int j = zeroY - 10; j > 0; j -= 10){
g.drawLine(0, j, panel.getWidth(), j);
}
g.setColor(Color.BLACK);
g.drawLine(zeroX, 0, zeroX, panel.getHeight());
g.drawLine(0, zeroY, panel.getWidth(), zeroY);
panel.paintComponents(g);
}
drawPoints looks like this
private void drawPoints() {
for(int i = 0; i < points.size(); i++){
int x = Integer.parseInt(points.get(i)[1]);
int y = Integer.parseInt(points.get(i)[2]);
drawPoint(x, y);
}
}
private void drawPoint(int x, int y) {
Graphics g = panel.getGraphics();
for (int i = -1; i < 2; i++) {
g.drawLine(zeroX + x + i, zeroY + y - 1, zeroX + x + i, zeroY + y);
}
panel.paintComponents(g);
}
g.setColor(Color.BLACK);
g.drawLine(zeroX, 0, zeroX, panel.getHeight());
g.drawLine(0, zeroY, panel.getWidth(), zeroY);
panel.paintComponents(g);
}
and drawConnections:
private void drawConnections(ArrayList<String[]> lines) {
for (String[] arr : lines) {
String x = arr[1];
String y = arr[2];
drawConnection(x, y);
}
}
private void drawConnection(String pointA, String pointB) {
int pointAX = 0, pointAY = 0, pointBX = 0, pointBY = 0;
Graphics g = panel.getGraphics();
for (String[] arr : points) {
if (arr[0].equals(pointA)) {
pointAX = Integer.parseInt(arr[1]);
pointAY = Integer.parseInt(arr[2]);
} else if (arr[0].equals(pointB)) {
pointBX = Integer.parseInt(arr[1]);
pointBY = Integer.parseInt(arr[2]);
}
}
g.drawLine(zeroX + pointAX, zeroY + pointAY, zeroX + pointBX, zeroY + pointBY);
panel.paintComponents(g);
}
What I don't understand here is that everything looks OK. I did debug and it looks ok and at the end of listener call, when everything is painted (although it paints over the old one instead of clearing it) it suddenly clears everything and nothing is visible at all.
You need to implement all the drawing in paintComponent(Graphics) in your JPanel class (or methods that are called from it). Calling getGraphics on the panel instance and using the Graphics object is not always guaranteed to work. It's also strange that you call panel.repaint() and immediately afterwards try to do additional drawing using the graphics object from the panel, that additional drawing should just be done in paintComponent(Graphics).
Using paintComponent(Graphics) will ensure your painting is done at the right time, and that the panel's graphics will be cleared (if you call the super method at least).
panel.repaint does not immediately repaint the panel, but it tells Swing that this panel should be repainted in the near future (see API docs: https://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#repaint(long,%20int,%20int,%20int,%20int). So your custom painting will be cleared once the panel repaints itself.
Create a custom component by inheriting from JPanel and overwriting the method paintComponent with your custom painting code. Then, whenever the panel needs to be repainted, your code is called, and not only the user pressed a button.

Swing: moving two graphics at the same time

I am working on Air Hockey Game for my midterm project.
I have problem with handling two graphics, in this case two handles each of them consists of 3 circles.
I can move only one handle because of keyPressed method.
Another problem is that I can't limit the moving domain, for example when you pressed → the red handle can go beyond the frame width.
I know first problem is related to thread, but I've studied this subject from last week.
My problems are in this class:
public class StartGamePanel extends JPanel implements KeyListener, ActionListener {
double xCircle1 = 200;
double yCircle1 = 100;
double xCircle2 = 200;
double yCircle2 = 700;
double velX = 0, velY = 0;
public StartGamePanel() {
Timer t = new Timer(5, this);
t.start();
addKeyListener(this);
setFocusable(true);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g.setColor(new Color(51, 153, 255));
g.fillRoundRect(5, 5, 485, 790, 10, 10);
addKeyListener(this);
Graphics2D southArc = (Graphics2D) g;
southArc.setColor(Color.WHITE);
southArc.setStroke(new BasicStroke(3));
southArc.drawArc(98, 640, 300, 300, 0, 180);
//
Graphics2D northArc = (Graphics2D) g;
northArc.setColor(Color.WHITE);
northArc.setStroke(new BasicStroke(3));
northArc.drawArc(98, -143, 300, 300, 180, 180);
Graphics2D line = (Graphics2D) g;
line.setStroke(new BasicStroke(3));
line.setColor(Color.white);
line.drawLine(6, 395, 488, 395);
Graphics2D dot = (Graphics2D) g;
dot.setColor(Color.black);
for (int j = 10; j < 800; j += 20) {
for (int i = 6; i < 502; i += 20) {
dot.drawLine(i, j, i, j);
}
}
Graphics2D circle1 = (Graphics2D) g;
circle1.setColor(new Color(255, 51, 51));
Shape theCircle = new Ellipse2D.Double(xCircle1 - 40, yCircle1 - 40, 2.0 * 40, 2.0 * 40);
circle1.fill(theCircle);
Graphics2D circle2 = (Graphics2D) g;
circle2.setColor(new Color(255, 102, 102));
Shape theCircle2 = new Ellipse2D.Double(xCircle1 - 35, yCircle1 - 35, 2.0 * 35, 2.0 * 35);
circle2.fill(theCircle2);
Graphics2D circle3 = (Graphics2D) g;
circle3.setColor(new Color(255, 51, 51));
Shape theCircle3 = new Ellipse2D.Double(xCircle1 - 20, yCircle1 - 20, 2.0 * 20, 2.0 * 20);
circle3.fill(theCircle3);
Graphics2D circleprim = (Graphics2D) g;
circleprim.setColor(new Color(0, 51, 102));
Shape theCircleprim = new Ellipse2D.Double(xCircle2 - 40, yCircle2 - 40, 2.0 * 40, 2.0 * 40);
circleprim.fill(theCircleprim);
Graphics2D circle2prim = (Graphics2D) g;
circle2prim.setColor(new Color(0, 102, 204));
Shape theCircle2prim = new Ellipse2D.Double(xCircle2 - 35, yCircle2 - 35, 2.0 * 35, 2.0 * 35);
circle2prim.fill(theCircle2prim);
Graphics2D circle3prim = (Graphics2D) g;
circle3prim.setColor(new Color(0, 51, 102));
Shape theCircle3prim = new Ellipse2D.Double(xCircle2 - 20, yCircle2 - 20, 2.0 * 20, 2.0 * 20);
circle3prim.fill(theCircle3prim);
Graphics2D ball = (Graphics2D) g;
ball.setColor(new Color(224, 224, 224));
Shape theball = new Ellipse2D.Double(200 - 20, 400 - 20, 2.0 * 20, 2.0 * 20);
ball.fill(theball);
Graphics2D ball2 = (Graphics2D) g;
ball2.setColor(new Color(160, 160, 160));
Shape theball2 = new Ellipse2D.Double(200 - 15, 400 - 15, 2.0 * 15, 2.0 * 15);
ball2.fill(theball2);
Graphics2D goal = (Graphics2D) g;
goal.setColor(Color.BLACK);
goal.fill3DRect(100, 0, 300, 10, true);
Graphics2D goal2 = (Graphics2D) g;
goal2.setColor(Color.BLACK);
goal2.fill3DRect(100, 790, 300, 10, true);
}
#Override
public void actionPerformed(ActionEvent e) {
repaint();
xCircle1 += velX;
yCircle1 += velY;
}
#Override
public void keyPressed(KeyEvent e) {
int code = e.getKeyCode();
if (code == KeyEvent.VK_UP) {
velY = -2;
velX = 0;
}
if (code == KeyEvent.VK_DOWN) {
velY = 2;
velX = 0;
}
if (code == KeyEvent.VK_LEFT) {
if (xCircle1 < 0) {
velY = 0;
velX = 0;
} else {
velY = 0;
velX = -2;
}
}
if (code == KeyEvent.VK_RIGHT) {
if (xCircle1 > 200) {
velY = 0;
velX = 0;
}
velY = 0;
velX = 2;
}
}
#Override
public void keyReleased(KeyEvent e) {
int code = e.getKeyCode();
if (code == KeyEvent.VK_UP) {
velY = 0;
velX = 0;
}
if (code == KeyEvent.VK_DOWN) {
velY = 0;
velX = 0;
}
if (code == KeyEvent.VK_LEFT) {
velY = 0;
velX = 0;
}
if (code == KeyEvent.VK_RIGHT) {
velY = 0;
velX = 0;
}
}
#Override
public void keyTyped(KeyEvent e) { }
}
Thanks!
Your key listener should be as quick as possible so it does not block following key events. Since several people press keys almost at the same time, this situation is common in games.
So the advice would be to use separate thread to listen for key presses which will quickly add events to a queue. This queue then will be processed on EDT(Swing main thread) and paint the results.
You can check out the KeyboardAnimation.java example found in Motion Using The Keyboard. It attempts to explain why Key Bindings are preferred over using a KeyListener.
The example code will animate two images. The left image controlled by W, A, S, D and the right image by Up, Down, Left and Right arrow keys. It also keeps the images within the window bounds. The code is not an actual game, it was just designed to show one way to use configurable Key Bindings.
You might want to also want to change the way the keypress's are handled. At the moment (I think) your only detecting one key down at a time.
You probably need to have an array for "heldkeys" and every time a key is down, add to that array. When a key is released, subtract that key from it.
Then, at a interval, check what keys are in the "heldkeys" array and act on them all.
At least, you will probably need something like this if you plan to have two players. (who both should be able to press keys at the same time).
I don't know Swing well enough to give you exact code, but it will be something like;
//an array of numbers to hold the key-codes of the keys currently held down
HashSet<Integer> HeldKeys = new HashSet<Integer>();
#Override
public void keyDown(KeyEvent e) {
// add key to keys currently held down
HeldKeys.add(e.getKeyCode());
}
#Override
public void keyUp(KeyEvent e) {
//remove key from list of things currently held
HeldKeys.remove(e.getKeyCode());
}
#Override
public void keyPress(KeyEvent e) {
Iterator<Integer> kit = HeldKeys.iterator();
//we loop over every key currently held down, running actions for them if
//there is any assigned.
//For example, one button might move a sprite on the left down
//Another might move a sprite on the right. One, the other, or both should be
//able to all move without waiting for the other.
while (kit.hasNext()) {
int keycode = kit.next();
if (keycode==48){
//do stuff if key with code 48 is held
//(move one of the circles, etc)
}
if (keycode==46){
//do stuff if key with code 46 is held
}
//...etc, for any number of keys you want to do different things for.
}
}
Note; With this method you are not detecting a keypress as one thing, but rather detecting both the key being held down, and then the key being released as separate events.
As people wont hit the keys at the exact same time, this lets you detect lots of keys at once.
This code is off the top of my head, Java, but I have a gwt background, so the names for "keydown" and "keyup" etc might be different.
The "keypress" event in gwt will repeat automatically, if that's not the case in Swing you might need to use a Timer instead, running at an interval. (you might prefer this anyway as its a bit more controllable).

Decreasing a Filled Rectangle's height from top

I am using a paintComponent Class in a project of mine, and I am currently wondering how I can decrease the size of the rectangle from the top making it's way downwards.
This is the part of the code:
public Battery(){
super();
firstTime = true;
f = new Font("Helvetica", Font.BOLD, 14);
m = Toolkit.getDefaultToolkit().getFontMetrics(f);
}
public void paintComponent(Graphics g){
if(firstTime){
firstTime = false;
batteryLevel = 1 + this.getHeight();
decr = batteryLevel / 20;
}else{
g.setColor(Color.RED);
g.fillRect(1, 0, this.getWidth(), this.getHeight());
g.setColor(Color.GREEN);
g.fillRect(1, 0, this.getWidth(), batteryLevel);
g.setColor(Color.BLACK);
g.setFont(f);
g.drawString("TEST", (getWidth() - m.stringWidth("TEST")) / 2 , this.getHeight() / 2);
}
}
public void decreaseBatteryLevel(){
batteryLevel -= decr;
this.repaint();
}
PS. Sorry if I did something wrong, I'm new to this forum.
As you want the visible battery level to descend you will want to increase your Y co-ordinate in relation to the value of batteryLevel. You could use:
g.fillRect(1, getHeight() - batteryLevel, getWidth(), batteryLevel);
Instead
g.fillRect(1, 0, this.getWidth(), batteryLevel);
Do
g.fillRect(1, batteryLevel, this.getWidth(), getHeight() - batteryLevel);
Also maybe repaint(50L) instead of repaint().
If your question meant: how to animate a change in the battery level.
Use a javax.swing.Timer:
int toPaintBatteryLevel = batteryLevel;
// In the paintComponent paint upto toPaintBatteryLevel.
Timer timer = new Timer(100, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (toPaintBatteryLevl == batteryLevel) {
return;
}
if (toPaintBatteryLevl > batteryLevel) {
--toPaintBatteryLevel; // Animate slowly
} else {
toPaintBatteryLevel = batteryLevel; // Change immediately
}
repaint(50L);
};
});
timer.start();
For ease of coding, there is a permanent timer. And externally one changes the batteryLevel,
and the time determines the toPaintBatteryLevel, which paintComponent uses to paint.

Java Swing : drawLine very slow

I am writing a java application using swing in which I need to draw a grid above a square. In order to do so, I am using the drawLine(...) method provided by the Graphics class.
Everything works fine except that it takes a lot of time to draw each line (more than 20 sec for 50 lines...). I can even see the lines being drawn in real time. One weird thing is that the horizontal lines are drawn way faster than the vertical lines (almost instantly).
I might be doing something wrong. Here is the code for the grid.
public void drawGrid(Graphics g){
g.setColor(new Color(255, 255, 255, 20));
int width = getWidth();
int height = (int) (width * Utils.PLATE_RATIO);
int step = pixelSize*gridSpacing;
Color bright = new Color(255, 255, 255, 100);
Color transparent = new Color(255, 255, 255, 20);
for(int ix = insets.left + step;
ix < width; ix += step){
if(((ix - insets.left) / step) % 10 == 0){
g.setColor(bright);
}
else{
g.setColor(transparent);
}
g.drawLine(ix, insets.top, ix, height+insets.top);
}
for(int iy = insets.top+step;
iy < (insets.top + height); iy += step){
if(((iy - insets.top) / step) % 10 == 0){
g.setColor(bright);
}
else{
g.setColor(transparent);
}
g.drawLine(insets.left, iy, width + insets.left, iy);
}
}
The code you have posted is fine, there is no problems in it.
Here is a working example of a component using your method (a bit simplified):
public static class MyGrid extends JComponent
{
private int step = 10;
public MyGrid ()
{
super ();
}
public Dimension getPreferredSize ()
{
return new Dimension ( 500, 500 );
}
protected void paintComponent ( Graphics g )
{
super.paintComponent ( g );
drawGrid ( g );
}
public void drawGrid ( Graphics g )
{
int width = getWidth ();
int height = getHeight ();
Color bright = new Color ( 255, 255, 255, 200 );
Color transparent = new Color ( 255, 255, 255, 100 );
for ( int ix = step; ix < width; ix += step )
{
if ( ( ix / step ) % 10 == 0 )
{
g.setColor ( bright );
}
else
{
g.setColor ( transparent );
}
g.drawLine ( ix, 0, ix, height );
}
for ( int iy = step; iy < height; iy += step )
{
if ( ( iy / step ) % 10 == 0 )
{
g.setColor ( bright );
}
else
{
g.setColor ( transparent );
}
g.drawLine ( 0, iy, width, iy );
}
}
}
I guess there is some problem outside that piece of code.
P.S. A bit an offtopic but...
I suggest you to calculate the visible part of the painting area (using either JComponent's getVisibleRect () method or Graphics g.getClip ().getBounds () method) and limit your paintings with only that area.
That small optimization could speedup component's painting in times if it is really large (for example with 10000x10000 pixels component's area).
Here is how I solved the problem using Double-Buffering, as advised by #sureshKumar. I am simply drawing on an offscreen image and simply calling drawImage() when the drawing is over. This seems to do the trick.
Edit: this seems to be useful only if you want to call your painting methods (in my case drawGrid()) from outside of the paintComponent(...) method.
Here is the code:
private Graphics bufferGraphics;
private Image offScreen;
private Dimension dim;
//other attributes not shown...
public CentralPanel(){
//Some initialization... (not shown)
//I added this listener so that the size of my rectangle
//and of my grid changes with the frame size
this.addComponentListener(new ComponentListener() {
#Override
public void componentResized(ComponentEvent e) {
dim = getVisibleRect().getSize();
offScreen = createImage(dim.width, dim.height);
bufferGraphics = offScreen.getGraphics();
repaint();
revalidate();
}
//other methods of ComponentListener not shown
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(offScreen, 0, 0, null);
}
public void drawGrid(){
int width = dim.width - insets.left - insets.right;
width -= (width % plateSize.getXSize());
int height = (int) (width*Utils.PLATE_RATIO);
height -= (height % plateSize.getYSize());
int step = pixelSize*gridSpacing;
Color bright = new Color(255, 255, 255, 100);
Color transparent = new Color(255, 255, 255, 20);
for(int ix = insets.left + step;
ix < (width+insets.left); ix += step){
if(((ix - insets.left) / step) % 10 == 0){
bufferGraphics.setColor(bright);
}
else{
bufferGraphics.setColor(transparent);
}
//I am now drawing on bufferGraphics instead
//of the component Graphics
bufferGraphics.drawLine(ix, insets.top, ix, height+insets.top);
}
step *= Utils.PLATE_RATIO;
for(int iy = insets.top+step;
iy < (insets.top + height); iy += step){
if(((iy - insets.top) / step) % 10 == 0){
bufferGraphics.setColor(bright);
}
else{
bufferGraphics.setColor(transparent);
}
bufferGraphics.drawLine(insets.left, iy, width + insets.left, iy);
}
}
P.S.: If this should be added as an edit to my question, please tell me and I'll do it.

Categories

Resources