How to properly remove images for animations in JavaFX - java

im trying to create a simple space invaders game. This is my first ever game, and so im having some issues. The invaders is a rectangle object with an image of the oldschool invader. In each frame im drawing those boxes 15 pixels to the right, and then using root.getChildren().remove(), to remove the sprites in each frame. But it causes some weird behaviour. It kind of looks like it isnt being removed quickly enough, and so just when i launch the app it kind of explodes, and after that there seems to be a little bit of latency causing two images to be displayed constantly.
I guess it would probably help for you to see it yourself and so i will post all of my files here.
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Space invaders");
primaryStage.setResizable(false);
Group root = new Group();
Scene game = new Scene(root, Color.BLACK);
primaryStage.setFullScreen(true);
Images images = new Images();
// Make canvas and add it to primaryStage
Canvas canvas = new Canvas(1280,720);
root.getChildren().add(canvas);
GraphicsContext gc = canvas.getGraphicsContext2D();
new AnimationTimer() {
int move = 0;
private long lastUpdate = 0;
ArrayList<Sprite> enemies = new ArrayList<>();
#Override
public void handle(long now) {
if (now - lastUpdate >= 150_000_000) {
int spacing = 0;
try {
for (int i = 0; i < enemies.size(); i++) {
root.getChildren().remove(enemies.get(i));
enemies.remove(i);
}
} catch (IndexOutOfBoundsException ioobe) {
System.out.println(ioobe.toString());
}
for (int i = 0; i < 12; i++) {
for (int j = 0; j < 6; j++) {
Sprite enemy = new Sprite(60 * i + spacing + move, 43 * j, 60, 43, "enemy", true, images.getEnemy1());
enemies.add(enemy);
root.getChildren().add(enemy);
}
spacing += 15;
}
move += 5;
lastUpdate = now;
}
}
}.start();
primaryStage.setScene(game);
primaryStage.show();
}
}
If you want to run the program yourself, her is the Sprite class:
public class Sprite extends Rectangle {
private String type;
private Boolean alive;
public Sprite(int x, int y, int w, int h, String type, boolean alive, Image image) {
this.setTranslateX(x);
this.setTranslateY(y);
this.setWidth(w);
this.setHeight(h);
this.type = type;
this.alive = alive;
this.setFill(new ImagePattern(image));
}
public String getType() {
return type;
}
public Boolean getAlive() {
return alive;
}
}

This is just not the way the scene graph is supposed to be used. Why do you constantly remove and re-add your sprites? Why don't you just update their positions via the setTranslateX/Y which you are already using anyway?

try {
for (int i = 0; i < enemies.size(); i++) {
root.getChildren().remove(enemies.get(i));
enemies.remove(i);
}
} catch (IndexOutOfBoundsException ioobe) {
System.out.println(ioobe.toString());
}
A correct implementation wouldn't require you to catch a IndexOutOfBoundsException. I don't know why this would throw such an exception though, since the check i < enemies.size() should ensure the index is valid.
Note however that this skips every other list element:
First you remove the first element of enemies from both lists (the one at index 0). This results in all elements remaining in the enemies list to be shifted to the left, i.e. the one that was at index 1 before the removal is now at index 0. Since you increment the index after each loop iteration, all elements at odd indices are skipped.
There's a simpler way of implementing this btw:
root.getChildren().removeAll(enemies);
enemies.clear();
Note
Recreating your sprites in every frame should be avoided. The memory requirement of Nodes doesn't make them objects you should throw away, if this can be avoided. Otherwise the garbage collector will be kept busy cleaning up the objects you "throw" away at best and an OutOfMemoryException happens at worst...
Reuse the sprites and modify the properties instead.
Also I recommend going with ImageView instead of Rectangles filled with ImagePattern. You can adjust the size using the fitWidth and fitHeight properties.

Related

Avoid repaint canvas when scrolling

I followed this tutorial, in order to display some graphical element on a canvas:
Here is where I come:
Scroll listener
vBar.addListener(SWT.Selection, new Listener() {
#Override
public void handleEvent(Event event) {
System.out.println("Scroll event");
int vSelection = vBar.getSelection();
int destY = -vSelection - origin.y;
canvas.scroll(0, destY, 0, 0, canvas.getSize().x, canvas.getSize().y, false);
origin.y = -vSelection;
}
});
Resize listener
canvas.addListener(SWT.Resize, new Listener() {
#Override
public void handleEvent(Event e) {
System.out.println("Resize event");
Rectangle client = canvas.getClientArea();
vBar.setThumb(Math.min(theoreticalScreenHeight, client.height));
vBar.setPageIncrement(Math.min(theoreticalScreenHeight, client.height));
vBar.setIncrement(20);
int vPage = canvas.getSize().y - client.height;
int vSelection = vBar.getSelection();
if (vSelection >= vPage) {
if (vPage <= 0)
vSelection = 0;
origin.y = -vSelection;
}
}
});
Paint listener
canvas.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
System.out.println("Paint event");
// Costly drawing here
...
// rectangle
for (int productIndex = 0; productIndex < products.size(); productIndex++) {
Product p = products.get(productIndex);
...
e.gc.drawRoundRectangle(rectEdge.x, rectEdge.y, (int) productWidth, productHeight, 30, 30);
Point prevSubRectEdge = rectEdge; // Coordinate of each result rectangle
for (int resultIndex = 0; resultIndex < productResultsAmount; resultIndex++) {
Result result1 = p.getResult(resultIndex);
...
e.gc.drawLine(sepStart.x, sepStart.y, sepEnd.x, sepEnd.y);
...
}
rectEdge.y += 300;
}
drawLinks(e);
}
});
Since I display a lot of graphical element, it can take some time to be displayed. That is why I would like to display them once at the beginning, and because there is a lot, I would like to scroll vertically on the canvas, so I can see all of my elements.
My problem here is that the scroll event trigger the repaint event, which is redrawing the elements. But since it's a costly operation, it is not the thing that I want.
I would like to avoid this repainting behavior, and just paint the elements once in the beginning, and then simply scroll to see them.
I have added the code, because I thought somebody would like to see it, but I don't think it is really helpfull, it was just to show the difference between the tutorial and my code. I think it is more a matter of swt comprehension, about listener and repainting life cycle ?
I also have to say that I never call canvas.redraw().
Here is a screenshot of my canvas:

Multiple viewports in javafx application

I am attempting to create a multi-user, multi-screen application within JavaFX, and I am having trouble with the multi-screen part.
Think an FPS with couch co-op: the screen splits evenly depending on how many people are connected locally. Every different view is looking in a different direction, and at a different place, but at the same 'world'.
I learned the hard way (confirmed in a comment here) that each node can only appear in the active scene graph once, so, for instance, I cannot have the same node spread across multiple distinct panes (which is conceptually ideal). And that's where I'm not sure where to go next.
Looking at other similar technologies like OpenGL, (example) most have the ability to create another viewport for the application, but JavaFX does not seem to have this.
Some things I have ruled out as unreasonable/impossible (correct me if I'm wrong):
Using shapes to create a clip mask for a pane (Can only use one mask per node)
Having a complete deep copy of each node for each view (too expensive, nodes moving constantly)
Having x number of users each have their own set of nodes and have one update loop update every node in every view (too expensive, too many nodes for scene graph, too much)
How would I go about creating multiple views of the same set of nodes, while still maintaining individual user control, and changing persistence/moving nodes, between every different view?
Thanks.
Thanks to the people in the comments for the solution. I ended up creating a background model for each view to mirror, and then creating a new set of nodes per view that has the relevant properties bound to the background model.
The update loop then has to only update the one background model, and the copies all update automatically. Each node copy has a reference to the model node that it is mimicking, so when a user inputs a change for a node, the model node is changed, which changes the copy node.
The solution is not too elegant and I will have to look more into multithreading (multitasking?) with Tasks (here) and Platform.runLater() (here) functions of JavaFX to increase functionality.
Here is a quick example of what I accomplished:
Main.java
public class Main extends Application {
private static Group root = new Group();
private static Scene initialScene = new Scene(root, Color.BLACK);
private static final int NUM_OF_CLIENTS = 8;
private static long updateSpeed = 20_666_666L;
private static double deltaTime;
private static double counter = 0;
#Override
public void start(Stage primaryStage) {
primaryStage.setFullScreen(true);
primaryStage.setScene(initialScene);
primaryStage.show();
initModel();
initModelViews();
startUpdates();
}
private void initModel() {
for (int i = 0; i < NUM_OF_CLIENTS; i++) {
Model.add(new UpdateObject());
}
}
private void initModelViews() {
//Correctly positioning the views
int xPanes = (NUM_OF_CLIENTS / 4.0 > 1.0) ? 4 : NUM_OF_CLIENTS;
int yPanes = (NUM_OF_CLIENTS / 4) + ((NUM_OF_CLIENTS % 4 > 0) ? 1 : 0);
for (int i = 0; i < NUM_OF_CLIENTS; i++) {
Pane clientView = new Pane(copyModelNodes());
clientView.setBackground(new Background(new BackgroundFill(Color.color(Math.random(), Math.random(), Math.random()), CornerRadii.EMPTY, Insets.EMPTY)));
System.out.println(clientView.getChildren());
clientView.relocate((i % 4) * (Main.initialScene.getWidth() / xPanes), (i / 4) * (Main.initialScene.getHeight() / yPanes)) ;
clientView.setPrefSize((Main.initialScene.getWidth() / xPanes), (Main.initialScene.getHeight() / yPanes));
root.getChildren().add(clientView);
}
}
private Node[] copyModelNodes() {
ObservableList<UpdateObject> model = Model.getModel();
Node[] modelCopy = new Node[model.size()];
for (int i = 0; i < model.size(); i++) {
ImageView testNode = new ImageView();
testNode.setImage(model.get(i).getImage());
testNode.layoutXProperty().bind(model.get(i).layoutXProperty());
testNode.layoutYProperty().bind(model.get(i).layoutYProperty());
testNode.rotateProperty().bind(model.get(i).rotateProperty());
modelCopy[i] = testNode;
}
return modelCopy;
}
private void startUpdates() {
AnimationTimer mainLoop = new AnimationTimer() {
private long lastUpdate = 0;
#Override
public void handle(long frameTime) {
//Time difference from last frame
deltaTime = 0.00000001 * (frameTime - lastUpdate);
if (deltaTime <= 0.1 || deltaTime >= 1.0)
deltaTime = 0.00000001 * updateSpeed;
if (frameTime - lastUpdate >= updateSpeed) {
update();
lastUpdate = frameTime;
}
}
};
mainLoop.start();
}
private void update() {
counter += 0.1;
if (counter > 10.0) {
counter = 0;
}
for (UpdateObject objectToUpdate : Model.getModel()) {
objectToUpdate.setLayoutX(objectToUpdate.getLayoutX() + 0.02 * counter * deltaTime);
objectToUpdate.setLayoutY(objectToUpdate.getLayoutY() + 0.02 * counter * deltaTime);
objectToUpdate.setRotate(objectToUpdate.getRotate() + 5);
}
}
}
UpdateObject.java
class UpdateObject extends ImageView {
private static Random random = new Random();
private static Image testImage = new Image("duckTest.png");
UpdateObject() {
this.setImage(testImage);
this.setLayoutX(random.nextInt(50));
this.setLayoutY(random.nextInt(50));
this.setRotate(random.nextInt(360));
}
}
Model.java
class Model {
private static ObservableList<UpdateObject> modelList = FXCollections.observableArrayList();
static void add(UpdateObject objectToAdd) {
modelList.add(objectToAdd);
}
static ObservableList<UpdateObject> getModel() {
return modelList;
}
}
Test image used

Random 2D Cityscape generator, how do I randomly generate?

I have to randomly generate a cityscape with 3 layered functions in processing. I'm doing so by drawing each floor in a loop that runs until a random integer, and doing the same thing with floors per building. Currently, the floors are initially randomly generated, but then they eventually fill out to the maximum of the random function. How do I get it so they stay random? Thanks, code is below.
int boxX = 0;
int boxY = 479;
void setup() {
size(1000, 500);
background(255);
frameRate(10);
}
void draw() {
for (int i = 0; i < 8; i++) {
building(boxX, boxY);
translate(150, 0);
}
}
void room(int boxX, int boxY) {
rect(boxX, boxY, 20, 20);
}
void floor(int boxX, int boxY) {
int randomNum = (int)random(3, 5);
for (int i=0; i<= randomNum; i++) {
room(boxX, boxY);
boxX += 20;
}
}
void building(int boxX, int boxY) {
int randomNum = int(random(10, 20));
for (int i = 0; i < randomNum; i++) {
floor(boxX, boxY);
boxY -= 20;
}
}
The problem is that you're generating a random cityscape every single frame, but you're never clearing out old frames. That means that your new frames are just drawn right on top of your old frames.
To better see what I'm talking about, clear out the old frames by adding a call to background() as the first line in your draw() function:
void draw() {
background(200);
for (int i = 0; i < 8; i++) {
building(boxX, boxY);
translate(150, 0);
}
}
You need to take a step back and ask yourself exactly what you want to happen. Do you want to generate a new cityscape every frame? If so leave the call to background() in. Do you just want to generate a single cityscape? If so then call noLoop() to prevent the draw() function from being called more than once, or store your cityscape in a data structure that you redraw every frame instead of regenerating.

Multiple enemy array in LibGDX

I am trying to make a multiple enemy array, where every 30 secods a new bullet comes from a random point. And if the bullet is clicked it should disapear and a pop like an explosion should appear. And if the bullet hits the ball then the ball pops.
so the bullet should change to a different sprite or texture. same with the ball pop.
But all that happens is the bullet if touched pops and nothing else happens. And if modified then the bullet keeps flashing as the update is way too much.
I have added COMMENTS in the code to explain more on the issues.
below is the code.
if more code is needed i will provide.
Thank you
public class GameRenderer {
private GameWorld myWorld;
private OrthographicCamera cam;
private ShapeRenderer shapeRenderer;
private SpriteBatch batcher;
// Game Objects
private Ball ball;
private ScrollHandler scroller;
private Background background;
private Bullet bullet1;
private BulletPop bPop;
private Array<Bullet> bullets;
// This is for the delay of the bullet coming one by one every 30 seconds.
/** The time of the last shot fired, we set it to the current time in nano when the object is first created */
double lastShot = TimeUtils.nanoTime();
/** Convert 30 seconds into nano seconds, so 30,000 milli = 30 seconds */
double shotFreq = TimeUtils.millisToNanos(30000);
// Game Assets
private TextureRegion bg, bPop;
private Animation bulletAnimation, ballAnimation;
private Animation ballPopAnimation;
public GameRenderer(GameWorld world) {
myWorld = world;
cam = new OrthographicCamera();
cam.setToOrtho(true, 480, 320);
batcher = new SpriteBatch();
// Attach batcher to camera
batcher.setProjectionMatrix(cam.combined);
shapeRenderer = new ShapeRenderer();
shapeRenderer.setProjectionMatrix(cam.combined);
// This is suppose to produce 10 bullets at random places on the background.
bullets = new Array<Bullet>();
Bullet bullet = null;
float bulletX = 00.0f;
float bulletY = 00.0f;
for (int i = 0; i < 10; i++) {
bulletX = MathUtils.random(-10, 10);
bulletY = MathUtils.random(-10, 10);
bullet = new Bullet(bulletX, bulletY);
AssetLoader.bullet1.flip(true, false);
AssetLoader.bullet2.flip(true, false);
bullets.add(bullet);
}
// Call helper methods to initialize instance variables
initGameObjects();
initAssets();
}
private void initGameObjects() {
ball = GameWorld.getBall();
bullet1 = myWorld.getBullet1();
bPop = myWorld.getBulletPop();
scroller = myWorld.getScroller();
}
private void initAssets() {
bg = AssetLoader.bg;
ballAnimation = AssetLoader.ballAnimation;
bullet1Animation = AssetLoader.bullet1Animation;
ballPopAnimation = AssetLoader.ballPopAnimation;
}
// This is to take the bullet away when clicked or touched.
public void onClick() {
for (int i = 0; i < bullets.size; i++) {
if (bullets.get(i).getBounds().contains(0, 0))
bullets.removeIndex(i);
}
}
private void drawBackground() {
batcher.draw(bg1, background.getX(), background.getY(), background.getWidth(), backgroundMove.getHeight());
}
public void render(float runTime) {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL30.GL_COLOR_BUFFER_BIT);
batcher.begin();
// Disable transparency
// This is good for performance when drawing images that do not require
// transparency.
batcher.disableBlending();
drawBackground();
batcher.enableBlending();
// when the bullet hits the ball, it should be disposed or taken away and a ball pop sprite/texture should be put in its place
if (bullet1.collides(ball)) {
// draws the bPop texture but the bullet does not go just keeps going around, and the bPop texture goes.
batcher.draw(AssetLoader.bPop, 195, 273);
}
batcher.draw(AssetLoader.ballAnimation.getKeyFrame(runTime), ball.getX(), ball.getY(), ball.getWidth(), ball.getHeight());
// this is where i am trying to make the bullets come one by one, and if removed via the onClick() then bPop animation
// should play but does not???
if(TimeUtils.nanoTime() - lastShot > shotFreq){
// Create your stuff
for (int i = 0; i < bullets.size; i++) {
bullets.get(i);
batcher.draw(AssetLoader.bullet1Animation.getKeyFrame(runTime), bullet1.getX(), bullet1.getY(), bullet1.getOriginX(), bullet1.getOriginY(), bullet1.getWidth(), bullet1.getHeight(), 1.0f, 1.0f, bullet1.getRotation());
if (bullets.removeValue(bullet1, false)) {
batcher.draw(AssetLoader.ballPopAnimation.getKeyFrame(runTime), bPop1.getX(), bPop1.getY(), bPop1.getWidth(), bPop1.getHeight());
}
}
/* Very important to set the last shot to now, or it will mess up and go full auto */
lastShot = TimeUtils.nanoTime();
}
// End SpriteBatch
batcher.end();
}
}
Thank you
Hmm...why are you drawing graphics from inside of the if where you are adding new bullets? This way all you draw will be drown only one frame per 30 seconds. Inside that if you should only add/remove objects and draw them outside, all the time. No drawing inside that if!
In addition to MilanG's answer
The bullets.get(i); line does nothing.. You'll want to store the returned Bullet into a variable, for which it seems you created the bullet1 var.
Also, you really shouldn't add elements to or remove elements from an array while looping through it. Consider using a second array for elements to be added/removed and use that to alter the main array or use an iterator.
[edit]
In this particular case you could also do something like this, though it would only work for one bullet per click
int index = -1;
for (int i = 0; i < bullets.size; i++) {
if (bullets.get(i).getBounds().contains(0, 0)) {
index = i;
break;
}
}
if(index > -1) bullets.removeIndex(index);
It also seems your .contains() should be passed the clicked position instead of 0,0?

Update object position before rendering in Java?

The problem I'm having is with my render loop. My application is a series of 'Tile' objects each with an x and y coordinate and image. When the program starts it creates a 10x10 grid of these tiles on screen. However, not all the squares can be seen at the same time, so you can use the arrow keys to pan around them. When the key is pressed it uses a for loop to cycle through all the currently rendered tile (stored in an ArrayList) and shifts them all 16 in the appropriate direction. The problem is some of the tiles flicker. I can see when scrolling that one half of the screen doesn't move in time to be rendered in the right spot, making a black gap between that and the other half of the tiles. how do I ensure that all tiles are moved before rendering?
render function from my Core class
public static void render()
{
while(true)
{
Graphics g = buffer.getDrawGraphics();
try
{
g.setColor(Color.black);
g.fillRect(0, 0, 1280, 720);
if(renderQueue != null)
{
for(int i = 0; i<renderQueue.size(); i++)
{
Tile t = renderQueue.get(i);
g.drawImage(t.getImage(), t.getX(), t.getY(), null);
}
}
if(!buffer.contentsLost())
{
buffer.show();
}
}
finally
{
if(g != null)
{
g.dispose();
}
}
}
}
And here's the movement update function from the Input class
public void keyPressed(KeyEvent ke)
{
int e = ke.getKeyCode();
switch(e)
{
case 38://up
if(scrollY > 0)
{
scrollY -= 16;
for(int i = 0; i<Core.renderQueue.size(); i++)
{
Core.renderQueue.get(i).incrementY(16);
}
}
break;
case 40://down
if(scrollY < 560)
{
scrollY += 16;
for(int i = 0; i<Core.renderQueue.size(); i++)
{
Core.renderQueue.get(i).incrementY(-16);
}
}
break;
case 37://right
if(scrollX < 0)
{
scrollX += 16;
for(int i = 0; i<Core.renderQueue.size(); i++)
{
Core.renderQueue.get(i).incrementX(16);
}
}
break;
case 39://left
if(scrollX > 0)
{
scrollX -= 16;
for(int i = 0; i<Core.renderQueue.size(); i++)
{
Core.renderQueue.get(i).incrementX(-16);
}
}
break;
}
Thanks in advance!
It sounds like the tiles are being rendered while the coordinates for some of the tiles still have to be changed by Input.keyPressed. You could fix that by directly using scrollX and scrollY to draw the tile images in Core.render, instead of changing the coordinates for each of the tiles. If you copy the scroll values to two local variables at the begin of the while loop in render, the same values will be used for each tile.
Another option is to create a new list with tiles that have the modified coordinates (you could use the images from the current list). When the new list is complete, you could set a flag like newRenderQueue which will be picked up in render. When a new iteration of the while loop in render starts, you can replace the render queue with the new list and reset the flag.
P.S. Welcome to Stack Overflow! As Andrew Thompson already mentioned, it's very helpful to provide a complete example of your problem. This way people can quickly investigate the issue and provide (hopefully useful) advice... ;-)

Categories

Resources