From 08c5eeb63db7370776d38e24b531ce2836ebfb7f Mon Sep 17 00:00:00 2001 From: Daniel Grigencha Date: Wed, 9 Oct 2024 23:28:02 +0200 Subject: [PATCH] added realistic visual effects to the game - imported the jme3-effects library - edited SeaSynchronizer to handle different effects - added to the ShipControl class in the controlUpdate(float tpf) method the handler that moves a destroyed ship downward --- Projekte/battleship/client/build.gradle | 2 + .../client/gui/SeaSynchronizer.java | 112 ++++++++++++++---- .../pp/battleship/client/gui/ShipControl.java | 16 +++ Projekte/settings.gradle | 1 + 4 files changed, 108 insertions(+), 23 deletions(-) diff --git a/Projekte/battleship/client/build.gradle b/Projekte/battleship/client/build.gradle index a8aeff3c..f9097e81 100644 --- a/Projekte/battleship/client/build.gradle +++ b/Projekte/battleship/client/build.gradle @@ -9,11 +9,13 @@ implementation project(":jme-common") implementation project(":battleship:model") implementation libs.jme3.desktop + implementation libs.jme3.effects runtimeOnly libs.jme3.awt.dialogs runtimeOnly libs.jme3.plugins runtimeOnly libs.jme3.jogg runtimeOnly libs.jme3.testdata + } application { diff --git a/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/SeaSynchronizer.java b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/SeaSynchronizer.java index 376b3007..41020354 100644 --- a/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/SeaSynchronizer.java +++ b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/SeaSynchronizer.java @@ -7,9 +7,12 @@ package pp.battleship.client.gui; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh; import com.jme3.material.Material; import com.jme3.material.RenderState.BlendMode; import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; import com.jme3.renderer.queue.RenderQueue; import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.Geometry; @@ -23,6 +26,10 @@ import pp.battleship.model.ShipMap; import pp.battleship.model.Shot; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; + + import static java.util.Objects.requireNonNull; import static pp.util.FloatMath.HALF_PI; import static pp.util.FloatMath.PI; @@ -34,6 +41,8 @@ * logic for the sea map. */ class SeaSynchronizer extends ShipMapSynchronizer { + private static final Logger LOGGER = System.getLogger(SeaSynchronizer.class.getName()); + private static final String UNSHADED = "Common/MatDefs/Misc/Unshaded.j3md"; //NON-NLS private static final String KING_GEORGE_V_MODEL = "Models/KingGeorgeV/KingGeorgeV.j3o"; //NON-NLS private static final String DESTROYER_MODEL = "Models/Destroyer/Destroyer.j3o"; //NON-NLS @@ -75,7 +84,7 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) { */ @Override public Spatial visit(Shot shot) { - return shot.isHit() ? handleHit(shot) : createCylinder(shot); + return shot.isHit() ? handleHit(shot) : handleMiss(shot); } /** @@ -90,33 +99,90 @@ private Spatial handleHit(Shot shot) { 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 ParticleEmitter debris = createDebrisEffect(shot); + shipNode.attachChild(debris); + + final ParticleEmitter fire = createFireEffect(shot, shipNode); + shipNode.attachChild(fire); return null; } - /** - * Creates a cylinder geometry representing the specified shot. - * The appearance of the cylinder depends on whether the shot is a hit or a miss. - * - * @param shot the shot to be represented - * @return the geometry representing the shot - */ - private Geometry createCylinder(Shot shot) { - final ColorRGBA color = shot.isHit() ? HIT_COLOR : SPLASH_COLOR; - final float height = shot.isHit() ? 1.2f : 0.1f; + private Spatial handleMiss(Shot shot) { + return createMissEffect(shot); + } - final Cylinder cylinder = new Cylinder(2, 20, 0.45f, height, true); - final Geometry geometry = new Geometry(SHOT, cylinder); + private ParticleEmitter createMissEffect(Shot shot) { + final ParticleEmitter water = new ParticleEmitter("WaterEmitter", ParticleMesh.Type.Triangle, 20); - geometry.setMaterial(createColoredMaterial(color)); - geometry.rotate(HALF_PI, 0f, 0f); - // compute the center of the shot in world coordinates - geometry.setLocalTranslation(shot.getY() + 0.5f, 0f, shot.getX() + 0.5f); + Material waterMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); + waterMaterial.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/flame.png")); + water.setMaterial(waterMaterial); - return geometry; + water.setImagesX(2); + water.setImagesY(2); + water.setStartColor(ColorRGBA.Cyan); + water.setEndColor(ColorRGBA.Blue); + water.getParticleInfluencer().setInitialVelocity(new Vector3f(0.1f, 0.1f, 0.1f)); + water.setStartSize(0.4f); + water.setEndSize(0.45f); + water.setGravity(0, -0.5f, 0); + water.setLowLife(1f); + water.setHighLife(1f); + water.setParticlesPerSec(0); + + water.setLocalTranslation(shot.getY() + 0.5f, 0f, shot.getX() + 0.5f); + water.emitAllParticles(); + return water; + } + + private ParticleEmitter createDebrisEffect(Shot shot) { + final ParticleEmitter debris = new ParticleEmitter("DebrisEmitter", ParticleMesh.Type.Triangle, 2); + + Material debrisMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); + debrisMaterial.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/Debris.png")); + debris.setMaterial(debrisMaterial); + + debris.setImagesX(2); + debris.setImagesY(2); + debris.setStartColor(ColorRGBA.White); + debris.setEndColor(ColorRGBA.White); + debris.getParticleInfluencer().setInitialVelocity(new Vector3f(0.1f, 2f, 0.1f)); + debris.setStartSize(0.1f); + debris.setEndSize(0.5f); + debris.setGravity(0, 3f, 0); + debris.getParticleInfluencer().setVelocityVariation(.40f); + debris.setLowLife(1f); + debris.setHighLife(1.5f); + debris.setParticlesPerSec(0); + + debris.setLocalTranslation(shot.getY() + 0.5f, 0f, shot.getX() + 0.5f); + debris.emitAllParticles(); + return debris; + } + + private ParticleEmitter createFireEffect(Shot shot, Node shipNode) { + ParticleEmitter fire = new ParticleEmitter("FireEmitter", ParticleMesh.Type.Triangle, 100); + + Material fireMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); + fireMaterial.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/flame.png")); + fire.setMaterial(fireMaterial); + + fire.setImagesX(2); + fire.setImagesY(2); + fire.setStartColor(ColorRGBA.Orange); + fire.setEndColor(ColorRGBA.Red); + fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 1.5f, 0)); + fire.setStartSize(0.2f); + fire.setEndSize(0.05f); + fire.setLowLife(1f); + fire.setHighLife(2f); + fire.getParticleInfluencer().setVelocityVariation(0.2f); + + fire.setLocalTranslation(shot.getY() + 0.5f, 0f, shot.getX() + 0.5f); + fire.getLocalTranslation().subtractLocal(shipNode.getLocalTranslation()); + + return fire; } /** @@ -164,8 +230,8 @@ private Spatial createShip(Battleship ship) { */ private Spatial createBox(Battleship ship) { final Box box = new Box(0.5f * (ship.getMaxY() - ship.getMinY()) + 0.3f, - 0.3f, - 0.5f * (ship.getMaxX() - ship.getMinX()) + 0.3f); + 0.3f, + 0.5f * (ship.getMaxX() - ship.getMinX()) + 0.3f); final Geometry geometry = new Geometry(SHIP, box); geometry.setMaterial(createColoredMaterial(BOX_COLOR)); geometry.setShadowMode(ShadowMode.CastAndReceive); diff --git a/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/ShipControl.java b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/ShipControl.java index 81acfbb1..2e012e90 100644 --- a/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/ShipControl.java +++ b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/ShipControl.java @@ -13,6 +13,7 @@ import com.jme3.renderer.ViewPort; import com.jme3.scene.control.AbstractControl; import pp.battleship.model.Battleship; +import pp.battleship.model.ShipMap; import static pp.util.FloatMath.DEG_TO_RAD; import static pp.util.FloatMath.TWO_PI; @@ -48,6 +49,10 @@ class ShipControl extends AbstractControl { */ private float time; + private final Battleship ship; + + private static final float SINKING_HEIGHT = -0.6f; + /** * Constructs a new ShipControl instance for the specified Battleship. * The ship's orientation determines the axis of rotation, while its length influences @@ -65,6 +70,8 @@ public ShipControl(Battleship ship) { // Set the cycle duration and amplitude based on the ship's length cycle = ship.getLength() * 2f; amplitude = 5f * DEG_TO_RAD / ship.getLength(); + + this.ship = ship; } /** @@ -78,6 +85,15 @@ protected void controlUpdate(float tpf) { // If spatial is null, do nothing if (spatial == null) return; + // Handle ship sinking by moving it downwards + if (ship.isDestroyed()) { + if (spatial.getLocalTranslation().getY() < SINKING_HEIGHT) { + spatial.getParent().detachChild(spatial); + } else { + spatial.move(0, -tpf * 0.1f, 0); + } + } + // Update the time within the oscillation cycle time = (time + tpf) % cycle; diff --git a/Projekte/settings.gradle b/Projekte/settings.gradle index 7440607c..bb7e8c72 100644 --- a/Projekte/settings.gradle +++ b/Projekte/settings.gradle @@ -19,6 +19,7 @@ library('jme3-testdata', 'org.jmonkeyengine', 'jme3-testdata').versionRef('jme') library('jme3-lwjgl', 'org.jmonkeyengine', 'jme3-lwjgl').versionRef('jme') library('jme3-lwjgl3', 'org.jmonkeyengine', 'jme3-lwjgl3').versionRef('jme') + library('jme3-effects', 'org.jmonkeyengine', 'jme3-effects').versionRef('jme') library('lemur', 'com.simsilica:lemur:1.16.0') library('lemur-proto', 'com.simsilica:lemur-proto:1.13.0')