This is my class that creates a circle shape with specific x and y coordinates. Similarly I have another same class but with different x and y coordinate positions. I create 1 object of each class and want to display them on specific positions on JFrame.When I add 2nd object to JFrmae, it overwrites the 1st object. I tried with different layouts of JFrmae but it did not work.
class Ballbewegung2 extends JPanel implements Runnable {
int x_pos = 10; int y_pos = 100; int radius = 20;
public void init() {
setBackground (Color.blue); }
public void start () {
Thread th = new Thread (this);
th.start (); }
public void run () {
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
while (true) {
x_pos ++; if(x_pos >= 400) x_pos = 10;
repaint();
try { Thread.sleep (20); }
catch (InterruptedException ex) {} thread.currentThread().setPriority(Thread.MAX_
PRIORITY); } } public void paint (Graphics g) { g.setColor (Color.red); g.fillOval (x_pos - radius, y_pos - radius, 2 * radius, 2 * radius); } }
If you make the layout of the JFrame null, you will be able to explicitly control the position of any component that you add. Also make sure that you are adding to the content pane of the JFrame.
Related
I am making my first game in Java. This is the first time using swing too. I am having difficulties adding objects correctly to the frame.
Some irrelevant code has been removed from other classes. There may be several components missing that are referenced.
Main:
public class Main {
Ball ball = new Ball();
int yBall = 0;
public static void main(String[] args) throws InterruptedException {
Main main = new Main();
main.run();
while (true) {
main.ball(100);
}
}
public void run() throws InterruptedException {
Data data = new Data();
Frame frame = new Frame(700, 500);
test.setAngleX();
frame.add(data, BorderLayout.CENTER);
}
}
When I add ball after data(background) my ball is visible on the frame, overlapping my data. If I would switch position here my data would be visible but not my ball, the ball still exists underneath.
frame.add(ball);
frame.setVisible(true);
public void ball(int yBall) throws InterruptedException {
ball.Ball();
yBall = ball.SendY();
}
This is my frame, no problems in here what I know:
public class Frame extends JFrame {
private int frameWidth;
private int frameHeight;
int yBall = 0;
public Frame(int frameWidth, int frameHeight) {
this.frameWidth=frameWidth;
this.frameHeight = frameHeight;
setSize(new Dimension(frameWidth, frameHeight));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Here's my Ball. paintComponent is at the end, before that it is just working logics that I'm guessing will be irrelevant.
Ball:
public class Ball extends JLabel{
Graphics g1;
int x = 325, y = 225;
int speed = 1;
int angleX = 0;
int angleY = 0;
public Ball() {
}
public void setAngleX() {
angleX = ThreadLocalRandom.current().nextInt(-5, 5 + 1);
angleY = ThreadLocalRandom.current().nextInt(-1, 1 + 1);
if (angleX == 0) {
setAngleX();
}
if (angleY == 0) {
setAngleX();
}
}
public void move() {
if (x + angleX < 0) {
angleX = speed;
} else if (x + angleX > getWidth() - 25) {
angleX = -speed;
} else if (y + angleY < 0) {
angleY = speed;
} else if (y + angleY > getHeight() - 25) {
angleY = -speed;
}
x = x + angleX;
y = y + angleY;
}
public void Ball() throws InterruptedException {
move();
repaint();
int sleepSpeed = angleX * speed;
if (sleepSpeed < 0) {
sleepSpeed = -sleepSpeed;
Thread.sleep(10*sleepSpeed);
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.WHITE);
g.fillOval(x, y, 25, 25);
}
public int SendY() {
return y;
}
And finally my Data;
public class Data extends JPanel{
private static final long serialVersionUID = 1L;
private Graphics2D g2;
public Data() {
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.BLACK);
g2.fillRect(0, 0, getSize().width, getSize().height);
g2.setColor(Color.lightGray);
g2.fillRect(350, 0, 10, 400);
}
So what happens is that I either get my data(background) on my frame or I get my Ball bouncing around as it should but on an empty background, depending on which one I add last in my main. I'm guessing it's bcz of overlapping but I can't find a solution working for my code, no matter how much I search..
that I either get my data(background) on my frame or I get my Ball bouncing around as it should but on an empty background, depending on which one I add last in my main.
You can't add both components to the frame. you need to have a parent/child relationship.
So the logic should be something like:
background.add( ball );
frame.add( background );
Then the frame will be painted, then the background and finally the ball.
Also, the Ball class will need to implement the getPreferredSize() method so component has a reasonable size. You should be painting the Ball at offset (0, 0) in the paint component method. When you want to move the ball on the background panel, then you just use setLocation(...) on the ball to reposition it on the background. When you create the Ball you would also need to set the size of the Ball to the preferred size so the Ball has a size/location to be painted on the background panel. The layout of the background panel would need to be set to null, so you can freely move the ball around the panel.
You should NOT be extending JLabel, you are not using any features of the JLabel. You are doing custom painting so just extend JPanel or JComponent. Read the section from the Swing tutorial on Custom Painting for more information and working examples.
Also, method names should NOT start with an upper case character. Ball() is not a proper method name. It looks like the class name. The method should be more descriptive.
Or the other approach, instead of having two components, is to have a single component. Then the paintComopnent(...) method of that component would paint both:
the background
the ball
Then this way you would paint the Ball relative to the background panel which is how your logic currently works.
I created an app that contains a square that bounces every time it touches an edge of the frame.I don't have issues lunching the app,the problem is that i don't know how to create various threads in order to have multiples squares inside the frame.
I tried multiple things but i can't figure out where i should create the threads.
I also noticed that the square is visible only when i add it directly inside the frame and not when i put it inside a JPanel.
Square.java
public class Square extends JComponent implements ActionListener {
int width = 20;
int height = 20;
double y = Math.random() * 360;
double x = Math.random() * 360;
boolean xMax = false;
boolean yMax = false;
boolean xMin = true;
boolean yMin = true;
Rectangle2D.Double square = new Rectangle2D.Double(x, y, width, height);
public Square() {
Timer t = new Timer(2, this);
t.start();
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
super.paintComponent(g);
g2.setColor(Color.BLUE);
g2.fill(square);
x_y_rules();
}
public void x_y_rules() {
if (xMax == true) {
x = x - 0.5;
if (x <= 0) {
xMax = false;
}
} else {
x = x + 0.5;
if (x >= this.getWidth()) {
xMax = true;
}
}
if (yMax == true) {
y = y - 0.5;
if (y <= 0) {
yMax = false;
}
} else {
y = y + 0.5;
if (y >= this.getHeight()) {
yMax = true;
}
}
square.setFrame(x, y, width, height);
}
#Override
public void actionPerformed(ActionEvent arg0) {
repaint();
}
}
App.java
public class App extends JFrame {
public static void main(String[] args) {
JFrame jf = new JFrame();
Square sqr = new Square();
jf.setSize(400, 400);
jf.setVisible(true);
jf.add(sqr);
jf.setDefaultCloseOperation(EXIT_ON_CLOSE);
jf.setLocationRelativeTo(null);
}
}
Is it normal that despite i put a time of 2 inside the Timer,the square moves very slowly?
Issues:
you've got program logic, the x_y_rules() method call, inside of the paintComponent method. Get it out as it does not belong there, and instead put it into the Timer's ActionListener code where it belongs.
you can give each Square its own Swing Timer if you want. This isn't really a threading issue since each Timer's ActionListener will run on the EDT.
Two milliseconds is an unrealistic time slice to expect to use in a Swing Timer and no timer will run that fast. 11 to 13 is about the fastest to expect or hope for.
if you want your sprite to move faster, give it a greater value for delta-x and delta-y in your movement code.
Your JComponent has no preferred size defined which is likely why it's not showing up in the JPanel, since the default FlowLayout will size it then to [0, 0]. Override its getPreferredSize() and have it return a reasonable Dimension value.
you're calling setVisible(true) on your JFrame before adding all components, a no-no.
Ok,i put a getPrefferedSize() inside the square class but i've encountered a problem: the squares are not "together",it's like they're bouncing on separate panels
Then your program structure is broken. You really don't want create separate Swing components, and in fact your Square class shouldn't extend JComponent or JPanel. Rather
Square should be a logical class, one that extends from nothing (other than default Object).
Give it a drawing method, say public void draw(Graphics g) {....}
Create one class that extends JPanel, say called DrawingPanel, and override its paintComponent method.
Give the DrawingPanel class an ArrayList<Square> so that it can hold multiple Square objects.
Give the DrawingPanel class a Swing Timer
In the DrawingPanel class's Timer, have it update the position of all the Squares in the ArrayList, and then call repaint()
In the paintComponent method, iterate through all the Squares in the list, using a for loop, and call each one's draw method.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class DrawingPanel extends JPanel {
private static final int PREF_W = 600;
private static final int PREF_H = PREF_W;
private static final int TIMER_DELAY = 20;
private static final Color[] SQUARE_COLOR = { Color.BLUE, Color.CYAN, Color.DARK_GRAY,
Color.BLACK, Color.GRAY, Color.GREEN, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
Color.PINK, Color.RED, Color.YELLOW };
List<Square> squareList = new ArrayList<>();
public DrawingPanel() {
// create a bunch of squares
for (int i = 0; i < SQUARE_COLOR.length; i++) {
squareList.add(new Square(SQUARE_COLOR[i], PREF_W, PREF_H));
}
setBackground(Color.WHITE);
// create and start the timer
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// simply draw all the squares in the list
for (Square square : squareList) {
square.draw(g);
}
}
// set size of JPanel
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
// simply iterate through list and move all squares
for (Square square : squareList) {
square.move();
}
repaint(); // then repaint the GUI
}
}
private static void createAndShowGui() {
DrawingPanel mainPanel = new DrawingPanel();
JFrame frame = new JFrame("Drawing Panel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
// this class does *not* extend JPanel or JComponent
class Square {
public static final int WIDTH = 20;
// location of Square
private double sqrX;
private double sqrY;
// X and Y speed
private double deltaX;
private double deltaY;
// width and height of DrawingPanel JPanel
private int dpWidth;
private int dpHeight;
// image to draw
private Image image;
public Square(Color color, int dpWidth, int dpHeight) {
this.dpWidth = dpWidth;
this.dpHeight = dpHeight;
// create square at random location with random speed
sqrX = Math.random() * (dpWidth - WIDTH);
sqrY = Math.random() * (dpHeight - WIDTH);
deltaX = Math.random() * 10 - 5;
deltaY = Math.random() * 10 - 5;
// one way to draw it is to create an image and draw it
image = new BufferedImage(WIDTH, WIDTH, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
g.setColor(color);
g.fillRect(0, 0, WIDTH, WIDTH);
g.dispose();
}
public void move() {
// check that we're not hitting boundaries
if (sqrX + deltaX < 0) {
deltaX = Math.abs(deltaX);
}
if (sqrX + deltaX + WIDTH >= dpWidth) {
deltaX = -Math.abs(deltaX);
}
sqrX += deltaX;
// check that we're not hitting boundaries
if (sqrY + deltaY < 0) {
deltaY = Math.abs(deltaY);
}
if (sqrY + deltaY + WIDTH >= dpHeight) {
deltaY = -Math.abs(deltaY);
}
sqrY += deltaY;
}
public void draw(Graphics g) {
int x = (int) sqrX;
int y = (int) sqrY;
g.drawImage(image, x, y, null);
}
}
I have created a class whit two shapes namely two ovals. Here I draw them.
import ...;
public class Drawings extends JPanel{
public double degrees = 0;
public void paintComponent(Graphics g){
super.paintComponent(g);
int xcen = getWidth() / 2;
int ycen = getHeight()/ 2;
int radius = 10;
degrees++;
double radians = Math.toRadians(degrees);
int posx = (int)(100.getDistance() * Math.cos(radians));
int posy = (int)(100.getDistance() * Math.sin(radians));
g.setColor(Color.BLUE);
g.FillOval(xcen + posx, ycen + posy, 20, 20);
g.setColor(Color.GREEN);
g.drawOval(xcen + posx, ycen + posy, 100,100)
}
}
Now I implement it in a main.
import ....;
public class Animate extends JFrame{
public static void main(String [] args)
{
JFrame window = new JFrame();
window.add(new Drawings());
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setSize(500,500);
window.setLocationRelativeTo(null);
window.setVisible(true);
//now I implement the thread to animate the two shapes
Thread paintThread = new Thread(new Runnable(){
#Override
public void run(){
while(true)
{
window.repaint();
try{
Thread.sleep(25);//determines how slow the ovals will move
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
});
paintThread.start();//start the animation
}
}
When the program runs the two Ovals rotate on the screen. But the two ovals rotates at the same speed as I would expect but I would like the two ovals to move at diffident speeds.
I have tried using a method to move them at different speed but with no success.
How would I get the two ovals moving at different speeds?
Make a class to represent an oval. Make two instances. Give the two instances different angular velocities. Currently because you increment degrees by 1.0 every 25 ms you have an angular velocity fixed at 40 degrees per second. If each oval has its own degrees field and you increment the two by different amounts, the ovals will rotate at different rates.
The easiest way is:
import ...;
public class Drawings extends JPanel{
public double degrees = 0;
private int firstOvalSpeed;
private int secondOvalSpeed;
public void paintComponent(Graphics g){
super.paintComponent(g);
int xcen = getWidth() / 2;
int ycen = getHeight()/ 2;
int radius = 10;
degrees++;
double radians = Math.toRadians(degrees);
int posx = (int)(100.getDistance() * Math.cos(radians));
int posy = (int)(100.getDistance() * Math.sin(radians));
g.setColor(Color.BLUE);
g.FillOval(xcen + posx*firstOvalSpeed, ycen + posy*firstOvalSpeed, 20, 20);
g.setColor(Color.GREEN);
g.drawOval(xcen + posx*secondOvalSpeed, ycen + posy*secondOvalSpeed, 100,100)
}
public void setFirstOvalSpeed(int firstOvalSpeed) {
this.firstOvalSpeed = firstOvalSpeed;
}
public void setSecondOvalSpeed(int secondOvalSpeed) {
this.secondOvalSpeed = secondOvalSpeed;
}
}
public class Animate extends JFrame {
public static void main(String[] args) {
final JFrame window = new JFrame();
Drawings drawings = new Drawings();
drawings.setFirstOvalSpeed(1);
drawings.setSecondOvalSpeed(2);
window.add(drawings);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setSize(500, 500);
window.setLocationRelativeTo(null);
window.setVisible(true);
// now I implement the thread to animate the two shapes
Thread paintThread = new Thread(new Runnable() {
#Override
public void run() {
while (true) {
window.repaint();
try {
Thread.sleep(25);// determines how slow the ovals will
// move
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
paintThread.start();// start the animation
}
}
I am trying to create a simple animation which draws random rectangles when a button is pressed. So far I managed to create rectangle on the press of a button. I want to further develop the code so that when I press the button, more than multiple random rectangles are created. I tried to create a for loop which asks the inner class to repaint itself but it still didn't work. can anyone help me please.
public class TwoButtonsRandomRec {
JFrame frame;
private int width = 500;
private int height = 500;
private DrawPanel dp;
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public static void main (String[] args)
{
TwoButtonsRandomRec test = new TwoButtonsRandomRec();
test.go();
}
public void go()
{
dp = new DrawPanel();
JButton start = new JButton("Start");
start.addActionListener(new startListener());
JButton stop = new JButton("Stop");
stop.addActionListener(new stopListener());
frame = new JFrame();
frame.setSize(getWidth(), getHeight());
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(BorderLayout.NORTH, start);
frame.getContentPane().add(BorderLayout.SOUTH, stop);
}
class startListener implements ActionListener{
public void actionPerformed(ActionEvent event){
frame.getContentPane().add(BorderLayout.CENTER, dp);
frame.repaint();
frame.getRootPane().revalidate();
for(int i=0; i<10; i++){
dp.repaint();
}
}
}
class stopListener implements ActionListener{
public void actionPerformed(ActionEvent event){
System.out.println("stop");
}
}
class DrawPanel extends JPanel{
public void paintComponent(Graphics g){
int w = 5+(int)(Math.random() * width-5);
int h = 5+(int)(Math.random() * height-5);
int maxX = width-w; // diffX & diffY are used to ensure that rectangle is
int maxY = width-h; // draw completely inside the window
int x = (int)(Math.random() * maxX);
int y = (int)(Math.random() * maxY);
Color color = new Color((int) (Math.random()*256), // random red
(int) (Math.random()*256), // random green
(int) (Math.random()*256));// random blue
g.setColor(color);
g.fillRect(x,y,w,h);
}
}
}
repaint() simply tells Swing "when you'll have time, please repaint this area". So if you add rectangles in a loop and call repaint at each iteration, all the rectangles will only appear after the loop has finished, and the action event has been handled.
To have an animation, you need to loop in a separate thread. The easiest way to do that is to use a Swing Timer. When the Start button is started, start a timer which adds a random rectangle and calls repaint() every X milliseconds. When the Stop button is pressed, stop the timer.
What you should do is to put the loop inside paintComponent method and not call repaint in the loop.
So your paintComponent method should look like this:
public void paintComponent(Graphics g){
for (int i = 0; i < 10; i++) {
int w = 5+(int)(Math.random() * width-5);
int h = 5+(int)(Math.random() * height-5);
int maxX = width-w; // diffX & diffY are used to ensure that rectangle is
int maxY = width-h; // draw completely inside the window
int x = (int)(Math.random() * maxX);
int y = (int)(Math.random() * maxY);
Color color = new Color((int) (Math.random()*256), // random red
(int) (Math.random()*256), // random green
(int) (Math.random()*256));// random blue
g.setColor(color);
g.fillRect(x,y,w,h);
}
}
And your action performed should look like this:
public void actionPerformed(ActionEvent event){
frame.getContentPane().add(BorderLayout.CENTER, dp);
frame.repaint();
frame.getRootPane().revalidate();
dp.repaint();
}
Well,here I have done a short EG for you.It displays random rectangles, random times on random screen location.
(You can set your own value of randomization, and the screen location max bound,as per your requirements.)
And also note
int i=(int)(Math.random()*10);
int j=(int)(Math.random()*10);
for(;i<j;i++)
Where at times i may be > than j.So,loop may not work on one or two cliks.Change as per your need.
Here is the working code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class SimpleStamper extends JApplet {
public void init() {
Display display = new Display();
setContentPane(display);
}
class Display extends JPanel implements MouseListener {
Display() {
setBackground(Color.black);
addMouseListener(this);
}
public void mousePressed(MouseEvent evt) {
if ( evt.isShiftDown() ) {
repaint();
return;
}
int x = evt.getX();
int y = evt.getY();
Graphics g = getGraphics();
//***MODIFY THE FOLLOWING LINES****//
int i=(int)(Math.random()*10);
int j=(int)(Math.random()*10);
for(;i<j;i++)
{ g.setColor(Color.red);
x=(int)(Math.random()*100);
y=(int)(Math.random()*100);
g.fillRect( x , y , 60, 30 );
g.setColor(Color.black);
g.drawRect(x , y , 60, 30 );}
g.dispose();
}
public void mouseEntered(MouseEvent evt) { }
public void mouseExited(MouseEvent evt) { }
public void mouseClicked(MouseEvent evt) { }
public void mouseReleased(MouseEvent evt) { }
}
}
I'm having a lot of trouble trying to make my code move a player (Ship) around a screen. I can get the planets to draw on the screen and the the player ship but I can not figure out how to implement the keyListener to at least print out something. Thank you in advance for all the help!
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class MapPanel extends JPanel {
public static final int WIDTH = 25;
public static final int HEIGHT = 20;
int zone = 0;
private int xValue;
private int yValue;
private Color color;
public Planet[][] planetGrid = new Planet[WIDTH][HEIGHT];
static Player currPlayer = new Player("h");
static Universe universe = new Universe(currPlayer);
/**
* Create the panel.
*/
public MapPanel(Universe univ, Player p) {
this.universe = univ;
currPlayer = p;
int i = 0;
this.setSize(new Dimension(450,450));
setVisible( true );
//this.addKeyListener(new KeyController());
KeyController kc = new KeyController();
this.addKeyListener(kc);
repaint();
}
/**
* Draw method to draw the playing field
* #param g Graphics object
* #param tileDimension dimension of the tile
*/
public void draw(Graphics g)
{
universe.draw(g);
// KeyController key = new KeyController();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
public static void main(String[] args)
{
MapPanel mp = new MapPanel(universe, currPlayer);
JFrame f = new JFrame();
f.add(mp);
f.setSize(new Dimension(450,450));
f.setVisible(true);
f.setFocusable(true);
}
private class KeyController implements KeyListener {
public KeyController()
{
System.out.println("ghgh");
setFocusable(true);
// addKeyListener(this);
}
#Override
public void keyPressed(final KeyEvent key) {
System.out.println("fgfgf");
if (currPlayer != null) {
int oldX = currPlayer.getPosition().x;
int oldY = currPlayer.getPosition().y;
switch (key.getKeyCode()) {
case KeyEvent.VK_RIGHT:
currPlayer.setPosition(new Point(oldX+1, oldY)); //move right
System.out.println("RIGHT");
break;
case KeyEvent.VK_LEFT:
currPlayer.setPosition(new Point(oldX-1, oldY)); //move left
break;
case KeyEvent.VK_DOWN:
currPlayer.setPosition(new Point(oldX, oldY+1)); //move down
break;
case KeyEvent.VK_UP:
currPlayer.setPosition(new Point(oldX, oldY-1)); //move up
break;
}
}
repaint();
}
#Override
public void keyReleased(KeyEvent e) {
System.out.println("ggg");
}
#Override
public void keyTyped(KeyEvent e) {
System.out.println("typeeeddd");
}
}
}
KeyListener is not the most appropriate method to achieve your results.
It would be better to use the key bindings API.
Apart from simplifying the code, it will also allow you to provide better focus control over when the keys should triggered.
here i have design an application similar to yours,where you can use arrow keys,n throw a bomb on the moving ship(this was my project few years back :) ),design a main method to run,hope this will help:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**
* This panel implements a simple arcade game in which the user tries to blow
* up a "submarine" (a black oval) by dropping "depth charges" (a red disk) from
* a "boat" (a blue roundrect). The user moves the boat with the left- and
* right-arrow keys and drops the depth charge with the down-arrow key.
* The sub moves left and right erratically along the bottom of the panel.
*/
public class SubKillerPanel extends JPanel {
private Timer timer; // Timer that drives the animation.
private int width, height; // The size of the panel -- the values are set
// the first time the paintComponent() method
// is called. This class is not designed to
// handle changes in size; once the width and
// height have been set, they are not changed.
// Note that width and height cannot be set
// in the constructor because the width and
// height of the panel have not been set at
// the time that the constructor is called.
private Boat boat; // The boat, bomb, and sub objects are defined
private Bomb bomb; // by nested classes Boat, Bomb, and Submarine,
private Submarine sub; // which are defined later in this class.
// Note that the objects are created in the
// paintComponent() method, after the width
// and height of the panel are known.
/**
* The constructor sets the background color of the panel, creates the
* timer, and adds a KeyListener, FocusListener, and MouseListener to the
* panel. These listeners, as well as the ActionListener for the timer
* are defined by anonymous inner classes. The timer will run only
* when the panel has the input focus.
*/
public SubKillerPanel() {
setBackground(Color.GREEN);
ActionListener action = new ActionListener() {
// Defines the action taken each time the timer fires.
public void actionPerformed(ActionEvent evt) {
if (boat != null) {
boat.updateForNewFrame();
bomb.updateForNewFrame();
sub.updateForNewFrame();
}
repaint();
}
};
timer = new Timer( 30, action ); // Fires every 30 milliseconds.
addMouseListener( new MouseAdapter() {
// The mouse listener simply requests focus when the user
// clicks the panel.
public void mousePressed(MouseEvent evt) {
requestFocus();
}
} );
addFocusListener( new FocusListener() {
// The focus listener starts the timer when the panel gains
// the input focus and stops the timer when the panel loses
// the focus. It also calls repaint() when these events occur.
public void focusGained(FocusEvent evt) {
timer.start();
repaint();
}
public void focusLost(FocusEvent evt) {
timer.stop();
repaint();
}
} );
addKeyListener( new KeyAdapter() {
// The key listener responds to keyPressed events on the panel. Only
// the left-, right-, and down-arrow keys have any effect. The left- and
// right-arrow keys move the boat while down-arrow releases the bomb.
public void keyPressed(KeyEvent evt) {
int code = evt.getKeyCode(); // Which key was pressed?
if (code == KeyEvent.VK_LEFT) {
// Move the boat left. (If this moves the boat out of the frame, its
// position will be adjusted in the boat.updateForNewFrame() method.)
boat.centerX -= 15;
}
else if (code == KeyEvent.VK_RIGHT) {
// Move the boat right. (If this moves boat out of the frame, its
// position will be adjusted in the boat.updateForNewFrame() method.)
boat.centerX += 15;
}
else if (code == KeyEvent.VK_DOWN) {
// Start the bomb falling, if it is not already falling.
if ( bomb.isFalling == false )
bomb.isFalling = true;
}
}
} );
} // end constructor
/**
* The paintComponent() method draws the current state of the game. It
* draws a gray or cyan border around the panel to indicate whether or not
* the panel has the input focus. It draws the boat, sub, and bomb by
* calling their respective draw() methods.
*/
public void paintComponent(Graphics g) {
super.paintComponent(g); // Fill panel with background color, green.
if (boat == null) {
// The first time that paintComponent is called, it assigns
// values to the instance variables.
width = getWidth();
height = getHeight();
boat = new Boat();
sub = new Submarine();
bomb = new Bomb();
}
if (hasFocus())
g.setColor(Color.CYAN);
else {
g.setColor(Color.RED);
g.drawString("CLICK TO ACTIVATE", 20, 30);
g.setColor(Color.GRAY);
}
g.drawRect(0,0,width-1,height-1); // Draw a 3-pixel border.
g.drawRect(1,1,width-3,height-3);
g.drawRect(2,2,width-5,height-5);
boat.draw(g);
sub.draw(g);
bomb.draw(g);
} // end drawFrame()
/**
* This nested class defines the boat. Note that its constructor cannot
* be called until the width of the panel is known!
*/
private class Boat {
int centerX, centerY; // Current position of the center of the boat.
Boat() { // Constructor centers the boat horizontally, 80 pixels from top.
centerX = width/2;
centerY = 80;
}
void updateForNewFrame() { // Makes sure boat has not moved off screen.
if (centerX < 0)
centerX = 0;
else if (centerX > width)
centerX = width;
}
void draw(Graphics g) { // Draws the boat at its current location.
g.setColor(Color.BLUE);
g.fillRoundRect(centerX - 40, centerY - 20, 80, 40, 20, 20);
}
} // end nested class Boat
/**
* This nested class defines the bomb.
*/
private class Bomb {
int centerX, centerY; // Current position of the center of the bomb.
boolean isFalling; // If true, the bomb is falling; if false, it
// is attached to the boat.
Bomb() { // Constructor creates a bomb that is initially attached to boat.
isFalling = false;
}
void updateForNewFrame() { // If bomb is falling, take appropriate action.
if (isFalling) {
if (centerY > height) {
// Bomb has missed the submarine. It is returned to its
// initial state, with isFalling equal to false.
isFalling = false;
}
else if (Math.abs(centerX - sub.centerX) <= 36 &&
Math.abs(centerY - sub.centerY) <= 21) {
// Bomb has hit the submarine. The submarine
// enters the "isExploding" state.
sub.isExploding = true;
sub.explosionFrameNumber = 1;
isFalling = false; // Bomb reappears on the boat.
}
else {
// If the bomb has not fallen off the panel or hit the
// sub, then it is moved down 10 pixels.
centerY += 10;
}
}
}
void draw(Graphics g) { // Draw the bomb.
if ( ! isFalling ) { // If not falling, set centerX and CenterY
// to show the bomb on the bottom of the boat.
centerX = boat.centerX;
centerY = boat.centerY + 23;
}
g.setColor(Color.RED);
g.fillOval(centerX - 8, centerY - 8, 16, 16);
}
} // end nested class Bomb
/**
* This nested class defines the sub. Note that its constructor cannot
* be called until the width of the panel is known!
*/
private class Submarine {
int centerX, centerY; // Current position of the center of the sub.
boolean isMovingLeft; // Tells whether the sub is moving left or right
boolean isExploding; // Set to true when the sub is hit by the bomb.
int explosionFrameNumber; // If the sub is exploding, this is the number
// of frames since the explosion started.
Submarine() { // Create the sub at a random location 40 pixels from bottom.
centerX = (int)(width*Math.random());
centerY = height - 40;
isExploding = false;
isMovingLeft = (Math.random() < 0.5);
}
void updateForNewFrame() { // Move sub or increase explosionFrameNumber.
if (isExploding) {
// If the sub is exploding, add 1 to explosionFrameNumber.
// When the number reaches 15, the explosion ends and the
// sub reappears in a random position.
explosionFrameNumber++;
if (explosionFrameNumber == 15) {
centerX = (int)(width*Math.random());
centerY = height - 40;
isExploding = false;
isMovingLeft = (Math.random() < 0.5);
}
}
else { // Move the sub.
if (Math.random() < 0.04) {
// In one frame out of every 25, on average, the sub
// reverses its direction of motion.
isMovingLeft = ! isMovingLeft;
}
if (isMovingLeft) {
// Move the sub 5 pixels to the left. If it moves off
// the left edge of the panel, move it back to the left
// edge and start it moving to the right.
centerX -= 5;
if (centerX <= 0) {
centerX = 0;
isMovingLeft = false;
}
}
else {
// Move the sub 5 pixels to the right. If it moves off
// the right edge of the panel, move it back to the right
// edge and start it moving to the left.
centerX += 5;
if (centerX > width) {
centerX = width;
isMovingLeft = true;
}
}
}
}
void draw(Graphics g) { // Draw sub and, if it is exploding, the explosion.
g.setColor(Color.BLACK);
g.fillOval(centerX - 30, centerY - 15, 60, 30);
if (isExploding) {
// Draw an "explosion" that grows in size as the number of
// frames since the start of the explosion increases.
g.setColor(Color.YELLOW);
g.fillOval(centerX - 4*explosionFrameNumber,
centerY - 2*explosionFrameNumber,
8*explosionFrameNumber,
4*explosionFrameNumber);
g.setColor(Color.RED);
g.fillOval(centerX - 2*explosionFrameNumber,
centerY - explosionFrameNumber/2,
4*explosionFrameNumber,
explosionFrameNumber);
}
}
} // end nested class Submarine
} // end class SubKiller