add img sinking eff
@ -7,6 +7,10 @@ description = 'Battleship Client'
|
||||
dependencies {
|
||||
implementation project(":jme-common")
|
||||
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 project(path: ':battleship:server')
|
||||
|
@ -10,6 +10,9 @@ package pp.battleship.client.gui;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
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.material.RenderState.BlendMode;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
@ -54,6 +57,199 @@ class SeaSynchronizer extends ShipMapSynchronizer {
|
||||
private final ShipMap map;
|
||||
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);
|
||||
|
||||
// Setze die Höhe auf 2f
|
||||
flame.setLocalTranslation(0, 2f, 0);
|
||||
|
||||
return flame;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Setze die Höhe auf 2f
|
||||
spark.setLocalTranslation(0, 2f, 0);
|
||||
|
||||
return spark;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Setze die Höhe auf 2f
|
||||
roundspark.setLocalTranslation(0, 2f, 0);
|
||||
|
||||
return roundspark;
|
||||
}
|
||||
|
||||
|
||||
|
||||
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.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);
|
||||
|
||||
// Setze die Höhe auf 2f
|
||||
smoketrail.setLocalTranslation(0, 2f, 0);
|
||||
|
||||
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.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);
|
||||
|
||||
// Setze die Höhe auf 2f
|
||||
debris.setLocalTranslation(0, 2f, 0);
|
||||
|
||||
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);
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
||||
mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Smoke/Smoke.png"));
|
||||
smokeEmitter.setMaterial(mat);
|
||||
|
||||
// Setze die Höhe auf 2f
|
||||
smokeEmitter.setLocalTranslation(0, 2f, 0);
|
||||
|
||||
return smokeEmitter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a {@code SeaSynchronizer} object with the specified application, root node, and ship map.
|
||||
*
|
||||
@ -93,11 +289,52 @@ class SeaSynchronizer extends ShipMapSynchronizer {
|
||||
final Battleship ship = requireNonNull(map.findShipAt(shot), "Missing ship");
|
||||
final Node shipNode = requireNonNull((Node) getSpatial(ship), "Missing ship node");
|
||||
|
||||
final Geometry representation = createCylinder(shot);
|
||||
representation.getLocalTranslation().subtractLocal(shipNode.getLocalTranslation());
|
||||
shipNode.attachChild(representation);
|
||||
//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");
|
||||
|
||||
return null;
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,116 @@
|
||||
package pp.battleship.client.gui;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.control.AbstractControl;
|
||||
|
||||
/**
|
||||
* Controls the burning, tilting, and sinking behavior of a battleship.
|
||||
* The ship will burn and tilt for a specified duration, then sink below the water surface.
|
||||
*/
|
||||
public class SinkingShip extends AbstractControl {
|
||||
|
||||
private float elapsedTime = 0f;
|
||||
private final float burnTiltDuration;
|
||||
private final float sinkDuration;
|
||||
private final float sinkDepth;
|
||||
private final float tiltAngle;
|
||||
private final Vector3f initialPosition;
|
||||
private boolean sinkingStarted = false;
|
||||
|
||||
/**
|
||||
* Constructs a new ShipSinkingControl instance.
|
||||
*
|
||||
* @param burnTiltDuration Time in seconds for the ship to burn and tilt on the surface
|
||||
* @param sinkDuration Time in seconds for the ship to fully sink
|
||||
* @param sinkDepth Depth below the water to sink the ship
|
||||
* @param tiltAngle Final tilt angle in degrees
|
||||
*/
|
||||
public SinkingShip(float burnTiltDuration, float sinkDuration, float sinkDepth, float tiltAngle) {
|
||||
this.burnTiltDuration = burnTiltDuration;
|
||||
this.sinkDuration = sinkDuration;
|
||||
this.sinkDepth = sinkDepth;
|
||||
this.tiltAngle = tiltAngle;
|
||||
this.initialPosition = new Vector3f(); // Placeholder; will be set in controlUpdate
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides controlUpdate in AbstractControl
|
||||
* regulates the burn and tilt timeframe
|
||||
* @param tpf time per frame (in seconds)
|
||||
*/
|
||||
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
if (spatial == null) return;
|
||||
|
||||
elapsedTime += tpf;
|
||||
|
||||
if (elapsedTime < burnTiltDuration) {
|
||||
float progress = elapsedTime / burnTiltDuration;
|
||||
|
||||
float angleInRadians = FastMath.DEG_TO_RAD * FastMath.interpolateLinear(progress, 0f, -tiltAngle);
|
||||
Quaternion tiltRotation = new Quaternion().fromAngles(angleInRadians, 0, 0);
|
||||
spatial.setLocalRotation(tiltRotation);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start sinking if it hasn't started yet
|
||||
if (!sinkingStarted) {
|
||||
sinkingStarted = true;
|
||||
// Save the initial position when sinking starts
|
||||
initialPosition.set(spatial.getLocalTranslation());
|
||||
|
||||
// Remove the hitEffectNode
|
||||
Node parentNode = (Node) spatial;
|
||||
Spatial hitEffects = parentNode.getChild("HitEffectNode");
|
||||
if (hitEffects != null) {
|
||||
parentNode.detachChild(hitEffects);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the progress of the sinking (0 to 1)
|
||||
float progress = Math.min((elapsedTime - burnTiltDuration) / sinkDuration, 1f);
|
||||
|
||||
// Apply the tilt angle (remains constant during sinking)
|
||||
Quaternion tiltRotation = new Quaternion().fromAngles(-FastMath.DEG_TO_RAD * tiltAngle, 0, 0);
|
||||
spatial.setLocalRotation(tiltRotation);
|
||||
|
||||
// Sink the ship by interpolating the Y position
|
||||
float currentY = FastMath.interpolateLinear(progress, initialPosition.y, sinkDepth);
|
||||
spatial.setLocalTranslation(initialPosition.x, currentY, initialPosition.z);
|
||||
|
||||
if (currentY <= sinkDepth) {
|
||||
Node parentNode = (Node) spatial.getParent();
|
||||
if (parentNode != null) {
|
||||
parentNode.detachChild(spatial);
|
||||
}
|
||||
spatial.removeControl(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called during the rendering phase, but it does not perform any
|
||||
* operations in this implementation as the control only influences the spatial's
|
||||
* transformation, not its rendering process.
|
||||
*
|
||||
* @param rm the RenderManager rendering the controlled Spatial (not null)
|
||||
* @param vp the ViewPort being rendered (not null)
|
||||
*/
|
||||
@Override
|
||||
protected void controlRender(RenderManager rm, ViewPort vp) {
|
||||
// No rendering logic is needed for this control
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the sinking process for the ship.
|
||||
*/
|
||||
public void startSinking() {
|
||||
// Nothing to do here as the control update handles the timing and sequence
|
||||
}
|
||||
}
|
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 |