I've messed around with Java a little bit and I decided to take a course to learn some more and I have been assigned a fairly easy assignment. I have it mostly done and I know what's left must be simple, but I cannot seem to quite get it. So far I have been able to successfully create a program that has 1 bouncing ball, but I would now like to allow the user to input the amount of balls to bounce. I have tried a few different loops in my Ball class, but none of them work. Would anyone be willing to give a quick hand? I am almost sure it would require either an Array or ArrayList and just storing the balls in it but I have yet to find a solution that works. I have looked at other problems like this on the website but none have quite solved my problem. Thank you if you can help!
Main Class :
public class mainClass {
/**
/**
* Frame to hold a bouncing ball panel, implemented in the BallPanel class.
* Controls the animation of the ball via pauses and calls to BallPanel's move
* method.
*
* #author Michael Peterson modified by Mr O Aug 2012
*/
public static class BallTest extends JFrame {
// size of the window
private static final int WINDOW_WIDTH = 500;
private static final int WINDOW_HEIGHT = 300;
// panel containing the bouncing ball
private BallPanel ballPanel;
/**
* Pause command used to control the speed of the bouncing ball animation.
* Currently pauses for 20 ms. Use smaller values for faster animation and
* vice versa.
*/
public static void pause() {
try {
Thread.sleep(20); // pause for 20 ms
} catch (Exception e) {
System.out.println(e);
e.printStackTrace();
}//end of catch
}//end of pause method
/**
* Creates a new instance of BallTest
*/
public BallTest() {
super("Bouncing Ball"); // set frame name
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
setLayout(new BorderLayout());
ballPanel = new BallPanel();
add(ballPanel);
center(this);
setVisible(true);
// infinite animation loop, program halts when window is closed.
while (true) {
pause();
ballPanel.move(ballPanel);
}//end of while loop of animation
} //end of BallTest Constructor
/**
* Helper routine to center a frame on the screen (will cause problems if
* frame is bigger than the screen!)
*/
public static void center(JFrame frame) {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
Point center = ge.getCenterPoint();
int w = frame.getWidth();
int h = frame.getHeight();
int x = center.x - w / 2, y = center.y - h / 2;
frame.setBounds(x, y, w, h);
frame.validate();
}//end of center method
}//end of BallPanel Class
public static void main(String[] args) {
BallTest t = new BallTest(); //make a BallTest object
}//end of main method
}//end of Fall2012Lab11StarterCode class
Ball Class :
public class Ball extends JPanel {
private int bxCoord; //the ball's x coordinate
private int byCoord; //the ball's y coordinate
private int bHeight; //the ball's height
private int bWidth; //the ball's weight
private int bRise; //the ball's y change
private int bRun; //the ball's x change
private Color bColor; //the ball's color
//Constructor
public Ball() {
bxCoord = setStartBxCoord();
byCoord = setStartByCoord();
bHeight = setStartBHeight();
bWidth = setStartBWidth();
bRise = setStartBRise();
bRun = setStartBRun();
bColor = setStartColor();
}
/**
* The setters, getters, and initial value for the ball's x coordinate
*/
public void setBxCoord(int xCoord) {
bxCoord = xCoord;
}
public int setStartBxCoord() {
int xCoord;
xCoord = (int) (Math.random() * 51);
return xCoord;
}
public int getBxCoord() {
return bxCoord;
}
/**
* The setters, getters, and initial value for the ball's y coordinate
*/
public void setByCoord(int yCoord) {
bxCoord = yCoord;
}
public int setStartByCoord() {
int yCoord;
yCoord = (int) (Math.random() * 51);
return yCoord;
}
public int getByCoord() {
return byCoord;
}
/**
* The setters, getters, and initial value for the ball's x height
*/
public void setBHeight(int height) {
bHeight = height;
}
public int setStartBHeight() {
int height;
height = (int) (10 + Math.random() * 11);
return height;
}
public int getBHeight() {
return bHeight;
}
public void setBWidth(int width) {
bWidth = width;
}
/**
* The setters, getters, and initial value for the ball's x width
*/
public int setStartBWidth() {
int width;
width = (int) (10 + Math.random() * 11);
return width;
}
public int getBWidth() {
return bWidth;
}
/**
* The setters, getters, and initial value for the ball's rise
*/
public void setBRise(int rise) {
bRise = rise;
}
public int setStartBRise() {
int rise;
rise = (int) (Math.random() * 11);
return rise;
}
public int getBRise() {
return bRise;
}
/**
* The setters, getters, and initial value for the ball's run
*/
public void setBRun(int run) {
bRun = run;
}
public int setStartBRun() {
int run;
run = (int) (Math.random() * 11);
return run;
}
public int getBRun() {
return bRun;
}
/**
* The movement of the ball in the x and y direction
*/
public void moveX(){
bxCoord += bRun;
}
public void moveY(){
byCoord += bRise;
}
/**
* The setters, getters, and initial value for the ball's color
*/
public void setColor(Color color) {
bColor = color;
}
public Color setStartColor() {
int red = (int) (Math.random() * 256);
int green = (int) (Math.random() * 256);
int blue = (int) (Math.random() * 256);
Color ranColor = new Color(red, green, blue);
return ranColor;
}
public Color getbColor() {
return bColor;
}
/**
* Computes the next position for the balls and updates their positions.
*/
public void move(BallPanel ballPanel) {
// If ball is approaching a wall, reverse direction
if ((getBxCoord() < (0 - getBRun())) || (getBxCoord() > (ballPanel.getWidth() - getBWidth()))) {
setBRun(-getBRun());
}
if ((getByCoord() < (0 - getBRise())) || (getByCoord() > (ballPanel.getHeight() - getBHeight()))) {
setBRise(-getBRise());
}
// "Move" ball according to values in rise and run
moveX();
moveY();
} // end method move
}//end of Ball Class
Ball Panel Class :
public class BallPanel extends JPanel {
Ball ball = new Ball(); //creat a ball.
/**
* Creates a new instance of BallPanel
*/
public BallPanel() {
super();
}
/**
* The move method moves the ball and repaints the panel
* PreCondtion: A panel containing a ball has been created
* PostCondition: The position of a ball on the panel has moved. The panell
* is repainted with the ball in the new position.
* #param ballPanel The name of the panel on which the ball is found
*/
public void move(BallPanel ballPanel) {
ball.move(ballPanel);
repaint();
}
/**
* Paints the balls at their current positions within the panel.
* PreCondition: A graphics object has been created and needs to be displayed
* PostCondition: The graphics object g has been displayed
* #param g The graphic object to be displayed
*/
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.black); // set color black
g.fillRect(0, 0, getWidth(), getHeight()); // paint background
// Paint the Ball
g.setColor(ball.getbColor());
g.fillOval(ball.getBxCoord(), ball.getByCoord(),
ball.getBWidth(), ball.getBHeight());
}//end of paintComponent method
}//end of BallPanel class
Well I'm answering you purely on previous codes i have done similar to this, because I didn't have the time to test out yours.
Your ballPanel class should look something more like this:
import java.util.ArrayList;
public class BallPanel extends JPanel{
private ArrayList<Ball> BallList = new ArrayList<Ball>();
private int num;
public BallPanel(int numberOfBalls){
super();
num = numberOfBalls;
for(int i = 0; i<num; i++){BallList.add(new Ball());}
}
//the rest of your methods, using for loops for the balls
Also I think you can use this instead of ArrayList (this is easier):
Ball[] BallList = new Ball[numberOfBalls];
then an example of your move method should look like this:
public void move(BallPanel ballPanel){
for(int i = 0; i<num; i++){
BallList[i].move(ballPanel);
}
repaint();
}
I had a similar issues with a Bouncing Ball program. Tried the previously posted code, but the public void move(BallPanel area isn't working. When I access an array in that location the ball stops moving. Here is my current code for move:
public void move(BallPanel ballPanel, ArrayList<Ball> ballList) {
for(int i = 0; i<1; i++){
System.out.println("mmm" + i);
ballList.get(i).move(ballPanel);
}
repaint();
Secondly as shown the paintComponent area above, only ball is used to paint the ball. Is that OK that paintComponent is called multiple times but only has a variable ball in it. Here is what that section looks like:
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.black); // set color black
g.fillRect(0, 0, getWidth(), getHeight()); // paint background
// Paint the Ball
g.setColor(ball.getbColor());
g.fillOval(ball.getBxCoord(), ball.getByCoord(),
ball.getBWidth(), ball.getBHeight());
Related
I am trying to bounce my text in a window, inserted from a text file without modifying the inputs from the text. I have the code to create and bounce the text, but some of it is lost when trying to bounce on the wall. The code consists of 3 classes.
This creates the text:
public class MyShape extends ClosedShape {
//The width and height of the text (major and minor axis)
private int width, height;
private String bouncingText;
private double textFont;
/**
* Creates an oval.
* #param x The display component's x position.
* #param y The display component's y position.
* #param vx The display component's x velocity.
* #param vy The display component's y velocity.
* #param width The width of the text (in pixels).
* #param height The height of the text (in pixels).
* #param colour The line colour or fill colour.
* #param isFilled True if the oval is filled with colour, false if opaque.
*/
public MyShape (int insertionTime, int x, int y, int vx, int vy, String bouncingText, int width, int height, Color colour, boolean isFilled, double textFont) {
super (insertionTime, x, y, vx, vy, colour, isFilled);
this.width = width;
this.height = height;
this.bouncingText = bouncingText;
this.textFont = textFont;
}
/**
* Method to convert a text to a string.
*/
public String toString () {
String result = "This is a text shape\n";
result += super.toString ();
result += "Its width is " + this.width + " and its height is " + this.height + "\n";
return result;
}
/**
* #param width Resets the width.
*/
public void setWidth (int width) {
this.width = width;
}
/**
* #param height Resets the height.
*/
public void setHeight (int height) {
this.height = height;
}
public void setBouncingText (String bouncingText) {
this.bouncingText = bouncingText;
}
public void setTextFont (double textFont) {
this.textFont = textFont;
}
/**
* #return The width of the text.
*/
public int getWidth() {
return width;
}
/**
* #return The height of the text.
*/
public int getHeight() {
return height;
}
public String getBouncingText() {
return bouncingText;
}
public double getTextFont() {
return textFont;
}
public void pulsing() {
if (textFont > 20) {
textFont = 10;
}else {
textFont = 50;
}
}
/**
* Draw the text.
* #param g The graphics object of the drawable component.
*/
public void draw (GraphicsContext g) {
Font bouncingTextFont = new Font ("TimesNew", textFont);
g.setFill (colour);
g.setStroke( colour );
if (isFilled) {
g.fillText(bouncingText, xPos, yPos );
g.setFont(bouncingTextFont);
}
else {
g.strokeText(bouncingText, xPos, yPos );
g.setFont(bouncingTextFont);
}
}
}
This is the bouncing code:
import java.util.ArrayList;
import javafx.animation.Animation;
import javafx.animation.AnimationTimer;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.scene.shape.ArcType;
import javafx.stage.Stage;
import javafx.util.Duration;
public class BouncingShapesWindow {
private static final int ANIMATION_DELAY = 10;
private static final String FRAME_TITLE = "Shape Booooiiinggg Frame";
private GraphicsContext gc;
private Queue shapesToAdd;
private ArrayList<ClosedShape> activeShapes;
private int currentTime = 0;
private boolean flag=true;
private String filename;
public BouncingShapesWindow(GraphicsContext gc,String filename) {
this.gc=gc;
activeShapes=new ArrayList<ClosedShape>();
this.initShapes(filename);
this.insertShapes ();
drawShapes();
actionPerformed();
}
private void drawShapes () {
for (ClosedShape s : activeShapes)
{
s.draw(gc);
}
}
private void initShapes (String filename) {
shapesToAdd = ReadShapeFile.readFile(filename);
}
private void insertShapes() {
//no more shapes to add, we are done
if (shapesToAdd.isEmpty ()) {
return;
}
//add shapes if needed
ClosedShape current = (ClosedShape) shapesToAdd.peek ();
while (!shapesToAdd.isEmpty () && (current.getInsertionTime() <= currentTime*ANIMATION_DELAY)) {
activeShapes.add(current);
shapesToAdd.dequeue();
if (!shapesToAdd.isEmpty ()) {
current = (ClosedShape) shapesToAdd.peek();
}
}
}
public void actionPerformed()
{
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(5), ae -> onTime()));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
}
private void onTime() {
currentTime++;
double h =gc.getCanvas().getHeight();
double w = gc.getCanvas().getWidth();
gc.clearRect(0, 0, w, h);
moveShapes();
insertShapes ();
drawShapes();
}
public void moveShapes()
{
double dimsY = gc.getCanvas().getHeight() ;
double dimsX = gc.getCanvas().getWidth() ;
for (ClosedShape s : activeShapes)
{
s.move();
// Move us back in and bounce if we went outside the drawing area.
if (s.outOfBoundsX(dimsX))
{
s.putInBoundsX(dimsX);
s.bounceX();
}
if (s.outOfBoundsY(dimsY))
{
s.putInBoundsY(dimsY);
s.bounceY();
}
}
}
}
and this is the super class:
import javafx.scene.paint.Color;
import javafx.scene.canvas.GraphicsContext;
/**
* A ClosedShape is any shape that can be drawn without
* taking a pencil off a piece of paper.
* It's representation on computer has a line colour
* and a position on the drawable screen component.
* It can be filled in with colour or opaque.
* This class is a super class for all shapes.
*/
public class ClosedShape {
/**
* The x position of the Shape.
*/
protected int xPos;
/**
* The y position of the Shape.
*/
protected int yPos = 20;
/**
* The x position of the Shape.
*/
protected int xVec;
/**
* The y position of the Shape.
*/
protected int yVec;
/**
* The line colour of the shape, or the filled in
* colour if the Shape has fill.
*/
protected Color colour;
/**
* Determines if the Shape has a fill colour or not.
*/
protected boolean isFilled;
/**
* Encodes the insertion time into the scene
*/
private int insertionTime;
/**
* Creates a closed shape object.
* #param x The x position.
* #param y the y position.
* #param colour The line or fill colour.
* #param isFilled True if the shape is filled, false if not.
*/
protected ClosedShape (int insertionTime, int x, int y, int vx, int vy, Color colour, boolean isFilled) {
this.xPos = x;
this.yPos = y;
this.xVec = vx;
this.yVec = vy;
this.colour = colour;
this.isFilled = isFilled;
this.insertionTime = insertionTime;
}
/**
* The method returns a string suitable for printing.
* #return string to print out shape.
*/
public String toString () {
String result = "";
result += "Its position is " + xPos + " " + yPos + "\n";
result += "Its velocity is " + xVec + " " + yVec + "\n";
result += "Its colour is " + colour + "\n";
if (isFilled)
result += "It is filled" + "\n";
else
result += "It is not filled" + "\n";
result += "It should be inserted at " + insertionTime + "\n";
return result;
}
/**
* Resets the x position.
*/
public void setX (int x) {
this.xPos = x;
}
/**
* Resets the y position.
*/
public void setY (int y) {
this.yPos = y;
}
/**
* Resets the x vector
*/
public void setVecX (int x) {
this.xVec = x;
}//end setVecX
/**
* Resets the y position.
*/
public void setVecY (int y) {
this.yVec = y;
}//end setVecY
/**
* Resets the colour.
*/
public void setColour (Color colour) {
this.colour = colour;
}
/**
* Sets the shape to filled.
*/
public void setFilled () {
isFilled = true;
}
/**
* Sets the shape to unfilled.
*/
public void unsetFilled () {
isFilled = false;
}
/**
* Sets the insertion time.
*/
public void setInsertionTime (int time) {
insertionTime = time;
}
/**
* #return The x position value.
*/
public int getX() {
return xPos;
}
/**
* #return The y position value.
*/
public int getY() {
return yPos;
}
/**
* #return The colour.
*/
public Color getColour() {
return colour;
}
/**
* #return True if the shape is filled, false if not.
*/
public boolean isFilled() {
return isFilled;
}
/**
* #return the insertion time.
*/
public int getInsertionTime () {
return insertionTime;
}
/**
* Puts the shape back in bounds in X
*/
public void putInBoundsX (double winX) {
if (xPos < 0) xPos = 0;
if (xPos + this.getWidth() > winX) {
xPos = (int) (winX - Math.ceil (this.getWidth ()));
}
}//end inBoundsX;
/**
* Puts the shape back in bounds
*/
public void putInBoundsY (double winY) {
if (yPos < 0) yPos = 0;
if (yPos + this.getHeight() > winY) {
yPos = (int) (winY - Math.ceil (this.getHeight ()));
}
}//end inBoundsY;
/**
* Bounces the shape off a vertical wall
*/
public void bounceX () {
xVec = -xVec;
}
/**
* Bounces the shape off a horizontal wall
*/
public void bounceY () {
yVec = -yVec;
}
/**
* Returns true if the shapes have gone out of bounds in X
*/
public boolean outOfBoundsX (double winX) {
return (xPos + this.getWidth()> winX) || (xPos < 0);
}
/**
* Returns true if the shapes have gone out of bounds in Y
*/
public boolean outOfBoundsY (double winY) {
return (yPos + this.getHeight() > winY) || (yPos < 0);
}
/**
* Takes in a direction and a velocity and moves the shape
* in that direction on unit
*/
public void move () {
xPos += xVec;
yPos += yVec;
}
/**
* Draws the object to the current component.
* #param g The graphics object associated with the drawing component.
*/
public void draw (GraphicsContext g) {
System.out.println ("You forgot to override this method! (draw)");
System.out.println ("Don't modify this method.");
}
/**
* Get the width of the current component
*/
public int getWidth () {
System.out.println ("You forgot to override this method! (getWidth)");
System.out.println ("Don't modify this method.");
return 1;
}
/**
* Get the width of the current component
*/
public int getHeight () {
System.out.println ("You forgot to override a method! (getHeight)");
System.out.println ("Don't modify this method.");
return 1;
}
public int getSide () {
System.out.println ("You forgot to override this method! (getSide)");
System.out.println ("Don't modify this method.");
return 1;
}
}
I tried reversing it, but nothing works. Using other shapes like circle or rect is OK, but with text it bounces under the head of the window and if I change the font size it loses its boundaries. I tried searching for solutions and it seems that everything uses JFrame and JPanel. I hope the code I provided leads to a useful solution.
JButtons only show up when I hover over them. Also disappear when I resize window. The error lies in the constructor here. I am extending a JFrame. Removing the JPanel doesn't fix it. Please help.
public GameBoard(String title, int width, int height)
{
super();
this.boardWidth = width;
this.boardHeight = height;
// Create game state
this.board = new GameSquare[width][height];
// Set up window
setTitle(title);
setSize(720,720);
setContentPane(boardPanel);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
boardPanel.setLayout(new GridLayout(height,width));
int decorator = 0;
for (int y = 0; y<height; y++){
decorator++;
for (int x = 0; x<width; x++){
if (decorator % 2 == 0)
board[x][y] = new GameSquare(x, y, "BLACK");
else
board[x][y] = new GameSquare(x, y, "WHITE");
board[x][y].addActionListener(this);
boardPanel.add(board[x][y]);
decorator++;
}
}
// make our window visible
setVisible(true);
}
MCV Example.
package chess;
public class GameBoard extends JFrame implements ActionListener
{
private JPanel boardPanel = new JPanel();
private int boardHeight;
private int boardWidth;
private GameSquare[][] board;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
GameBoard gameBoard = new GameBoard("The Revolutionary War",8,8);
}
});
}
/**
* Create a new game board of the given size.
* As soon as an instance of this class is created, it is visualised
* on the screen.
*
* #param title the name printed in the window bar.
* #param width the width of the game area, in squares.
* #param height the height of the game area, in squares.
*/
public GameBoard(String title, int width, int height)
{
super();
this.boardWidth = width;
this.boardHeight = height;
// Create game state
this.board = new GameSquare[width][height];
// Set up window
setTitle(title);
setSize(720,720);
setContentPane(boardPanel);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
boardPanel.setLayout(new GridLayout(height,width));
for (int y = 0; y<height; y++){
for (int x = 0; x<width; x++){
board[x][y] = new GameSquare(x, y, this);
board[x][y].setEnabled(true);
board[x][y].addActionListener(this);
boardPanel.add(board[x][y]);
}
}
// make our window visible
setVisible(true);
}
/**
* Returns the GameSquare instance at a given location.
* #param x the x co-ordinate of the square requested.
* #param y the y co-ordinate of the square requested.
* #return the GameSquare instance at a given location
* if the x and y co-ordinates are valid - null otherwise.
*/
public GameSquare getSquareAt(int x, int y)
{
if (x < 0 || x >= boardWidth || y < 0 || y >= boardHeight)
return null;
return (GameSquare) board[x][y];
}
public void actionPerformed(ActionEvent e)
{
// The button that has been pressed.s
GameSquare square = (GameSquare)e.getSource();
}
//BufferedImage background = ImageIO.read(new File("/path/to/image.jpg"));
}
The other class is here:
package chess;
public class GameSquare extends JButton {
/**
*
*/
private static final long serialVersionUID = 1L;
private int x;
private int y;
private ChessPiece chessPiece;
private boolean selected;
private ImageIcon icon;
GameSquare(int xPos, int yPos, GameBoard board){
super();
x = xPos;
y = yPos;
chessPiece = null;
selected = false;
// Test to see colour of the base tile
if ((xPos+yPos) % 2 == 1){
icon = new ImageIcon(getClass().getResource("/pics/black.png"));
this.setIcon(icon);
}
else if ((xPos+yPos) % 2 == 0) {
icon = new ImageIcon(getClass().getResource("/pics/white.png"));
this.setIcon(icon);
}
}
public int getX(){
return x;
}
public int getY(){
return y;
}
public void setChessPiece(ChessPiece piece){
chessPiece = piece;
}
public ChessPiece getChessPiece(){
return chessPiece;
}
public void setImage(){
setIcon(chessPiece.getIcon());
}
public void select(){
selected = true;
setIcon(new ImageIcon(getClass().getResource("pics/selected.png")));
}
public void deselect(){
selected = false;
if (chessPiece == null) setIcon(icon);
else setImage();
}
}
Your problem is due to your overriding JPanel's getX() and getY(). These methods are used by the container's layout managers to help place components, and by overriding these key methods, you mess up the ability of the layouts to do this. Please change the names of those methods to something that does not override a key method of the class. If this is a chess square, I'd rename the variables to rank and file, and have getRank() and getFile() methods.
public class GameSquare extends JButton {
private int file;
private int rank;
// ....
GameSquare(int xPos, int yPos, GameBoard board){
super();
file = xPos;
rank = yPos;
chessPiece = null;
selected = false;
// .....
}
public int getFile(){
return file;
}
public int getRank(){
return rank;
}
// .....
}
add
pack();
in the GameBoard class, before setting visible. This will ensure that the size is correct and make room for any buttons which are hanging off the edge... This will make the frame the size of the JPanel, exactly as it is rendered after all objects are added.
also maybe
setResizable(false);
as it will stop you resizing your windows, and with images this can cause buttons to disappear as well as reappear if the user accidentally clicks the sides. I'd put this underneath: setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
I'm new enough to Java and I'm learning Game Design at the minute. I'm kind of at the beginning so its not a Game really.
I'm dealing with a single thread and an array of numbers.I have to get multiple square shapes moving around a screen at the same time. I have the code running fine for one square but run into trouble when I try implement it to multiple squares. I'm working with two Interfaces and I don't know how to use the Array to make multiple cases of a single random square. Any help would really be appreciated.
Cheers guys.
import java.awt.*;
import javax.swing.*;
public class MovingSquares extends JFrame implements Runnable
{
private static final Dimension WindowSize = new Dimension(600, 600);
private static final int NUMGAMEOBJECTS = 30;
private GameObject[] GameObjectsArray = new GameObject[NUMGAMEOBJECTS];
static int strtCoX = (int) (Math.random() * 600);
static int strtCoY = (int) (Math.random() * 600);
public MovingSquares() {
this.setTitle("Crazy squares");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Dimension screensize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
int x = screensize.width/2 - WindowSize.width/2;
int y = screensize.height/2 - WindowSize.height/2;
setBounds(x, y, WindowSize.width, WindowSize.height);
setVisible(true);
Thread t = new Thread(this);
t.start();
}
public void run() {
while (true) {
try {
Thread.sleep(20);
} catch (InterruptedException e) { }
GameObject.move();
this.repaint();
}
}
public void paint(Graphics g) {
g.setColor(Color.WHITE);
g.fillRect(0, 0, WindowSize.width, WindowSize.height);
int size = 50;
int R = (int) (Math.random() * 256);
int G = (int) (Math.random() * 256);
int B = (int) (Math.random() * 256);
Color c = new Color(R, G, B);
g.setColor(c);
g.fillRect(strtCoX, strtCoY, size, size);
}
public static void main(String[] args) {
MovingSquares M = new MovingSquares();
}
}
Second Interface - GameObject class.
import java.awt.*;
public class GameObject
{
private static double x;
private static double y;
private Color c;
public static int ranCoX = (int) (Math.random() * 20);
public static int ranCoY = (int) (Math.random() * 20);
public GameObject() {
int strtX = (int) x;
int strtY = (int) y;
int speedX = (int) (Math.random() * 10);
int speedY = (int) (Math.random() * 10);
}
public static void move() {
int velX = (int) (Math.random() * ranCoX);
int velY = (int) (Math.random() * ranCoY);
if (MovingSquares.strtCoY > 600)
MovingSquares.strtCoY = MovingSquares.strtCoY - 550;
else if (MovingSquares.strtCoX > 600)
MovingSquares.strtCoX = MovingSquares.strtCoX - 550;
MovingSquares.strtCoX += velX;
MovingSquares.strtCoY += velY;
}
public static void paint(Graphics g) {
int size = 50;
x = (Math.random() * 600);
y = (Math.random() * 600);
int R = (int) (Math.random() * 256);
int G = (int) (Math.random() * 256);
int B = (int) (Math.random() * 256);
Color c = new Color(R, G, B);
g.setColor(c);
g.fillRect((int)x, (int) y, size, size);
}
}
Here's a version of Moving Squares that hopefully will help you understand how to do an animation.
One of the first things I did was to separate the classes into either a model class, a view class, or a controller class. The model / view / controller pattern helps to separate concerns.
Let's look at the revised version of your model class, the GameObject class.
package com.ggl.moving.squares;
import java.awt.Color;
import java.awt.Graphics;
public class GameObject {
private static final int size = 50;
private double x;
private double y;
private double dx;
private double dy;
private int drawingWidth;
public GameObject(int drawingWidth) {
x = Math.random() * drawingWidth;
y = Math.random() * drawingWidth;
dx = Math.random() * 30D - 15D;
dy = Math.random() * 30D - 15D;
this.drawingWidth = drawingWidth;
}
public void move() {
int lowerLimit = size;
int upperLimit = drawingWidth - size;
x += dx;
if (x < lowerLimit) {
x += upperLimit;
} else if (x > upperLimit) {
x -= upperLimit;
}
y += dy;
if (y < lowerLimit) {
y += upperLimit;
} else if (y > upperLimit) {
y -= upperLimit;
}
}
public void draw(Graphics g) {
g.setColor(generateRandomColor());
g.fillRect((int) x, (int) y, size, size);
}
private Color generateRandomColor() {
int R = (int) (Math.random() * 256);
int G = (int) (Math.random() * 256);
int B = (int) (Math.random() * 256);
Color c = new Color(R, G, B);
return c;
}
}
There's only one static field left, the size of the square. Everything else is a dynamic variable or method. This allows us to create more than one instance of the GameObject.
I changed the name of the drawing method from paint to draw. This is so we don't get confused about which methods are Swing methods, and which methods are our methods.
We pass the width of the drawing panel into the constructor. That way, we only have to define the width in one place. You can see in the move method that we allow a margin the size of the square in the drawing area.
The constructor defines a random initial position and initial velocity. Th move method merely keeps the square moving in a straight line.
Next, let's look at the revised version of your main class, the MovingSquares class.
package com.ggl.moving.squares;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class MovingSquares implements Runnable {
private static final int DRAWING_WIDTH = 600;
private static final int NUMGAMEOBJECTS = 30;
private GameObject[] gameObjectsArray = new GameObject[NUMGAMEOBJECTS];
private JFrame frame;
private MovingPanel movingPanel;
private ObjectsRunnable objectsRunnable;
public MovingSquares() {
for (int i = 0; i < gameObjectsArray.length; i++) {
gameObjectsArray[i] = new GameObject(DRAWING_WIDTH);
}
}
#Override
public void run() {
frame = new JFrame();
frame.setTitle("Crazy squares");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent event) {
exitProcedure();
}
});
movingPanel = new MovingPanel(gameObjectsArray, DRAWING_WIDTH);
frame.add(movingPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
objectsRunnable = new ObjectsRunnable(this, gameObjectsArray);
new Thread(objectsRunnable).start();
}
private void exitProcedure() {
objectsRunnable.setRunning(false);
frame.dispose();
System.exit(0);
}
public void repaintMovingPanel() {
movingPanel.repaint();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new MovingSquares());
}
}
We define the width of the drawing panel here, as well as an array to hold the game objects.
We start the Swing application on the Event Dispatch thread (EDT) by invoking the SwingUtilities invokeLater method. A Swing application must always start on the EDT.
We create the game objects in the constructor and create the Swing components in the run method. I moved the drawing panel into its own class, which we'll talk about later.
We use a window listener so we can stop the thread when we're done with the application.
We pack the JFrame. The only place we're specifying a size is when we create the drawing panel. We use a JFrame. The only time you extend a Swing component, or any Java class, is when you want to override one of the methods.
Let's look at the drawing panel class, the MovingPanel class.
package com.ggl.moving.squares;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;
public class MovingPanel extends JPanel {
private static final long serialVersionUID = -6291233936414618049L;
private GameObject[] gameObjectsArray;
public MovingPanel(GameObject[] gameObjectsArray, int width) {
this.gameObjectsArray = gameObjectsArray;
setPreferredSize(new Dimension(width, width));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
for (int i = 0; i < gameObjectsArray.length; i++) {
gameObjectsArray[i].draw(g);
}
}
}
We override the paintComponent method to draw on the JPanel. Since the game objects draw themselves, we call the draw method on each game object.
Finally, let's look at the animation runnable, the ObjectsRunnable class.
package com.ggl.moving.squares;
import javax.swing.SwingUtilities;
public class ObjectsRunnable implements Runnable {
private volatile boolean running;
private GameObject[] gameObjectsArray;
private MovingSquares movingSquares;
public ObjectsRunnable(MovingSquares movingSquares,
GameObject[] gameObjectsArray) {
this.movingSquares = movingSquares;
this.gameObjectsArray = gameObjectsArray;
this.running = true;
}
#Override
public void run() {
while (running) {
updateObjects();
xxx();
sleep();
}
}
private void updateObjects() {
for (int i = 0; i < gameObjectsArray.length; i++) {
gameObjectsArray[i].move();
}
}
private void xxx() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
movingSquares.repaintMovingPanel();
}
});
}
private void sleep() {
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
}
}
public synchronized void setRunning(boolean running) {
this.running = running;
}
}
This is a straightforward animation or game loop. I put the repaint call in another SwingUtilities invokeLater method to ensure that the Swing components are updated on the EDT.
I hope this was helpful to you.
Static variables will only be available once per Runtime. To create several GameObjects you have to avoid the static keywords.
Then call new GameObject() several times to create serveral instances of GameObject, each with its own set of variables.
edit:
move the variables strtCoX and strtCoY into GameObject (as suggested by #andrew-thomson )
fix all references to strtCoX and strtCoY inside of GameObject
change g.fillRect(strtCoX, strtCoY, size, size); to for (GameObject currentObject : GameObjectsArray) g.fillRect(currentObject.strtCoX, currentObject.strtCoY, size, size);
Explanation: The coordinate are attributes of GameObject. Each GameObject should have its own coordinates.
=> Your code should work just as before
remove all static keywords in GameObject
change GameObject.move(); to for (GameObject currentObject : GameObjectsArray) currentObject.move();
initialize you GameObjectArray in public MovingSquares() constructor like this: for (int i = 0; i < GameObjectsArray.length; i++) GameObjectsArray[i] = new GameObject();
Explanation: with new GameObject() we are creating 30 instances (in that loop inside the constructor). Therefore we also have to call move() 30 times (and that is why that is also nested in a loop)
=> Wear sunglasses!
This is my first Graphical Java program and what I'm trying to do is re-create a simple classic program where I have multiple balls bouncing in a JFrame window.
So far I have successfully been able to get one ball to bounce around using code inside the run() method. That works for one ball object that I create, but now I want to have many balls so I'm trying to create a method in my Ball class that will make each ball object that I create bounce in my "ball world" independently.
Right now all I care about is them bouncing off the walls, not each other (I will figure that out later).
The problem: In my ballMove(int, int, int, int) method I have four int parameters where the first two parameters are the width and height of the window, and the last two parameters are the Xspeed, and Yspeed. When I go through my if statements it will temperately set the x and y speed parameters to negative when the ball hits the right or bottom wall, but when the run() method executes the ballMove(int, int, int, int) method again, they go back to being positive and the balls disappear from the window. I have tried using a bunch of getter and setter methods in my ball class. I have tried temporary variables within my ballMove(int, int, int, int) method. Nothing I've tried works.
Question: By using my Ball class method, how do I prevent my parameters Xspeed and Yspeed from reinitializing my instance speed variables to positive when the ball(s) collide with the walls?
Because I'm new to graphical programming any helpful suggestions would be greatly appreciated.
import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;
import javax.swing.JFrame;
public class Main extends JFrame implements Runnable {
/**
*
*/
private static final long serialVersionUID = 1L;
private int width = 800;
private int height= 600;
private int ballRadius = 50;
private Random rand = new Random();
//Create and initialize a ball object
public Ball ball = new Ball(Color.BLUE, ballRadius, ballRadius, rand.nextInt(500), rand.nextInt(500));
//public Ball ball2 = new Ball(Color.RED, ballRadius, ballRadius, rand.nextInt(500), rand.nextInt(500));
//public Ball ball3 = new Ball(Color.GREEN, ballRadius, ballRadius, rand.nextInt(500), rand.nextInt(500));
//public Ball ball4 = new Ball(Color.ORANGE, ballRadius, ballRadius, rand.nextInt(500), rand.nextInt(500));
//public Ball ball5 = new Ball(Color.YELLOW, ballRadius, ballRadius, rand.nextInt(500), rand.nextInt(500));
//constructor
public Main(){
setSize(width, height);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
//Paint the ball(s)
public void paint(Graphics g){
super.paint(g);
g.setColor(ball.getColor());
g.fillOval(ball.getBallX(), ball.getBallY(), ball.getWidth(), ball.getHeight());
//g.setColor(ball2.getColor());
//g.fillOval(ball2.getBallX(), ball2.getBallY(), ball2.getWidth(), ball2.getHeight());
//g.setColor(ball3.getColor());
//g.fillOval(ball3.getBallX(), ball3.getBallY(), ball3.getWidth(), ball3.getHeight());
//g.setColor(ball4.getColor());
//g.fillOval(ball4.getBallX(), ball4.getBallY(), ball4.getWidth(), ball4.getHeight());
//g.setColor(ball5.getColor());
//g.fillOval(ball5.getBallX(), ball5.getBallY(), ball5.getWidth(), ball5.getHeight());
}
//Run the program
public static void main(String [] args){
Main main = new Main();
main.setVisible(true);
main.run();
}
#Override
public void run() {
// TODO Auto-generated method stub
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ball.ballMove(width, height, 20, 5);
repaint();
//ball2.ballMove(width, height, 15, 3);
//repaint();
//ball3.ballMove(width, height, 3, 20);
//repaint();
//ball4.ballMove(width, height, 10, 10);
//repaint();
//ball5.ballMove(width, height, 10, 20);
//repaint();
}
}
}
Here is my Ball class
import java.awt.Color;
import javax.swing.JFrame;
public class Ball extends JFrame {
/**
*
*/
private static final long serialVersionUID = 1L;
private int width, height, ball_X, ball_Y;
private int Xspeed;
private int Yspeed;
private Color color;
public Ball(Color color, int width, int height, int ball_X, int ball_Y){
this.width = width;
this.height = height;
this.color = color;
this.ball_X = ball_X;
this.ball_Y = ball_Y;
}
public Color getColor(){
return this.color;
}
public int getWidth(){
return this.width;
}
public int getHeight(){
return this.height;
}
public int getBallX(){
return this.ball_X;
}
public int getBallY(){
return this.ball_Y;
}
public void setSpeedX(int x){
this.Xspeed = x;
}
public void setSpeedY(int x){
this.Yspeed = x;
}
public int getSpeedX(){
return this.Xspeed;
}
public int getSpeedY(){
return this.Yspeed;
}
public void setBallX(int x){
this.ball_X = x;
}
public void setBallY(int y){
this.ball_Y = y;
}
public void ballMove(int X, int Y, int xSpeed, int ySpeed){
//initialize Xspeed and Yspeed with the parameters of the function
this.setSpeedX(xSpeed);
this.setSpeedY(ySpeed);
//Moves the balls by adding the set speed to the position of the balls each time thread is executed
this.setBallX(this.getBallX() + this.getSpeedX());
this.setBallY(this.getBallY() + this.getSpeedY());
//When the balls hit the walls they are suppose to bounce back until they hit another wall.
if(this.getBallX() + 50 >= X){
this.setSpeedX(-xSpeed);
}
if(this.getBallY() + 50 >= Y){
this.setSpeedY(-ySpeed);
}
if(this.getBallX() + 25 <= 0){
this.setBallX(xSpeed);
}
if(this.getBallY() + 25 <= 0){
this.setSpeedY(ySpeed);
}
}
}
Your problem is right there:
ball.ballMove(width, height, 20, 5);
Since this is a loop, every time it's called you make it move in the same direction. You're inverting the speed at the end of ballmove if it hits a wall but it doesn't matter because next time you call it, the ball still moves towards +20, +5.
My suggestion is to add the speed parameters when you create your instance of ball and have the ballmove update its own speed.
ball.ballMove(width, height);
Other suggestion: Put your collision check before actually moving the ball. That way you can make sure you're not going in the wrong direction before actually moving it.
Now, another problem is that in your main, you're calling the run() method of your runnable. Run gets executed on the current thread. You'd need to do something like this:
Thread t = new Thread(main);
t.start();
To do drawings and calculations independently of the JFrame.
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