Here is the deal, basically I have to have a code that has a bucket that can catch the falling fruit and every time it catches it you get a point a new fruit falls.
So I know how to make the bucket move and how to make the fruit go again once it reaches the bottom. However, I don't know how to make it actually fall. So far i got a switch but no idea what to do with it. I got the fruit popping up in random places which is a start. Anyways, here is my code. All help appreciated. Again, i need to have a random fruit drop once one of them reaches the bottom.
import java.awt.Color;
import java.awt.event.KeyEvent;
import acm.graphics.GOval;
import acm.graphics.GPolygon;
import acm.graphics.GRect;
import acm.program.GraphicsProgram;
import acm.util.RandomGenerator;
import java.awt.event.*;
public class FruitCatcher extends GraphicsProgram {
private static final int APPLET_WIDTH = 500;
private static final int APPLET_HEIGHT = 500;
private static final int BUCKET_X = 250;
private static final int BUCKET_Y = 500;
private static final int BUCKET_SPEED = 10;
private static final int BUCKET_SPEED2 = -10;
private GPolygon Bucket;
public void init() {
setSize(APPLET_WIDTH, APPLET_HEIGHT);
addKeyListeners();
}
public void run() {
RandomGenerator random = new RandomGenerator();
makeBucket();
for (int i = 1; i <= 3; i++) {
int randomX = random.nextInt(0, 300 - 20);
addFruit(i, randomX, 0);
}
while (true)
;
}
public void makeBucket() {
Bucket = new GPolygon(BUCKET_X, BUCKET_Y);
Bucket.addVertex(-60, 0);
Bucket.addVertex(-70, -85);
Bucket.addVertex(10, -85);
Bucket.addVertex(0, 0);
add(Bucket);
Bucket.setFilled(true);
Bucket.setFillColor(Color.GRAY);
}
public void addFruit(int a, int x, int y) {
switch (a) {
case 1:
GRect Banana = new GRect(x, y, 10, 60);
Banana.setColor(Color.YELLOW);
Banana.setFilled(true);
add(Banana);
break;
case 2:
GOval lime = new GOval(x, y, 20, 20);
lime.setColor(Color.GREEN);
lime.setFilled(true);
add(lime);
break;
case 3:
GOval Orange = new GOval(x, y, 30, 30);
Orange.setColor(Color.ORANGE);
Orange.setFilled(true);
add(Orange);
}
}
public void keyPressed(KeyEvent event) {
int keyCode = event.getKeyCode();
switch (keyCode) {
case KeyEvent.VK_LEFT:
if (Bucket.getX() > 0) {
Bucket.move(-BUCKET_SPEED, 0);
}
break;
case KeyEvent.VK_RIGHT:
if (Bucket.getX() < APPLET_WIDTH) {
Bucket.move(BUCKET_SPEED, 0);
}
break;
}
}
}
In my code you can see a while (true) I am just assuming that is where I would write it. However, I am at a little loss what would actually go there.
You need to keep a list of references to all the fruits, so you can manipulate them later
Each iteration of the game loop, move each fruit down
You may want to implement some timing mechanism, so that the fruit
speed is not dependent on the CPU speed.
package jsyn;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import acm.graphics.GOval;
import acm.graphics.GPolygon;
import acm.graphics.GRect;
import acm.program.GraphicsProgram;
import acm.util.RandomGenerator;
import java.awt.event.*;
public class FruitCatcher extends GraphicsProgram {
private static final int APPLET_WIDTH = 500;
private static final int APPLET_HEIGHT = 500;
private static final int BUCKET_X = 250;
private static final int BUCKET_Y = 500;
private static final int BUCKET_SPEED = 10;
private static final int BUCKET_SPEED2 = -10;
//Speed of fruit falling
private static final int FRUIT_PX_PER_MS = 10;
private GPolygon Bucket;
public void init() {
setSize(APPLET_WIDTH, APPLET_HEIGHT);
addKeyListeners();
fruits = new ArrayList<Component>();
}
// Keep list of fruits
List<GObject> fruits;
public void run() {
RandomGenerator random = new RandomGenerator();
makeBucket();
for (int i = 1; i <= 3; i++) {
int randomX = random.nextInt(0, 300 - 20);
addFruit(i, randomX, 0);
}
long last = System.currentTimeMillis();
while (true) {
long current = System.currentTimeMillis();
update(current - last);
last = current;
}
}
void update(long delta) {
for (GObject fruit : fruits) {
//this code may not work, replace with code that moves fruit down
fruit.setLocation(fruit.getX(), fruit.getY() + delta * FRUIT_PX_PER_MS);
}
}
public void makeBucket() {
Bucket = new GPolygon(BUCKET_X, BUCKET_Y);
Bucket.addVertex(-60, 0);
Bucket.addVertex(-70, -85);
Bucket.addVertex(10, -85);
Bucket.addVertex(0, 0);
add(Bucket);
Bucket.setFilled(true);
Bucket.setFillColor(Color.GRAY);
}
public void addFruit(int a, int x, int y) {
switch (a) {
case 1:
GRect Banana = new GRect(x, y, 10, 60);
Banana.setColor(Color.YELLOW);
Banana.setFilled(true);
add(Banana);
fruits.add(Banana);
break;
case 2:
GOval lime = new GOval(x, y, 20, 20);
lime.setColor(Color.GREEN);
lime.setFilled(true);
add(lime);
fruits.add(lime);
break;
case 3:
GOval Orange = new GOval(x, y, 30, 30);
Orange.setColor(Color.ORANGE);
Orange.setFilled(true);
add(Orange);
fruits.add(Orange);
}
}
public void keyPressed(KeyEvent event) {
int keyCode = event.getKeyCode();
switch (keyCode) {
case KeyEvent.VK_LEFT:
if (Bucket.getX() > 0) {
Bucket.move(-BUCKET_SPEED, 0);
}
break;
case KeyEvent.VK_RIGHT:
if (Bucket.getX() < APPLET_WIDTH) {
Bucket.move(BUCKET_SPEED, 0);
}
break;
}
}
}
Related
I'm currently making a snake game in java. First I created a class named GamePanel for my snake. And here I wrote the codes of the apple eaten by the snake, the codes of the snake, and the movements of the snake in this class. Now I want to create another class by taking some codes from them. So I created a separate Data class for this and put the information I wrote at the beginning of the GamePanel.
There was a method in the GamePanel that I called newApple. I placed it in a separate class called SnakeFoodApple. After that, come to GamePanel and
public SnakeFoodApple apple = new SnakeFoodApple();
I wrote this code. Where there is newApple, I said apple.newApple.
I'm running the game now and I'm not getting any errors. But it does not perform the event of throwing apples at random different places, which is the feature of newApple. So the apple is always in the same coordinates. How can I solve this?
Also, how is it possible to transfer information on both sides in this transition between classes? So, can I switch from class A to class B and from class B to class A? If I can, with what code can I do it?
My code;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Random;
public class GamePanel extends JPanel implements ActionListener {
public SnakeFoodApple apple = new SnakeFoodApple();
static final int SCREEN_WIDTH = 600;
static final int SCREEN_HEIGHT = 600;
static final int UNIT_SIZE = 25;
static final int GAME_UNITS = (SCREEN_WIDTH * SCREEN_HEIGHT) / UNIT_SIZE;
static final int DELAY = 75;
final int X[] = new int[GAME_UNITS];
final int Y[] = new int[GAME_UNITS];
int bodyParts = 6;
int applesEaten;
int appleX;
int appleY;
char direction = 'R';
boolean running = false;
Timer timer;
Random random = new Random();
GamePanel() {
random = new Random();
this.setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
this.setBackground(Color.black);
this.setFocusable(true);
this.addKeyListener(new MyKeyAdapter());
startGame();
}
public void startGame() {
apple.newApple();
running = true;
timer = new Timer(DELAY, this);
timer.start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
public void draw(Graphics g) {
if (running) {
g.setColor(Color.red);
//g.fillOval(int X, int Y, Width, Height)
g.fillOval(appleX, appleY, UNIT_SIZE, UNIT_SIZE);
for (int i = 0; i < bodyParts; i++) {
if (i == 0) {
g.setColor(Color.green);
g.fillRect(X[i], Y[i], UNIT_SIZE, UNIT_SIZE);
} else {
g.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
g.fillRect(X[i], Y[i], UNIT_SIZE, UNIT_SIZE);
}
}
g.setColor(Color.red);
g.setFont(new Font("Ink Free", Font.BOLD, 40));
FontMetrics metrics = getFontMetrics(g.getFont());
g.drawString("SCORE: " + applesEaten, (SCREEN_WIDTH - metrics.stringWidth("SCORE: " + applesEaten)) / 2, g.getFont().getSize());
} else {
gameOver(g);
}
}
public void move() {
for (int i = bodyParts; i > 0; i--) {
X[i] = X[i - 1];
Y[i] = Y[i - 1];
}
switch (direction) {
case 'U':
Y[0] = Y[0] - UNIT_SIZE;
break;
case 'D':
Y[0] = Y[0] + UNIT_SIZE;
break;
case 'L':
X[0] = X[0] - UNIT_SIZE;
break;
case 'R':
X[0] = X[0] + UNIT_SIZE;
break;
}
}
public void checkApple() {
if ((X[0] == appleX) && (Y[0] == appleY)) {
bodyParts++;
applesEaten++;
apple.newApple();
}
}
public void checkCollisions() {
for (int i = bodyParts; i > 0; i--) {
if ((X[0] == X[i]) && (Y[0] == Y[i])) {
running = false;
}
}
if (X[0] < 0) {
running = false;
}
if (X[0] > SCREEN_WIDTH) {
running = false;
}
if (Y[0] < 0) {
running = false;
}
if (Y[0] > SCREEN_HEIGHT) {
running = false;
}
if (!running) {
timer.stop();
}
}
public void gameOver(Graphics g) {
g.setColor(Color.red);
g.setFont(new Font("Ink Free", Font.BOLD, 40));
FontMetrics metrics1 = getFontMetrics(g.getFont());
g.drawString("SCORE: " + applesEaten, (SCREEN_WIDTH - metrics1.stringWidth("SCORE: " + applesEaten)) / 2, g.getFont().getSize());
g.setColor(Color.red);
g.setFont(new Font("Ink Free", Font.BOLD, 75));
FontMetrics metrics2 = getFontMetrics(g.getFont());
g.drawString("GAME OVER", (SCREEN_WIDTH - metrics2.stringWidth("GAME OVER")) / 2, SCREEN_HEIGHT / 2);
}
//class GamePanel' ı implements (implıments) ettiğimiz için otomotik olarak geldi
#Override
public void actionPerformed(ActionEvent e) {
if (running) {
move();
checkApple();
checkCollisions();
}
repaint();
}
public class MyKeyAdapter extends KeyAdapter {
#Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
if (direction != 'R') {
direction = 'L';
}
break;
case KeyEvent.VK_RIGHT:
if (direction != 'L') {
direction = 'R';
}
break;
case KeyEvent.VK_UP:
if (direction != 'D') {
direction = 'U';
}
break;
case KeyEvent.VK_DOWN:
if (direction != 'U') {
direction = 'D';
}
break;
}
}
}
}
public class SnakeFoodApple extends Data{
public void newApple(){
appleX = random.nextInt((int) (SCREEN_WIDTH / UNIT_SIZE)) * UNIT_SIZE;
appleY = random.nextInt((int) (SCREEN_HEIGHT / UNIT_SIZE)) * UNIT_SIZE;
}
}
import java.util.Random;
import javax.swing.Timer;
public class Data {
static final int SCREEN_WIDTH = 600;
static final int SCREEN_HEIGHT = 600;
static final int UNIT_SIZE = 25;
static final int GAME_UNITS = (SCREEN_WIDTH * SCREEN_HEIGHT) / UNIT_SIZE;
static final int DELAY = 75;
final int X[] = new int[GAME_UNITS];
final int Y[] = new int[GAME_UNITS];
int bodyParts = 6;
int applesEaten;
int appleX;
int appleY;
char direction = 'R';
boolean running = false;
Timer timer;
Random random = new Random();
}
Your code is a bit of a mess - sorry to be that harsh, but could not find a "softer" expression:
You have the same member fields in GamePanel and in Data.
You extend Data with SnakeFoodApple but the only thing you add or change in the extended class is a method to set some member fields of the parent class.
You did not declare any field with a visibility other than default (aka package private).
What you see is cause from a combination of the three topics above. You have to get rid of the class SnakeFoodApple, that single method can easily go into Data (you can rename Data afterwards back to SnakeFoodApple if you like that name.
Also all those static fields in Data should not be static, but normal member fields, and initialized in a constructor rather than having fixed initializers.
Finally you must not store appleX and appleY in your main class GamePanel but access them from Data every time you need the values. Create getters for that.
Also clean up Data from all values you are not actually using inside there. Don't forget to remove unnecessary imports.
Per request: this would be my implementation of Data (renamed to SnakeFoodApple):
package examples.stackoverflow.q71987622;
import java.util.Random;
public class SnakeFoodApple {
private final int screenWidth;
private final int screenHeight;
private final int unitSize;
private final Random random = new Random();
private int appleX;
private int appleY;
public SnakeFoodApple(int screenWidth, int screenHeight, int unitSize) {
this.screenWidth = screenWidth;
this.screenHeight = screenHeight;
this.unitSize = unitSize;
}
public void newApple() {
appleX = random.nextInt(screenWidth / unitSize) * unitSize;
appleY = random.nextInt(screenHeight / unitSize) * unitSize;
}
public int getX() {
return appleX;
}
public int getY() {
return appleY;
}
}
To fix your problem, you have then to replace every appleX and appleY in the class GamePanel with a apple.getX() respectively apple.getY().
Since you didn't provide a [mre], and the code doesn't run on it's own, I could not really test this, but there are a lot of possible improvements left in the GamePanel.
I might be missing something, but it looks as though you have forgotten to fillOval using apple here:
public void draw(Graphics g) {
if (running) {
g.setColor(Color.red);
//g.fillOval(int X, int Y, Width, Height)
g.fillOval(appleX, appleY, UNIT_SIZE, UNIT_SIZE);
Should probably be:
g.fillOval(this.apple.appleX, this.apple.appleY, this.apple.UNIT_SIZE, this.apple.UNIT_SIZE);
I'm not clear why you have all this in GamePanel still, is this not supposed to be happening in Data (SnakeFoodApple) now?
static final int SCREEN_WIDTH = 600;
static final int SCREEN_HEIGHT = 600;
static final int UNIT_SIZE = 25;
static final int GAME_UNITS = (SCREEN_WIDTH * SCREEN_HEIGHT) / UNIT_SIZE;
static final int DELAY = 75;
final int X[] = new int[GAME_UNITS];
final int Y[] = new int[GAME_UNITS];
int bodyParts = 6;
int applesEaten;
int appleX;
int appleY;
char direction = 'R';
boolean running = false;
Timer timer;
Random random = new Random();
In general, it is good practice to set fields as private and use getters and setters. It would be good to look at that.
I'm not sure what you mean by your second question.
I've been looking for answers in similar threads here but I can't find out what's wrong with my code.
I'm trying to make an agar.io simulator and the agar object doesn't draw. Food draws but agar doesn't draw regardless of what I do.
Agar class code:
import java.awt.*;
import java.awt.event.*;
public class Agar {
//instance field
public static final int DEFAULT_SIZE = 1000, DEFAULT_X =
ArenaPanel.PANEL_WIDTH/2, DEFAULT_Y = ArenaPanel.PANEL_HEIGHT/2;
private int x, y, size;
private Color clr;
public Agar(Color c) {
x = DEFAULT_X;
y = DEFAULT_Y;
clr = c;
}
public void move() {
//blank for now, not used yet
}
//grows by 10% width
public void grow() {
size += 10;
}
public void draw(Graphics fred) {
fred.setColor(clr);
fred.fillOval(0,0,size,size);
fred.setColor(Color.black);
fred.drawOval(0,0,size,size);
}
}
Panel class code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.ArrayList;
public class ArenaPanel extends JPanel {
//instance field
public static final int PANEL_WIDTH = 1000, PANEL_HEIGHT = 500;
private ArrayList<Food> food;
private Timer foodAdder;
private Agar agar;
public ArenaPanel() {
//agar stuff
agar = new Agar(getRandomColor());
//food stuff
food = new ArrayList<Food>();
//add some initial food
for (int i = 0; i < 50; i++)
addRandomFood();
//"this will last a while"
foodAdder = new Timer(3000, new FoodAdder());
foodAdder.start();
//listeners
addComponentListener(new ResizeListener());
//more basic stuff
setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT));
setBackground(Color.white);
}
public void paintComponent(Graphics fred) {
super.paintComponent(fred);
agar.draw(fred);
for (int i = 0; i < food.size(); i++)
food.get(i).draw(fred);
}
//put it here so I can use it for both agar and food
private Color getRandomColor() {
int rand = (int)(Math.random()*7);
Color c;
switch (rand) {
case 0:
c = Color.red;
break;
case 1:
c = Color.orange;
break;
case 2:
c = Color.yellow;
break;
case 3:
c = Color.green;
break;
case 4:
c = Color.cyan;
break;
case 5:
c = Color.blue;
break;
default:
c = Color.pink;
}
return c;
}
private void addRandomFood() {
int x = (int)(Math.random()*PANEL_WIDTH);
int y = (int)(Math.random()*PANEL_HEIGHT);
food.add(new Food(x,y,getRandomColor()));
}
private class FoodAdder implements ActionListener {
public void actionPerformed(ActionEvent e) {
addRandomFood();
repaint();
}
}
private class ResizeListener extends ComponentAdapter {
public void componentResized(ComponentEvent e) {
setPreferredSize(getSize());
}
}
}
I tried debugging (placing System.out.println command into draw(), paintComponent(), agar's constructor) and all methods are in fact running properly. So I thought maybe it was just not showing up, but even when I tried changing agar's colour to black square (which should contrast a lot to colourful circular food) located at 0,0 but it still doesn't show.
What could be the problem here?
It has zero size. Call method grow at leas once.
I have a thread for player1 keyEvents which works, but when I add another thread for player2 keyevents, only the player2 keypresses are registered. Is it an issue with the threads or with the keyEvents() method? I don't know where to go next with this.
//keyEvents thread for player 1
AnimationTimer player1_timer = new AnimationTimer(){
#Override
public void handle(long now){
keyEvents(player1, 1);
}
};
player1_timer.start();
if(true){ //if I set this to false, player1 keyEvents are registered again.
//keyEvents thread for player 2
AnimationTimer player2_timer = new AnimationTimer(){
#Override
public void handle(long now){
keyEvents(player2, 2);
}
};
player2_timer.start();
}
private void keyEvents(Unit player, int playerNumber){
map.setOnKeyPressed((KeyEvent e) -> {
if(playerNumber == 1){
if(null != e.getCode())switch (e.getCode()) {
case LEFT:
player1.rotateLeft();
break;
default:
break;
}
}
if(playerNumber == 2){
if(null != e.getCode())switch (e.getCode()) {
case A:
player2.rotateLeft();
break;
default:
break;
}
}
});
}
Updated Code:
//keyEvents thread for players
AnimationTimer playerTimer = new AnimationTimer(){
#Override
public void handle(long now){
keyEvents();
}
};
playerTimer.start();
private void keyEvents(){
Bullet bullet = new Bullet();
map.setOnKeyPressed((KeyEvent e) -> {
if(null != e.getCode())switch (e.getCode()) {
case A:
player2.rotateLeft();
break;
case LEFT:
player1.rotateLeft();
break;
default:
break;
}
});
}
First, this is like only my second JavaFX program, so I'm sure there are a number of improvements that could be made.
You have to recognise that you're still only dealing with a single thread, JavaFX's Event Dispatching Thread.
The AnimationTimer and EventHandlers are all called within the context of this thread, so adding more of them isn't going to provide you with any additional benefit, and could actually be departmental.
Instead, you want to add a single handler for keyPressed and keyReleased. In these handlers, you want to set the state of a given direction for a given player.
One of simplest ways to do this is through a Set, which you can add and remove a "key" object, which would represent the direction for a given player.
You would then use a single AnimationTimer to act as the "main loop" and which would, based on the state of the Set, make determinations about the direction that the player objects should be moved.
This is very common concept in game development, it's simple, but it also decouples the logic from the state, so that the state can be set through any means (mouse/joystick/AI/network) and the logic won't care.
import java.util.Set;
import java.util.TreeSet;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import static javafx.scene.input.KeyCode.DOWN;
import static javafx.scene.input.KeyCode.SHIFT;
import static javafx.scene.input.KeyCode.UP;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Test extends Application {
enum Direction {
UP, DOWN, LEFT, RIGHT;
}
private static final double W = 600, H = 400;
private Image playerOneImage;
private Node playerOneNode;
private Image playerTwoImage;
private Node playerTwoNode;
private Set<Direction> playerOneDirection = new TreeSet<>();
private Set<Direction> playerTwoDirection = new TreeSet<>();
boolean running;
#Override
public void start(Stage stage) throws Exception {
playerOneImage = makePlayerOne();
playerOneNode = new ImageView(playerOneImage);
playerTwoImage = makePlayerTwo();
playerTwoNode = new ImageView(playerTwoImage);
Group dungeon = new Group(playerOneNode, playerTwoNode);
movePlayerTo(playerOneNode, 0, 0);
movePlayerTo(playerTwoNode, W - 12.5, H - 12.5);
Scene scene = new Scene(dungeon, W, H, Color.FORESTGREEN);
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
switch (event.getCode()) {
case UP:
playerOneDirection.add(Direction.UP);
break;
case DOWN:
playerOneDirection.add(Direction.DOWN);
break;
case LEFT:
playerOneDirection.add(Direction.LEFT);
break;
case RIGHT:
playerOneDirection.add(Direction.RIGHT);
break;
case W:
playerTwoDirection.add(Direction.UP);
break;
case S:
playerTwoDirection.add(Direction.DOWN);
break;
case A:
playerTwoDirection.add(Direction.LEFT);
break;
case D:
playerTwoDirection.add(Direction.RIGHT);
break;
}
}
});
scene.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
switch (event.getCode()) {
case UP:
playerOneDirection.remove(Direction.UP);
break;
case DOWN:
playerOneDirection.remove(Direction.DOWN);
break;
case LEFT:
playerOneDirection.remove(Direction.LEFT);
break;
case RIGHT:
playerOneDirection.remove(Direction.RIGHT);
break;
case W:
playerTwoDirection.remove(Direction.UP);
break;
case S:
playerTwoDirection.remove(Direction.DOWN);
break;
case A:
playerTwoDirection.remove(Direction.LEFT);
break;
case D:
playerTwoDirection.remove(Direction.RIGHT);
break;
}
}
});
stage.setScene(scene);
stage.show();
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
movePlayer(playerOneNode, playerOneDirection);
movePlayer(playerTwoNode, playerTwoDirection);
}
};
timer.start();
}
private void movePlayer(Node playerNode, Set<Direction> direction) {
int dx = 0;
int dy = 0;
if (direction.contains(Direction.UP)) {
dy -= 1;
}
if (direction.contains(Direction.DOWN)) {
dy += 1;
}
if (direction.contains(Direction.RIGHT)) {
dx += 1;
}
if (direction.contains(Direction.LEFT)) {
dx -= 1;
}
if (running) {
dx *= 3;
dy *= 3;
}
if (dx == 0 && dy == 0) {
return;
}
final double cx = playerNode.getBoundsInLocal().getWidth() / 2;
final double cy = playerNode.getBoundsInLocal().getHeight() / 2;
double x = cx + playerNode.getLayoutX() + dx;
double y = cy + playerNode.getLayoutY() + dy;
movePlayerTo(playerNode, x, y);
}
private void movePlayerTo(Node playerNode, double x, double y) {
final double cx = playerNode.getBoundsInLocal().getWidth() / 2;
final double cy = playerNode.getBoundsInLocal().getHeight() / 2;
if (x - cx >= 0
&& x + cx <= W
&& y - cy >= 0
&& y + cy <= H) {
playerNode.relocate(x - cx, y - cy);
}
}
protected Image makePlayerOne() {
return makePlayer(Color.RED);
}
protected Image makePlayerTwo() {
return makePlayer(Color.BLUE);
}
protected Image makePlayer(Color color) {
WritableImage image = new WritableImage(25, 25);
PixelWriter writer = image.getPixelWriter();
for (int y = 0; y < 25; y++) {
for (int x = 0; x < 25; x++) {
writer.setColor(x, y, color);
}
}
return image;
}
public static void main(String[] args) {
launch(args);
}
}
I made a program to display interference patterns for light waves. I did this by using the paint method on a JPanel to draw 2 sources and then drawing concentric circles around them. This would be double slit interference, so I allowed one of the sources to move around to experiment with the slit width.
The problem is that when I run this, my computer says it is using 80% of my CPU! There's really not much to it. Circle in the middle, circles around it, and it moves. My code follows.
main class:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class main {
static final int UP = -1;
static final int DOWN = 1;
static final int RIGHT = 1;
static final int LEFT = -1;
static final int NOMOVEMENT = 0;
static int verticalMovement = NOMOVEMENT;
static int horizontalMovement = NOMOVEMENT;
public static void main(String[] args) {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Point s1 = new Point((int)(screenSize.getWidth()/3), 50);
Point s2 = new Point((int)(2*screenSize.getWidth()/3), 50);
JFrame frame = new JFrame("Physics Frame");
frame.setPreferredSize(screenSize);
PhysicsPane pane = new PhysicsPane(screenSize, 15, s1, s2);
pane.setPreferredSize(screenSize);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(pane);
frame.pack();
Timer time = new Timer(1000 / 20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
switch (verticalMovement){
case UP:
s2.changeY(UP);
break;
case DOWN:
s2.changeY(DOWN);
break;
default:
break;
}
switch (horizontalMovement){
case RIGHT:
s2.changeX(RIGHT);
break;
case LEFT:
s2.changeX(LEFT);
break;
default:
break;
}
pane.repaint();
}
});
frame.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()==37){
horizontalMovement = LEFT;
} else if (e.getKeyCode()==38){
verticalMovement = UP;
} else if (e.getKeyCode()==39){
horizontalMovement = RIGHT;
} else if (e.getKeyCode()==40){
verticalMovement = DOWN;
}
if(e.getKeyChar()=='a'){
pane.setWaveLength(2);
}
if(e.getKeyChar()=='s'){
pane.setWaveLength(-2);
}
}
#Override
public void keyReleased(KeyEvent e) {
switch (e.getKeyCode()){
case 37:
horizontalMovement = NOMOVEMENT;
break;
case 38:
verticalMovement = NOMOVEMENT;
break;
case 39:
horizontalMovement = NOMOVEMENT;
break;
case 40:
verticalMovement = NOMOVEMENT;
break;
}
}
});
frame.setVisible(true);
time.start();
}
}
Panel class. If there's an inefficiency with the drawing method, it would be here.
import javax.swing.*;
import java.awt.*;
public class PhysicsPane extends JPanel {
private Dimension size;
private Point[] pointArray = new Point[2];
private double waveLength;
public PhysicsPane(Dimension size, double wavelength, Point source1, Point source2) {
this.size = size;
pointArray[0] = source1;
pointArray[1] = source2;
setPreferredSize(size);
this.waveLength = wavelength;
}
#Override
public void paintComponent(Graphics g){
for (int i = 0; i < 2; i++) {
g.setColor(Color.black);
double x = this.pointArray[i].getX();
double y = this.pointArray[i].getY();
g.fillOval((int)x, (int)y, 2, 2);
for (int j = (int)waveLength; j < 1500; j+=waveLength) {
g.setColor(Color.red);
g.drawOval((int)x-(j/2+1), (int)y-(j/2+1), 2 + j, 2 + j);
}
}
}
public void setWaveLength(double increment){
this.waveLength+=increment;
}
}
Point Class: Really just puts x and y coordinates into one construct. Not important particularly
public class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public void changeX(double dX){
this.x+=dX;
}
public void changeY(double dY){
this.y+=dY;
}
}
So what is wrong with my program? Why is such a simple animation consuming so much of my processor?
UPDATED CODE SECTION:
#Override
public void paintComponent(Graphics g){
BufferedImage bi = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_RGB);
Graphics graphics = bi.getGraphics();
for (int i = 0; i < 2; i++) {
graphics.setColor(Color.black);
double x = this.pointArray[i].getX();
double y = this.pointArray[i].getY();
graphics.fillOval((int)x, (int)y, 2, 2);
for (int j = (int)waveLength; j < 1500; j+=waveLength) {
graphics.setColor(Color.red);
graphics.drawOval((int)x-(j/2+1), (int)y-(j/2+1), 2 + j, 2 + j);
}
}
g.drawImage(bi, 0, 0, new ImageObserver() {
#Override
public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
return false;
}
});
}
The improvement that I recommend is removal of unnecessary tasks being performed, notably how your code updates the pane being drawn on, even when there aren't changes.
The following update reduced CPU usage from 12% to 0% (static frame):
Timer time = new Timer(1000 / 20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
boolean refresh = false;
switch (verticalMovement) {
case UP:
s2.changeY(UP);
refresh = true;
break;
case DOWN:
s2.changeY(DOWN);
refresh = true;
break;
default:
break;
}
switch (horizontalMovement) {
case RIGHT:
s2.changeX(RIGHT);
refresh = true;
break;
case LEFT:
s2.changeX(LEFT);
refresh = true;
break;
default:
break;
}
if (refresh == true) {
pane.repaint();
}
}
});
I'm making a tiled map and I came across this problem:
When i'm moving my character it's going off the map and then falls (due to gravity)
How do I make this map infinite?
And also, how do I store which blocks are destroyed and which not? So that i can repaint the screen with the same map and when you walk back to the starting point the brocken blocks are still there.
Just Tell me if I need to provide Code.
I'll give you my world.java
package game.test.src;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import javax.swing.ImageIcon;
public class World {
public Rectangle[] blocks;
public boolean[] isSolid;
public Image[] blockImg;
public final int arrayNum = 500;
//Block Images
public Image BLOCK_GRASS, BLOCK_DIRT, BLOCK_STONE, BLOCK_SKY;
private int x, y, xDirection, yDirection;;
//map navigation
static final int PAN_UP = 0, PAN_DOWN = 1, PAN_LEFT= 2, PAN_RIGHT = 3;
public World(){
BLOCK_GRASS = new ImageIcon("H:/2D game test/Game test 2/src/game/test/src/images/tile_grass.png").getImage();
BLOCK_DIRT = new ImageIcon("H:/2D game test/Game test 2/src/game/test/src/images/tile_dirt.png").getImage();
BLOCK_STONE = new ImageIcon("H:/2D game test/Game test 2/src/game/test/src/images/tile_stone.png").getImage();
BLOCK_SKY = new ImageIcon("H:/2D game test/Game test 2/src/game/test/src/images/tile_sky.png").getImage();
blocks = new Rectangle[500];
blockImg = new Image[500];
isSolid = new boolean[arrayNum];
loadArrays();
}
private void loadArrays(){
for(int i = 0; i < arrayNum; i++){
if(x >= 500){
x = 0;
y += 20;
}
if(i >= 0 && i < 100){
blockImg[i] = BLOCK_SKY;
isSolid[i] = false;
blocks[i] = new Rectangle(x, y, 20, 20);
}
if(i >= 100 && i < 125){
blockImg[i] = BLOCK_GRASS;
isSolid[i] = true;
blocks[i] = new Rectangle(x, y, 20, 20);
}
if(i >= 125 && i < 225){
blockImg[i] = BLOCK_DIRT;
isSolid[i] = true;
blocks[i] = new Rectangle(x, y, 20, 20);
}
if(i >= 225 && i < 500){
blockImg[i] = BLOCK_STONE;
isSolid[i] = true;
blocks[i] = new Rectangle(x, y, 20, 20);
}
x += 20;
}
}
public void draw(Graphics g){
for(int i = 0; i < arrayNum; i++){
g.drawImage(blockImg[i], blocks[i].x, blocks[i].y, null);
}
}
public void moveMap(){
for(Rectangle r : blocks){
r.x += xDirection;
r.y += yDirection;
}
}
public void stopMoveMap(){
setXDirection(0);
setYDirection(0);
}
private void setXDirection(int dir){
xDirection = dir;
}
private void setYDirection(int dir){
yDirection = dir;
}
public void navigateMap(int nav){
switch(nav){
default:
System.out.println("default case entered... Doing nothing.");
break;
case PAN_UP:
setYDirection(-1);
break;
case PAN_DOWN:
setYDirection(1);
break;
case PAN_LEFT:
setXDirection(-1);
break;
case PAN_RIGHT:
setXDirection(1);
break;
}
}
}
here is my Player.java
package game.test.src;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.ImageIcon;
public class Player {
static final int MOVE_UP = 0, MOVE_DOWN = 1, MOVE_LEFT= 2, MOVE_RIGHT = 3;
private World world;
private Rectangle playerRect;
private Image playerImg;
//Block Variables
private int hoverX, hoverY;
private boolean hovering = false;
protected static int xDirection;
protected static int yDirection;
private Weapon weapon;
public Player(World world){
this.world = world;
playerImg = new ImageIcon("H:/2D game test/Game test 2/src/game/test/src/images/Character.png").getImage();
playerRect = new Rectangle(50, 0, 10, 36);
weapon = new Weapon(weapon.PICKAXE);
}
private static void setXDirection(int d){
xDirection = d;
}
private static void setYDirection(int d){
yDirection = d;
}
public void update()
{
move();
checkForCollision();
}
private void checkForCollision() {
}
private void move()
{
playerRect.x += xDirection;
playerRect.y += yDirection;
gravity();
}
private void gravity()
{
for(int i=0;i<world.arrayNum; i++)
{
if(!world.isSolid[i])
{
setYDirection(1);
}
else if(world.isSolid[i] && playerRect.intersects(world.blocks[i]))
{
setYDirection(0);
}
}
}
//MotionEvents
public void mousePressed(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
}
public void mouseClicked(MouseEvent e)
{
}
public void mouseMoved(MouseEvent e)
{
int x = e.getX();
int y = e.getY();
int px = playerRect.x;
int py = playerRect.y;
for(int i = 0; i < world.arrayNum; i++)
{
if(weapon.isEquipped(Weapon.PICKAXE) &&
x > world.blocks[i].x && x < world.blocks[i].x + world.blocks[i].width &&
y > world.blocks[i].x && y < world.blocks[i].y + world.blocks[i].height && world.isSolid[i] &&
(world.blocks[i].x + (world.blocks[i].width / 2) ) <= (px + playerRect.width/2) + weapon.WEAPON_RADIUS &&
(world.blocks[i].x + (world.blocks[i].width / 2) ) >= (px + playerRect.width/2) - weapon.WEAPON_RADIUS &&
(world.blocks[i].y + (world.blocks[i].height / 2) ) <= (py + playerRect.height/2) + weapon.WEAPON_RADIUS &&
(world.blocks[i].y + (world.blocks[i].height / 2) ) >= (py + playerRect.height/2) - weapon.WEAPON_RADIUS)
{
hovering = true;
hoverX = world.blocks[i].x;
hoverY = world.blocks[i].y;
break;
}
else
hovering = false;
}
}
public void mouseDragged(MouseEvent e)
{
}
public void mouseEntered(MouseEvent e)
{
}
public void mouseExited(MouseEvent e)
{
}
//Drawing Methods
public void draw(Graphics g)
{
g.drawImage(playerImg, playerRect.x, playerRect.y, null);
if(hovering)
drawBlockOutline(g);
}
private void drawBlockOutline(Graphics g)
{
g.setColor(Color.black);
g.drawRect(hoverX, hoverY, world.blocks[0].width,world.blocks[0].height);
}
private class Weapon
{
public static final int UNARMED = 0;
public static final int PICKAXE = 1;
public static final int GUN = 2;
public int CURRENT_WEAPON;
public int WEAPON_RADIUS;
public Weapon(int w)
{
switch(w)
{
default:
System.out.println("No weapon selected");
break;
case UNARMED:
CURRENT_WEAPON = UNARMED;
WEAPON_RADIUS = 100;
break;
case PICKAXE:
CURRENT_WEAPON = PICKAXE;
WEAPON_RADIUS = 100;
break;
case GUN:
CURRENT_WEAPON = GUN;
WEAPON_RADIUS = 100;
break;
}
}
public void selectWeapon(int w)
{
switch(w)
{
default:
System.out.println("No weapon selected");
break;
case UNARMED:
CURRENT_WEAPON = UNARMED;
WEAPON_RADIUS = 100;
break;
case PICKAXE:
CURRENT_WEAPON = PICKAXE;
WEAPON_RADIUS = 100;
break;
case GUN:
CURRENT_WEAPON = GUN;
WEAPON_RADIUS = 100;
break;
}
}
public boolean isEquipped(int w)
{
if(w == CURRENT_WEAPON)
{
return true;
}
else
return false;
}
}
public void moveMap(){
for(Rectangle r : world.blocks){
r.x += xDirection;
r.y += yDirection;
}
}
public static void stopMoveMap(){
setXDirection(0);
setYDirection(0);
}
private static void setXDirection1(int dir){
xDirection = dir;
}
private static void setYDirection1(int dir){
yDirection = dir;
}
public static void navigatePlayer(int nav){
switch(nav){
default:
System.out.println("default case entered... Doing nothing.");
break;
case MOVE_UP:
setYDirection1(-1);
break;
case MOVE_DOWN:
setYDirection1(1);
break;
case MOVE_LEFT:
setXDirection1(-1);
break;
case MOVE_RIGHT:
setXDirection1(1);
break;
}
}
}
Thanks for the help!
At a basic level you need a 3 dimensional array to store every single block. The problem is, that won't get you an "Infinite" world, it will get you one limited to memory.
Notch solved it by using "Chunks"--which are 3D arrays of a fixed size that can be swapped to disk when necessary.
You should also learn about how bits can be used to pack storage, for anything large you will need it--For your example, each block can be held in 3 bits, 2 for the blocks and one more for "broken". If you used this instead of a byte array you would use less than 1/2 the storage, which means you could maybe go twice as far in your world before needing to read another chunk from disk.
If you want an easier introduction to writing this kind of app, look into writing a minecraft mod using Bukkit--much of the detail work is handled for you and you can actually pick up a lot of knowledge about how stuff is done before trying to write a Minecraft clone from scratch.
So what you need is essentially a two-dimensional data structure which can be extended indefinitely (or until memory runs out) into both dimensions.
There are myriads of ways to solve this problem.
One way would be a two dimensional double-linked list (double-linked net?) where each map tile has a reference to the four adjacent tiles. That means you keep track of the tile in the center of the viewport and render the scene by iterating into all four directions until you leave the screen. When you hit an un-initialized tile, it's time to generate it.