From 865b0fc33c25b05e6d447da0e439439230df82d9 Mon Sep 17 00:00:00 2001 From: Lukas Bauer <157071544+Heady045@users.noreply.github.com> Date: Tue, 8 Oct 2024 19:43:07 +0200 Subject: [PATCH] Aufgabe 12 added class ImpactEffectManager to implement the funktionality with hit or miss particles added map4 for particle testing purposes edited class SeaSynchronizer in order to implement the particle feature edited class ShipControl in order to implement the particle feature edited settings.gradle in order to implement the particle feature --- Projekte/battleship/client/build.gradle | 1 + Projekte/battleship/client/maps/map4.json | 66 ++++++++ .../client/gui/ImpactEffectManager.java | 150 ++++++++++++++++++ .../client/gui/SeaSynchronizer.java | 8 +- .../pp/battleship/client/gui/ShipControl.java | 33 +++- Projekte/settings.gradle | 2 + 6 files changed, 249 insertions(+), 11 deletions(-) create mode 100644 Projekte/battleship/client/maps/map4.json create mode 100644 Projekte/battleship/client/src/main/java/pp/battleship/client/gui/ImpactEffectManager.java diff --git a/Projekte/battleship/client/build.gradle b/Projekte/battleship/client/build.gradle index a8aeff3c..248d1e83 100644 --- a/Projekte/battleship/client/build.gradle +++ b/Projekte/battleship/client/build.gradle @@ -9,6 +9,7 @@ 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 diff --git a/Projekte/battleship/client/maps/map4.json b/Projekte/battleship/client/maps/map4.json new file mode 100644 index 00000000..3c823c2a --- /dev/null +++ b/Projekte/battleship/client/maps/map4.json @@ -0,0 +1,66 @@ +{ + "width": 10, + "height": 10, + "ships": [ + { + "length": 1, + "x": 9, + "y": 0, + "rot": "RIGHT" + }, + { + "length": 1, + "x": 9, + "y": 1, + "rot": "RIGHT" + }, + { + "length": 1, + "x": 8, + "y": 0, + "rot": "RIGHT" + }, + { + "length": 1, + "x": 8, + "y": 1, + "rot": "RIGHT" + }, + { + "length": 2, + "x": 8, + "y": 2, + "rot": "RIGHT" + }, + { + "length": 2, + "x": 6, + "y": 0, + "rot": "RIGHT" + }, + { + "length": 2, + "x": 6, + "y": 1, + "rot": "RIGHT" + }, + { + "length": 3, + "x": 5, + "y": 2, + "rot": "RIGHT" + }, + { + "length": 3, + "x": 7, + "y": 3, + "rot": "RIGHT" + }, + { + "length": 4, + "x": 6, + "y": 4, + "rot": "RIGHT" + } + ] +} \ No newline at end of file diff --git a/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/ImpactEffectManager.java b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/ImpactEffectManager.java new file mode 100644 index 00000000..76e37da3 --- /dev/null +++ b/Projekte/battleship/client/src/main/java/pp/battleship/client/gui/ImpactEffectManager.java @@ -0,0 +1,150 @@ +package pp.battleship.client.gui; + +import com.jme3.app.Application; +import com.jme3.asset.AssetManager; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.control.AbstractControl; +import pp.battleship.model.Shot; + +import java.lang.System.Logger; +import java.lang.System.Logger.Level; + +/** + * Manages the visual effects for impact events such as hits and misses. + */ +public class ImpactEffectManager { + + private final AssetManager assetManager; + private static final Logger LOGGER = System.getLogger(ImpactEffectManager.class.getName()); + + private Material particleMaterial; + + /** + * Constructor to initialize the asset manager via the main application. + * + * @param app The main application instance. + */ + public ImpactEffectManager(Application app) { + this.assetManager = app.getAssetManager(); + this.particleMaterial = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + } + + /** + * Generates a visual effect for a hit at the specified position. + * + * @param battleshipNode The node where the effect should be attached. + * @param shot The details of the shot where the hit occurred. + */ + public void triggerHitEffect(Node battleshipNode, Shot shot) { + ParticleEmitter hitEffect = createParticleEmitter("HitEffect", 30, ColorRGBA.Orange, ColorRGBA.Red, 0.45f, 0.1f, 1f, 2f); + hitEffect.setLocalTranslation(shot.getY() + 0.5f, 0, shot.getX() + 0.5f); + + LOGGER.log(Level.DEBUG, "Hit effect created at position: {0}", hitEffect.getLocalTranslation().toString()); + + hitEffect.emitAllParticles(); + battleshipNode.attachChild(hitEffect); + hitEffect.addControl(new EffectCleanupControl(hitEffect, battleshipNode)); + } + + /** + * Creates a visual effect for a missed shot at the specified location. + * + * @param shot The details of the missed shot. + * @return The particle emitter representing the miss effect. + */ + public ParticleEmitter triggerMissEffect(Shot shot) { + ParticleEmitter missEffect = createParticleEmitter("MissEffect", 15, ColorRGBA.Blue, ColorRGBA.Cyan, 0.3f, 0.05f, 0.5f, 1.5f); + missEffect.setLocalTranslation(shot.getY() + 0.5f, 0, shot.getX() + 0.5f); + missEffect.emitAllParticles(); + missEffect.addControl(new EffectCleanupControl(missEffect)); + return missEffect; + } + + /** + * Helper method to create a particle emitter with predefined parameters. + * + * @param name The name of the particle emitter. + * @param count The number of particles to emit. + * @param startColor The initial color of the particles. + * @param endColor The final color of the particles. + * @param startSize The initial size of the particles. + * @param endSize The final size of the particles. + * @param lowLife The minimum lifetime of the particles. + * @param highLife The maximum lifetime of the particles. + * @return The configured ParticleEmitter instance. + */ + private ParticleEmitter createParticleEmitter(String name, int count, ColorRGBA startColor, ColorRGBA endColor, float startSize, float endSize, float lowLife, float highLife) { + ParticleEmitter emitter = new ParticleEmitter(name, Type.Triangle, count); + emitter.setMaterial(particleMaterial); + emitter.setImagesX(2); + emitter.setImagesY(2); + emitter.setStartColor(startColor); + emitter.setEndColor(endColor); + emitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 1, 0)); + emitter.setStartSize(startSize); + emitter.setEndSize(endSize); + emitter.setGravity(0, -0.5f, 0); + emitter.setLowLife(lowLife); + emitter.setHighLife(highLife); + emitter.setParticlesPerSec(0); + return emitter; + } + + /** + * Custom control class to handle the cleanup of particle effects once they are no longer visible. + */ + private static class EffectCleanupControl extends AbstractControl { + + private final ParticleEmitter emitter; + private final Node attachedNode; + + /** + * Constructor for managing cleanup when a particle emitter is attached to a specific node. + * + * @param emitter The particle emitter to manage. + * @param attachedNode The node to which the emitter is attached. + */ + public EffectCleanupControl(ParticleEmitter emitter, Node attachedNode) { + this.emitter = emitter; + this.attachedNode = attachedNode; + } + + /** + * Constructor for managing cleanup of a standalone particle emitter. + * + * @param emitter The particle emitter to manage. + */ + public EffectCleanupControl(ParticleEmitter emitter) { + this(emitter, null); + } + + /** + * Removes the emitter when all particles are no longer visible. + * + * @param tpf Time per frame. + */ + @Override + protected void controlUpdate(float tpf) { + if (emitter.getParticlesPerSec() == 0 && emitter.getNumVisibleParticles() == 0) { + if (attachedNode != null) { + attachedNode.detachChild(emitter); + } + } + } + + /** + * No custom rendering needed; particle behavior is handled in controlUpdate. + * @param rm handles rendering, and ViewPort + * @param vp defines the view where the scene is displayed. + */ + @Override + protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) { + // No rendering-specific behavior needed for this control. + } + } +} 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 e9842b15..18167c89 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 @@ -47,6 +47,7 @@ class SeaSynchronizer extends ShipMapSynchronizer { private final ShipMap map; private final BattleshipApp app; + private ImpactEffectManager effectHandler; /** * Constructs a {@code SeaSynchronizer} object with the specified application, root node, and ship map. @@ -59,6 +60,7 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) { super(app.getGameLogic().getOwnMap(), root); this.app = app; this.map = map; + effectHandler = new ImpactEffectManager(app); addExisting(); } @@ -72,7 +74,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) : effectHandler.triggerMissEffect(shot); } /** @@ -87,9 +89,7 @@ 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); + effectHandler.triggerHitEffect(shipNode, shot); return null; } 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..2c0214c4 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 @@ -14,6 +14,10 @@ import com.jme3.scene.control.AbstractControl; import pp.battleship.model.Battleship; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; + + import static pp.util.FloatMath.DEG_TO_RAD; import static pp.util.FloatMath.TWO_PI; import static pp.util.FloatMath.sin; @@ -47,6 +51,14 @@ class ShipControl extends AbstractControl { * The current time within the oscillation cycle, used to calculate the ship's pitch angle. */ private float time; + /** + * The current Ship + */ + private final Battleship battleship; + /** + * + */ + static final Logger LOGGER = System.getLogger(ShipControl.class.getName()); /** * Constructs a new ShipControl instance for the specified Battleship. @@ -56,6 +68,7 @@ class ShipControl extends AbstractControl { * @param ship the Battleship object to control */ public ShipControl(Battleship ship) { + battleship = ship; // Determine the axis of rotation based on the ship's orientation axis = switch (ship.getRot()) { case LEFT, RIGHT -> Vector3f.UNIT_X; @@ -63,7 +76,7 @@ public ShipControl(Battleship ship) { }; // Set the cycle duration and amplitude based on the ship's length - cycle = ship.getLength() * 2f; + cycle = battleship.getLength() * 2f; amplitude = 5f * DEG_TO_RAD / ship.getLength(); } @@ -80,15 +93,21 @@ protected void controlUpdate(float tpf) { // Update the time within the oscillation cycle time = (time + tpf) % cycle; + if (battleship.isDestroyed() && spatial.getLocalTranslation().getY() <= -0.6f) { + LOGGER.log(Level.INFO, "Ship removed {0}", spatial.getName()); + spatial.getParent().detachChild(spatial); + } else if (battleship.isDestroyed()) { + spatial.move(0, -0.05f * tpf, 0); + } else { + // Update the time within the oscillation cycle + time = (time + tpf) % cycle; - // Calculate the current angle of the oscillation - final float angle = amplitude * sin(time * TWO_PI / cycle); + // Apply the pitch rotation to the spatial + spatial.setLocalRotation(pitch); + } - // Update the pitch Quaternion with the new angle - pitch.fromAngleAxis(angle, axis); - - // Apply the pitch rotation to the spatial spatial.setLocalRotation(pitch); + } /** diff --git a/Projekte/settings.gradle b/Projekte/settings.gradle index 7440607c..d9526adc 100644 --- a/Projekte/settings.gradle +++ b/Projekte/settings.gradle @@ -29,6 +29,8 @@ library('mockito-core', 'org.mockito:mockito-core:3.+') library('groovy-jsr223', 'org.apache.groovy:groovy-jsr223:4.0.22') library('slf4j-nop', 'org.slf4j:slf4j-nop:2.0.13') + + library('jme3-effects', 'org.jmonkeyengine', 'jme3-effects').versionRef('jme') } } }