Aufgabe 13
edited class BattleState in order to implement the 2D map feature edited class GameSound to add the Missile launch sound edited class MapViewSynchronizer in order to implement the 2D map feature edited class ImpactEffectManager removed unused import edited class SeaSynchronizer removed unused code edited class Shell in order to implement the 2D map feature edited class ShellControl in order to implement the 2D map feature edited Sound added Missile launch enum edited FloatMath to improve the animation for the 2D projectile added missileLaunch.wav
This commit is contained in:
@@ -34,6 +34,7 @@ public class GameSound extends AbstractAppState implements GameEventListener {
|
||||
private AudioNode splashSound;
|
||||
private AudioNode shipDestroyedSound;
|
||||
private AudioNode explosionSound;
|
||||
private AudioNode missileLaunch;
|
||||
|
||||
/**
|
||||
* Checks if sound is enabled in the preferences.
|
||||
@@ -78,6 +79,7 @@ public void initialize(AppStateManager stateManager, Application app) {
|
||||
shipDestroyedSound = loadSound(app, "Sound/Effects/sunken.wav"); //NON-NLS
|
||||
splashSound = loadSound(app, "Sound/Effects/splash.wav"); //NON-NLS
|
||||
explosionSound = loadSound(app, "Sound/Effects/explosion.wav"); //NON-NLS
|
||||
missileLaunch = loadSound(app, "Sound/Effects/missileLaunch.wav");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,13 +95,16 @@ private AudioNode loadSound(Application app, String name) {
|
||||
sound.setLooping(false);
|
||||
sound.setPositional(false);
|
||||
return sound;
|
||||
}
|
||||
catch (AssetLoadException | AssetNotFoundException ex) {
|
||||
} catch (AssetLoadException | AssetNotFoundException ex) {
|
||||
LOGGER.log(Level.ERROR, ex.getMessage(), ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void missileLaunch() {
|
||||
missileLaunch.playInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays the splash sound effect.
|
||||
*/
|
||||
@@ -126,6 +131,7 @@ public void shipDestroyed() {
|
||||
|
||||
/**
|
||||
* Checks the according case for the soundeffect
|
||||
*
|
||||
* @param event the received event
|
||||
*/
|
||||
@Override
|
||||
@@ -134,6 +140,7 @@ public void receivedEvent(SoundEvent event) {
|
||||
case EXPLOSION -> explosion();
|
||||
case SPLASH -> splash();
|
||||
case DESTROYED_SHIP -> shipDestroyed();
|
||||
case MISSILE_LAUNCH -> missileLaunch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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;
|
||||
@@ -105,29 +104,16 @@ private ParticleEmitter createParticleEmitter(String name, int count, ColorRGBA
|
||||
private static class EffectCleanupControl extends AbstractControl {
|
||||
|
||||
private final ParticleEmitter emitter;
|
||||
// private final Node attachedNode;
|
||||
|
||||
private float currentTime = 0;
|
||||
private final float duration = 3.5f;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
this.emitter = emitter;
|
||||
}
|
||||
|
||||
@@ -140,10 +126,8 @@ public EffectCleanupControl(ParticleEmitter emitter) {
|
||||
protected void controlUpdate(float tpf) {
|
||||
currentTime += tpf;
|
||||
if (currentTime <= duration) {
|
||||
|
||||
}
|
||||
if (currentTime >= 1f && currentTime <= 1.1) {
|
||||
|
||||
//Start
|
||||
emitter.setNumParticles(50);
|
||||
emitter.setParticlesPerSec(50);
|
||||
|
||||
@@ -12,9 +12,13 @@
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import pp.battleship.model.Battleship;
|
||||
import pp.battleship.model.IntPoint;
|
||||
import pp.battleship.model.Shell;
|
||||
import pp.battleship.model.Shot;
|
||||
import pp.util.FloatMath;
|
||||
import pp.util.FloatPoint;
|
||||
import pp.util.Position;
|
||||
import java.lang.System.Logger;
|
||||
|
||||
/**
|
||||
* Synchronizes the visual representation of the ship map with the game model.
|
||||
@@ -28,6 +32,8 @@ class MapViewSynchronizer extends ShipMapSynchronizer {
|
||||
private static final float SHIP_DEPTH = 0f;
|
||||
private static final float INDENT = 4f;
|
||||
|
||||
private static final float SHELL_DEPTH = 8f;
|
||||
|
||||
// Colors used for different visual elements
|
||||
private static final ColorRGBA HIT_COLOR = ColorRGBA.Red;
|
||||
private static final ColorRGBA MISS_COLOR = ColorRGBA.Blue;
|
||||
@@ -110,9 +116,22 @@ public Spatial visit(Battleship ship) {
|
||||
return shipNode;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param shell
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Spatial visit(Shell shell) {
|
||||
return null;
|
||||
LOGGER.log(Logger.Level.DEBUG, "Visiting {0}", shell);
|
||||
final Node shellNode = new Node("shell");
|
||||
final Position startPosition = view.modelToView(shell.getCurrentPosition().x,shell.getCurrentPosition().z);
|
||||
|
||||
shellNode.attachChild(view.getApp().getDraw().makeRectangle(startPosition.getX(),startPosition.getY(), SHELL_DEPTH, 30, 30, ColorRGBA.Black));
|
||||
|
||||
shellNode.addControl(new ShellControl(shell, this.view, shell.getLogic()));
|
||||
return shellNode;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -109,7 +109,7 @@ private Geometry createCylinder(Shot shot) {
|
||||
|
||||
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);
|
||||
|
||||
return geometry;
|
||||
@@ -140,14 +140,14 @@ public Spatial visit(Shell shell) {
|
||||
final Spatial bombModel = app.getAssetManager().loadModel(BOMB);
|
||||
|
||||
// Apply transformations to the bomb model (scale, rotate, etc. if needed)
|
||||
bombModel.scale(0.05f); // Adjust the scale based on your model size
|
||||
bombModel.rotate(-HALF_PI, 0f, 0f); // Rotate the model if necessary
|
||||
bombModel.scale(0.05f);
|
||||
bombModel.rotate(-HALF_PI, 0f, 0f);
|
||||
|
||||
// Set the position of the bomb at the shell's current position
|
||||
bombModel.setLocalTranslation(shell.getCurrentPosition());
|
||||
|
||||
// Add a control to animate the bomb's movement (similar to the previous cylinder animation)
|
||||
bombModel.addControl(new ShellControl(shell));
|
||||
bombModel.addControl(new ShellControl(shell,shell.getLogic()));
|
||||
|
||||
return bombModel;
|
||||
}
|
||||
|
||||
@@ -12,8 +12,13 @@
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.scene.control.AbstractControl;
|
||||
import pp.battleship.game.client.ClientGameLogic;
|
||||
import pp.battleship.message.server.EffectMessage;
|
||||
import pp.battleship.model.Battleship;
|
||||
import pp.battleship.model.Shell;
|
||||
import pp.battleship.notification.Sound;
|
||||
import pp.util.FloatPoint;
|
||||
import pp.util.Position;
|
||||
|
||||
import java.lang.System.Logger;
|
||||
import java.lang.System.Logger.Level;
|
||||
@@ -28,21 +33,6 @@
|
||||
* The ship oscillates to simulate a realistic movement on water, based on its orientation and length.
|
||||
*/
|
||||
class ShellControl extends AbstractControl {
|
||||
/**
|
||||
* The axis of rotation for the ship's pitch (tilting forward and backward).
|
||||
*/
|
||||
private final Vector3f axis;
|
||||
|
||||
/**
|
||||
* The duration of one complete oscillation cycle in seconds.
|
||||
*/
|
||||
private final float cycle;
|
||||
|
||||
/**
|
||||
* The amplitude of the pitch oscillation in radians, determining how much the ship tilts.
|
||||
*/
|
||||
private final float amplitude;
|
||||
|
||||
/**
|
||||
* A quaternion representing the ship's current pitch rotation.
|
||||
*/
|
||||
@@ -54,6 +44,10 @@ class ShellControl extends AbstractControl {
|
||||
private float time;
|
||||
|
||||
private Shell shell;
|
||||
private MapView view;
|
||||
private ClientGameLogic logic;
|
||||
private EffectMessage msg;
|
||||
private boolean hasPlayedSound = false;
|
||||
/**
|
||||
* Logger for logging messages related to ShipControl operations.
|
||||
*/
|
||||
@@ -66,14 +60,22 @@ class ShellControl extends AbstractControl {
|
||||
*
|
||||
* @param shell the Battleship object to control
|
||||
*/
|
||||
public ShellControl(Shell shell) {
|
||||
public ShellControl(Shell shell, ClientGameLogic clientGameLogic) {
|
||||
this.shell = shell;
|
||||
// Determine the axis of rotation based on the ship's orientation
|
||||
axis = Vector3f.UNIT_X;
|
||||
this.logic = clientGameLogic;
|
||||
this.msg = shell.getMsg();
|
||||
}
|
||||
|
||||
// Set the cycle duration and amplitude based on the ship's length
|
||||
cycle = 1 * 2f;
|
||||
amplitude = 5f * DEG_TO_RAD / 1;
|
||||
/**
|
||||
* @param shell
|
||||
* @param view
|
||||
* @param clientGameLogic
|
||||
*/
|
||||
public ShellControl(Shell shell, MapView view, ClientGameLogic clientGameLogic) {
|
||||
this.shell = shell;
|
||||
this.view = view;
|
||||
this.logic = clientGameLogic;
|
||||
this.msg = shell.getMsg();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,11 +87,25 @@ public ShellControl(Shell shell) {
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
// If spatial is null, do nothing
|
||||
if (spatial == null) return;
|
||||
|
||||
shell.updatePosition(tpf);
|
||||
spatial.setLocalTranslation(shell.getCurrentPosition());
|
||||
|
||||
if (spatial == null)
|
||||
return;
|
||||
if (shell.isFinished() && !hasPlayedSound) {
|
||||
if (!msg.getShot().isHit())
|
||||
logic.playSound(Sound.SPLASH);
|
||||
else if (msg.getDestroyedShip() == null)
|
||||
logic.playSound(Sound.EXPLOSION);
|
||||
else
|
||||
logic.playSound(Sound.DESTROYED_SHIP);
|
||||
hasPlayedSound = true;
|
||||
}
|
||||
if (view == null) {
|
||||
shell.updatePosition(tpf);
|
||||
spatial.setLocalTranslation(shell.getCurrentPosition());
|
||||
} else {
|
||||
shell.updatePosition(tpf);
|
||||
Position pos2d = view.modelToView(shell.getCurrentPosition().x - 0.3f, shell.getCurrentPosition().z - 0.3f);
|
||||
spatial.setLocalTranslation(pos2d.getY(), pos2d.getX(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Binary file not shown.
@@ -55,11 +55,11 @@ else if (logic.getOpponentMap().isValid(pos))
|
||||
@Override
|
||||
public void receivedEffect(EffectMessage msg) {
|
||||
ClientGameLogic.LOGGER.log(Level.INFO, "report effect: {0}", msg); //NON-NLS
|
||||
playSound(msg);
|
||||
myTurn = msg.isMyTurn();
|
||||
logic.playSound(Sound.MISSILE_LAUNCH);
|
||||
logic.setInfoText(msg.getInfoTextKey());
|
||||
affectedMap(msg).add(msg.getShot());
|
||||
affectedMap(msg).add(new Shell(new Vector3f(0, 10, 0), new Vector3f(msg.getShot().getY() + 0.5f, -0.4f, msg.getShot().getX() + 0.5f), 1f));
|
||||
affectedMap(msg).add(new Shell(new Vector3f(0, 50, 0), new Vector3f(msg.getShot().getY() + 0.5f, -0.4f, msg.getShot().getX() + 0.5f), 1f,msg,logic));
|
||||
if (destroyedOpponentShip(msg))
|
||||
logic.getOpponentMap().add(msg.getDestroyedShip());
|
||||
if (msg.isGameOver()) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package pp.battleship.model;
|
||||
|
||||
import com.jme3.math.Vector3f;
|
||||
import pp.battleship.game.client.ClientGameLogic;
|
||||
import pp.battleship.message.server.EffectMessage;
|
||||
import pp.util.FloatMath;
|
||||
|
||||
public class Shell implements Item {
|
||||
@@ -8,26 +10,38 @@ public class Shell implements Item {
|
||||
private Vector3f targetPosition; // Zielposition des Geschosses0
|
||||
private Vector3f currentPosition; // Aktuelle Position des Geschosses
|
||||
private float speed; // Geschwindigkeit des Geschosses
|
||||
private EffectMessage msg;
|
||||
private ClientGameLogic logic;
|
||||
|
||||
private float progress;
|
||||
private float progress;
|
||||
|
||||
public Shell(Vector3f startPosition, Vector3f targetPosition, float speed) {
|
||||
/**
|
||||
* @param startPosition
|
||||
* @param targetPosition
|
||||
* @param speed
|
||||
* @param msg
|
||||
* @param logic
|
||||
*/
|
||||
public Shell(Vector3f startPosition, Vector3f targetPosition, float speed, EffectMessage msg, ClientGameLogic logic) {
|
||||
this.startPosition = startPosition;
|
||||
this.targetPosition = targetPosition;
|
||||
this.currentPosition = new Vector3f(startPosition); // Initiale Position ist die Startposition
|
||||
this.speed = speed;
|
||||
this.msg = msg;
|
||||
this.logic = logic;
|
||||
}
|
||||
|
||||
// Methode, um die Position des Geschosses basierend auf der Zeit zu aktualisieren
|
||||
public void updatePosition(float deltaTime) {
|
||||
progress+=deltaTime*speed;
|
||||
progress += deltaTime * speed;
|
||||
progress = FloatMath.clamp(progress, 0.0f, 1.0f);
|
||||
|
||||
float t = FloatMath.easeInOutElastic(progress);
|
||||
|
||||
// Interpoliere die Position zwischen Start- und Zielposition basierend auf dem Fortschritt
|
||||
currentPosition.x = FloatMath.interpolateLinear(progress,startPosition.x,targetPosition.x);
|
||||
currentPosition.y = FloatMath.interpolateLinear(progress,startPosition.y,targetPosition.y);
|
||||
currentPosition.z = FloatMath.interpolateLinear(progress,startPosition.z,targetPosition.z);
|
||||
|
||||
//currentPosition.interpolateLocal(startPosition, targetPosition, progress);
|
||||
currentPosition.y = FloatMath.extrapolateLinear(t, startPosition.y, targetPosition.y);
|
||||
currentPosition.x = FloatMath.extrapolateLinear(t, startPosition.x, targetPosition.x);
|
||||
currentPosition.z = FloatMath.extrapolateLinear(t, startPosition.z, targetPosition.z);
|
||||
}
|
||||
|
||||
// Aktuelle Position des Geschosses
|
||||
@@ -40,22 +54,44 @@ public boolean isAtTarget() {
|
||||
return currentPosition.distance(targetPosition) < 0.0001f; // Toleranz für die Zielgenauigkeit
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param visitor the visitor performing operations on the item
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public <T> T accept(Visitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts a visitor that does not return a value. This method is part of the
|
||||
* Visitor design pattern.
|
||||
*
|
||||
* @param visitor the visitor to accept
|
||||
*/
|
||||
@Override
|
||||
public void accept(VoidVisitor visitor) {
|
||||
visitor.visit(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public boolean isFinished() {
|
||||
return progress >= 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public EffectMessage getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public ClientGameLogic getLogic() {
|
||||
return logic;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -22,5 +22,9 @@ public enum Sound {
|
||||
/**
|
||||
* Sound of a ship being destroyed.
|
||||
*/
|
||||
DESTROYED_SHIP
|
||||
DESTROYED_SHIP,
|
||||
/**
|
||||
* Sound of a fired Missile
|
||||
*/
|
||||
MISSILE_LAUNCH
|
||||
}
|
||||
|
||||
@@ -96,6 +96,51 @@ public static float extrapolateLinear(float scale, float startValue, float endVa
|
||||
return ((1f - scale) * startValue) + (scale * endValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates a value to range [0,1] after given easing function
|
||||
* https://easings.net/#easeInOutElastic
|
||||
* @param value the value to interpolate
|
||||
* @return A new value in range [0,1]
|
||||
*/
|
||||
public static float easeInOutElastic(float value) {
|
||||
var c5 = (2 * Math.PI) / 4.5;
|
||||
|
||||
return value == 0
|
||||
? 0
|
||||
: (float) (value == 1
|
||||
? 1
|
||||
: value < 0.5
|
||||
? -(Math.pow(2, 20 * value - 10) * Math.sin((20 * value - 11.125) * c5)) / 2
|
||||
: (Math.pow(2, -20 * value + 10) * Math.sin((20 * value - 11.125) * c5)) / 2 + 1);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fancy effect for 2D Map animation
|
||||
* @param x Progress state of animation
|
||||
* @return new Value for animation
|
||||
*/
|
||||
public static float easeOutBounce(float x) {
|
||||
float n1 = 7.5625f;
|
||||
float d1 = 2.75f;
|
||||
|
||||
if (x < 1 / d1) {
|
||||
return n1 * x * x;
|
||||
} else if (x < 2 / d1) {
|
||||
return (float) (n1 * (x -= 1.5 / d1) * x + 0.75);
|
||||
} else if (x < 2.5 / d1) {
|
||||
return (float) (n1 * (x -= 2.25 / d1) * x + 0.9375);
|
||||
} else {
|
||||
return (float) (n1 * (x -= 2.625 / d1) * x + 0.984375);
|
||||
}
|
||||
}
|
||||
|
||||
public static float easeInBounce(float x) {
|
||||
return 1 - easeOutBounce(1 - x);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the arc cosine of a value.<br>
|
||||
* Special cases:
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* Abstract base class for keeping the scene graph (=view) in sync with the model.
|
||||
*/
|
||||
public abstract class ModelViewSynchronizer<I> {
|
||||
private static final Logger LOGGER = System.getLogger(ModelViewSynchronizer.class.getName());
|
||||
protected static final Logger LOGGER = System.getLogger(ModelViewSynchronizer.class.getName());
|
||||
private final Node itemNode = new Node("items"); //NON-NLS
|
||||
private final Map<I, Spatial> itemMap = new HashMap<>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user