JavaFX: Moving hundreds of ImageViews simultaneously inside a Pane (Performance problems) - java

I am currently developing a tower defence game for a university project.
At higher waves there are hundreds of enemies moving arround. My problem is that it's getting very inperforment at about 300+ enemies moving simultaneously.
Every enemy is a children of a Pane which is displayed in my scene.
I've got a method in my EnemyGraphics class which updates the positon by calling the update method:
public class EnemyGraphics extends ImageView implements EventObserver {
...
#Override
public void update() {
Platform.runLater(() -> {
relocate(enemy.getxCoordinate(), enemy.getyCoordinate());
});
}
}
I guess it's getting laggy because every enemy is updating its location on its own every time it moves by calling update().
Is there a way that I can set new coordinates for my ImageView object, without redrawing the scene and in the main FX-Thread creating a timer which redraws the entire scene in a certain interval? Or is there a other solution / method I can call to move images performantly over the pane?

Ok I've found my mistake. Everytime my enemyLogic class compute a new position it called the enemyGraphic object to update its position. During testing I've removed the functionality of the method in the enemyGraphics class but not the call.
To update the postions of all the enemys I've written a method in my GUIcontroller class:
private void startUpdateTicker() {
final AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long timestamp) {
updateEnemies();
}
};
timer.start();
}
public synchronized void updateEnemies() {
for (EnemieGUI enemy : enemyList) {
enemy.relocate(enemy.getEnemie().getxCoordinate(), enemy.getEnemie().getyCoordinate());
}
}

Related

JPanels not being drawn

I'm working on a vertical scrolling game, and I'm using a thread to generate new enemies every 2 seconds. Each enemy is an image in a JPanel. For some reason, The generated enemies are not showing up in the JFrame, but they are present. When the player collides with one of the enemies, all the enemies show up.
Here's the code:
private void checkCollision() {
for(AlienShip as : enemies) {
if(player.getBounds().intersects(as.getBounds()))
player.setVisible(false);
}
}
private void setAlien() {
alien = new AlienShip();
add(alien);
enemies.add(alien);
System.out.println("Enemies: " + enemies.size());
}
public Thread alienGenerator() {
for(int i = 0; i < 3; i++) { // these are being drawn
setAlien();
}
return new Thread(new Runnable() {
#Override
public void run() {
int sleepTime = 2000;
while(true) {
try {
Thread.sleep(sleepTime);
} catch(InterruptedException e) {
System.out.println(e);
}
setAlien(); //these aren't
}
}
});
}
private void gameLoop() {
alienGenerator().start();
mainTimer = new Timer(50, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
repaint();
checkCollision();
}
});
mainTimer.start();
}
It always seems that you're Darned If You Do And Darned If You Don't. As far as I'm concerned the code you had placed in your earlier post was adequate, as a matter of fact, it was still lacking (no PlayerShip Class). The code example in this post does even less justice. Never the less......
Before I get started I just want you to know that I personally would have tackled this task somewhat differently and the meager assistance provided here will be solely based on the code you have already provided in this and previous posts.
The reason you are not seeing your Alien Ships displaying onto the Game Board upon creation is because you don't revalidate the board panel. As you currently have your code now this can be done from within the Board.setAlien() method where the Alien Ships are added. Directly under the code lines:
alien = new AlienShip();
add(alien);
enemies.add(alien);
add the code line: revalidate();, so the code would look like this:
alien = new AlienShip();
add(alien);
enemies.add(alien);
revalidate();
Your Alien Ships should now display.
On A Side Note:
What is to happen when any Alien Ship actually makes it to the bottom of the Game Board? As a suggestion, have them re-spawn to the top of the game board (serves ya right fer missin em). This can be done from within the AlienShip.scrollShip() method by checking to see if the Alien Ship has reached the bottom of the board, for example:
public void scrollShip() {
if (getY() + 1 > this.getParent().getHeight()) {
setY(0 - PANEL_HEIGHT);
}
else {
setY(getY() + 1);
}
}
In my opinion, PANEL_HEIGHT is the wrong field name to use. I think it would be more appropriate to use something like ALIEN_SHIP_WIDTH and ALIEN_SHIP_HEIGHT. Same for the variables panelX and panelY, could be alienShipX and alienShipY. Food for thought.
As you can see in the code above the current Game Board height is acquired by polling the Game Board's getHeight() method with: this.getParent().getHeight(). This allows you to change the Game Board size at any time and the Alien Ships will know where that current boundary is when scrolling down. All this then means that the setResizable(false); property setting done in the Main Class for the Game's JFrame window can now be resizable: setResizable(true);.
You will also notice that when the Alien Ship is re-spawned at top of the Game Board it is actually out of site and it flows into view as it moves downward. I think this is a much smoother transition into the gaming area rather than just popping into view. This is accomplished with the setY(0 - PANEL_HEIGHT); code line above. As a matter of fact even when the game initially starts, your Alien Ships should flow into the the gaming area this way and that can be done from within the AlienShip.initAlienShip() method by initializing the panelY variable to panelY = -PANEL_HEIGHT;.
This now takes me to the initialization of the PANEL_WIDTH and PANEL_HEIGHT fields. The values seem enormous (224 and 250 respectively). Of course you may have set to these sizes for collision testing purposes, etc but I think an image size of 64 x 35 would most likely suffice:
This image should be a PNG image with a transparent background which then eliminates the need for the setBackground(Color.BLUE); code line located within the AlienShip.initAlienShip() method.
The AlienShip.getX() and AlienShip.getY() methods should be overridden:
#Override
public int getX() { ... }
#Override
public int getY() { ... }
I think extending the AlienShip Class to JLabel would be better than to JPanel. To JPanel seems like overkill:
public class AlienShip extends JLabel { ... }
Adding a background image to the Game Board can add pizazz to the game. This can be achieved by adding the following code to the Board.paintComponent() method:
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
ImageIcon imgIcon = new ImageIcon("images/StarBackground.png");
Image img = imgIcon.getImage();
g.drawImage(img, 0, 0, this.getSize().width, this.getSize().height, this);
}
Images can be acquired here.
This should keep you going for a while. Before to long it'll be Alien mayhem.

Designing a simple event-driven GUI

I am creating a simple event-driven GUI for a video game I am making with LibGDX. It only needs to support buttons (rectangular) with a single act() function called when they are clicked. I would appreciate some advice on structuring because the solution I've thought of so far seems to be far from ideal.
My current implementation involves all buttons extending a Button class. Each button has a Rectangle with its bounds and an abstract act() method.
Each game screen (e.g. main menu, character select, pause menu, the in-game screen) has a HashMap of Buttons. When clicked, the game screen iterates through everything in the HashMap, and calls act() on any button that was clicked.
The problem I'm having is that Buttons have to have their act() overridden from their superclass in order to perform their action, and that the Buttons aren't a member of the Screen class which contains all the game code. I am subclassing Button for each button in the game. My main menu alone has a ButtonPlay, ButtonMapDesigner, ButtonMute, ButtonQuit, etc. This is going to get messy fast, but I can't think of any better way to do it while keeping a separate act() method for each button.
Since my mute button isn't a part of the main menu screen and can't access game logic, it's act() is nothing more than mainMenuScreen.mute();. So effectively, for every button in my game, I have to create a class class that does nothing more than <currentGameScreen>.doThisAction();, since the code to actually do stuff must be in the game screen class.
I considered having a big if/then to check the coordinates of each click and call the appropriate action if necessary. For example,
if (clickWithinTheseCoords)
beginGame();
else if(clickWithinTheseOtherCoords)
muteGame();
...
However, I need to be able to add/remove buttons on the fly. When a unit is clicked from the game screen, a button to move it needs to appear, and then disappear when the unit is actually moved. With a HashMap, I can just map.add("buttonMove", new ButtonMove()) and map.remove("buttonMove") in the code called when a unit is clicked or moved. With the if/else method, I won't need a separate class for every button, but I would need to keep track of whether each clickable area tested is visible and clickable by the user at this point in the game, which seems like an even bigger headache that what I have right now.
I would provide a runnable to all the buttons which u will run in the act method. To give u a simple example.
private final Map<String, Button> buttons = new HashMap<>();
public void initialiseSomeExampleButtons() {
buttons.put("changeScreenBytton", new Button(new Runnable() {
#Override
public void run() {
//Put a change screen action here.
}
}));
buttons.put("muteButton", new Button(new Runnable() {
#Override
public void run() {
//Do a mute Action here
}
}));
}
public class Button {
//Your other stuff like rectangle
private final Runnable runnable;
protected Button(Runnable runnable) {
this.runnable = runnable;
}
public void act() {
runnable.run();
}
}
You keep track of your buttons via the map and just need to pass a runnable action to every button in the constructor. I intentionally skipped some code so that you can try yourself. If you have any questions, let me know.
Sneh's response reminded me of a fairly major oversight - instead of having to create a separate class for every button, I could use anonymous inner classes whenever I created a button, specifying its coordinates and act() method every time. I explored lambda syntax as a possible shorter method to do this, but ran into limitations with it. I ended up with a flexible solution, but ended up reducing it a bit further to fit my needs. Both ways are presented below.
Each game screen in my game is subclassed from a MyScreen class, which extends LibGDX's Screen but adds universal features like updating the viewport on resize, having a HashMap of Buttons, etc. I added to the MyScreen class a buttonPressed() method, which takes in as its one parameter an enum. I have ButtonValues enum which contains all the possible buttons (such as MAINMENU_PLAY, MAINMENU_MAPDESIGNER, etc.). In each game screen, buttonPressed() is overriden and a switch is used to perform the correct action:
public void buttonPressed(ButtonValues b) {
switch(b) {
case MAINMENU_PLAY:
beginGame();
case MAINMENU_MAPDESIGNER:
switchToMapDesigner();
}
}
The other solution has the button store a lambda expression so that it can perform actions on its own, instead of requiring buttonPressed() to act as an intermediary that performs the correct action based on what button was pressed.
To add a button, it is created with its coordinates and type (enum), and added to the HashMap of buttons:
Button b = new Button(this,
new Rectangle(300 - t.getRegionWidth() / 2, 1.9f * 60, t.getRegionWidth(), t.getRegionHeight()),
tex, ButtonValues.MAINMENU_PLAY);
buttons.put("buttonPlay", b);
To remove it, just buttons.remove("buttonPlay"). and it'll disappear from the screen and be forgotten by the game.
The arguments are the game screen which owns it (so the button can call buttonPressed() on the game screen), a Rectangle with its coordinates, its texture (used to draw it), and its enum value.
And here's the Button class:
public class Button {
public Rectangle r;
public TextureRegion image;
private MyScreen screen;
private ButtonValues b;
public Button(MyScreen screen, Rectangle r, TextureRegion image, ButtonValues b) {
this.screen = screen;
this.r = r;
this.image = image;
this.b = b;
}
public void act() {
screen.buttonPressed(b);
}
public boolean isClicked(float x, float y) {
return x > r.x && y > r.y && x < r.x + r.width && y < r.y + r.height;
}
}
isClicked() just takes in an (x, y) and checks whether that point is contained within the button. On mouse click, I iterate through all the buttons and call act() if a button isClicked.
The second way I did it was similar, but with a lambda expression instead of the ButtonValues enum. The Button class is similar, but with these changes (it's a lot simpler than it sounds):
The field ButtonValues b is replaced with Runnable r, and this is removed from the constructor. Added is a setAction() method which takes in a Runnable and sets r to the Runnable passed to it. The act() method is just r.run(). Example:
public class Button {
[Rectangle, Texture, Screen]
Runnable r;
public Button(screen, rectangle, texture) {...}
public void setAction(Runnable r) { this.r = r; }
public void act() { r.run(); }
}
To create a button, I do the following:
Button b = new Button(this,
new Rectangle(300 - t.getRegionWidth() / 2, 1.9f * 60, t.getRegionWidth(), t.getRegionHeight()),
tex);
b.setAction(() -> b.screen.doSomething());
buttons.put("buttonPlay", b);
First, a button is created with its containing game screen class, its bounding box, and its texture. Then, in the second command, I set its action - in this case, b.screen.doSomething();. This can't be passed to the constructor, because b and b.screen don't exist at that point. setAction() takes a Runnable and sets it as that Button's Runnable that is called when act() is called. However, Runnables can be created with lambda syntax, so you don't need to create an anonymous Runnable class and can just pass in the function it performs.
This method allows much more flexibility, but with one caveat. The screen field in Button holds a MyScreen, the base screen class from which all of my game screens are extended. The Button's function can only use methods that are part of the MyScreen class (which is why I made buttonPressed() in MyScreen and then realized I could just scrap the lambda expressions completely). The obvious solution is to cast the screen field, but for me it wasn't worth the extra code when I could just use the buttonPressed() method.
If I had a beginGame() method in my MainMenuScreen class (which extends MyScreen), the lambda expression passed to the button would need to involve a cast to MainMenuScreen:
b.setAction(() -> ((MainMenuScreen) b.screen).beginGame());
Unfortunately, even wildcard syntax doesn't help here.
And finally, for completeness, the code in the game loop to operate the buttons:
public abstract class MyScreen implements Screen {
protected HashMap<String, Button> buttons; // initialize this in the constructor
// this is called in every game screen's game loop
protected void handleInput() {
if (Gdx.input.justTouched()) {
Vector2 touchCoords = new Vector2(Gdx.input.getX(), Gdx.input.getY());
g.viewport.unproject(touchCoords);
for (HashMap.Entry<String, Button> b : buttons.entrySet()) {
if (b.getValue().isClicked(touchCoords.x, touchCoords.y))
b.getValue().act();
}
}
}
}
And to draw them, located in a helper class:
public void drawButtons(HashMap<String, Button> buttons) {
for (HashMap.Entry<String, Button> b : buttons.entrySet()) {
sb.draw(b.getValue().image, b.getValue().r.x, b.getValue().r.y);
}
}

Java/libGDX - issues with Actions.fadeIn() and Actions.fadeOut()

it's my first time posting and I'm self taught so be please gentle!
I've been building a bomberman replica game in libGDX using Game and Screen classes:
public class Main extends Game {
...
#Override
public void create() {
levelScreen = new LevelScreen(playerCount, new int[playerCount]);
levelScreen.level.addAction(Actions.sequence(Actions.alpha(0), Actions.fadeIn(2f)));
this.setScreen(levelScreen);
}
However when the game launches there is no fade effect.
public class LevelScreen implements Screen {
...
#Override
public void render(float delta) {
Gdx.gl.glClearColor(1, 0.1f, 0.5f, 0);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
level.act();
level.draw();
batch.end();
}
I want this levelScreen to fade in from black but it just doesn't!
When the round is over I want to fadeOut of this levelScreen to black, then fadeIn to a trophyScreen from black:
(From Main Class)
#Override
public void render() {
super.render();
if (endRoundTimer <= 0) {
trophyScreen = new TrophyScreen(playerCount, levelScreen.getScore());
levelScreen.level.addAction(Actions.sequence(Actions.fadeOut(1), Actions.run(new Runnable() {
#Override
public void run() {
setScreen(trophyScreen);
}
})));
}
}
And I've tried using the show() method in the TrophyScreen:
public class TrophyScreen implements Screen {
...
#Override
public void show() {
stage.addAction(Actions.sequence(Actions.alpha(0), Actions.fadeIn(1)));
}
I've done loads of searching and tried various things but no joy. I'm sure I'm missing something somewhere in a draw() or render() method that is preventing the fade Action from taking place.
UPDATE1
#Override public void draw() {
super.draw();
if (roundOver) {
this.getBatch().begin(); String s = String.format("%s", message);
font_text.draw(this.getBatch(), s, (90 + (2 * 30)), (this.getHeight() / 2));
this.getBatch().end();
}
For fading to work on actors, they must properly apply their own color's alpha in the draw method. And for an entire hierarchy of objects to fade at once, they must all also apply the parentAlpha parameter from the draw method signature.
So your draw method in any custom Actor subclass should look like this:
public void draw (Batch batch, float parentAlpha) {
Color color = getColor();
batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);
//..do drawing
}
If you are using a Sprite in your Actor instead of a TextureRegion (which I don't recommend due to redundancies) you must apply the color to the Sprite instead of Batch.
Note that this method of fading the whole game is not a "clean" fade. Any actors that are overlapping other actors will show through each other when the parent alpha is less than 1 during the fade. An alternative that would provide a clean-looking fade would be to draw a copy of your background (or black) over your entire scene and fade that instead.
I assume that level is an object of class that extends Stage and you are creating a control inside the stage, which is weird. You are not appling color to your font_text which I assume it is a BitmapFont
Solution, the weird way
If you want to do it in this way you will need something like that:
#Override public void draw() {
super.draw();
if (roundOver) {
getBatch().begin();
String s = String.format("%s", message);
font_text.setColor(getRoot().getColor())
font_text.draw(this.getBatch(), s, (90 + (2 * 30)), (this.getHeight() / 2));
getBatch().end();
}
}
getRoot() gets Group from Stage, we do it, because every action applied to Stage is actually applied to this Group root element. We get color (which has alpha channel) and we copy the color to the bitmapFont.
This solution is weird, because you are actually creating an Label inside Stage. It is pointless, actors plays on stage, not inside.
Solution, the good way
You want to draw text, right? So just use Label which is an actor, who shows a text. Actors do jobs for you:
stage = new Stage();
Label.LabelStyle labelStyle = new Label.LabelStyle(bitmapFont, Color.WHITE);
Label label = new Label("Hi, I am a label!", labelStyle);
stage.addActor(label);
Then you can apply actions and they will work fine (and every actor can have own actions applied).
stage.addAction(Actions.sequence(Actions.alpha(0), Actions.fadeIn(5)));
label.addAction(Actions.moveBy(0, 300, 15));
There is a lot of different actors like TextButton, Image, ScrollPane. They are customizable, easy to manage and they can be integrated in groups and tables.
Output:
A better way would be to just start by drawing a black image over everything, so you don't have to mess with every scene object's alpha. Use layering to do that. This post may be helpful.
Then you can control it's alpha channel, change it's rendering to 0 right before unpausing the game action to get it's drawing cycles back. Reactivate it on stage ending for your fade out effect.
Thank you cray, it's way better like this.

JavaFX Canvas Double Buffering

I am replicating a classic game, Pong, in Java using JavaFX. I am using java.util.Timer, java.util.TimerTask for the game loop and JavaFX's Canvas for rendering. Is there a way to add double buffering to the Canvas so the animation doesn't flicker? Or should I approach this differently? Bellow is the code. I removed some parts of it, that I think are irrelevant, since the code is around 200 lines long.
Canvas canvas = new Canvas(stageW, stageH);
GraphicsContext gc;
public void start(Stage stage) throws Exception {
Group root = new Group();
gc = canvas.getGraphicsContext2D();
Timer loop = new Timer();
root.getChildren().add(canvas);
loop.schedule(new GameLoop(), 0, 1000 / 60);
stage.setScene(new Scene(root,stageW, stageH));
stage.show();
}
public class GameLoop extends TimerTask {
#Override
public void run() {
draw(gc);
collisionDetect();
ball.move();
}
}
public void draw() {
gc.setFill(Color.BLACK);
gc.fillRect(0, 0, stageW, stageH);
gc.setFill(Color.WHITE);
gc.fillRect(lBat.getX(), lBat.getY(), lBat.getW(), lBat.getH());
gc.fillRect(rBat.getX(), rBat.getY(), rBat.getW(), rBat.getH());
gc.fillRect(ball.getX(), ball.getY(), ball.getW(), ball.getH());
}
You should do this differently.
Timer runs its own thread. You don't need an additional thread for this task.
You are executing modifications to the displayed canvas off of the JavaFX application thread (you should not modify objects in the scene off of the JavaFX thread).
JavaFX has an in-built timer based upon a pulse that is generated for each frame by the JavaFX system. This timer is called an AnimationTimer, you should use that.
You don't need double buffering.
Other higher level facilities such as Timeline or Transitions could also be used, but they are primarily for scene graph objects and you are currently basing your implementation on a Canvas which is not well suited to them.
You could consider switching your implementation from using canvas to the scene graph, which might make the implementation a bit easier, but you can code it either way.
You don't need to double-buffer the canvas as the JavaFX architecture is a delayed drawing architecture. You issue drawing commands and invoke api to adjust the scene graph on the JavaFX application thread, then, when you are done, you relinquish control of the JavaFX application thread. JavaFX will work out internally what needs to be rendered and issue updates to the viewed image using it's internal rendering technology, which just draws complete scenes (or patches the dirty bits). The canvas internal implementation has a command queue which is flushed for each frame to render any changes to the canvas, so you don't get partial updates.
Additionally, given you have a physics based game like Pong, you might want to introduce concepts such as velocity that you apply to moving objects such as the ball and update the object position on each iteration of the callback from the animation timer (this technique is demonstrated in the bouncing ball demo below).
You may be interested in reading a couple of resources:
Background information on game loops in JavaFX
Explanation of the AnimationTimer
Bouncing Ball Demo
Sample AnimationTimer code (from the bouncing ball demo linked):
final LongProperty lastUpdateTime = new SimpleLongProperty(0);
final AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long timestamp) {
if (lastUpdateTime.get() > 0) {
long elapsedTime = timestamp - lastUpdateTime.get();
checkCollisions(ballContainer.getWidth(), ballContainer.getHeight());
updateWorld(elapsedTime);
frameStats.addFrame(elapsedTime);
}
lastUpdateTime.set(timestamp);
}
};
timer.start();
The best way to achieve 60 fps is using AnimationTimer:
You can extend it through a costume class
public class AnimationClass extends AnimationTimer {
#Override
public void handle(long nano) {
//Code here
}
}
You can implement it instantly with an anonymous class
new AnimationTimer() {
#Override
public void handle(long now) {
}
}.start();
}
A nice example is here.

LibGdx Screen: render and lifecycle

I have a problem, I think its related to Screen render and its lifecycle.
Basically I have two screens (Menu and Game). In GameScreen render method i call World.update and after that my Render. In hide method (of GameScreen) i dispose of the SpriteBatch from Redner class.
So when I change the screen from Game to Menu (within World.update) Java crashes. As far as I can tell, the dispose is making the crash.
So my question is, when i set a new screen in the middle of the render cycle, is that render cycle still going to finish with its old screen? Meaning, am I calling batch.dispose before the rendering was finished, and that is why i get the problem?
Thank you for all the help
public class GameScreen extends AbstractGameScreen {
private static final String TAG = GameScreen.class.getName();
private WorldController worldController;
private WorldRenderer worldRenderer;
private boolean paused;
public GameScreen(Game game) {
super(game);
}
#Override
public void render(float deltaTime) {
// Do not update game world when paused
if (!paused) {
// Update game world by the time that has passed since last render time
worldController.update(deltaTime);
}
// Sets the clear screen color to: Cornflower Blue
Gdx.gl.glClearColor(0x64 / 255.0f, 0x95 / 255.0f, 0xed / 255.0f, 0xff / 255.0f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// Render game world to screen
worldRenderer.render();
}
#Override
public void resize(int width, int height) {
worldRenderer.resize(width, height);
}
#Override
public void show() { // Similar as create method
worldController = new WorldController(game);
worldRenderer = new WorldRenderer(worldController);
Gdx.input.setCatchBackKey(true);
}
#Override
public void hide() { // Similar to dispose method
worldRenderer.dispose();
Gdx.input.setCatchBackKey(false);
}
#Override
public void pause() {
paused = true;
}
#Override
public void resume() {
super.resume();
// Only called on Android
paused = false;
}
}
That's basically correct. The screen that calls setScreen from within its render method will have hide called on itself and then will continue through the rest of its code in its render method. So you are killing your sprite batch right before trying to draw with it.
So don't call dispose from within your hide method. In fact, it is probably bad practice for a Screen to ever call dispose on itself. You can reserve that for the Game class that owns it. For example, you could do something like this in your game class:
#Override
public void render() {
super.render();
if (getScreen() != gameScreen && gameScreen != null) {
gameScreen.dispose();
gameScreen = null;
}
}
By the way, you should probably put the SpriteBatch in your Game subclass and let all the different screens share it. It's a fairly big object to be allocating and deallocating for no reason.

Categories