Two good points by both MadPrgrammer and camickr. I'm like to illustrate those points.
MapPrgogrammer: You should have a single timer which is responsible for updating the state of the game/model and scheduling the repaints, don't use multiple timers
What you should do is have a have a List
of Spell
or BlueSpell
(whatever). What you want to do is iterate through the list in the Timer
and your painting method. Something like
public class GamePanel extends JPanel {
List<BlueSpell> spells;
Timer timer = null;
public GamePanel() {
spells = new ArrayList<>();
timer = new Timer(40, new ActionListener(){
public void actionPerformed(ActionEvent e) {
If (spells.size() > 0) {
Iterator spellIt = spells.iterator();
while (spellIt.hasNext()) {
BlueSpell spell = (BlueSpell)spellIt.next();
if (spell.collidesWithSomthing()) {
// do something, then remove from list
spellIt.remove();
} else if (spell.isOffScreen()) {
spellIt.remove(); // remove the spell from list
} else {
spell.animate(); // animate for each tick of timer
}
}
}
repaint(); // just repaint once in each timer tick after all
} // state id update.
});
timer.start();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
If (spells.size() > 0) {
for (BlueSpell spell: spells) {
spell.drawSpell(g);
}
}
}
}
You can see the basic idea of the spells
list in the Timer
callback. It iterates through the timer and if the list has BlueSpells
, then it will continue to check if the spells collides with something (if you need to), it checks if the spells is off the screen. With either, you'll want to remove it from the list. (Don't be tempted to use a for each loop. In this case you should use an iterator). If it is still on screen and hasn't hit anything, then the spell animates (maybe move it x position or something) then finally for each tick, the panel is repainted. Notice the if
checks in both the paintComponent
and Timer
. There's no need to paint or iterate through the list if it is empty.
You'll want to maintain all the game's state in the single timer, not just the spell. If the spells are the only thing that animates, you may want to add a else { timer.stop() }
in the timer, if the list is empty.
camickr: Don't use a KeyListener to control player movement. See Motion Using the Keyboard for more information and an alternative approach using Key Bindings
What you can do is when the space is pressed, add a new BlueSpell
to the list (and start the timer [only if necessary] ). Something like
public GamePanel() {
...
InputMap inputMap = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
inputMap.put(KeyStroke.getKeyStroke("SPACE"), "shootSpell");
getActionMap().put("shootSpell", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
spells.add(new BlueSpell(...));
}
});
}
Once the spell is added to the list, the spell will begin to animate until it collides with something or is off the screen.
You can see a working example of both points in this answer.
Also you can see a bunch of other "animating multiple objects" answers here and here and here and here and here and here.
UPDATE
"the only question is in my repaint method do I just call the ImageIcon I created or do I have to create a new one?"
public class BlueSpell {
BufferedImage bi;
int x, y;
int width = someWidth;
int height = someHeight;
JPanel panel;
public BlueSpell(BufferedImage bi, int x, int y, panel) {
this.bi = bi;
this.x = x;
this.y = y;
this.panel = panel;
}
public void drawSpell(Graphics g) {
g.drawImage(bi, x, y, width, height, panel);
}
public void animate() {
x += 5;
}
}
You should create just the one BufferedImage
in the GamePanel
and when you create a new BlueSpell
in the in the key binding, just pass the image to it, along with the other arguments.