add effects when ships are hit

This commit is contained in:
Tamino Mueller 2024-10-14 08:07:57 +02:00
parent b962444506
commit 0fd0555dea
12 changed files with 340 additions and 70 deletions

View File

@ -9,6 +9,7 @@ dependencies {
implementation project(":battleship:model") implementation project(":battleship:model")
implementation libs.jme3.desktop implementation libs.jme3.desktop
implementation libs.jme3.effects
implementation project(path: ':battleship:server') implementation project(path: ':battleship:server')
implementation project(path: ':battleship:server') implementation project(path: ':battleship:server')

View File

@ -0,0 +1,243 @@
package pp.battleship.client.gui;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh.Type;
import com.jme3.effect.shapes.EmitterSphereShape;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import pp.battleship.client.BattleshipApp;
/**
* Factory class responsible for creating particle effects used in the game.
* This centralizes the creation of various types of particle emitters.
*/
public class ParticleEffectFactory {
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;
private final BattleshipApp app;
ParticleEffectFactory(BattleshipApp app) {
this.app = app;
}
/**
* Creates a flame particle emitter.
*
* @return a configured flame particle emitter
*/
ParticleEmitter createFlame() {
ParticleEmitter 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;
}
/**
* Creates a flash particle emitter.
*
* @return a configured flash particle emitter
*/
ParticleEmitter createFlash() {
ParticleEmitter 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;
}
/**
* Creates a round spark particle emitter.
*
* @return a configured round spark particle emitter
*/
ParticleEmitter createRoundSpark() {
ParticleEmitter 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;
}
/**
* Creates a spark particle emitter.
*
* @return a configured spark particle emitter
*/
ParticleEmitter createSpark() {
ParticleEmitter 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;
}
/**
* Creates a smoke trail particle emitter.
*
* @return a configured smoke trail particle emitter
*/
ParticleEmitter createSmokeTrail() {
ParticleEmitter 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.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;
}
/**
* Creates a debris particle emitter.
*
* @return a configured debris particle emitter
*/
ParticleEmitter createDebris() {
ParticleEmitter 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.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;
}
/**
* Creates a shockwave particle emitter.
*
* @return a configured shockwave particle emitter
*/
ParticleEmitter createShockwave() {
ParticleEmitter shockwave = new ParticleEmitter("Shockwave", Type.Triangle, 1 * COUNT_FACTOR);
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;
}
/**
* Creates a moving smoke emitter.
*
* @return a configured smoke emitter
*/
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
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;
}
}

View File

@ -7,6 +7,7 @@
package pp.battleship.client.gui; package pp.battleship.client.gui;
import com.jme3.effect.ParticleEmitter;
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;
@ -21,7 +22,6 @@ import pp.battleship.model.Battleship;
import pp.battleship.model.Rotation; 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;
@ -34,14 +34,13 @@ import static pp.util.FloatMath.PI;
*/ */
class SeaSynchronizer extends ShipMapSynchronizer { class SeaSynchronizer extends ShipMapSynchronizer {
private static final String UNSHADED = "Common/MatDefs/Misc/Unshaded.j3md"; //NON-NLS private static final String UNSHADED = "Common/MatDefs/Misc/Unshaded.j3md"; //NON-NLS
private static final String UX23 = "Models/UX23/UX23.j3o"; private static final String UX23 = "Models/UX23/UX23.j3o";
private static final String BOJE = "Models/Boje/Boje.j3o"; private static final String BOJE = "Models/Boje/Boje.j3o";
private static final String ALIENSHIP = "Models/Alienship/Alienship.j3o"; private static final String ALIENSHIP = "Models/Alienship/Alienship.j3o";
private static final String MARLOW66 = "Models/Marlow66/Marlow66.j3o"; private static final String MARLOW66 = "Models/Marlow66/Marlow66.j3o";
private static final String KING_GEORGE_V_MODEL = "Models/KingGeorgeV/KingGeorgeV.j3o"; //NON-NLS private static final String KING_GEORGE_V_MODEL = "Models/KingGeorgeV/KingGeorgeV.j3o";
private static final String COLOR = "Color"; //NON-NLS private static final String COLOR = "Color"; //NON-NLS
private static final String SHIP = "ship"; //NON-NLS private static final String SHIP = "ship"; //NON-NLS
private static final String SHOT = "shot"; //NON-NLS private static final String SHOT = "shot"; //NON-NLS
@ -51,6 +50,7 @@ class SeaSynchronizer extends ShipMapSynchronizer {
private final ShipMap map; private final ShipMap map;
private final BattleshipApp app; private final BattleshipApp app;
private final ParticleEffectFactory particleFactory;
/** /**
* 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.
@ -63,6 +63,7 @@ class SeaSynchronizer extends ShipMapSynchronizer {
super(app.getGameLogic().getOwnMap(), root); super(app.getGameLogic().getOwnMap(), root);
this.app = app; this.app = app;
this.map = map; this.map = map;
this.particleFactory = new ParticleEffectFactory(app);
addExisting(); addExisting();
} }
@ -79,7 +80,7 @@ class SeaSynchronizer extends ShipMapSynchronizer {
return shot.isHit() ? handleHit(shot) : createCylinder(shot); return shot.isHit() ? handleHit(shot) : createCylinder(shot);
} }
/** /**
* Handles a hit by attaching its representation to the node that * Handles a hit by attaching its representation to the node that
* contains the ship model as a child so that it moves with the ship. * contains the ship model as a child so that it moves with the ship.
* *
@ -91,9 +92,47 @@ 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 = particleFactory.createFlame();
ParticleEmitter flash = particleFactory.createFlash();
ParticleEmitter spark = particleFactory.createSpark();
ParticleEmitter roundSpark = particleFactory.createRoundSpark();
ParticleEmitter smokeTrail = particleFactory.createSmokeTrail();
ParticleEmitter debris = particleFactory.createDebris();
ParticleEmitter shockwave = particleFactory.createShockwave();
ParticleEmitter movingSmoke = particleFactory.createMovingSmokeEmitter();
// 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;
} }
@ -149,72 +188,20 @@ class SeaSynchronizer extends ShipMapSynchronizer {
*/ */
private Spatial createShip(Battleship ship) { private Spatial createShip(Battleship ship) {
switch (ship.getLength()) { switch (ship.getLength()) {
case 4: return createBattleship(ship);
case 3: return createMarlow66(ship);
case 2: return createUX23(ship);
case 2: case 1: return createAllienship(ship);
return createUX23(ship); default: return createBox(ship);
case 3:
return createMarlow66(ship);
case 4:
return createBattleship(ship);
case 1:
return createAllienship(ship);
default:
return createBox(ship);
} }
} }
private Spatial createAllienship(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(ALIENSHIP);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
model.scale(0.10f);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;}
private Spatial createUX23(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(UX23);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
// model.move(0f, -0.05f, 0f);
model.scale(0.89f);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;
}
private Spatial createMarlow66(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(MARLOW66);
model.rotate(0f, calculateRotationAngle(ship.getRot()), 0f);
model.move(0f, 0.25f, 0f);
model.scale(0.135f);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;
}
private Spatial createBoje(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(BOJE);
model.rotate(0f, calculateRotationAngle(ship.getRot()), 0f);
model.move(0.00f, 0.25f, 0.00f);
model.scale(12.23f);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;
}
/** /**
* Creates a simple box to represent a battleship that is not of the "King George V" type. * Creates a simple box to represent a battleship that is not of the "King George V" type.
* *
* @param ship the battleship to be represented * @param ship the battleship to be represented
* @return the geometry representing the battleship as a box * @return the geometry representing the battleship as a box
*/ */
private Spatial createBox(Battleship ship) { private Spatial createBox(Battleship ship) {
final Box box = new Box(0.5f * (ship.getMaxY() - ship.getMinY()) + 0.3f, final Box box = new Box(0.5f * (ship.getMaxY() - ship.getMinY()) + 0.3f,
0.3f, 0.3f,
@ -256,6 +243,42 @@ class SeaSynchronizer extends ShipMapSynchronizer {
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f); model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
model.scale(1.48f); model.scale(1.48f);
// model.scale(0.0007f);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;
}
private Spatial createAllienship(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(ALIENSHIP);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
model.scale(0.10f);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;}
private Spatial createUX23(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(UX23);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
// model.move(0f, -0.05f, 0f);
model.scale(0.89f);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;
}
private Spatial createMarlow66(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(MARLOW66);
model.rotate(0f, calculateRotationAngle(ship.getRot()), 0f);
model.move(0f, 0.25f, 0f);
model.scale(0.135f);
model.setShadowMode(ShadowMode.CastAndReceive); model.setShadowMode(ShadowMode.CastAndReceive);
return model; return model;

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -17,6 +17,9 @@ dependencyResolutionManagement {
library('jme3-plugins', 'org.jmonkeyengine', 'jme3-plugins').versionRef('jme') library('jme3-plugins', 'org.jmonkeyengine', 'jme3-plugins').versionRef('jme')
library('jme3-jogg', 'org.jmonkeyengine', 'jme3-jogg').versionRef('jme') library('jme3-jogg', 'org.jmonkeyengine', 'jme3-jogg').versionRef('jme')
library('jme3-testdata', 'org.jmonkeyengine', 'jme3-testdata').versionRef('jme') library('jme3-testdata', 'org.jmonkeyengine', 'jme3-testdata').versionRef('jme')
library('jme3-effects', 'org.jmonkeyengine', 'jme3-effects').versionRef('jme')
library('jme3-lwjgl', 'org.jmonkeyengine', 'jme3-lwjgl').versionRef('jme') library('jme3-lwjgl', 'org.jmonkeyengine', 'jme3-lwjgl').versionRef('jme')
library('jme3-lwjgl3', 'org.jmonkeyengine', 'jme3-lwjgl3').versionRef('jme') library('jme3-lwjgl3', 'org.jmonkeyengine', 'jme3-lwjgl3').versionRef('jme')