1

Hi I am developing a game that a fighter moves right and left and shoots. For the shooting part, I tried to use a for loop to slow the speed down and user can see the bullet. But it wasn't enough. I used sleep too but not a good answer. Now I have no idea what to do. Here is my paintComponent calss:

package game;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.JPanel;



public class PaintComponent extends JPanel implements KeyListener {
    int dx = 200-15;
    int dy = 450;
    int my = 450;

    ArrayList<Bullet> bullets = new ArrayList<>();
    public Rectangle2D rec =new Rectangle2D.Double(dx , dy, 30, 10);
    Rectangle2D recB = new Rectangle2D.Double(dx+13 , my, 6, 6);

//    public Polygon pol = new Polygon
    private BufferedImage imageBg, imageFi, imageBu;


    public PaintComponent() {
        this.addKeyListener(this);
        this.setFocusable(true);
        this.setBackground(Color.white);

        try {                
          imageBg = ImageIO.read(new File("C:\\Java\\NetbeansProjects\\Game\\bg.jpg"));
          imageBu = ImageIO.read(new File("C:\\Java\\NetbeansProjects\\Game\\bul.png"));
          imageFi = ImageIO.read(new File("C:\\Java\\NetbeansProjects\\Game\\fi.png"));
       } catch (IOException ex) {
            System.out.println("No background image is available!");
       }
    }

    public void shoot(){
        if(bullets != null){
            for(int i=0; i<bullets.size(); i++){
                for(int j=0; j<200; j++){
                    bullets.get(i).setdy(my-j);
                }
                System.out.println(bullets.get(i).getdy());
            }
        }
    }
    public void moveRec(KeyEvent evt){
        switch(evt.getKeyCode()){
            case KeyEvent.VK_LEFT:
            dx -= 5;
            rec.setRect(dx, dy, 30, 10);
            recB.setRect(dx+13, dy, 6, 6);
            repaint();
            break;
        case KeyEvent.VK_RIGHT:
            dx += 5;
            rec.setRect(dx, dy, 30, 10);
            recB.setRect(dx+13, dy, 6, 6);
            repaint();
            break;
        case KeyEvent.VK_SPACE:
            Bullet b = new Bullet(dx, dy);
            bullets.add(b);
            shoot();
            break;

    }
}

        @Override
        public void paintComponent(Graphics grphcs)
            {super.paintComponent(grphcs);
            Graphics2D gr = (Graphics2D) grphcs;
                int X = (int) rec.getCenterX();
                int Y = (int) rec.getCenterY();
                gr.drawImage(imageBg, 0, 0, null);
                gr.drawImage(imageFi, X-50, Y-75, null);
                gr.setColor(Color.GRAY);
                if(bullets != null){
                    for(int i=0;i<bullets.size();i++){
                   gr.drawImage(imageBu, null, bullets.get(i).getdx(), bullets.get(i).getdy());
                   repaint();
                    }
                }
                gr.draw(recB);
            }

    @Override
    public void keyTyped(KeyEvent e) {
    }
    @Override
    public void keyPressed(KeyEvent e) 
    {moveRec(e);}
    @Override
    public void keyReleased(KeyEvent e) 
    {}
}

and this is my bullet calss:

package game;

public class Bullet {
    private int x, y, newy;

    public Bullet(int x, int y){
        this.x = x;
        this.y = y;
    }

    public int getdy(){
        return y;
    }
    public void setdy(int newy){
        y = newy;
    }
    public int getdx(){
        return x;
    }
}
Ebola
  • 43
  • 10
  • 1
    You should use javax.swing.Timer for your animation – ControlAltDel May 11 '15 at 14:48
  • See: [How to Use Swing Timers](http://docs.oracle.com/javase/tutorial/uiswing/misc/timer.html). Also, don't invoke repaint() from the paintComponent() method. – camickr May 11 '15 at 14:51
  • @ControlAltDel Thx, but can you explain more about timers? what should I do with a timer? – Ebola May 11 '15 at 14:53
  • @camickr Thx, why not to invoke repaint()? Do you mean in my "for" loop in paintcomponent ?! – Ebola May 11 '15 at 14:55
  • http://stackoverflow.com/questions/8088002/how-to-use-a-swing-timer-to-start-stop-animation – ControlAltDel May 11 '15 at 15:00
  • @Ebola camickr is right. You should never call repaint() in paintComponent - fundamentally, the purpose of repaint is to put in a request to call paintComponent again. It's circular logic – ControlAltDel May 11 '15 at 15:03
  • @ControlAltDel I had a problem that when I press spacebar to shoot, it didn't draw the bullet until I moved the fighter.I added the repaint() in my loop! it worked better then! So, what should I do with my problem if I remove repaint()?! – Ebola May 11 '15 at 15:10
  • @Ebola as I advised in the first place, you should use javax.swing.Timer – ControlAltDel May 11 '15 at 15:12

3 Answers3

1

I think you are going wrong with slowing down the loop. The last thing you want to do is slow down the game loop or sleep the game loop. This will affect all you objects with in the game.

There are multiple way to go about this:

Smaller increments per tick

One of the most obvious things you could do is make the increment of the bullet smaller. Lets take a look at your shoot(); method:

  public void shoot(){
    if(bullets != null){
        for(int i=0; i<bullets.size(); i++){
            for(int j=0; j<200; j++){
                bullets.get(i).setdy(my-j);
            }
            System.out.println(bullets.get(i).getdy());
        }
    }
}

As far as i understand you are iterating 200x over all the bullets, each tick the bullet's y axis gets changed, using the formula "my - j" or "450 - the tick index"

In order to slow down the bullet you would need to divide the j with a certain number to get the desired speed of the bullet. For instance: "my - (j / 2)" would impact the speed of the bullet. Try to play around with these variables to get the desired speed.

Adding a speed modifier

What a lot of games to is a speed modifier or a base speed for each projectile. This could be of use to you, the only thing i noticed that you are kinda trying to simulate loss of velocity. To achieve this result you would need another variable. Let call that "time to live" for right now.

So if we modify the bullet class it would look like this. Noticed we also have a new function called Move();. This will calculate the next move based upon the variables.

public class Bullet {
private int x, y, newy;
private speed, ttl; //ttl = time to live

public Bullet(int x, int y, int speed){
    this.x = x;
    this.y = y;
    this.speed= speed;
    this.ttl = 250;
}

public int Move()
{
    //Do some calculation to perform loss of velocity within a reasonable range. Because these number might be overkill
    this.speed -= (ttl / 100);
    y += this.speed;
    ttl--;
}

public int getdy(){
    return y;
}
public void setdy(int newy){
    y = newy;
}
public int getdx(){
    return x;
}
}

What the code now does is it calculates the speed based upon the time to live variable the longer the bullet live the less velocity it will have. Adjusting the speed variable makes you able to control the bullet better. And to say so myself it looks a lot more neater in the shoot method:

public void shoot(){
    if(bullets != null){
        for(int i=0; i<bullets.size(); i++){
            bullets.get(i).Move();
        }
    }
}

Of course there is more to it, like checking if the speed and time to live dont go out of bounds and stuff, but i think your smart enough to figure that out ;)

Running it off a timer

As ControlAltDel said you can implement a timer, im not an expert on java so im not going in depth on this. But it surely it is a possibility. Its nothing more then implement the current shoot method inside the tick function of the timer. Of course removing the for i<200 loop. Since its not very effecient.

Anyways

If i did get something wrong or misunderstood (or even grammer mistaked :) ) the problem, im sorry. If there are any question i loved to hear from you ;).

And please not that this is untested code and im only here to explain things you could try to get it working a intended!

Sincerly,

Syntasu.

UPDATE:

Some explaining on how to update the bullet's. In order to update the bullets we need to make it run off a loop. Since in this case the main loop is were also where the drawing is happening, the "paintComponent" method. There is already a loop withing the paint component to draw the bullet, only thing we have to do is to add our logic regarding the .Move(); method.

The paint component will look as following ( + i also fixed the tabbing ):

@Override
public void paintComponent(Graphics grphcs)
{
    super.paintComponent(grphcs);

    Graphics2D gr = (Graphics2D) grphcs;
    int X = (int) rec.getCenterX();
    int Y = (int) rec.getCenterY();
            
    gr.drawImage(imageBg, 0, 0, null);
    gr.drawImage(imageFi, X-50, Y-75, null);
    gr.setColor(Color.GRAY);
            
    if(bullets != null)
    {
        for(int i=0;i<bullets.size();i++)
        {
            //Here is were we loop over the bullet list, lets add the move method
            bullets.get(i).Move();

            gr.drawImage(imageBu, null, bullets.get(i).getdx(), bullets.get(i).getdy());
            repaint();
        }
    }
    
    gr.draw(recB);
}

The thing is added is "bullets.get(i).Move();". This will now run every frame. This will work in theory (inb4 im not testing these codes). Going by the assumption you use multiple instance's of the bullet class, each class should encapsulate their own speed and time to live variable.

Implementing this will make the shoot method obsolete. What you can do is move the code inside the paintComponent that is related to shooting and move that to the shoot function.

Regarding the time to live variable, i would like to add one more piece to the code. This will take care of garbage collection of bullets. Since now they live indefinitly:

for(int i=0;i<bullets.size();i++)
{
    Bullet b = bullets.get(i);
    if(b.ttl >= 1)
    {
        bullets.get(i).Move();
        gr.drawImage(imageBu, null, b.getdx(), b.getdy());
    }
    else
    {
        //Remove the bullet from the list
        //In C# its bullets.Remove(b);
    }

    repaint();
}

Hopefully this resolves the issue of the bullet not moving. And potential performance issue due the bullets not being destroyed. In before, it there are any questions i love to hear them! ;)

Community
  • 1
  • 1
Syntasu
  • 69
  • 7
  • If he were to use a speed modifier like you have shown then each time they call the shoot method it will only make the bullets move once. They would need a way for the bullets to continually move. – Forseth11 May 11 '15 at 15:23
  • HI, Thx a lot! It was very nice, but there was a problem! when I shoot, first bullet doesn't move until the next bullet is fired! Can you help me with that too?! I used the codes you wrote! – Ebola May 11 '15 at 15:52
  • You should store the bullet class reference's in an array list. In the main game loop you should call the shoot function. This will solve the problem. – Syntasu May 11 '15 at 16:10
  • @Syntasu I used an arraylist to store bullets, but it's not working! check out my code on the top of the page! in the paintcomponent class! have I done it wrong? – Ebola May 11 '15 at 20:08
  • Added a new update section under the current post. Hopefully this answers your questions :) – Syntasu May 12 '15 at 09:32
0

Finally I did it with adding a timer in my bullet class and repaint() in my paintcomponent method!

    package game;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;

public class Bullet {
    private int x, y;
    private int speed, ttl;
    public final Timer timer1;

    public Bullet(int x, int y, int speed){

        this.x = x;
        this.y = y;
        this.speed= speed;
        this.ttl = 250;

        ActionListener actListener = new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                Move();
            }
        };
        this.timer1 = new Timer(50, actListener);
        timer1.start();
    }

    public int getdy(){
        return y;
    }
    public void setdy(int newy){
        y = newy;
    }
    public int getdx(){
        return x;
    }
    public int Move(){
    //Do some calculation to perform loss of velocity within a reasonable range. Because these number might be overkill
    this.speed -= (ttl / 100);
    y += this.speed;
    ttl--;
    return y;
    }
}
Ebola
  • 43
  • 10
-2

In your game loop make it update the bullet every so many ticks. You most likely never want to use sleep to slow down something in a game as it will need a new thread or it will sleep the whole game.

If you do not know what a game loop is, a game loop is basically a loop which continually takes input, updates the game (such as a bullet), renders everything, then pauses the entire program (you can use sleep) for the amount of time which the loop has left over from what is expected. You also want to update the game according to how many ticks or milliseconds have passed since the last update. This will prevent the game from running faster or slower on different computers.

I haven't read this all the way but THIS might help.

Also, not sure if this is correct, I think using Canvas instead of a JPanel will be better for making a game. It is what I used for my first java game.

enter image description here

Edit:

If you want to use JPanel and no game loop you can make the shoot method create a new thread and use the sleep to slow it down. Also the way you have the shoot method setup can cause problems if multiple bullets are shot because it will loop through each bullet in two separate loops and if you have a separate thread the bullets array can be modified while it is looping in one thread therefor causing an error.

Try this:

public void shoot(Bullet bullet){
      new Thread(new Runnable(){
            for(int j=0; j<200; j++){
                bullet.setdy(my-j);
                try{
                  Thread.sleep(time);//set the time
                catch(Exception e){
                  e.printStackTrace();
                }
            }
            System.out.println(bullet.getdy());
            getBullets().remove(bullet);
        }).start();
    }

Create a method called getBullets(). Make sure it has the synchronized modifier.

protected synchronized ArrayList<Bullet> getBullets(){
  return bullets;
}

This will make sure it can only be modified by one thread at a time.

Finally where the player presses space, change bullets.add(b); to getBullets().add(b);

Forseth11
  • 1,418
  • 1
  • 12
  • 21
  • Thx a lot! I didn't want to write something professional! So i decided to use JPanel. The Link was useful! – Ebola May 11 '15 at 15:01
  • You are entirely ignoring the fact that the question is about doing it in Swing, and thus your answer is simply wrong here. A classic game loop is a bad idea in an event driven system such as Swing. – Gimby May 11 '15 at 15:02
  • Your information about Cancas vs JPanel is incomplete and incorrect, and it doesn't address the fundamental issue – ControlAltDel May 11 '15 at 15:03
  • There I added an edit so @Ebola can know how to do it with JPanel or Canvas. – Forseth11 May 11 '15 at 15:20