I'm trying to create a Java applet that bounces several balls within an applet window, each with its own thread. With the code below all the balls are drawn, but only the first one moves. What am I doing wrong?
import java.applet.Applet;
import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import static java.awt.Color.*;
public class BouncingBalls extends Applet implements Runnable {
List<Ball> ballList = new ArrayList(); // holds Ball objects
Color[] colors = new Color[]{BLACK, GRAY, WHITE, PINK, RED, ORANGE, YELLOW,
GREEN, BLUE, CYAN}; // array holding available colors
static int width, height; // variables for applet dimensions
int ballCount; // number of balls to be created, set by html parameter
Random random = new Random(); // random number generator
public void init() {
// get window dimensions
width = getSize().width;
height = getSize().height;
//get number of balls from html
String ballCountString = this.getParameter("ballCount");
try {
ballCount = Integer.parseInt(ballCountString);
} catch (NumberFormatException e) {
ballCount = 10; // set to 10 by default
}
for (int i = 0; i < ballCount; i++) {
// randomly assign ballDiameter between 1 and 20
int ballDiameter = random.nextInt(20) + 1;
// create and add balls to ballList
ballList.add(new Ball(
random.nextInt(width - ballDiameter), // set x coordinate
random.nextInt(height - ballDiameter), // set y coordinate
ballDiameter, // set ballDiameter
random.nextInt(ballDiameter) + 1, // deltaX <= ballDiameter
random.nextInt(ballDiameter) + 1, // deltaY <= ballDiameter
colors[i % 10] // use remainder to choose colors[] element
)
);
} // end for
} // end init
public void start() {
for (Ball ball: ballList) {
Thread t;
t = new Thread(this);
t.start();
} // end for
} // end start
public void run() {
for (Ball ball : ballList) {
// infinite loop: ball moves until applet is closed
while (true) {
ball.move();
repaint(); // call paint method to draw circle in new location
// set ball repaint delay using Thread sleep method
try {
Thread.sleep(20); // wait 20 msec before continuing
} catch (InterruptedException e) {
return;
}
} // end while
} // end for
} // end run
public void paint(Graphics g) {
super.paint(g);
for (Ball ball : ballList) {
// set current color
g.setColor(ball.ballColor);
// draw filled oval using current x and y coordinates and diameter
g.fillOval(ball.x, ball.y, ball.diameter, ball.diameter);
} // end for
} // end paint
}
class Ball {
int x, y, // coordinates of upper-left corner of circle
diameter, // circle diameter
deltaX, deltaY; // number of pixels ball moves each time it's repainted
Color ballColor;
public Ball(int x, int y, int diameter, int deltaX, int deltaY,
Color ballColor) {
this.x = x;
this.y = y;
this.diameter = diameter;
this.deltaX = deltaX;
this.deltaY = deltaY;
this.ballColor = ballColor;
} // end Ball constructor
public void move() {
// update x and y coordinates using delta values
x += deltaX;
y += deltaY;
// reverse x direction when ball reaches boundary
if (x >= BouncingBalls.width - diameter || x <= 0){
deltaX = -deltaX;
} // end if
// reverse y direction when ball reaches boundary
if (y >= BouncingBalls.height - diameter || y <= 0) {
deltaY = -deltaY;
} // end if
} // end move
} // end BouncingBalls
Your while(true) should be outside the for loop. It sits on the first ball returned from the iterator.
That being said, you may want to go over your logic for one ball-per-thread. It actually appears to create N number of threads (N being the number of balls) in which each thread will move all the balls instead of just one.
Edit to address my second point:
Let's say you have 10 balls. You start 10 threads, and each thread iterates over all balls.
For instance:
Thread 1:
public void run(){
for(Ball b : ballList){
b.move();
b.repaint();
}
}
Thread 2:
public void run(){
for(Ball b : ballList){
b.move();
b.repaint();
}
}
Thread N:
public void run(){
for(Ball b : ballList){
b.move();
b.repaint();
}
}
This is done because you create threads with the same this instance of a runnable that iterates over each ball.
public void start() {
for (Ball ball: ballList) {
Thread t;
t = new Thread(this);
t.start();
} // end for
} // end start
So I would think, if each ball is supposed to move 1 unit every 20 milliseconds. You should end up seeing every 20 milliseconds moving N*1 units in this case 10 every 20 milliseconds.
Edit - With regards to a suggestion.
Instead of setting this as the runnable you should remove the implementation of Runnable from the this class and create a new Runnable that will take a single Ball as a parameter.
private static class MovingRunnable implements Runnable{
private final Ball b;
private MovingRunnable(Ball b){this.b=b;}
public void run(){
for(;;){
b.move();
b.repaint();
Thread.sleep(20);
}
}
}
Later in your start method
public void start() {
for (Ball ball: ballList) {
Thread t;
t = new Thread(new MovingRunnable(ball));
t.start();
} // end for
} // end start
So here each Ball has it's own thread with it's own Runnable. Each ball will now only invoke move once per thread every 20 milliseconds.
But it still isn't perfect because repaint should only be invoked by the UI thread, having it invoked by each thread can cause different issues (though you may not notice any, it's just worth saying).
Related
Me and my partner are attempting to create the game Pong for our computer science final project. We created a reference code where 2 cubes can be controlled upwards and downwards and it works fine. The problem occurs when attempting to control both cubes at the same time (only 1 cube will move at a time). We want to make both cubes move at the same time.
WE want to say that:
yPos - is the y position of the black cube
xPos - is the x position of the black cube
xPos2 - is the x position of the blue cube
YPos2 - is the y position of the blue cube
Keys:
A - Go up for black cube
Z - Go down for black cube
K - Go up for blue cube
M - go down for blue cube
We have tried using a more complicated version which used j-label animation. How ever we want to make our pong game through the graphics function. But we do not understand:
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class PongHelpNeed extends JFrame implements KeyListener
{
// booleans to tell which key is pressed
boolean upKey;
boolean downKey;
boolean upKey2;
boolean downKey2;
// the position variables
int yPos;
int xPos;
int xPos2;
int yPos2;
public PongHelpNeed ()
{
//create window
super ("Controller");
setSize (660, 700);
// set keys to false and original positions
upKey = false;
downKey = false;
upKey2 = false;
downKey2 = false;
xPos = 100;
yPos = 350;
xPos2 = 500;
yPos2 = 350;
// add the frame as a listener to your keys
addKeyListener (this);
// Show the frame
setVisible(true);
}
//needs to be here because the class implements KeyListener
public void keyTyped (KeyEvent e)
{
System.out.println (e.getKeyCode () + " Typed");
}
//needs to be here because the class implements KeyListener
public void keyPressed (KeyEvent e) {
//check if keys a,z or k,m are pressed
if (e.getKeyCode () == KeyEvent.VK_A)
{
upKey = true;
}
else if (e.getKeyCode () == KeyEvent.VK_Z)
{
downKey = true;
}
else if (e.getKeyCode () == KeyEvent.VK_K)
{
upKey2 = true;
}
else if (e.getKeyCode () == KeyEvent.VK_M)
{
downKey2 = true;
}
//repaint the window everytime you press a key
repaint ();
}
//needs to be here because the class implements KeyListener
public void keyReleased (KeyEvent e)
{
System.out.println (e.getKeyCode () + " Released");
}
//paints the pictures
public void paint (Graphics g)
{
//set background
g.setColor(Color.WHITE);
g.fillRect(0, 0, 660, 700);
//cube 1
g.setColor(Color.BLACK);
g.fillRect(xPos,yPos,50, 50);
//draw cube 2
g.setColor(Color.BLUE);
g.fillRect(xPos2,yPos2, 50, 50);
//if keys are pressed move the cubes accordingly up or down
if (upKey == true)
{
yPos = yPos - 15;
upKey = false;
}
else if (downKey == true)
{
yPos = yPos + 15;
downKey = false;
}
else if (downKey2 == true){
yPos2 = yPos2 + 15;
downKey2 = false;
}
else if (upKey2 == true) {
yPos2 = yPos2 - 15;
upKey2 = false;
}
}
public static void main (String[] args)
{
new PongHelpNeed ();
}
}
Our expected results are we are trying to move both cube at the same time. So when we press the A key and the K key the black square should move and the blue cube should move.
Calling repaint() does not trigger a call to the paint immediately, so it's possible that the keyPressed is triggered twice (or more) before paint.
In your paint method you are checking the keys in if-else, which means that if one of the flags is true, the rest are not checked. You also have a race condition where the keyPressed is fighting with paint over the flags. Also, if you press a key quickly multiple times, you'll lose all the extra key presses between the first handled event and the next repaint.
Instead of doing the move within paint, you should do it within the keyPressed handler. Don't set a flag to e.g. upKey = true;, but instead do the action directly: yPos = yPos - 15;. The paint method will then just refresh the view to reflect the current (updated) state.
I've been working on a small "game," which I think is called Pachinko. I have uploaded an image of what the game screen looks like. I will be dropping balls, and having them look like they are rolling off pegs, ending up being caught in the bottom "gates."
My problem is that I cannot get the repaint() method to work. Does the repaint() method require a timer, or action to work? Please look at at these two classes. I have created a Ball class object inside the GameWindow class (near the bottom), and would like to update the ball's x/y values using the Ball's setPos() method, then repaint, so the ball appears to move.
What am I doing wrong? Do I need an update() method to use the repaint() method?
Game Window Image:
public class GameWindow extends JPanel{
private int numBalls = 0;
// GameWindow Constructor (Sets Ball amount from user)
public GameWindow(int balls){
JFrame myFrame = new JFrame("Game Window");
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Globally set ball amount
setBallAmount(balls);
myFrame.add(this);
myFrame.setSize(325, 790);
myFrame.setLocationRelativeTo(this);
myFrame.setResizable(false);
myFrame.setVisible(true);
} // End GameWindow Constructor
// Function setPegAmount;
// Passes the amount of balls the user to class variable.
public void setBallAmount(int balls)
{
numBalls = balls * 2;
}
public void paintComponent(Graphics g){
super.paintComponent(g); // housekeeping, etc.
this.setBackground(Color.WHITE); // Background
int counter = 0; // count what number peg we are painting
int row = 1; // calculate what row we are creating
int rowSpacer = 55;
boolean evenRow = false;
int columnSpacer = 60;
// DRAW PEGS TO SCREEN (4 rows of 8, 4 rows of 7)
for (int x = 0; x < 60; x++)
{
// For odd rows
if (row % 2 == 1)
{
g.setColor(Color.BLACK);
g.fillOval(rowSpacer - 40, columnSpacer, 10, 10);
rowSpacer += 40;
counter++;
}
// For Even rows
else
{
g.setColor(Color.BLACK);
g.fillOval(rowSpacer - 20, columnSpacer, 10, 10);
rowSpacer += 40;
counter++;
}
// Check to see if we are finished with odd row
if (counter % 8 == 0 && evenRow == false)
{
row++;
rowSpacer = 55;
columnSpacer += 60;
evenRow = true;
counter = 0;
}
else if(counter % 7 == 0 && evenRow == true)
{
row++;
rowSpacer = 55;
columnSpacer += 60;
evenRow = false;
counter = 0;
}
} // END DRAWING PEGS TO SCREEN
// DRAW RECTANGULAR WALLS TO SCREEN
g.setColor(Color.BLACK); // Wall Color
g.fillRect(0, 0, 5, 760); // LEFT Wall
g.fillRect(315, 0, 5, 760); // RIGHT Wall
g.fillRect(0, 0, 315, 5); // TOP Wall
g.fillRect(0, 755, 320, 5); // BOTTOM Wall
// DRAW BOTTOM GATES
int gateSeperator = 35;
for (int x = 0; x < 7; x++)
{
g.setColor(Color.BLACK);
g.fillRect(gateSeperator, 500, 10, 255);
gateSeperator += 40;
}
// Create instance of ball object
Ball myBall = new Ball();
// Test draw ball
myBall.drawBall(g); // The ball is drawn to screen
myBall.setPos(50, 50); // Change the x and y coordinates of the Ball
repaint(); // Also tried "this.repaint();" but neither does anything
} // Ends paintComponent
} // End GameWindow Class
Ball.java:
public class Ball{
private int x = 5;
private int y = 30;
public void setPos(int xPos, int yPos)
{
x = xPos;
y = yPos;
}
public void drawBall(Graphics g)
{
g.setColor(Color.GREEN);
g.fillOval(x, y, 30, 30);
}
}
I don't think that's the way to do it. Swing's not my specialty but calling repaint in paintComponent, according to my experience, is incorrect.
For example, tell the component to repaint itself.
/**
* Tells the view to repaint itself.
*/
public void update() {
repaint();
}
As soon as possible, repaint ends up calling paintComponent via paint.
/**
* Paints the component.
* #param g The graphics object for the view.
*/
#Override
protected void paintComponent(Graphics g) {
// Draw some stuff...
}
So calling repaint inside of paintComponentis likely not what you're wanting to do. What you should be doing is using repaint to invode paintComponent.
I don't think you can rely on putting the repaint or update at the end of paintComponent because, I believe, multiple calls to repaint get lumped into a single update. So, yes, to properly animate object you should look into using a Swing Timer. For example,
Timer timer = Timer(delay, action);
timer.start();
The above timer will invoke the given action on the delay given in milliseconds. Please see this for more details.
I'm kind of new to Java and OO programming, here is the code of moving black and white balls problem. First let me explain the program that I want in the output: there are n balls(for example 6 balls) on the window, one black and one white, in each move we only are allowed to move just one ball and this movement should be shown on the screen, and at the end all the white balls should be on one side and all the black balls should be on the other side. Here is an example of six balls:
I have written the program and it seems working good and no flaws in the algorithm, but my problem is that I can't show animation of the movement of the balls, in each movement one ball should swap its place with its neighbor ball, but all I get is the final arrangements of the balls. Please someone help me with the animation part. I would be really thankful for that.
code:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import javax.swing.*;
public class DrawPanel extends JPanel implements ActionListener
{
Timer myTimer = new Timer(2000, this);
public static final int NUMBER_OF_CIRCLES = 10; //number of circles which are to moved
static int[] circles = new int[NUMBER_OF_CIRCLES];
public void paintComponent(Graphics g)
{
int x = 0; //start point of circles;
int length = 40; //diagonal of the circles
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
Ellipse2D circle;
//painting n circles based on the array
for(int index = 0; index<10; index++)
{
if(circles[index] == 0){ //if the element of the arrayy is 0 then draw a void circle
circle = new Ellipse2D.Double(x, 120, length, length);
g2.draw(circle);
}
else if(circles[index] == 1){ //if the element of the array is 1 them draw a filled circle
circle = new Ellipse2D.Double(x, 120, length, length);
g2.fill(circle);
}
x += 45; //increas start pont of the next circle 45 pixles
}
myTimer.start();
}
public void actionPerformed(ActionEvent e)
{
int tmp; //template for swaping elements
int condition; //condition of the forS
arrayFill(circles); //fills the array based on the writen method, one 1 and one 0 like: 0 1 0 1 0 1 0 1
//here is the part which works good, it changes palces of an elemen at time.
//at the end of this part the array would be like: 1 1 1 1 0 0 0 0
if(NUMBER_OF_CIRCLES % 2 == 0)
condition = circles.length/2 -1;
else
condition = circles.length/2;
for(int i = circles.length-1, k = 1; i>condition; i--, k++)
{
for(int j = i - k; j<i ;j++)
{
tmp = circles[j];
circles[j] = circles[j+1];
circles[j+1] = tmp;
//if we call arrayPrint method it will print the array but I don't know why repaint is not working here
//arrayPrint(circles);
repaint();
}
}
}
//fills the array, one 1 and one 0. Example: 0 1 0 1 0 1 0 1 0 1
public static void arrayFill(int[] array)
{
for(int i = 0; i<array.length; i++)
{
if( i%2 == 0)
array[i] = 0;
else
array[i] = 1;
}
}
}//end of class
And the main Class:
import javax.swing.JFrame;
public class BlackAndWhiteBallsMoving {
public static void main(String[] args)
{
DrawPanel myPanel = new DrawPanel();
JFrame myFrame = new JFrame();
myFrame.add(myPanel);
myFrame.setSize(600, 500);
myFrame.setTitle("Black And White Balls Moving");
myFrame.setVisible(true);
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}//end of class
The events triggered by the Timer are performed on the same event thread as the repaints. Calling repaint does not actively perform a paint event, rather it queues one for later. When you call your repaints from within the timer event, they will only get executed once the timer event is completed.
What you need to do is refactor your loop so that only a single swap is performed each time the timer triggers. I've done this for you as an example:
public class DrawPanel extends JPanel implements ActionListener {
public static final int NUMBER_OF_CIRCLES = 10;
Timer myTimer = new Timer(500, this);
int[] circles = new int[NUMBER_OF_CIRCLES];
public DrawPanel() {
arrayFill(circles);
if(NUMBER_OF_CIRCLES % 2 == 0) {
condition = circles.length/2 -1;
} else {
condition = circles.length/2;
}
i = circles.length - 1;
k = 1;
myTimer.start();
}
int i, j, k;
int condition;
boolean outer = true;
#Override
public void actionPerformed(ActionEvent e) {
if(outer) {
if(i > condition) {
j = i - k; // set j
outer = false; // and move to the inner loop swap
} else {
myTimer.stop(); // the outer loop is done so stop the timer
}
}
if(!outer) {
int tmp = circles[j];
circles[j] = circles[j+1];
circles[j+1] = tmp;
repaint();
j++;
if(j >= i) {
i--;
k++;
outer = true; // move to the outer condition
} // next time the timer triggers
}
}
#Override
protected void paintComponent(Graphics g) {
int x = 0;
int length = 40;
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
Ellipse2D circle;
for(int index = 0; index<10; index++) {
if(circles[index] == 0){
circle = new Ellipse2D.Double(x, 120, length, length);
g2.draw(circle);
} else if(circles[index] == 1){
circle = new Ellipse2D.Double(x, 120, length, length);
g2.fill(circle);
}
x += 45;
}
//myTimer.start();
}
public static void arrayFill(int[] array) {
for(int i = 0; i<array.length; i++) {
if( i%2 == 0) {
array[i] = 0;
} else {
array[i] = 1;
}
}
}
}
(I'm sure it could be factored another way.)
Also:
I added #Override annotations which you should use. Doing so will warn you when you make certain mistakes. (Like misspelling a method name or incorrectly declaring its signature.)
I moved circles to an instance variable because I don't see a reason it should be static. It is part of the state of the DrawPanel instance.
I created a constructor which initializes variables such as circles.
paintComponent is a protected method and it should remain so unless there is a reason to promote it to public.
(I removed your comments and changed the bracing style just to condense the code for my answer.)
As a side note, you should read the tutorial Initial Threads. You are not creating your GUI on the Swing event thread. Basically you need to wrap your code in main inside a call to invokeLater:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
// create and show your GUI
}
});
}
The basic problem is in your actionPerformed method. Your two for loops are rearranging the array to its final arrangement very quickly. Each loop iteration will take a matter of nanoseconds to milliseconds to complete (it depends on how the repaint() method works). The entire process is finished in less than 50 milliseconds or so. That's too fast for your eyes to keep up.
Basically, the repaint() method is working, but it's working too fast for human eyes to keep up.
If you break up the for loops into something that does one step of the algorithm each time it's called, you can trigger that from a timer and see the animation at a human-detectable speed.
add a paint thread. it should always call repaint() like,
new Thread(){ // this can be started on main or constructor of object
public void run(){
while(true){
repaint();
try {
Thread.sleep(50);
} catch(Exception e){ }
}
}
}.start();
and then, on action performed, mark moving objects like movingObjects, keep a animate_x = 0 and keep a boolean variable like existAnimation
then on paintComponent, increase animate_x
animate_x = animate_x + 1;
if (animate_x >= MAX_WIDTH_OF_ANIMATION){
existAnimation = false;
}
and use this existAnimation, animate_x and movingObjects
like,
public void paintComponent(Graphics g)
{
int x = 0; //start point of circles;
int length = 40; //diagonal of the circles
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
Ellipse2D circle;
//painting n circles based on the array
for(int index = 0; index<10; index++)
{
int paint_x = x;
if (movingObjects.has(circles[index])){
paint_x += animate_x;
}
if(circles[index] == 0){ //if the element of the arrayy is 0 then draw a void circle
circle = new Ellipse2D.Double(paint_x, 120, length, length);
g2.draw(circle);
}
else if(circles[index] == 1){ //if the element of the array is 1 them draw a filled circle
circle = new Ellipse2D.Double(paint_x, 120, length, length);
g2.fill(circle);
}
x += 45; //increas start pont of the next circle 45 pixles
}
myTimer.start();
}
I'm working on a Mario game and am in need of assistance and suggestions on how to go about creating hit detection for a tilemap.
Currently, the player has the ability to walk/jump through the blocks.
I added in a fixed detection to the ground for now which I am hoping to replace with regular hit detection.
I understand that there are four sides to each block and the player. Only some blocks need hit detection and some things you might need to know is that the player stays at 300px(middle of screen) 98% of the time.
The only thing that moves is the map
The map is rendered from a .txt file and is rendered like so:
for(int y=0;y<map.length;y++) {
for(int x=0;x<map[y].length;x++) {
int index = map[y][x];
int yOffset = 0;
if(index>(tileSheet.getWidth() / Engine.TILE_WIDTH) -1) {
yOffset++;
index = index - (tileSheet.getWidth() / Engine.TILE_WIDTH);
}
g.drawImage(tileSheet,
((x * Engine.TILE_WIDTH)*scale)+position,
((y * Engine.TILE_HEIGHT)*scale),
(((x * Engine.TILE_WIDTH) + Engine.TILE_WIDTH )*scale)+position,
(((y * Engine.TILE_HEIGHT) + Engine.TILE_HEIGHT)*scale),
index * Engine.TILE_WIDTH,
yOffset * Engine.TILE_HEIGHT,
(index * Engine.TILE_WIDTH) + Engine.TILE_WIDTH,
(yOffset * Engine.TILE_HEIGHT) + Engine.TILE_HEIGHT,
null
);
}
}
//This code is actually longer(included file later on)
Colour hit detection is too slow and inconsistent for multi coloured tiles
Since the map is moving I suppose I need to move the hit detection boxes with it. As for selecting the boxes that it should detect might be difficult. Maybe it would be a better idea to make the code NOT hit detect certain tiles.
My attempts have ended in obfuscation of code. Can anyone suggest the easiest way to implement the hit detection? (keep in mind I have jumping).
The important codes are listed below:
Board.java(The panel where everything is drawn)
package EvilMario; //Include this class in the EvilMario game package
import java.awt.*; //Imported to allow use of Image
import java.awt.event.*; //Imported to allow use of ActionListener
import javax.swing.*; //Import swing
public class Board extends JPanel implements ActionListener { //Class Board
private TileLayer l; //Instance of TileLayer class
private Menu m; //Instance of menu class
private Player p; //Instance of player class
Timer time; //A timer
public static enum STATE {MENU,GAME}; //The game states
public static STATE State = STATE.MENU; //Set the first state to menu
//END
//GLOBAL
//DECLARATIONS
public Board() {
l = TileLayer.FromFile("D:/ICS3U1/EvilMario/map.txt"); //Tile map data from .txt file
this.addMouseListener(new MouseInput()); //Listen for mouse input
this.addKeyListener(new AL()); //Listen for key input
p = new Player(); //Start running Player class
m = new Menu(); //Start running Menu class
setFocusable(true); //Allows movement
time = new Timer(20,this); //Timer set to update "this" class every 20 milliseconds(Approximately 50fps)
time.start(); //Actually start the timer
}
public void actionPerformed(ActionEvent e) {
p.move(); //Call the move method from the player class
repaint(); //Repaint
}
public void paintComponent(Graphics g) { //Graphics method
super.paintComponent(g); //Super hero?
Graphics2D g2d = (Graphics2D) g; //Cast 2D graphics
if(State==STATE.GAME) {
if(p.distanceTraveled<300)l.DrawLayer(g,0);else l.DrawLayer(g, -(p.distanceTraveled-300)); //Draw the tile map
g2d.drawImage(p.getImage(), p.getX(), p.getY(), 48, 48, null); //Draw the player
if(p.distanceTraveled==3488) System.out.println("You have won the game!"); //Draw the end game screen
} else {
m.render(g); //Render the menu
}
}
private class AL extends KeyAdapter { //Action Listener extends key adapter
public void keyPressed(KeyEvent e) { //On key press
p.keyPressed(e); //Send whatever key was pressed TO the keyPressed method in the player class
}
public void keyReleased(KeyEvent e) { //On key release
p.keyReleased(e); //Send whatever key was released TO the keyReleased method in the player class
}
}
}
Player.java(player logic)
package EvilMario; //Include this class in the EvilMario game package
import java.awt.Image;
import java.awt.event.KeyEvent;
import javax.swing.ImageIcon;
public class Player {
int x, dx, y, distanceTraveled; //x coordinate,change in x coordinate,y coordinate,1st rep bg,2nd rep bg,dist traveled
Image player; //The player variable
ImageIcon walk_L_anim = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/walk_L_anim.gif");
ImageIcon walk_L_idle = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/walk_L_idle.png");
ImageIcon walk_R_anim = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/walk_R_anim.gif");
ImageIcon walk_R_idle = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/walk_R_idle.png");
ImageIcon jump_L_anim = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/jump_L_anim.gif");
ImageIcon jump_L_idle = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/jump_L_idle.png");
ImageIcon jump_R_anim = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/jump_R_anim.gif");
ImageIcon jump_R_idle = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/jump_R_idle.png");
boolean holdingLeft = false;
boolean holdingRight = false;
static boolean jumping = false;
static boolean falling = false;
static int jumpingTime = 350;
public Player() {
player = walk_R_idle.getImage(); //Give the player the image
x = 75; //The original x position of the player
y = 277; //The original y position of the player
distanceTraveled = 75; //Original distance traveled
}
public void move() {
if(x>=0 && x<=300) { //If the player is within the moving area
x = x+dx; //The x position is updated to become itself+the amount you moved
}
if(x<0) //If the player has reached he very left side of the screen(0px)
x=0; //Move him up a pixel so he can move again
if(x>300) //If the player has reached the center of the screen(300px)
x=300; //Move him down a pixel so he can move again
distanceTraveled=distanceTraveled+dx; //Calculate distanceTraveled
if(distanceTraveled<0) //Make sure distanceTraveled isn't a negative
distanceTraveled=0; //Make sure distanceTraveled isn't a negative
if(distanceTraveled>=300) //Keep player at center position once past 300 mario meters
x=300; //Keep player at center position once past 300 mario meters
if(holdingLeft && !holdingRight) {
if(distanceTraveled<300)dx=-5; else dx=-4;
if(jumping && !falling) {
player = jump_L_anim.getImage();
y-=8;
} else {
player = walk_L_anim.getImage();
if(y<277)
y+=8;
}
} else if(holdingRight && !holdingLeft) {
if(distanceTraveled<300)dx=5; else dx=4;
if(jumping && !falling) {
player = jump_R_anim.getImage();
y-=8;
} else {
player = walk_R_anim.getImage();
if(y<277)
y+=8;
}
} else if(!holdingRight && !holdingLeft) {
dx = 0;
if(jumping && !falling) {
player = jump_R_anim.getImage();
y-=8;
} else {
if(y<277)
y+=8;
}
}
if(y==277) {
falling = false;
}
System.out.println("LEFT: "+holdingLeft+" JUMP: "+jumping+" RIGHT: "+holdingRight+" FALLING: "+falling+" Y: "+y);
}
public int getX() { return x; } //This method will return the x. Is used by other classes
public int getY() { return y; } //This method will return the y. Is used by other classes
public Image getImage() { return player; } //This method will return the player. Is used by other classes
public void keyPressed(KeyEvent e) { //Called from the board class, the argument is whatever key was pressed
int key = e.getKeyCode(); //The key originally sent from the board class
if(key == KeyEvent.VK_LEFT && !holdingLeft)
holdingLeft = true;
if(key == KeyEvent.VK_RIGHT && !holdingRight)
holdingRight = true;
if(key == KeyEvent.VK_UP && !jumping && !falling)
new Thread(new JumpThread(this)).start();
}
public void keyReleased(KeyEvent e) { //Called from the board class, the argument is whatever key was released
int key = e.getKeyCode(); //The key originally sent from the board class
if(key == KeyEvent.VK_LEFT) { //If the left or right key was released
dx = 0; //Stop moving
holdingLeft = false;
player = walk_L_idle.getImage();
}
if(key == KeyEvent.VK_RIGHT) {
dx = 0;
holdingRight = false;
player = walk_R_idle.getImage();
}
}
}
TileLayer.java (Rendering of the tile layer)(Probably most important part relating to the question)
package EvilMario; //Include this class in the EvilMario game package
import java.awt.Graphics; //
public class TileLayer {
private int[][] map; //2D array
private BufferedImage tileSheet; //The tile sheet
public TileLayer(int[][] existingMap) { //
map = new int[existingMap.length][existingMap[0].length]; //map initialized
for(int y=0;y<map.length;y++) { //Loop through all boxes
for(int x=0;x<map[y].length;y++) { //Loop through all boxes
map[y][x] = existingMap[y][x]; //Update the map
}
}
tileSheet = LoadTileSheet("D:/ICS3U1/EvilMario/images/tilemap.gif"); //Load the tilesheet
}
public TileLayer(int width, int height) {
map = new int[height][width];
}
public static TileLayer FromFile(String fileName) {
TileLayer layer = null;
ArrayList<ArrayList<Integer>> tempLayout = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
String currentLine;
while((currentLine = br.readLine()) !=null) {
if(currentLine.isEmpty())
continue;
ArrayList<Integer> row = new ArrayList<>();
String[] values = currentLine.trim().split(" ");
for(String string: values) {
if(!string.isEmpty()) {
int id = Integer.parseInt(string);
row.add(id);
}
}
tempLayout.add(row);
}
} catch(IOException e) {
System.out.println("ERROR");
}
int width = tempLayout.get(0).size();
int height = tempLayout.size();
layer = new TileLayer(width,height);
for(int y=0;y<height;y++) {
for(int x=0;x<width;x++) {
layer.map[y][x] = tempLayout.get(y).get(x);
}
}
layer.tileSheet = layer.LoadTileSheet("D:/ICS3U1/EvilMario/images/tilemap.gif");
return layer;
}
public BufferedImage LoadTileSheet(String fileName) {
BufferedImage img = null;
try {
img = ImageIO.read(new File(fileName));
} catch(Exception e) {
System.out.println("Could not load image");
}
return img;
}
int scale = 2;
public void DrawLayer(Graphics g, int position) {
for(int y=0;y<map.length;y++) {
for(int x=0;x<map[y].length;x++) {
int index = map[y][x];
int yOffset = 0;
if(index>(tileSheet.getWidth() / Engine.TILE_WIDTH) -1) {
yOffset++;
index = index - (tileSheet.getWidth() / Engine.TILE_WIDTH);
}
g.drawImage(tileSheet,
((x * Engine.TILE_WIDTH)*scale)+position,
((y * Engine.TILE_HEIGHT)*scale),
(((x * Engine.TILE_WIDTH) + Engine.TILE_WIDTH )*scale)+position,
(((y * Engine.TILE_HEIGHT) + Engine.TILE_HEIGHT)*scale),
index * Engine.TILE_WIDTH,
yOffset * Engine.TILE_HEIGHT,
(index * Engine.TILE_WIDTH) + Engine.TILE_WIDTH,
(yOffset * Engine.TILE_HEIGHT) + Engine.TILE_HEIGHT,
null
);
}
}
}
}
Engine.java (Not as important)(Simple variables for tile sizes)
package EvilMario;
public class Engine {
public static final int TILE_WIDTH = 16;
public static final int TILE_HEIGHT = 16;
}
If you need other pieces of code, just ask for them. I am not asking you to give me a specific answer to the question but simply a method that would work with my following code.
A specific answer would be nice though :)
I also believe the answer to this question will be useful to others because this method was explained in a popular java 2d game tutorial video(They never showed hit detection).
Methods I tried:
Creating a new java file called HitDetectionLayer with the exact code in TileLayer.java that stored positions in arrays. It failed :(
Ok, I'm not entirely sure what you are doing, if you throw up some images it would be more clear.
At any rate, 'hit detection' aka collision detection is a very complex topic, but it depends on what you want to do. If you want everything to be boxes or circles, then it is quite easy. If however you want things to rotate or you want collision for complex shapes it becomes extreme difficult.
Most games use circles or spheres for collision. You put the majority of your graphics (it may not fit perfectly either leaving part of your images in or out of the circle but that's life). Now lets say you have your mario sprite and one of those turtles. Well, you have circles around them both and once the circles touch you trigger your event.
The math for this is very easy because circles are by definition a perimeter around a constant length. Look at this:
You probably already know this, and it may seem obvious, but if you think about it this is what a circle really is: a consistent length in every fathomable direction. The directions are measured in degrees and from there you move on to trigonometry but you don't need that. What you need is coordinance aka vectors. So look at this:
All you need to determine circle collision is the distance between the circles. No matter what angle the circles collide from it does not matter because the distances from the circle's centre are consistent all the way around. Even if the circles are different sizes, it doesn't matter, just account for the radii difference.
Too compute all of this, you would write a method like this:
public boolean testDistanceBetween( float radius1, float radius2,
float x1, float x2, float y1, float y2 ){
double distanceBetween = Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
if(distanceBetween < (radius1+radius2) ){
return true;
}
return false;
}
The moral of the story is that circles are just good that way. If you want to do rectangle collision you take the bottom-left and top right point and you test if other rectangles are in between those points. This should be pretty straight forward, each point is a vector, each rectangle has 4 points. If any of the 4 points of one rectangle are between points on the other rectangle, there is collision.
You can use this system to handle ground and walls also. For example, if ground is at Y=300, then if your sprite's y coordinance are == 300, you suspend your gravity.
The main thing I wanted to explain is that if you intend to have rotating rectangles or polygons and you want to detect collision on them... good luck. It can be done yes, but you should understand you are implementing complex physics, especially when/if you implement gravity.
So my answer is cautionary: there is NO easy way to detect collision of rotating rectangles or polygons. Circles and static rectangles are the limits. If you really want to do rotating rectangles/polygons get a physics engine. Box2d is pretty good and has a Java version Jbox2d.
I am trying to make a program that generates 25 random ovals then draw a ball and make it bounce, I got it somewhat done, I generated the ovals and I got the ball to move but when I added the thread it kept repeating the draw oval loop, I get somewhat why this is happening but I have no idea how to fix it.
Basically my program should:
draw 25 random sized ovals on random locations within the border - completed
draw a ball and make it move - completed
make the ball bounce - not done but I know how to do it
but it keeps repeating step one.
this is my code that I have written, oh and I have to use applets right now its part of the course please don't suggest I use swing or something else:
import java.applet.Applet;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
public class As4B extends Applet implements Runnable
{
public int x, y;
public int width = 854;
public int height = 480;
public int border = 20;
public Image offscreen;
public Graphics d;
public void init()
{
setSize(width,height);
Thread th = new Thread(this);
th.start();
offscreen = createImage(width,height);
d = offscreen.getGraphics();
}
public void run()
{
x = 100;
y = 100;
while(true)
{
x ++;
y ++;
repaint();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void paint(Graphics gfx)
{
d.setColor(java.awt.Color.black);
d.fillRect(0, 0, width, height);
d.setColor(java.awt.Color.green);
d.fillRect(0 + border, 0 + border, (width - (border * 2)), (height - (border* 2)));
genOval(25, d);
d.setColor(Color.gray);
d.fillOval(x, y, 50, 50);
gfx.drawImage(offscreen, 0, 0, this);
}
public int random(int low, int high)
{
int answer =(int)((Math.random()*(high-low))+ low);
return answer;
}
public void genOval(int amount, Graphics f)
{
int ranWidth, ranHeight, ranX, ranY, red, blue, green;
int i = 0;
while(i < 25)
{
green = random(0,255);
blue = random(0,255);
red = random(0,255);
f.setColor(new Color(red,green,blue));
ranWidth = random(30,400);
ranHeight = random(30,200);
ranX = random(0 + border, ((width - border)- (ranWidth)));
ranY = random(0 + border , ((height - border)- (ranHeight)));
f.fillOval(ranX, ranY, ranWidth, ranHeight);
i++;
}
}
public void update(Graphics gfx) {
paint(gfx);
}
}
Your genOval() method has no persistent backing. Every time repaint() is called (by your thread), the paint() method is called, and this generates new locations for the random ovals. You need to create a persistent source for that information, like so:
List<Rectangle> rectangles = new ArrayList<Rectangle>();
List<Color> colors = new ArrayList<Color>();
public void init() {
...
for (int i = 0; i < 25; i++) {
int green = random(0,255);
int blue = random(0,255);
int red = random(0,255);
colors.add(new Color(red,green,blue));
ranWidth = random(30,400);
ranHeight = random(30,200);
ranX = random(0 + border, ((width - border)- (ranWidth)));
ranY = random(0 + border , ((height - border)- (ranHeight)));
rectangles.add(new Rectangle(ranX, ranY, ranWidth, ranHeight));
}
}
public void genOval(Graphics g) {
for (int i = 0; i < 25; i++) {
Color color = colors.get(i);
Rectangle rectangle = rectangle.get(i);
// Draw using color & rectangle
}
}