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);
}
}
Related
I'm making a snake game and well It's done I can just export it and everything. Just one simple problem; For whatever reason the snake leaves spaces everytime the bodyParts increases. I didn't calculate by how much exactly but everytime the bodyParts increases, the tail leaves spaces more.
I've tried to look for the problem.
This is my first Game Project ever so please don't judge me xd.
These are the code.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.sql.Time;
import java.util.Random;
import javax.swing.Timer;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
public class GamePanel extends JPanel implements ActionListener {
static final int SCREEN_HEIGHT = 600;
static final int SCREEN_WIDTH = 600;
static final int UNIT_SIZE = 25;
static final int GAME_UNITS = (SCREEN_HEIGHT*SCREEN_WIDTH)/UNIT_SIZE;
static final int DELAY = 175;
final int x[] = new int [GAME_UNITS];
final int y[] = new int [GAME_UNITS];
int bodyParts = 1;
int applesEaten;
int appleX;
int appleY;
char direction = 'R';
boolean running = false;
Timer timer;
Random random;
private Image tail;
private Image apple;
private Image head;
GamePanel() {
random = new Random();
this.setPreferredSize(new Dimension(SCREEN_HEIGHT,SCREEN_WIDTH));
this.setBackground(Color.black);
this.setFocusable(true);
this.addKeyListener(new MyKeyAdapter());
startGame();
loadImages();
}
public void startGame() {
running = true;
newApple();
Timer timer = new Timer(DELAY,this);
timer.start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
private void loadImages() {
ImageIcon iid = new ImageIcon("src/res/tail.png");
tail = iid.getImage();
ImageIcon iia = new ImageIcon("src/res/apple.png");
apple = iia.getImage();
ImageIcon iih = new ImageIcon("src/res/head.png");
head = iih.getImage();
}
public void draw(Graphics g) {
g.setColor(Color.red);
for (int i = 0; i < SCREEN_HEIGHT/UNIT_SIZE; i++) {
//g.drawLine(i*UNIT_SIZE, 0 , i*UNIT_SIZE, SCREEN_HEIGHT);
// g.drawLine(0, i*UNIT_SIZE, SCREEN_WIDTH, i*UNIT_SIZE); //grids
}
g.drawImage(apple, appleX, appleY, this); //apple
for (int z = 0; z < bodyParts; z++) {
if (z == 0) {
g.drawImage(head, x[z], y[z], this);
} else {
g.drawImage(tail, x[z], y[z], this);
}
}
Toolkit.getDefaultToolkit().sync();
}
public void newApple() {
appleX = random.nextInt((SCREEN_WIDTH/UNIT_SIZE))*UNIT_SIZE;
appleY = random.nextInt((SCREEN_HEIGHT/UNIT_SIZE))*UNIT_SIZE;
}
public void move() {
for(int z = bodyParts;z>0;z--) {
x[z] = x[(z-1)];
y[z] = y[(z-1)];
switch(direction) {
case 'U':
y[0] -= UNIT_SIZE;
break;
case 'D':
y[0] += UNIT_SIZE;
break;
case 'L':
x[0] -= UNIT_SIZE;
break;
case 'R':
x[0] += UNIT_SIZE;
break;
}
}
}
public void checkApple() {
if((x[0] == appleX) && (y[0] == appleY)) {
bodyParts++;
applesEaten++;
newApple();
}
}
public void checkCollisions() {
//checks if head collides with body
for(int i = bodyParts;i<0;i--) {
if((x[0] == x[i])&& (y[0] == y[i])) {
running = false;
}
}
//checks if head touches left border
if(x[0] < 0) {
running = false;
}
//check if head touches right border
if(x[0] > SCREEN_WIDTH) {
running = false;
}
//check if head touches top border
if(y[0] < 0) {
running = false;
}
//check if head touches bottom border
if(y[0] > SCREEN_HEIGHT) {
running = false;
}
if(!running) {
timer.stop();
}
}
public void gameOver(Graphics g) {
}
#Override
public void actionPerformed(ActionEvent e) {
if(running) {
move();
checkApple();
checkCollisions();
loadImages();
}
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;
}
}
}
}
This is when the bodyParts = 2 bodyParts = 2;
And, this is when the bodyParts = 3
bodyParts = 3;
I'm making a maze game for a class assignment, and one of the requirements is playing a sound at the start of the game and when the player reaches the exit.
I'm using onCompletionListener because when the player reaches the exit I have to wait until the sound has finished playing before playing the sound at the start of the next level, otherwise the app will hang.
However, now the screen is not being redrawn after the method createMaze() is called. If the player moves again, only then the canvas is redrawn and shows the newly created maze.
Here's what I expected the code to do:
Player reaches exit
Play Exit sound
After finishing playing the sound, create a new maze and draw it on the screen
Here's my code:
package com.example.labirinto;
import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorManager;
import android.media.MediaPlayer;
import android.os.Vibrator;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.Random;
import java.util.Stack;
public class GameView extends View implements MediaPlayer.OnCompletionListener {
private enum Direction {
UP, DOWN, LEFT, RIGHT
}
private Vibrator vibrator;
private SensorManager sensorManager;
private Sensor gyroscopeSensor;
private static final int MAX_LEVELS = 3;
private int currentLevel = 1;
private String nextAction;
private Cell[][] cells;
private Cell player, exit;
private static final int COLS = 7, ROWS = 10;
private static final String
ACTION_CREATE_MAZE = "ACTION_CREATE_MAZE",
ACTION_END_GAME = "ACTION_END_GAME";
private static final float WALL_THICKNESS = 4;
private float cellSize, hMargin, vMargin;
private Paint wallPaint, playerPaint, exitPaint;
private Random random;
MediaPlayer mp;
public GameView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
wallPaint = new Paint();
wallPaint.setColor(Color.BLACK);
wallPaint.setStrokeWidth(WALL_THICKNESS);
playerPaint = new Paint();
playerPaint.setColor(Color.RED);
exitPaint = new Paint();
exitPaint.setColor(Color.BLUE);
random = new Random();
vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
gyroscopeSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
createMaze();
}
private void createMaze() {
Stack<Cell> stack = new Stack<>();
Cell current, next;
cells = new Cell[COLS][ROWS];
for (int x = 0; x < COLS; x++) {
for (int y = 0; y < ROWS; y++) {
cells[x][y] = new Cell(x, y);
}
}
player = cells[0][0];
exit = cells[COLS - 1][ROWS - 1];
current = cells[0][0];
current.visited = true;
do {
next = getNeighbour(current);
if (next != null) {
removeWall(current, next);
stack.push(current);
current = next;
current.visited = true;
} else {
current = stack.pop();
}
} while (!stack.empty());
nextAction = "NOTHING";
playSound("START");
}
private Cell getNeighbour(Cell cell) {
ArrayList<Cell> neighbours = new ArrayList<>();
//left neighbour
if (cell.col > 0 && !cells[cell.col - 1][cell.row].visited) {
neighbours.add(cells[cell.col - 1][cell.row]);
}
//right neighbour
if (cell.col < (COLS - 1) && !cells[cell.col + 1][cell.row].visited) {
neighbours.add(cells[cell.col + 1][cell.row]);
}
//top neighbour
if (cell.row > 0 && !cells[cell.col][cell.row - 1].visited) {
neighbours.add(cells[cell.col][cell.row - 1]);
}
//bottom neighbour
if (cell.row < (ROWS - 1) && !cells[cell.col][cell.row + 1].visited) {
neighbours.add(cells[cell.col][cell.row + 1]);
}
if (neighbours.size() > 0 ) {
int index = random.nextInt(neighbours.size());
return neighbours.get(index);
}
return null;
}
private void removeWall(Cell current, Cell next) {
//current under next
if (current.col == next.col && current.row == next.row + 1) {
current.topWall = false;
next.bottomWall = false;
}
//current above next
if (current.col == next.col && current.row == next.row - 1) {
current.bottomWall = false;
next.topWall = false;
}
//current to the right of the next
if (current.col == next.col + 1 && current.row == next.row) {
current.leftWall = false;
next.rightWall = false;
}
//current to the left of the next
if (current.col == next.col - 1 && current.row == next.row) {
current.rightWall = false;
next.leftWall = false;
}
}
public void drawCurrentLevelText(Canvas canvas) {
Paint textPaint = new Paint();
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setColor(Color.BLACK);
textPaint.setTextSize(48f);
int xPos = (canvas.getWidth() / 2);
canvas.drawText("Level " + currentLevel, xPos, 56, textPaint);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
drawCurrentLevelText(canvas);
int width = getWidth();
int height = getHeight();
if (width/height < COLS/ROWS) {
cellSize = width/(COLS + 1) - 8;
} else {
cellSize = height/(ROWS + 1) - 8;
}
hMargin = (width - COLS*cellSize)/2;
vMargin = (height - ROWS*cellSize)/2;
canvas.translate(hMargin, vMargin);
if (currentLevel == 2) {
wallPaint.setColor(Color.BLUE);
} else if (currentLevel == 3) {
wallPaint.setColor(Color.RED);
}
for (int x = 0; x < COLS; x++) {
for (int y = 0; y < ROWS; y++) {
if (cells[x][y].topWall) {
canvas.drawLine(x*cellSize, y*cellSize, (x + 1)*cellSize, y*cellSize, wallPaint);
}
if (cells[x][y].leftWall && !(x == 0 && y == 0)) {
canvas.drawLine(x*cellSize, y*cellSize, x*cellSize, (y+1)*cellSize, wallPaint);
}
if (cells[x][y].rightWall && !(x == COLS - 1 && y == ROWS - 1)) {
canvas.drawLine((x+1)*cellSize, y*cellSize, (x + 1)*cellSize, (y+1)*cellSize, wallPaint);
}
if (cells[x][y].bottomWall) {
canvas.drawLine(x*cellSize, (y+1)*cellSize, (x+1)*cellSize, (y+1)*cellSize, wallPaint);
}
}
}
float margin = cellSize/10;
canvas.drawRect(
player.col*cellSize+margin,
player.row*cellSize+margin,
(player.col + 1)*cellSize-margin,
(player.row + 1)*cellSize-margin,
playerPaint
);
}
private void movePlayer(Direction direction) {
switch (direction) {
case UP:
if (!player.topWall) {
player = cells[player.col][player.row - 1];
} else {
vibrator.vibrate(400);
}
break;
case DOWN:
if (!player.bottomWall) {
player = cells[player.col][player.row + 1];
} else {
vibrator.vibrate(400);
}
break;
case LEFT:
if (!(player.leftWall || player.col == 0)) {
player = cells[player.col - 1][player.row];
} else {
vibrator.vibrate(400);
}
break;
case RIGHT:
if (!player.rightWall) {
player = cells[player.col + 1][player.row];
} else {
vibrator.vibrate(400);
}
break;
}
checkExit();
invalidate();
}
private void playSound(String type) {
int resId;
switch (type) {
case "START":
resId = R.raw.start;
break;
case "ERROR":
resId = R.raw.retardado;
break;
case "EXIT":
resId = R.raw.miseravel_genio;
break;
default:
resId = 0;
break;
}
mp = MediaPlayer.create(getContext(), resId);
mp.setOnCompletionListener(this);
mp.start();
}
#Override
public void onCompletion(MediaPlayer mediaPlayer) {
mp.release();
switch (nextAction) {
case ACTION_CREATE_MAZE:
currentLevel++;
createMaze();
break;
case ACTION_END_GAME:
getContext().startActivity(new Intent(getContext(),com.example.labirinto.GameOver.class));
break;
default:
System.out.println("Finished");
break;
}
}
private void checkExit() {
if (player == exit) {
if (currentLevel < MAX_LEVELS) {
nextAction = ACTION_CREATE_MAZE;
} else {
nextAction = ACTION_END_GAME;
}
playSound("EXIT");
}
}
public void onSensorChanged(SensorEvent event) {
System.out.println(event.sensor.getName());
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
return true;
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
float x = event.getX();
float y = event.getY();
float playerCenterX = hMargin + (player.col + 0.5f)*cellSize;
float playerCenterY = vMargin + (player.row + 0.5f)*cellSize;
float dx = x - playerCenterX;
float dy = y - playerCenterY;
float absDx = Math.abs(dx);
float absDy = Math.abs(dy);
if (absDx > cellSize || absDy > cellSize) {
if (absDx > absDy) {
//move in x-direction
if (dx > 0) {
//move to the right
movePlayer(Direction.RIGHT);
} else {
//move to the left
movePlayer(Direction.LEFT);
}
} else {
//move in y-direction
if (dy > 0) {
//move down
movePlayer(Direction.DOWN);
} else {
//move up
movePlayer(Direction.UP);
}
}
}
return true;
}
return super.onTouchEvent(event);
}
private class Cell {
boolean topWall = true, leftWall = true, bottomWall = true, rightWall = true, visited = false;
int col , row;
public Cell(int col, int row) {
this.col = col;
this.row = row;
}
}
}
On the checkExit() method, if the player has reached the exit, I check if it's the final level to define the next action, and then I play the exit sound.
Then, on the playSound() method, I select the sound to play, set the listener and play the sound.
Finally, on the onCompletion() method, I release call the release() method on my MediaPlayer object, then I call the next action.
So, I was able to find a solution by calling invalidate() inside the onCompletion() method. By doing this, the canvas is redrawn automatically.
I'm not sure if that was the best way to solve this, though.
#Override
public void onCompletion(MediaPlayer mediaPlayer) {
mp.release();
switch (nextAction) {
case ACTION_CREATE_MAZE:
currentLevel++;
createMaze();
invalidate();
break;
case ACTION_END_GAME:
getContext().startActivity(new Intent(getContext(),com.example.labirinto.GameOver.class));
invalidate();
break;
default:
System.out.println("Finished");
break;
}
}
I am still pretty new to java and javafx and I have created a minesweeper game. I want to add a small menu bar where the user can select the game difficulty (the number of tiles). I have created a menu method, but I am unsure of where to add it to scene. Everywhere I have tried to put the menu method I get exception errors.
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Minesweeper extends Application {
private int TILE_SIZE = 50;
private static final int W = 800;
private static final int H = 600;
private int X_TILES = W / TILE_SIZE;
private int Y_TILES = H / TILE_SIZE;
private final String[] gameType = {"Easy", "Medium", "Hard", "Very Hard"};
private String difficulty;
#FXML
private Menu gameMenu;
private Tile[][] grid = new Tile[X_TILES][Y_TILES];
private Scene scene;
public void menu() {
for(String game : gameType){
MenuItem menuItem = new MenuItem(game);
menuItem.setUserData(game);
menuItem.setOnAction((ActionEvent event) -> {
selectGame(event);
});
gameMenu.getItems().add(menuItem);
}
}
private void selectGame(ActionEvent event) {
MenuItem menuItem = (MenuItem)event.getSource();
difficulty = (String)menuItem.getUserData();
switch (difficulty) {
case "Easy":
TILE_SIZE = 200;
break;
case "Medium":
TILE_SIZE = 100;
break;
case "Hard":
TILE_SIZE = 50;
break;
case "Very Hard":
TILE_SIZE = 40;
break;
default:
break;
}
}
private Parent createContent() {
Pane root = new Pane();
root.setPrefSize(W, H);
for (int y = 0; y < Y_TILES; y++) {
for (int x = 0; x < X_TILES; x++) {
Tile tile = new Tile(x, y, Math.random() < 0.2);
grid[x][y] = tile;
root.getChildren().add(tile);
}
}
for (int y = 0; y < Y_TILES; y++) {
for (int x = 0; x < X_TILES; x++) {
Tile tile = grid[x][y];
if (tile.hasBomb)
continue;
long bombs = getNeighbors(tile).stream().filter(t -> t.hasBomb).count();
if (bombs > 0)
tile.text.setText(String.valueOf(bombs));
}
}
return root;
}
private List<Tile> getNeighbors(Tile tile) {
List<Tile> neighbors = new ArrayList<>();
int[] points = new int[] {
-1, -1,
-1, 0,
-1, 1,
0, -1,
0, 1,
1, -1,
1, 0,
1, 1
};
for (int i = 0; i < points.length; i++) {
int dx = points[i];
int dy = points[++i];
int newX = tile.x + dx;
int newY = tile.y + dy;
if (newX >= 0 && newX < X_TILES
&& newY >= 0 && newY < Y_TILES) {
neighbors.add(grid[newX][newY]);
}
}
return neighbors;
}
private class Tile extends StackPane {
private int x, y;
private boolean hasBomb;
private boolean isOpen = false;
private Rectangle border = new Rectangle(TILE_SIZE - 2, TILE_SIZE - 2);
private Text text = new Text();
Alert alert = new Alert(AlertType.CONFIRMATION, "Game Over! Play Again?");
public Tile(int x, int y, boolean hasBomb) {
this.x = x;
this.y = y;
this.hasBomb = hasBomb;
border.setStroke(Color.LIGHTGRAY);
text.setFont(Font.font(18));
text.setText(hasBomb ? "X" : "");
text.setVisible(false);
getChildren().addAll(border, text);
setTranslateX(x * TILE_SIZE);
setTranslateY(y * TILE_SIZE);
setOnMouseClicked(e -> open());
}
public void open() {
if (isOpen)
return;
if (hasBomb) {
Optional<ButtonType> result = alert.showAndWait();
if (result.isPresent() && result.get() == ButtonType.OK) {
scene.setRoot(createContent());
}
return;
}
isOpen = true;
text.setVisible(true);
border.setFill(null);
if (text.getText().isEmpty()) {
getNeighbors(this).forEach(Tile::open);
}
switch (text.getText()) {
case "1":
text.setFill(Color.BLUE);
break;
case "2":
text.setFill(Color.FORESTGREEN);
break;
case "3":
text.setFill(Color.RED);
break;
case "4":
text.setFill(Color.DARKBLUE);
break;
case "5":
text.setFill(Color.MAROON);
break;
case "6":
text.setFill(Color.AQUAMARINE);
break;
case "7":
text.setFill(Color.BLACK);
break;
case "8":
text.setFill(Color.GRAY);
break;
default:
break;
}
}
}
#Override
public void start(Stage stage) throws Exception {
scene = new Scene(createContent());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
You can add it like this:
public MenuBar menu() {
gameMenu = new Menu("Difficulty");
for(String game : gameType){
MenuItem menuItem = new MenuItem(game);
menuItem.setUserData(game);
menuItem.setOnAction((ActionEvent event) -> {
selectGame(event);
});
gameMenu.getItems().add(menuItem);
}
MenuBar menuBar = new MenuBar(gameMenu);
return menuBar;
}
private Parent createContent() {
VBox root = new VBox();
Pane content = new Pane();
content.setPrefSize(W, H);
for (int y = 0; y < Y_TILES; y++) {
for (int x = 0; x < X_TILES; x++) {
Tile tile = new Tile(x, y, Math.random() < 0.2);
grid[x][y] = tile;
content.getChildren().add(tile);
}
}
for (int y = 0; y < Y_TILES; y++) {
for (int x = 0; x < X_TILES; x++) {
Tile tile = grid[x][y];
if (tile.hasBomb)
continue;
long bombs = getNeighbors(tile).stream().filter(t -> t.hasBomb).count();
if (bombs > 0)
tile.text.setText(String.valueOf(bombs));
}
}
root.getChildren().addAll(menu(), content);
return root;
}
In the menu method I created the instance for gameMenu which is annotated in your example with #FXML, so this line might not be needed. The method however returns a MenuBar that contains the menu.
This bar is then added as a child to the root element. I also introduced a new layer for layouting (VBox) with the menu and the original content as children.
See also this oracle article.
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.