added fire and explosion effect, when a ship is hit
@ -7,6 +7,9 @@ description = 'Battleship Client'
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation project(":jme-common")
|
implementation project(":jme-common")
|
||||||
implementation project(":battleship:model")
|
implementation project(":battleship:model")
|
||||||
|
implementation 'org.jmonkeyengine:jme3-core:3.6.0-stable'
|
||||||
|
implementation 'org.jmonkeyengine:jme3-effects:3.6.0-stable'
|
||||||
|
|
||||||
|
|
||||||
implementation libs.jme3.desktop
|
implementation libs.jme3.desktop
|
||||||
|
|
||||||
@ -14,6 +17,8 @@ dependencies {
|
|||||||
runtimeOnly libs.jme3.plugins
|
runtimeOnly libs.jme3.plugins
|
||||||
runtimeOnly libs.jme3.jogg
|
runtimeOnly libs.jme3.jogg
|
||||||
runtimeOnly libs.jme3.testdata
|
runtimeOnly libs.jme3.testdata
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
|
@ -7,9 +7,16 @@
|
|||||||
|
|
||||||
package pp.battleship.client.gui;
|
package pp.battleship.client.gui;
|
||||||
|
|
||||||
|
import com.jme3.effect.ParticleEmitter;
|
||||||
|
import com.jme3.effect.ParticleMesh;
|
||||||
|
import com.jme3.effect.ParticleMesh.Type;
|
||||||
|
import com.jme3.effect.shapes.EmitterSphereShape;
|
||||||
import com.jme3.material.Material;
|
import com.jme3.material.Material;
|
||||||
import com.jme3.material.RenderState.BlendMode;
|
import com.jme3.material.RenderState.BlendMode;
|
||||||
import com.jme3.math.ColorRGBA;
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.math.FastMath;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.renderer.queue.RenderQueue;
|
||||||
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
|
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
|
||||||
import com.jme3.scene.Geometry;
|
import com.jme3.scene.Geometry;
|
||||||
import com.jme3.scene.Node;
|
import com.jme3.scene.Node;
|
||||||
@ -22,6 +29,8 @@ import pp.battleship.model.Rotation;
|
|||||||
import pp.battleship.model.ShipMap;
|
import pp.battleship.model.ShipMap;
|
||||||
import pp.battleship.model.Shot;
|
import pp.battleship.model.Shot;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static pp.util.FloatMath.HALF_PI;
|
import static pp.util.FloatMath.HALF_PI;
|
||||||
import static pp.util.FloatMath.PI;
|
import static pp.util.FloatMath.PI;
|
||||||
@ -49,6 +58,213 @@ class SeaSynchronizer extends ShipMapSynchronizer {
|
|||||||
private final ShipMap map;
|
private final ShipMap map;
|
||||||
private final BattleshipApp app;
|
private final BattleshipApp app;
|
||||||
|
|
||||||
|
private ParticleEmitter flame, flash, spark, roundspark, smoketrail, debris,
|
||||||
|
shockwave;
|
||||||
|
|
||||||
|
|
||||||
|
private static final int COUNT_FACTOR = 1;
|
||||||
|
private static final float COUNT_FACTOR_F = 1f;
|
||||||
|
|
||||||
|
private static final boolean POINT_SPRITE = true;
|
||||||
|
private static final Type EMITTER_TYPE = POINT_SPRITE ? Type.Point : Type.Triangle;
|
||||||
|
/**
|
||||||
|
* Creates a Flame texture with indefininite Lifetime
|
||||||
|
* @return flame texture
|
||||||
|
*/
|
||||||
|
|
||||||
|
private ParticleEmitter createFlame(){
|
||||||
|
flame = new ParticleEmitter("Flame", EMITTER_TYPE, 32 * COUNT_FACTOR);
|
||||||
|
flame.setSelectRandomImage(true);
|
||||||
|
flame.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (1f / COUNT_FACTOR_F)));
|
||||||
|
flame.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f));
|
||||||
|
flame.setStartSize(0.1f);
|
||||||
|
flame.setEndSize(0.5f);
|
||||||
|
flame.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f));
|
||||||
|
flame.setParticlesPerSec(0);
|
||||||
|
flame.setGravity(0, -5, 0);
|
||||||
|
flame.setLowLife(.4f);
|
||||||
|
flame.setHighLife(.5f);
|
||||||
|
flame.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 7, 0));
|
||||||
|
flame.getParticleInfluencer().setVelocityVariation(1f);
|
||||||
|
flame.setImagesX(2);
|
||||||
|
flame.setImagesY(2);
|
||||||
|
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/flame.png"));
|
||||||
|
mat.setBoolean("PointSprite", POINT_SPRITE);
|
||||||
|
flame.setMaterial(mat);
|
||||||
|
|
||||||
|
return flame;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParticleEmitter createFlash(){
|
||||||
|
flash = new ParticleEmitter("Flash", EMITTER_TYPE, 24 * COUNT_FACTOR);
|
||||||
|
flash.setSelectRandomImage(true);
|
||||||
|
flash.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1f / COUNT_FACTOR_F));
|
||||||
|
flash.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f));
|
||||||
|
flash.setStartSize(.1f);
|
||||||
|
flash.setEndSize(0.5f);
|
||||||
|
flash.setShape(new EmitterSphereShape(Vector3f.ZERO, .05f));
|
||||||
|
flash.setParticlesPerSec(0);
|
||||||
|
flash.setGravity(0, 0, 0);
|
||||||
|
flash.setLowLife(.2f);
|
||||||
|
flash.setHighLife(.2f);
|
||||||
|
flash.getParticleInfluencer()
|
||||||
|
.setInitialVelocity(new Vector3f(0, 5f, 0));
|
||||||
|
flash.getParticleInfluencer().setVelocityVariation(1);
|
||||||
|
flash.setImagesX(2);
|
||||||
|
flash.setImagesY(2);
|
||||||
|
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/flash.png"));
|
||||||
|
mat.setBoolean("PointSprite", POINT_SPRITE);
|
||||||
|
flash.setMaterial(mat);
|
||||||
|
|
||||||
|
return flash;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParticleEmitter createRoundSpark(){
|
||||||
|
roundspark = new ParticleEmitter("RoundSpark", EMITTER_TYPE, 20 * COUNT_FACTOR);
|
||||||
|
roundspark.setStartColor(new ColorRGBA(1f, 0.29f, 0.34f, (float) (1.0 / COUNT_FACTOR_F)));
|
||||||
|
roundspark.setEndColor(new ColorRGBA(0, 0, 0, 0.5f / COUNT_FACTOR_F));
|
||||||
|
roundspark.setStartSize(0.2f);
|
||||||
|
roundspark.setEndSize(0.8f);
|
||||||
|
roundspark.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f));
|
||||||
|
roundspark.setParticlesPerSec(0);
|
||||||
|
roundspark.setGravity(0, -.5f, 0);
|
||||||
|
roundspark.setLowLife(1.8f);
|
||||||
|
roundspark.setHighLife(2f);
|
||||||
|
roundspark.getParticleInfluencer()
|
||||||
|
.setInitialVelocity(new Vector3f(0, 3, 0));
|
||||||
|
roundspark.getParticleInfluencer().setVelocityVariation(.5f);
|
||||||
|
roundspark.setImagesX(1);
|
||||||
|
roundspark.setImagesY(1);
|
||||||
|
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/roundspark.png"));
|
||||||
|
mat.setBoolean("PointSprite", POINT_SPRITE);
|
||||||
|
roundspark.setMaterial(mat);
|
||||||
|
|
||||||
|
return roundspark;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParticleEmitter createSpark(){
|
||||||
|
spark = new ParticleEmitter("Spark", Type.Triangle, 30 * COUNT_FACTOR);
|
||||||
|
spark.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1.0f / COUNT_FACTOR_F));
|
||||||
|
spark.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f));
|
||||||
|
spark.setStartSize(.5f);
|
||||||
|
spark.setEndSize(.5f);
|
||||||
|
spark.setFacingVelocity(true);
|
||||||
|
spark.setParticlesPerSec(0);
|
||||||
|
spark.setGravity(0, 5, 0);
|
||||||
|
spark.setLowLife(1.1f);
|
||||||
|
spark.setHighLife(1.5f);
|
||||||
|
spark.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 20, 0));
|
||||||
|
spark.getParticleInfluencer().setVelocityVariation(1);
|
||||||
|
spark.setImagesX(1);
|
||||||
|
spark.setImagesY(1);
|
||||||
|
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/spark.png"));
|
||||||
|
spark.setMaterial(mat);
|
||||||
|
|
||||||
|
return spark;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParticleEmitter createSmokeTrail(){
|
||||||
|
smoketrail = new ParticleEmitter("SmokeTrail", Type.Triangle, 22 * COUNT_FACTOR);
|
||||||
|
smoketrail.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1.0f / COUNT_FACTOR_F));
|
||||||
|
smoketrail.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f));
|
||||||
|
smoketrail.setStartSize(.2f);
|
||||||
|
smoketrail.setEndSize(1f);
|
||||||
|
|
||||||
|
// smoketrail.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f));
|
||||||
|
smoketrail.setFacingVelocity(true);
|
||||||
|
smoketrail.setParticlesPerSec(0);
|
||||||
|
smoketrail.setGravity(0, 1, 0);
|
||||||
|
smoketrail.setLowLife(.4f);
|
||||||
|
smoketrail.setHighLife(.5f);
|
||||||
|
smoketrail.getParticleInfluencer()
|
||||||
|
.setInitialVelocity(new Vector3f(0, 12, 0));
|
||||||
|
smoketrail.getParticleInfluencer().setVelocityVariation(1);
|
||||||
|
smoketrail.setImagesX(1);
|
||||||
|
smoketrail.setImagesY(3);
|
||||||
|
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/smoketrail.png"));
|
||||||
|
smoketrail.setMaterial(mat);
|
||||||
|
|
||||||
|
return smoketrail;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParticleEmitter createDebris(){
|
||||||
|
debris = new ParticleEmitter("Debris", Type.Triangle, 15 * COUNT_FACTOR);
|
||||||
|
debris.setSelectRandomImage(true);
|
||||||
|
debris.setRandomAngle(true);
|
||||||
|
debris.setRotateSpeed(FastMath.TWO_PI * 4);
|
||||||
|
debris.setStartColor(new ColorRGBA(1f, 0.59f, 0.28f, 1.0f / COUNT_FACTOR_F));
|
||||||
|
debris.setEndColor(new ColorRGBA(.5f, 0.5f, 0.5f, 0f));
|
||||||
|
debris.setStartSize(.10f);
|
||||||
|
debris.setEndSize(.15f);
|
||||||
|
|
||||||
|
// debris.setShape(new EmitterSphereShape(Vector3f.ZERO, .05f));
|
||||||
|
debris.setParticlesPerSec(0);
|
||||||
|
debris.setGravity(0, 12f, 0);
|
||||||
|
debris.setLowLife(1.4f);
|
||||||
|
debris.setHighLife(1.5f);
|
||||||
|
debris.getParticleInfluencer()
|
||||||
|
.setInitialVelocity(new Vector3f(0, 15, 0));
|
||||||
|
debris.getParticleInfluencer().setVelocityVariation(.60f);
|
||||||
|
debris.setImagesX(3);
|
||||||
|
debris.setImagesY(3);
|
||||||
|
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/Debris.png"));
|
||||||
|
debris.setMaterial(mat);
|
||||||
|
|
||||||
|
return debris;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParticleEmitter createShockwave(){
|
||||||
|
shockwave = new ParticleEmitter("Shockwave", Type.Triangle, 1 * COUNT_FACTOR);
|
||||||
|
// shockwave.setRandomAngle(true);
|
||||||
|
shockwave.setFaceNormal(Vector3f.UNIT_Y);
|
||||||
|
shockwave.setStartColor(new ColorRGBA(.48f, 0.17f, 0.01f, .8f / COUNT_FACTOR_F));
|
||||||
|
shockwave.setEndColor(new ColorRGBA(.48f, 0.17f, 0.01f, 0f));
|
||||||
|
|
||||||
|
shockwave.setStartSize(0f);
|
||||||
|
shockwave.setEndSize(3f);
|
||||||
|
|
||||||
|
shockwave.setParticlesPerSec(0);
|
||||||
|
shockwave.setGravity(0, 0, 0);
|
||||||
|
shockwave.setLowLife(0.5f);
|
||||||
|
shockwave.setHighLife(0.5f);
|
||||||
|
shockwave.getParticleInfluencer()
|
||||||
|
.setInitialVelocity(new Vector3f(0, 0, 0));
|
||||||
|
shockwave.getParticleInfluencer().setVelocityVariation(0f);
|
||||||
|
shockwave.setImagesX(1);
|
||||||
|
shockwave.setImagesY(1);
|
||||||
|
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/shockwave.png"));
|
||||||
|
shockwave.setMaterial(mat);
|
||||||
|
|
||||||
|
return shockwave;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParticleEmitter createMovingSmokeEmitter() {
|
||||||
|
ParticleEmitter smokeEmitter = new ParticleEmitter("SmokeEmitter", Type.Triangle, 300);
|
||||||
|
smokeEmitter.setGravity(0, 0, 0);
|
||||||
|
smokeEmitter.getParticleInfluencer().setVelocityVariation(1);
|
||||||
|
smokeEmitter.setLowLife(1);
|
||||||
|
smokeEmitter.setHighLife(1);
|
||||||
|
smokeEmitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, .5f, 0));
|
||||||
|
smokeEmitter.setImagesX(15); // Assuming the smoke texture is a sprite sheet with 15 frames
|
||||||
|
|
||||||
|
// Set the material for the emitter
|
||||||
|
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Smoke/Smoke.png"));
|
||||||
|
smokeEmitter.setMaterial(mat);
|
||||||
|
|
||||||
|
return smokeEmitter;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a {@code SeaSynchronizer} object with the specified application, root node, and ship map.
|
* Constructs a {@code SeaSynchronizer} object with the specified application, root node, and ship map.
|
||||||
*
|
*
|
||||||
@ -88,13 +304,54 @@ class SeaSynchronizer extends ShipMapSynchronizer {
|
|||||||
final Battleship ship = requireNonNull(map.findShipAt(shot), "Missing ship");
|
final Battleship ship = requireNonNull(map.findShipAt(shot), "Missing ship");
|
||||||
final Node shipNode = requireNonNull((Node) getSpatial(ship), "Missing ship node");
|
final Node shipNode = requireNonNull((Node) getSpatial(ship), "Missing ship node");
|
||||||
|
|
||||||
final Geometry representation = createCylinder(shot);
|
|
||||||
representation.getLocalTranslation().subtractLocal(shipNode.getLocalTranslation());
|
|
||||||
shipNode.attachChild(representation);
|
// Create a new node specifically for the hit effects
|
||||||
|
Node hitEffectNode = new Node("HitEffectNode");
|
||||||
|
|
||||||
|
// Create particle effects
|
||||||
|
ParticleEmitter flame = createFlame();
|
||||||
|
ParticleEmitter flash = createFlash();
|
||||||
|
ParticleEmitter spark = createSpark();
|
||||||
|
ParticleEmitter roundSpark = createRoundSpark();
|
||||||
|
ParticleEmitter smokeTrail = createSmokeTrail();
|
||||||
|
ParticleEmitter debris = createDebris();
|
||||||
|
ParticleEmitter shockwave = createShockwave();
|
||||||
|
ParticleEmitter movingSmoke = createMovingSmokeEmitter(); // New moving smoke emitter
|
||||||
|
|
||||||
|
// Attach all effects to the hitEffectNode
|
||||||
|
hitEffectNode.attachChild(flame);
|
||||||
|
hitEffectNode.attachChild(flash);
|
||||||
|
hitEffectNode.attachChild(spark);
|
||||||
|
hitEffectNode.attachChild(roundSpark);
|
||||||
|
hitEffectNode.attachChild(smokeTrail);
|
||||||
|
hitEffectNode.attachChild(debris);
|
||||||
|
hitEffectNode.attachChild(shockwave);
|
||||||
|
hitEffectNode.attachChild(movingSmoke);
|
||||||
|
|
||||||
|
// Set the local translation for the hit effect to the point of impact
|
||||||
|
hitEffectNode.setLocalTranslation(shot.getY() + 0.5f - shipNode.getLocalTranslation().x,
|
||||||
|
0.5f, // Adjust as needed for height above the ship
|
||||||
|
shot.getX() + 0.5f - shipNode.getLocalTranslation().z);
|
||||||
|
|
||||||
|
// Attach the hitEffectNode to the shipNode so it moves with the ship
|
||||||
|
shipNode.attachChild(hitEffectNode);
|
||||||
|
|
||||||
|
// Emit particles when the hit happens
|
||||||
|
flash.emitAllParticles();
|
||||||
|
spark.emitAllParticles();
|
||||||
|
smokeTrail.emitAllParticles();
|
||||||
|
debris.emitAllParticles();
|
||||||
|
shockwave.emitAllParticles();
|
||||||
|
flame.emitAllParticles();
|
||||||
|
roundSpark.emitAllParticles();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a cylinder geometry representing the specified shot.
|
* Creates a cylinder geometry representing the specified shot.
|
||||||
* The appearance of the cylinder depends on whether the shot is a hit or a miss.
|
* The appearance of the cylinder depends on whether the shot is a hit or a miss.
|
||||||
|
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 28 KiB |