Add hit/miss effects
This commit is contained in:
@@ -9,6 +9,7 @@ implementation project(":jme-common")
|
|||||||
implementation project(":battleship:model")
|
implementation project(":battleship:model")
|
||||||
|
|
||||||
implementation libs.jme3.desktop
|
implementation libs.jme3.desktop
|
||||||
|
implementation libs.jme3.effects
|
||||||
|
|
||||||
runtimeOnly libs.jme3.awt.dialogs
|
runtimeOnly libs.jme3.awt.dialogs
|
||||||
runtimeOnly libs.jme3.plugins
|
runtimeOnly libs.jme3.plugins
|
||||||
|
|||||||
@@ -91,19 +91,25 @@ private void connect() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a server or if not host connects to one
|
||||||
|
*/
|
||||||
private void connectLocally() {
|
private void connectLocally() {
|
||||||
if(hostServer){
|
if(hostServer){
|
||||||
startServer();
|
startServer();
|
||||||
try {
|
try {
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LOGGER.log(Level.WARNING, e.getMessage(), e);
|
LOGGER.log(Level.WARNING, e.getMessage(), e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connect();
|
connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a host server in a new thread
|
||||||
|
*/
|
||||||
private void startServer() {
|
private void startServer() {
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try{
|
try{
|
||||||
@@ -115,11 +121,13 @@ private void startServer() {
|
|||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles client/host mode
|
||||||
|
*/
|
||||||
private void toggleServerHost(){
|
private void toggleServerHost(){
|
||||||
hostServer = !hostServer;
|
hostServer = !hostServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a dialog indicating that the connection is in progress.
|
* Creates a dialog indicating that the connection is in progress.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
package pp.battleship.client.gui;
|
||||||
|
|
||||||
|
import com.jme3.effect.ParticleEmitter;
|
||||||
|
import com.jme3.effect.ParticleMesh;
|
||||||
|
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 com.jme3.scene.control.Control;
|
||||||
|
import pp.battleship.client.BattleshipApp;
|
||||||
|
import pp.battleship.model.Shot;
|
||||||
|
|
||||||
|
public class ParticleHandler {
|
||||||
|
private final BattleshipApp app;
|
||||||
|
static final System.Logger LOGGER = System.getLogger(ParticleHandler.class.getName());
|
||||||
|
|
||||||
|
private Material material;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new ParticleHandler
|
||||||
|
*
|
||||||
|
* @param app the main battleship application
|
||||||
|
*/
|
||||||
|
public ParticleHandler(BattleshipApp app) {
|
||||||
|
this.app = app;
|
||||||
|
material = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this method is used to create a hit effect at a position
|
||||||
|
*
|
||||||
|
* @param node the node of the battleship where the effect should be attached to
|
||||||
|
* @param shot the shot that hit a target
|
||||||
|
*/
|
||||||
|
public void createHitParticles(Node node, Shot shot) {
|
||||||
|
ParticleEmitter particles1 = new ParticleEmitter("Effect", ParticleMesh.Type.Triangle,30);
|
||||||
|
particles1.setMaterial(material);
|
||||||
|
particles1.setImagesX(2);
|
||||||
|
particles1.setImagesY(2);
|
||||||
|
particles1.setStartColor(ColorRGBA.White);
|
||||||
|
particles1.setEndColor(ColorRGBA.Red);
|
||||||
|
particles1.getParticleInfluencer().setInitialVelocity(new Vector3f(0,1,0));
|
||||||
|
particles1.setStartSize(0.45f);
|
||||||
|
particles1.setEndSize(0.1f);
|
||||||
|
particles1.setGravity(0, -0.5f, 0);
|
||||||
|
particles1.setLowLife(1f);
|
||||||
|
particles1.setHighLife(4f);
|
||||||
|
particles1.setParticlesPerSec(0);
|
||||||
|
particles1.setLocalTranslation(shot.getY() + 0.5f, 0 , shot.getX() + 0.5f);
|
||||||
|
particles1.emitAllParticles();
|
||||||
|
|
||||||
|
ParticleEmitter particles2 = new ParticleEmitter("Effect", ParticleMesh.Type.Triangle,30);
|
||||||
|
particles2.setMaterial(material);
|
||||||
|
particles2.setImagesX(2);
|
||||||
|
particles2.setImagesY(2);
|
||||||
|
particles2.setStartColor(ColorRGBA.Orange);
|
||||||
|
particles2.setEndColor(ColorRGBA.Yellow);
|
||||||
|
particles2.getParticleInfluencer().setInitialVelocity(new Vector3f(0,2,0));
|
||||||
|
particles2.setStartSize(0.7f);
|
||||||
|
particles2.setEndSize(0.1f);
|
||||||
|
particles2.setGravity(0, -0.5f, 0);
|
||||||
|
particles2.setLowLife(0.5f);
|
||||||
|
particles2.setHighLife(1f);
|
||||||
|
particles2.setParticlesPerSec(0);
|
||||||
|
particles2.setLocalTranslation(shot.getY() + 1f, 0 , shot.getX() + 1f);
|
||||||
|
particles2.emitAllParticles();
|
||||||
|
|
||||||
|
ParticleEmitter particles3 = new ParticleEmitter("Effect", ParticleMesh.Type.Triangle,30);
|
||||||
|
particles3.setMaterial(material);
|
||||||
|
particles3.setImagesX(1);
|
||||||
|
particles3.setImagesY(1);
|
||||||
|
particles3.setStartColor(ColorRGBA.Red);
|
||||||
|
particles3.setEndColor(ColorRGBA.Gray);
|
||||||
|
particles3.getParticleInfluencer().setInitialVelocity(new Vector3f(0,0.5f,0));
|
||||||
|
particles3.setStartSize(0.8f);
|
||||||
|
particles3.setEndSize(0.1f);
|
||||||
|
particles3.setGravity(0, -0.5f, 0);
|
||||||
|
particles3.setLowLife(0.5f);
|
||||||
|
particles3.setHighLife(0.5f);
|
||||||
|
particles3.setParticlesPerSec(0);
|
||||||
|
particles3.setLocalTranslation(shot.getY() + 1f, 0 , shot.getX() + 1f);
|
||||||
|
particles3.emitAllParticles();
|
||||||
|
|
||||||
|
node.attachChild(particles1);
|
||||||
|
particles1.addControl((Control) new ParticleControl(particles1, node));
|
||||||
|
|
||||||
|
node.attachChild(particles2);
|
||||||
|
particles2.addControl((Control) new ParticleControl(particles2, node));
|
||||||
|
|
||||||
|
node.attachChild(particles3);
|
||||||
|
particles3.addControl((Control) new ParticleControl(particles3, node));
|
||||||
|
|
||||||
|
LOGGER.log(System.Logger.Level.DEBUG, "Hit-particles at {0}", particles1.getLocalTranslation().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a miss effect at a certain location
|
||||||
|
* @param shot the shot that missed
|
||||||
|
*/
|
||||||
|
public ParticleEmitter createMissParticles(Shot shot) {
|
||||||
|
ParticleEmitter particles = new ParticleEmitter("MissEffect", ParticleMesh.Type.Triangle, 15);
|
||||||
|
particles.setMaterial(material);
|
||||||
|
particles.setImagesX(2);
|
||||||
|
particles.setImagesY(2);
|
||||||
|
particles.setStartColor(ColorRGBA.Blue);
|
||||||
|
particles.setEndColor(ColorRGBA.Black);
|
||||||
|
particles.getParticleInfluencer().setInitialVelocity(new Vector3f(0.1f, 0.2f, 0));
|
||||||
|
particles.setStartSize(0.6f);
|
||||||
|
particles.setEndSize(0.05f);
|
||||||
|
particles.setGravity(0, -0.1f, 0);
|
||||||
|
particles.setLowLife(0.5f);
|
||||||
|
particles.setHighLife(3.0f);
|
||||||
|
particles.setParticlesPerSec(0);
|
||||||
|
particles.setLocalTranslation(shot.getY() + 0.5f, 0 , shot.getX() + 0.5f);
|
||||||
|
particles.emitAllParticles();
|
||||||
|
|
||||||
|
LOGGER.log(System.Logger.Level.DEBUG, "Hit-particles at {0}", particles.getLocalTranslation().toString());
|
||||||
|
|
||||||
|
return particles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This inner class is used to control the effects
|
||||||
|
*/
|
||||||
|
private static class ParticleControl extends AbstractControl {
|
||||||
|
private final ParticleEmitter emitter;
|
||||||
|
private final Node parentNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this constructor is used to when the particle should be attached to a specific node
|
||||||
|
*
|
||||||
|
* @param emitter the Particle emitter to be controlled
|
||||||
|
* @param parentNode the node to be attached
|
||||||
|
*/
|
||||||
|
public ParticleControl(ParticleEmitter emitter, Node parentNode) {
|
||||||
|
this.emitter = emitter;
|
||||||
|
this.parentNode = parentNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constructor is used when the particle shouldn't be attached to
|
||||||
|
* a specific node
|
||||||
|
*
|
||||||
|
* @param emitter the Particle emitter to be controlled
|
||||||
|
*/
|
||||||
|
public ParticleControl(ParticleEmitter emitter) {
|
||||||
|
this.emitter = emitter;
|
||||||
|
this.parentNode = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method which checks if the particle is not rendered anymore so it can be removed
|
||||||
|
*
|
||||||
|
* @param tpf time per frame (in seconds)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void controlUpdate(float tpf) {
|
||||||
|
if (emitter.getParticlesPerSec() == 0 && emitter.getNumVisibleParticles() == 0) {
|
||||||
|
if (parentNode != null)
|
||||||
|
parentNode.detachChild(emitter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param rm the RenderManager rendering the controlled Spatial (not null)
|
||||||
|
* @param vp the ViewPort being rendered (not null)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,8 +45,9 @@ class SeaSynchronizer extends ShipMapSynchronizer {
|
|||||||
private static final ColorRGBA SPLASH_COLOR = new ColorRGBA(0f, 0f, 1f, 0.4f);
|
private static final ColorRGBA SPLASH_COLOR = new ColorRGBA(0f, 0f, 1f, 0.4f);
|
||||||
private static final ColorRGBA HIT_COLOR = new ColorRGBA(1f, 0f, 0f, 0.4f);
|
private static final ColorRGBA HIT_COLOR = new ColorRGBA(1f, 0f, 0f, 0.4f);
|
||||||
|
|
||||||
private final ShipMap map;
|
private final ShipMap map;
|
||||||
private final BattleshipApp app;
|
private final BattleshipApp app;
|
||||||
|
private final ParticleHandler handler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@@ -59,6 +60,9 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) {
|
|||||||
super(app.getGameLogic().getOwnMap(), root);
|
super(app.getGameLogic().getOwnMap(), root);
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.map = map;
|
this.map = map;
|
||||||
|
|
||||||
|
handler = new ParticleHandler(app);
|
||||||
|
|
||||||
addExisting();
|
addExisting();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +76,7 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Spatial visit(Shot shot) {
|
public Spatial visit(Shot shot) {
|
||||||
return shot.isHit() ? handleHit(shot) : createCylinder(shot);
|
return shot.isHit() ? handleHit(shot) : handler.createMissParticles(shot);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,9 +91,7 @@ private Spatial handleHit(Shot shot) {
|
|||||||
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);
|
handler.createHitParticles(shipNode, shot);
|
||||||
representation.getLocalTranslation().subtractLocal(shipNode.getLocalTranslation());
|
|
||||||
shipNode.attachChild(representation);
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,12 @@
|
|||||||
* The ship oscillates to simulate a realistic movement on water, based on its orientation and length.
|
* The ship oscillates to simulate a realistic movement on water, based on its orientation and length.
|
||||||
*/
|
*/
|
||||||
class ShipControl extends AbstractControl {
|
class ShipControl extends AbstractControl {
|
||||||
|
/**
|
||||||
|
* The sinking height, after wich the ship will get removed
|
||||||
|
*/
|
||||||
|
private static final Float SINKING_HEIGHT = -0.6f;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The axis of rotation for the ship's pitch (tilting forward and backward).
|
* The axis of rotation for the ship's pitch (tilting forward and backward).
|
||||||
*/
|
*/
|
||||||
@@ -48,6 +54,11 @@ class ShipControl extends AbstractControl {
|
|||||||
*/
|
*/
|
||||||
private float time;
|
private float time;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ship, that the ShipControl controls
|
||||||
|
*/
|
||||||
|
private final Battleship ship;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new ShipControl instance for the specified Battleship.
|
* Constructs a new ShipControl instance for the specified Battleship.
|
||||||
* The ship's orientation determines the axis of rotation, while its length influences
|
* The ship's orientation determines the axis of rotation, while its length influences
|
||||||
@@ -56,6 +67,8 @@ class ShipControl extends AbstractControl {
|
|||||||
* @param ship the Battleship object to control
|
* @param ship the Battleship object to control
|
||||||
*/
|
*/
|
||||||
public ShipControl(Battleship ship) {
|
public ShipControl(Battleship ship) {
|
||||||
|
this.ship = ship;
|
||||||
|
|
||||||
// Determine the axis of rotation based on the ship's orientation
|
// Determine the axis of rotation based on the ship's orientation
|
||||||
axis = switch (ship.getRot()) {
|
axis = switch (ship.getRot()) {
|
||||||
case LEFT, RIGHT -> Vector3f.UNIT_X;
|
case LEFT, RIGHT -> Vector3f.UNIT_X;
|
||||||
@@ -78,6 +91,13 @@ protected void controlUpdate(float tpf) {
|
|||||||
// If spatial is null, do nothing
|
// If spatial is null, do nothing
|
||||||
if (spatial == null) return;
|
if (spatial == null) return;
|
||||||
|
|
||||||
|
if (ship.isDestroyed() && spatial.getLocalTranslation().getY() < SINKING_HEIGHT) { // removes the ship, if it is completely sunk
|
||||||
|
spatial.getParent().detachChild(spatial);
|
||||||
|
}
|
||||||
|
else if (ship.isDestroyed() && spatial.getLocalTranslation().getY() >= SINKING_HEIGHT) { // sink the ship, if it's not completely sunk
|
||||||
|
spatial.move(0, tpf * -0.03f, 0);
|
||||||
|
}
|
||||||
|
|
||||||
// Update the time within the oscillation cycle
|
// Update the time within the oscillation cycle
|
||||||
time = (time + tpf) % cycle;
|
time = (time + tpf) % cycle;
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
library('jme3-testdata', 'org.jmonkeyengine', 'jme3-testdata').versionRef('jme')
|
library('jme3-testdata', 'org.jmonkeyengine', 'jme3-testdata').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')
|
||||||
|
library('jme3-effects', 'org.jmonkeyengine', 'jme3-effects').versionRef('jme')
|
||||||
|
|
||||||
library('lemur', 'com.simsilica:lemur:1.16.0')
|
library('lemur', 'com.simsilica:lemur:1.16.0')
|
||||||
library('lemur-proto', 'com.simsilica:lemur-proto:1.13.0')
|
library('lemur-proto', 'com.simsilica:lemur-proto:1.13.0')
|
||||||
|
|||||||
Reference in New Issue
Block a user