Add menu to javafx game - java

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.

Related

On Android, canvas is not being redrawn after onCompletion of playing a sound

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;
}
}

Two threads calling the same keyEvents method

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);
}
}

Method: "getMap" and "map" doesn't work

I am a very beginner programmer, so I follow a youtube-tutorial that shows me how to build a simple snake app. The men at the youtube-tutorial got an older version of AndroidStudio than me. He uses AndroidStudio version 2.1.2, I use AndroidStudio version 2.2.2 with java language.
My problem is that he uses the commands getMap and map and it works. When I do that it doesn't work.My guestion is what i have to replace in the >methods "getMap" and "map"
The youtube-tutorial that I follow: https://www.youtube.com/watch?v=bPlG7ra83lo
At 12:00 he uses this command first time.
Game Engine
package pelgrom.laurens.snake101.engine;
import android.service.quicksettings.Tile;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import pelgrom.laurens.snake101.Classes.Coordinate;
import pelgrom.laurens.snake101.enums.TileType;
/**
* Created by Laptop on 23-1-2017.
*/
public class GameEngine {
public static final int GameWidth = 28;
public static final int Gameheight = 42;
private List<Coordinate> walls = new ArrayList<>();
public GameEngine() {
}
public void initGame(){
AddWalls();
}
public TileType()() getMap() {
TileType()() map = new TileType(GameWidth) (Gameheight);
for (int x = 0; x < GameWidth; x++) {
for (int y = 0; y < Gameheight; y++) {
map(X)(Y) = TileType.Nothing;
}
}
for (Coordinate wall: walls) {
map(wall.getX())(wall.getY()) = TileType.Wall;
}
return map;
}
//fout zit hem in de "map" en de "getMap"
private void AddWalls() {
//Top and bottom walls
for (int x = 0; x < GameWidth; x ++) {
walls.add(new Coordinate(x,0));
walls.add(new Coordinate(x,Gameheight-1));
}
//Left and Right walls
for (int y = 1; y < Gameheight; y++){
walls.add(new Coordinate(0,y));
walls.add (new Coordinate(GameWidth -1 , y));
}
}
}
`
SnakeView
`package pelgrom.laurens.snake101.views;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import pelgrom.laurens.snake101.enums.TileType;
/**
* Created by Laptop on 23-1-2017.
*/
public class SnakeView extends View {
private Paint mPaint = new Paint();
private TileType snakeViewMap()();
public SnakeView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setSnakeViewMap(TileType()() map )( this.snakeViewMap = map; )
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if( snakeViewMap != null ){
float tileSizeX = canvas.getWidth() / snakeViewMap.length;
float tileSizeY = canvas.getHeight() / snakeViewMap.length;
float circleSize = Math.min(tileSizeX, tileSizeY) / 2;
for (int x = 0; x < snakeViewMap.lenght; x++) {
for (int y = 0; y < snakeViewMap(x). lenght; y++) {
switch (snakeViewMap(x)) {
case Nothing:
mPaint.setColor(Color.WHITE);
break;
case Wall:
mPaint.setColor(Color.GREEN);
break;
case SnakeHead:
mPaint.setColor(Color.RED);
break;
case SnakeTail:
mPaint.setColor(Color.GREEN);
break;
case Apple:
mPaint.setColor(Color.RED);
break;
}
canvas.drawCircle(x * tileSizeX + tileSizeX / 2f + circleSize / 2, y * tileSizeY + tileSizeY / 2f + circleSize / 2, circleSize, mPaint);
}
}
}
}
}
`
main activity
`package pelgrom.laurens.snake101;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import pelgrom.laurens.snake101.engine.GameEngine;
import pelgrom.laurens.snake101.views.SnakeView;
public class Activity extends AppCompatActivity {
private GameEngine gameEngine;
private SnakeView snakeView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout);
gameEngine = new GameEngine();
gameEngine.initGame();
snakeView = (SnakeView)findViewById(R.id.snakeView);
snakeView.setSnakeViewMap(gameEngine.getMapAsync());
snakeView.invalidate();
}
}
`
The problem with your code is that you are using Round Bracket () while it has to square brackets [] since you are declaring two dimensional arrays. Replace them like :
public TileType[][] getMap() {
TileType[][] map = new TileType[GameWidth][Gameheight];
for (int x = 0; x < GameWidth; x++) {
for (int y = 0; y < Gameheight; y++) {
map[x][y] = TileType.Nothing;
}
}
for (Coordinate wall: walls) {
map[wall.getX()][wall.getY()] = TileType.Wall;
}
return map;
}
PS: you really need to start with the basics.

Threading issues with custom JPanels (Java Swing)

I'm sorry if you've been annoyed by the recent posts. I usually can figure things if I Google them or use the API, but this project may have been over my head. I am using Swing to create a simple calculator that displays in a LED format in the IDE BlueJ (which offers no macros).
I'm sorry that the code is long but here is the JFrame I am displaying it with.
//Swing classes
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.Box;
import javax.swing.JTextField;
import javax.swing.JButton;
//Graphics classes
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.geom.AffineTransform;
import java.awt.Dimension;
import java.awt.event.*;
import java.awt.FlowLayout;
//Array, and math classes
import java.util.Arrays;
import java.util.ArrayList;
import java.lang.Math;
public class MCVE
{
public static void main(String[] args)
{
SwingUtilities.invokeLater( new Runnable() {
public void run() {
//frame width and height
final int FRAME_WIDTH = 317;
final int FRAME_HEIGHT = 415;
final JFrame myFrame = new JFrame();
myFrame.setSize(FRAME_WIDTH,FRAME_HEIGHT);
final JPanel myPanel = new JPanel();
myPanel.setLayout(null);
final JFrame ledFrame = new JFrame();
ledFrame.setLayout(new FlowLayout());
final MLED[] ledDisplay = new MLED[8];
for(int i = 0; i < ledDisplay.length; i++)
{
ledDisplay[i] = new MLED();
}
for(int i = 0; i < ledDisplay.length; i++)
{
ledFrame.add(ledDisplay[i]);
}
//Buttons on Simple Calculator
final JButton zeroButton = new JButton("0");
zeroButton.setSize(50,50);
zeroButton.setLocation(62,206);
final JButton endButton = new JButton("End");
endButton.setSize(102,50);
endButton.setLocation(186,275);
ledFrame.pack();
ledFrame.setSize(480,170);
ledFrame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
ledFrame.setLocationRelativeTo( null );
ledFrame.setVisible( true );
myFrame.add(myPanel);
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.setVisible(true);
myPanel.add(zeroButton);
myPanel.add(endButton);
ledFrame.pack();
ledFrame.setSize(480,170);
ledFrame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
ledFrame.setLocationRelativeTo( null );
ledFrame.setVisible( true );
class CalculatorListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
//The entry
int x=0;
//final answer
int finalAnswer = 0;
//the variable count has two uses: to give an index to store into the array storage and to signal to the program that the operation has been completed and that it is time to move into a new operation.
int count = 0;
int count2 = 6;
String tempString;
ArrayList<Character> toLedArray = new ArrayList<Character>();
if(e.getSource() == zeroButton)
{
if(count>1)
{
x = 0;
count = 0;
}
else
{
if(x!=0)
{
x*=10;
x+=0;
}
else
{
x = 0;
}
}
tempString = String.valueOf(x);
for (char c : tempString.toCharArray()) {
toLedArray.add(c);
}
count = toLedArray.size();
if(count <= 8)
{
for(int i = 0; i < count ; i++)
{
tempString += toLedArray.get(i);
System.out.println("Calculator: 0 Button: Count = " + count2);
ledDisplay[count2].display(toLedArray.get(i));
count2--;
}
}
}
else if(e.getSource() == endButton)
{
myFrame.dispose();
}
}
}
//buttons are given life
CalculatorListener myListener = new CalculatorListener();
zeroButton.addActionListener(myListener);
endButton.addActionListener(myListener);
}
});
}
}
This class creates a JFrame that holds 2 buttons, one with the number zero on it and one that says "end". The zero button when pressed is supposed to light up the right most 7 segment display into the shape of a zero. Unfortunately, this is not the case. I suspect that there may be some threading issues in coming from the Swing operations.
I was hoping that you could take a look at it and point out my logic errors in the code.
Here are the two classes for the custom JPanel and JButton
public class MLED extends JPanel
{
// instance variables - replace the example below with your own
private static Bar[] bars = new Bar[7];
private GridBagConstraints c = new GridBagConstraints();
private final Dimension SIZE = new Dimension(60, 140);
/**
* Constructor for objects of class LED
*/
public MLED()
{
bars[0] = new MBar(30, 10);
bars[1] = new MBar(10, 30);
bars[2] = new MBar(10, 30);
bars[3] = new MBar(30, 10);
bars[4] = new MBar(10, 30);
bars[5] = new MBar(10, 30);
bars[6] = new Bar(30, 10);
this.setLayout(new GridBagLayout());
System.out.println("The LED class is being accessed");
}
public void display(char str)
{
if(str == '0')
{
System.out.println("LED: The zero has been pressed");
bars[0].lightUp();
bars[1].lightUp();
bars[2].lightUp();
bars[4].lightUp();
bars[5].lightUp();
bars[6].lightUp();
}
repaint();
}
public void clear()
{
bars[0].dim();
bars[1].dim();
bars[2].dim();
bars[3].dim();
bars[4].dim();
bars[5].dim();
bars[6].dim();
bars[7].dim();
repaint();
}
#Override public void paintComponent(Graphics g)
{
super.paintComponent(g);
for(int i = 0; i < bars.length; i++)
{
switch(i)
{
case 0:
c.gridx = 1;
c.gridy = 0;
break;
case 1:
c.gridx = 0;
c.gridy = 1;
break;
case 2:
c.gridx = 2;
c.gridy = 1;
break;
case 3:
c.gridx = 1;
c.gridy = 2;
break;
case 4:
c.gridx = 0;
c.gridy = 3;
break;
case 5:
c.gridx = 2;
c.gridy = 3;
break;
case 6:
c.gridx = 1;
c.gridy = 4;
break;
}
this.add(bars[i], c);
}
}
#Override public Dimension getPreferredSize()
{
return SIZE;
}
}
And the custom JComponent:
public class MBar extends JComponent
{
// instance variables - replace the example below with your own
private boolean litUp = false;
private boolean vertical = false;
private boolean rotated = false;
private boolean rotClockwise = false;
private int positionX;
private int positionY;
private final Dimension SIZE;
public MBar(int sizeX, int sizeY)
{
litUp = false;
//vertical = vert;
SIZE = new Dimension(sizeX, sizeY);
//positionX = posX;
//positionY = posY;
repaint();
}
public void lightUp()
{
litUp = true;
repaint();
}
public void dim()
{
litUp = false;
repaint();
}
public void setDirection(boolean vert)
{
vertical = vert;
repaint();
}
public void rotate(boolean rot, boolean dir)
{
rotated = rot;
rotClockwise = dir;
repaint();
}
public void moveRight()
{
positionX = positionX + 11;
repaint();
}
public void moveLeft()
{
positionX = positionX - 11;
repaint();
}
#Override public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2D = (Graphics2D)g;
System.out.println("BAR: Paint Component");
Color color;
int sizeX;
int sizeY;
// if(vertical == true)
// {
// sizeX = 10;
// sizeY = 30;
if(rotated == true)
{
if(rotClockwise == true)
{
g2D.rotate(0.3398);
}
else
{
g2D.rotate(-0.3398);
}
}
if(litUp == true)
{
color = Color.red;
}
else
{
color = Color.black;
}
// else{
// sizeX = 30;
// sizeY = 10;
// if(litUp == true)
// {
// color = Color.red;
// }
// else
// {
// color = Color.black;
// }
// }
g2D.setColor(color);
g2D.fillRect(0 , 0, SIZE.width, SIZE.height);
}
#Override public Dimension getPreferredSize(){
return SIZE;
}
}
The problem is private static Bar[] bars = new Bar[7];.
Every instance of MLED you create re-initialises this with their own values, meaning that each instance of MLED actually gets the SAME reference, instead of there own, which they should have.
When I make it private Bar[] bars = new Bar[7]; it produces...

How do I make an infinite tiled map? (java)

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.

Categories