Hey,
I’ve just, hopefully, finished converting my particle system from pure Java to libGDX. Everything seems to be working properly, except for the ‘death’ of the particles. The particles die and then reappear, or so it seems, for some reason unknown to me.
Each particle starts with a certain lifetime, and each update to the particle decreases the remaining lifetime. The alpha of the particle is calculated using (float)(remainingLife/totalLife). When the lifeTime reaches 0 then the particle is deleted because it’s alpha is 0.
Here’s the compiled project, you can see how the particles disappear and reappear. Exit with Alt+F4.
https://mega.co.nz/#!IstGjR6b!VZCkZ9ClDBUedZLHIPnchx9thb4Gs_asFvnlZpX7qo8
I’m thinking that the error is in how I setup libGDX in the Driver/Main classes, but it could also be with how I set the color/alpha in the RainbowSnow/Particle classes. Normally I’d be able to figure this kind of thing out pretty quickly, but I barely know how to use libGDX atm. Thanks for any ideas.
Driver:
package core.desktop;
import java.awt.Dimension;
import java.awt.Toolkit;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
import core.Main;
public class DesktopLauncher {
public static void main (String[] arg) {
Dimension screenDimensions = Toolkit.getDefaultToolkit().getScreenSize();
LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
config.title = "Particle System";
config.width = (int)screenDimensions.getWidth();
config.height = (int)screenDimensions.getHeight();
config.fullscreen = true;
config.useGL30 = true;
new LwjglApplication(new Main(), config);
}
}
Main:
package core;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import effect.Effect;
import effect.RainbowSnow;
public class Main extends ApplicationAdapter {
Effect e;
ShapeRenderer shapeRenderer;
@Override
public void create() {
e = new RainbowSnow(0.0, Gdx.graphics.getHeight() + 50, Gdx.graphics.getWidth());
shapeRenderer = new ShapeRenderer();
}
@Override
public void render() {
((RainbowSnow)e).update();
Gdx.gl.glClearColor(0, 0, 0, 0);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
((RainbowSnow)e).render(shapeRenderer, e.getIsOval());
}
@Override
public void resize(int width, int height) {
}
@Override
public void pause() {
}
@Override
public void resume() {
}
@Override
public void dispose() {
}
}
Effect:
package effect;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import particle.Particle;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
public class Effect {
protected final static Random RANDOM = new Random();
/** A collection of particles that make up the snow.*/
protected final List<Particle> PARTICLES = new ArrayList<Particle>();
/** The origin of this snow on the X-axis.*/
protected final double ORIGIN_X;
/** The origin of this snow on the Y-axis.*/
protected final double ORIGIN_Y;
/** Whether or not to render the particles as ovals. If not then render as squares. Ovals are extremely CPU intensive for large effects*/
protected final boolean IS_OVAL;
protected Iterator<Particle> iterator;
protected int counter = 0;
public Effect(final double ORIGIN_X, final double ORIGIN_Y, final boolean IS_OVAL) {
this.ORIGIN_X = ORIGIN_X;
this.ORIGIN_Y = ORIGIN_Y;
this.IS_OVAL = IS_OVAL;
}
/**
* Updates the effect.
*/
public void Update() {}
/**
* Renders the snow to the screen.
* @param g Graphics object with which to draw.
*/
public void render(final ShapeRenderer SHAPE_RENDERER, final boolean IS_OVAL) {
if(counter == 0) { System.out.println(PARTICLES.size()); }
Gdx.gl.glEnable(GL30.GL_BLEND);
Gdx.gl.glBlendFunc(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA);
SHAPE_RENDERER.begin(ShapeType.Filled);
iterator = PARTICLES.iterator();
while(iterator.hasNext()) {
iterator.next().render(SHAPE_RENDERER, IS_OVAL);
}
SHAPE_RENDERER.end();
Gdx.gl.glDisable(GL30.GL_BLEND);
}
/** @return Whether or not to render the particles as ovals. If not then render as squares. */
public boolean getIsOval() { return IS_OVAL; }
}
RainbowSnow:
package effect;
import com.badlogic.gdx.graphics.Color;
import particle.Particle;
/**
* Represents rainbow snow fall.
* @author Valkryst
* --- Last Edit 05-Apr-2014
*/
public class RainbowSnow extends Effect {
/** The length (x-axis) of the screen. */
private final double SCREEN_LENGTH;
private final double COLOR_CHANGE_CONSTANT = 0.25;
private float red = 255, green = 0, blue = 0;
private boolean isRed = false, isGreen = true, isBlue = false;
/**
* Constructs a new Snow object.
*/
public RainbowSnow(final double ORIGIN_X, final double ORIGIN_Y, final double SCREEN_LENGTH) {
super(ORIGIN_X, ORIGIN_Y - 50, false);
this.SCREEN_LENGTH = SCREEN_LENGTH;
}
/**
* Updates the snow.
*/
public void update() {
Color c = new Color();
c.set(red/255f, green/255f, blue/255f, 1.0f);
if(counter == 10) {
for(int i=0;i<RANDOM.nextInt(1800) + 120;i++) { newParticle(c, RANDOM.nextInt(8), 40); }
counter = 0;
} else {
counter++;
}
if(isRed) {
if(blue > 0) { blue -= COLOR_CHANGE_CONSTANT; }
if(red < 255) {
red += COLOR_CHANGE_CONSTANT;
} else if(red == 255 && blue == 0) {
isRed = false;
isGreen = true;
}
} else if(isGreen) {
if(red > 0) { red -= COLOR_CHANGE_CONSTANT; }
if(green < 255) {
green += COLOR_CHANGE_CONSTANT;
} else if(green == 255 && red == 0) {
isGreen = false;
isBlue = true;
}
} else if(isBlue) {
if(green > 0) { green -= COLOR_CHANGE_CONSTANT; }
if(blue < 255) {
blue += COLOR_CHANGE_CONSTANT;
} else if(blue == 255 && green == 0) {
isBlue = false;
isRed = true;
}
}
iterator = PARTICLES.iterator();
while(iterator.hasNext()) {
if(iterator.next().update()) {
iterator.remove();
}
}
}
/**
* Creates a new Particle object.
* @param sizeIn The size, in pixels^2, of the new Particle.
* @param decayTimeIn The number of movements before the new Particle decays.
*/
public void newParticle(final Color COLOR, int SIZE, int LIFE) {
PARTICLES.add(new Particle(RANDOM.nextInt((int)SCREEN_LENGTH), super.ORIGIN_Y, RANDOM.nextDouble() * (RANDOM.nextBoolean() ? -2 : 2), RANDOM.nextDouble() * -2.5, 0.0050 *(RANDOM.nextBoolean() ? -1 : 1), 0.0, SIZE + RANDOM.nextInt(8), LIFE + RANDOM.nextInt(800), COLOR));
}
}
Particle:
package particle;
import java.awt.Dimension;
import java.awt.Toolkit;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
/**
* Represents a particle in 2D space.
* @author Valkryst
* --- Last Edit 05-Apr-2014
*/
public class Particle {
private final static Dimension SCREEN_DIMENSIONS = Toolkit.getDefaultToolkit().getScreenSize();
/** The location of the particle on the X-axis. */
private double x;
/** The location of the particle on the Y-axis. */
private double y;
/** The change in X, per update, of the particle. */
private double dx;
/** The change in Y, per update, of the particle. */
private double dy;
/** The gravitational pull to the left (negative) and right (positive) acting on this particle. */
private final double GRAVITY_X;
/** The gravitational pull to the up (negative) and down (positive) acting on this particle. */
private final double GRAVITY_Y;
/** The size in pixels^2 of the particle. */
private final int SIZE;
/** The remaining lifetime of the particle. */
private double currentLife;
/** The total lifetime of the particle. */
private final double TOTAL_LIFE;
/** The color of the particle. */
private Color color;
/**
* Constructs a new particle with the specified data.
* @param X The location of the particle on the X-axis.
* @param Y The location of the partivcle on the Y-axis.
* @param DX The change in X, per update, of the particle.
* @param DY The change in Y, per update, of the particle.
* @param GRAVITY_X The gravitational pull to the left (negative) and right (positive) acting on this particle.
* @param GRAVITY_Y The gravitational pull to the up (negative) and down (positive) acting on this particle.
* @param SIZE The size in pixels^2 of the particle.
* @param LIFE The remaining lifetime of the particle.
* @param COLOR The color of the particle.
*/
public Particle(final double X, final double Y, final double DX, final double DY, final double GRAVITY_X, final double GRAVITY_Y, final int SIZE, final double LIFE, final Color COLOR) {
x = X;
y = Y;
dx = DX;
dy = DY;
this.GRAVITY_X = GRAVITY_X;
this.GRAVITY_Y = GRAVITY_Y;
this.SIZE = SIZE;
currentLife = LIFE;
TOTAL_LIFE = LIFE;
color = COLOR;
}
/**
* Updates the particle's position, change in x, change in y,
* remaining lifetime, and color.
* @return Whether the particle is 'dead' or not.
*/
public boolean update() {
if(x > SCREEN_DIMENSIONS.width + 32 || x < -32 || y > SCREEN_DIMENSIONS.height + 32) {
return true;
} else {
x += dx;
y += dy;
dx += GRAVITY_X;
dy += GRAVITY_Y;
currentLife--;
color.set(color.r, color.g, color.b, (float)(currentLife/TOTAL_LIFE));
return (currentLife <= 0 ? true : false);
}
}
/**
* Renders the particle to the screen.
* @param G The shaperenderer object to render with.
* @param IS_OVAL Whether or not to render the particle as an oval.
*/
public void render(final ShapeRenderer SHAPE_RENDERER, final boolean IS_OVAL) {
SHAPE_RENDERER.setColor(color);
if(IS_OVAL) {
SHAPE_RENDERER.circle((int)x-(SIZE / 2), (int)y-(SIZE / 2), SIZE, SIZE); //x-(size/2) & y-(size/2) make sure the particle is rendered at (x, y).
} else {
SHAPE_RENDERER.rect((int)x-(SIZE / 2), (int)y-(SIZE / 2), SIZE, SIZE); //x-(size/2) & y-(size/2) make sure the particle is rendered at (x, y).
}
}
}
Edit:
Ignore the JavaDocs if they don’t make sense. I haven’t updated them recently.