finished 12

This commit is contained in:
Cedric Beck
2024-10-08 14:22:42 +02:00
parent 5edd4ebe0b
commit 4492843ca1
19 changed files with 350 additions and 52 deletions

View File

@@ -26,7 +26,10 @@ map.own=maps/map1.json
robot.targets=2, 0,\
2, 1,\
2, 2,\
2, 3
2, 3,\
2, 4,\
2, 5,\
2, 6
#
# Delay in milliseconds between each shot fired by the RobotClient.
robot.delay=500

View File

@@ -40,6 +40,9 @@
import java.io.IOException;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.LogManager;
@@ -122,6 +125,8 @@ public class BattleshipApp extends SimpleApplication implements BattleshipClient
*/
private final ActionListener escapeListener = (name, isPressed, tpf) -> escape(isPressed);
private EffectHandler effectHandler;
static {
// Configure logging
LogManager manager = LogManager.getLogManager();
@@ -155,6 +160,7 @@ private BattleshipApp() {
logic.addListener(this);
setShowSettings(config.getShowSettings());
setSettings(makeSettings());
effectHandler = null;
}
/**
@@ -224,6 +230,7 @@ public void simpleInitApp() {
setupInput();
setupStates();
setupGui();
effectHandler = new EffectHandler(this);
serverConnection.connect();
}
@@ -302,6 +309,19 @@ public void simpleUpdate(float tpf) {
super.simpleUpdate(tpf);
dialogManager.update(tpf);
logic.update(tpf);
handleTimers(tpf);
}
private void handleTimers(float tpf) {
Iterator<Timer> iter = timerList.iterator();
while (iter.hasNext()) {
Timer next = iter.next();
next.update(tpf);
if (next.isFinished()){
iter.remove();
}
}
}
/**
@@ -437,4 +457,14 @@ void errorDialog(String errorMessage) {
.build()
.open();
}
public EffectHandler getEffectHandler(){
return effectHandler;
}
private final List<Timer> timerList = new ArrayList<>();
public void setTimer(float time, Runnable runnable){
timerList.add(new Timer(time, runnable));
}
}

View File

@@ -0,0 +1,140 @@
package pp.battleship.client;
import com.jme3.app.Application;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
import com.jme3.effect.influencers.RadialParticleInfluencer;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import pp.battleship.model.Battleship;
import pp.battleship.model.IntPoint;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class EffectHandler {
private BattleshipApp app;
private Map<Battleship, List<ParticleEmitter>> effects;
public EffectHandler(BattleshipApp app){
this.app = app;
effects = new HashMap<>();
}
public Node createFire(Vector3f point, Battleship ship){
Node parent = new Node();
parent.setLocalTranslation(point);
ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 50);
Material matRed = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
matRed.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/flame.png"));
fire.setMaterial(matRed);
fire.setImagesX(2);
fire.setImagesY(2);
fire.setEndColor(new ColorRGBA(1f, 0f, 0f, 1f));
fire.setStartColor(new ColorRGBA(1f, 1f, 0f, 0.5f));
fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 1.5f, 0));
fire.setStartSize(.4f);
fire.setEndSize(0.05f);
fire.setGravity(0, 0, 0);
fire.setLowLife(1f);
fire.setHighLife(2f);
fire.getParticleInfluencer().setVelocityVariation(0.2f);
parent.attachChild(fire);
ParticleEmitter smoke = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 600);
Material matBlack = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
matBlack.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Smoke/Smoke.png"));
smoke.setMaterial(matBlack);
smoke.setImagesX(15);
smoke.setImagesY(1);
smoke.setEndColor(new ColorRGBA(1f,1f,1f,0f));
smoke.setStartColor(new ColorRGBA(0.5f,0.5f,0.5f,0.5f));
smoke.getParticleInfluencer().setInitialVelocity(new Vector3f(0,1f, 0));
smoke.setStartSize(.2f);
smoke.setEndSize(0.1f);
smoke.setGravity(0, 0, 0);
smoke.setLowLife(1f);
smoke.setHighLife(5f);
smoke.getParticleInfluencer().setVelocityVariation(0.25f);
parent.attachChild(smoke);
List<ParticleEmitter> oldEffects = new ArrayList<>(effects.getOrDefault(ship,new ArrayList<>()));
oldEffects.add(fire);
oldEffects.add(smoke);
effects.put(ship,oldEffects);
return parent;
}
public void destroyShip(Battleship ship){
for (ParticleEmitter emitter: effects.get(ship)){
emitter.setParticlesPerSec(0);
}
}
public Geometry waterSplash(Vector3f pos){
ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 100);
Material matRed = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
matRed.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/flash.png"));
fire.setMaterial(matRed);
fire.setImagesX(2);
fire.setImagesY(2); // 2x2 texture animation
fire.setEndColor(new ColorRGBA(0.3f, 0.8f, 1f, 0f));
fire.setStartColor(new ColorRGBA(0f, 0f, 1f, 1f));
RadialParticleInfluencer inf = new RadialParticleInfluencer();
inf.setRadialVelocity(5);
inf.setVelocityVariation(0.3f);
inf.setInitialVelocity(new Vector3f(0,3,0));
fire.setParticleInfluencer(inf);
fire.setStartSize(.6f);
fire.setEndSize(0.05f);
fire.setGravity(0, 4f, 0);
fire.setLowLife(1f);
fire.setHighLife(1.5f);
fire.setLocalTranslation(pos);
fire.emitAllParticles();
fire.setParticlesPerSec(0);
app.setTimer(2,()->deleteSplash(fire));
return fire;
}
private void deleteSplash(Geometry splash){
splash.getParent().detachChild(splash);
}
public Geometry debrisSplash(Vector3f pos){
ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 50);
Material matRed = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
matRed.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/Debris.png"));
fire.setMaterial(matRed);
fire.setImagesX(3);
fire.setImagesY(3); // 2x2 texture animation
fire.setEndColor(new ColorRGBA(0.1f, 0.1f, 0.1f, 0f));
fire.setStartColor(new ColorRGBA(0.5f, 0.5f, 0.5f, .8f));
fire.setStartSize(0.1f);
fire.setEndSize(0.5f);
fire.setGravity(0, 2f, 0);
fire.setLowLife(1f);
fire.setHighLife(1.5f);
fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0,2f,0));
fire.getParticleInfluencer().setVelocityVariation(.5f);
fire.setLocalTranslation(pos);
fire.emitAllParticles();
fire.setParticlesPerSec(0);
return fire;
}
}

View File

@@ -3,18 +3,13 @@
import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetLoadException;
import com.jme3.asset.AssetNotFoundException;
import com.jme3.audio.AudioData;
import com.jme3.audio.AudioNode;
import pp.battleship.notification.GameEventListener;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.prefs.Preferences;
import static pp.JmeUtil.loadSound;
import static pp.util.PreferencesUtils.getPreferences;
import static pp.util.Util.loadSound;
/**
* An application state that plays sounds.

View File

@@ -18,8 +18,8 @@
import java.lang.System.Logger.Level;
import java.util.prefs.Preferences;
import static pp.JmeUtil.loadSound;
import static pp.util.PreferencesUtils.getPreferences;
import static pp.util.Util.loadSound;
/**
* An application state that plays sounds.

View File

@@ -0,0 +1,28 @@
package pp.battleship.client;
public class Timer {
private float time;
private final Runnable runnable;
private boolean finished;
public Timer(float time, Runnable runnable){
this.time = time;
this.runnable = runnable;
this.finished = false;
}
public void update(float delta){
if(!finished){
time -= delta;
if(time < 0){
finished = true;
runnable.run();
}
}
}
public boolean isFinished(){
return finished;
}
}

View File

@@ -7,17 +7,13 @@
package pp.battleship.client.gui;
import com.jme3.bounding.BoundingVolume;
import com.jme3.collision.Collidable;
import com.jme3.collision.CollisionResults;
import com.jme3.collision.UnsupportedCollisionException;
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.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.SceneGraphVisitor;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Cylinder;
@@ -27,8 +23,6 @@
import pp.battleship.model.ShipMap;
import pp.battleship.model.Shot;
import java.util.Queue;
import static java.util.Objects.requireNonNull;
import static pp.util.FloatMath.HALF_PI;
import static pp.util.FloatMath.PI;
@@ -76,7 +70,11 @@ 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);
}
private Spatial handleMiss(Shot shot) {
return app.getEffectHandler().waterSplash(mapToWorldCord(shot.getX(), shot.getY()));
}
/**
@@ -91,13 +89,24 @@ 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);
shipNode.getControl(ShipEffectControl.class).hit(shot);
if(ship.isDestroyed()){
shipNode.getControl(ShipEffectControl.class).destroyed();
shipNode.getControl(ShipMovementControl.class).destroyed();
app.setTimer(9,()->handleShipDestroy(shipNode));
}
return null;
}
private void handleShipDestroy(Node shipNode) {
shipNode.getParent().detachChild(shipNode);
}
private Vector3f mapToWorldCord(int x, int y){
return new Vector3f(y+0.5f, 0, x+0.5f);
}
/**
* Creates a cylinder geometry representing the specified shot.
* The appearance of the cylinder depends on whether the shot is a hit or a miss.
@@ -136,7 +145,8 @@ public Spatial visit(Battleship ship) {
final float x = 0.5f * (ship.getMinY() + ship.getMaxY() + 1f);
final float z = 0.5f * (ship.getMinX() + ship.getMaxX() + 1f);
node.setLocalTranslation(x, 0f, z);
node.addControl(new ShipControl(ship));
node.addControl(new ShipMovementControl(ship));
node.addControl(new ShipEffectControl(node, ship, app));
return node;
}

View File

@@ -0,0 +1,55 @@
package pp.battleship.client.gui;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Node;
import com.jme3.scene.control.AbstractControl;
import pp.battleship.client.BattleshipApp;
import pp.battleship.client.EffectHandler;
import pp.battleship.model.Battleship;
import pp.battleship.model.Shot;
public class ShipEffectControl extends AbstractControl {
private final Node shipNode;
private final Battleship battleship;
private final BattleshipApp app;
public ShipEffectControl(Node node, Battleship battleship, BattleshipApp app){
this.shipNode = node;
this.battleship = battleship;
this.app = app;
}
@Override
protected void controlUpdate(float tpf) {
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
public void hit(Shot shot){
Vector3f shipNodePos = shipNode.getLocalTranslation();
Vector3f shotWorld = mapToWorldCord(shot.getX(),shot.getY());
Vector3f firePos = shotWorld.subtract(shipNodePos);
shipNode.attachChild(app.getEffectHandler().createFire(firePos, battleship));
}
private Vector3f mapToWorldCord(int x, int y){
return new Vector3f(y+0.5f, 0, x+0.5f);
}
public void destroyed() {
shipNode.attachChild(app.getEffectHandler().debrisSplash(shipNode.getLocalTranslation()));
app.setTimer(4,this::stopEffects);
}
private void stopEffects(){
app.getEffectHandler().destroyShip(battleship);
}
}

View File

@@ -22,7 +22,10 @@
* Controls the oscillating pitch motion of a battleship model in the game.
* The ship oscillates to simulate a realistic movement on water, based on its orientation and length.
*/
class ShipControl extends AbstractControl {
class ShipMovementControl extends AbstractControl {
private final float sinkingSpeed = 0.04f;
private final float sinkRotSpeed = 0.1f;
/**
* The axis of rotation for the ship's pitch (tilting forward and backward).
*/
@@ -48,14 +51,17 @@ class ShipControl extends AbstractControl {
*/
private float time;
private boolean sinking;
/**
* Constructs a new ShipControl instance for the specified Battleship.
* Constructs a new ShipMovementControl instance for the specified Battleship.
* The ship's orientation determines the axis of rotation, while its length influences
* the cycle duration and amplitude of the oscillation.
*
* @param ship the Battleship object to control
*/
public ShipControl(Battleship ship) {
public ShipMovementControl(Battleship ship) {
sinking = false;
// Determine the axis of rotation based on the ship's orientation
axis = switch (ship.getRot()) {
case LEFT, RIGHT -> Vector3f.UNIT_X;
@@ -75,6 +81,18 @@ public ShipControl(Battleship ship) {
*/
@Override
protected void controlUpdate(float tpf) {
if(sinking) handleSinking(tpf);
else handlePitch(tpf);
}
private void handleSinking(float tpf) {
if (spatial == null) return;
spatial.setLocalTranslation(spatial.getLocalTranslation().add(new Vector3f(0,-1,0).mult(tpf * sinkingSpeed)));
spatial.rotate(tpf * sinkRotSpeed, 0, 0);
}
private void handlePitch(float tpf){
// If spatial is null, do nothing
if (spatial == null) return;
@@ -103,4 +121,8 @@ protected void controlUpdate(float tpf) {
protected void controlRender(RenderManager rm, ViewPort vp) {
// No rendering logic is needed for this control
}
public void destroyed() {
sinking = true;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -7,14 +7,14 @@
package pp.util;
import com.jme3.app.Application;
import com.jme3.asset.AssetLoadException;
import com.jme3.asset.AssetNotFoundException;
import com.jme3.audio.AudioData;
import com.jme3.audio.AudioNode;
//import com.jme3.app.Application;
//import com.jme3.asset.AssetLoadException;
//import com.jme3.asset.AssetNotFoundException;
//import com.jme3.audio.AudioData;
//import com.jme3.audio.AudioNode;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
//import java.lang.System.Logger.Level;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -25,8 +25,6 @@
* A class with auxiliary functions.
*/
public class Util {
private static final Logger LOGGER = System.getLogger(Util.class.getName());
private Util() { /* do not instantiate */ }
/**
@@ -97,24 +95,4 @@ public static <T, E extends T> Set<T> add(Set<T> set, E element) {
newSet.add(element);
return newSet;
}
/**
* Loads a sound from the specified file.
*
* @param app The application
* @param name The name of the sound file.
* @return The loaded AudioNode.
*/
public static AudioNode loadSound(Application app, String name) {
try {
final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer);
sound.setLooping(false);
sound.setPositional(false);
return sound;
}
catch (AssetLoadException | AssetNotFoundException ex) {
LOGGER.log(Level.ERROR, ex.getMessage(), ex);
}
return null;
}
}

View File

@@ -0,0 +1,37 @@
package pp;
import com.jme3.app.Application;
import com.jme3.asset.AssetLoadException;
import com.jme3.asset.AssetNotFoundException;
import com.jme3.audio.AudioData;
import com.jme3.audio.AudioNode;
import pp.util.Util;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
public class JmeUtil {
private JmeUtil(){ /* Do not initialize */ }
private static final Logger LOGGER = System.getLogger(Util.class.getName());
/**
* Loads a sound from the specified file.
*
* @param app The application
* @param name The name of the sound file.
* @return The loaded AudioNode.
*/
public static AudioNode loadSound(Application app, String name) {
try {
final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer);
sound.setLooping(false);
sound.setPositional(false);
return sound;
}
catch (AssetLoadException | AssetNotFoundException ex) {
LOGGER.log(Level.ERROR, ex.getMessage(), ex);
}
return null;
}
}