merge development into dev/client_beck

This commit is contained in:
Cedric Beck
2024-12-07 17:04:43 +01:00
180 changed files with 9314 additions and 1696 deletions

View File

@@ -39,6 +39,7 @@ public enum Asset {
shieldCard,
shieldSymbol("Models/shieldCard/shieldSymbol.j3o", "Models/shieldCard/shieldCard_diff.png"),
dice,
missile("Models/missile/AVMT300.obj", "Models/missile/texture.jpg", 0.1f),
tankShoot("Models/tank/tankShoot_bot.j3o", "Models/tank/tank_diff.png"),
tankShootTop("Models/tank/tankShoot_top.j3o", "Models/tank/tank_diff.png"),
treesSmallBackground("Models/treeSmall/treesSmallBackground.j3o", "Models/treeSmall/treeSmall_diff.png", 1.2f),

View File

@@ -149,25 +149,16 @@ else if(boardSelect != null) {
if (name.equals("Right")) {
isRotateRight = !isRotateRight;
}
if(name.equals("Test") &&isPressed){
if(name.equals("Test2") &&isPressed){
if(app.getView() instanceof GameView gameView){
if(p == null) {
p = UUID.randomUUID();
gameView.getBoardHandler().addPlayer(Color.AIRFORCE,List.of(p,UUID.randomUUID(),UUID.randomUUID(),UUID.randomUUID()));
gameView.getBoardHandler().movePieceStartAnim(p,0);
gameView.getGuiHandler().addCardOwn(BonusCard.SHIELD);
gameView.getGuiHandler().setSelectableCards(List.of(BonusCard.SHIELD));
// gameView.getBoardHandler().movePieceAnim(p,0, 8);
//gameView.getBoardHandler().movePieceAnim(p,0, 8);
} else {
// gameView.getBoardHandler().throwBombAnim(p);
gameView.getBoardHandler().throwPiece(p,Color.ARMY);
// gameView.getBoardHandler().movePieceAnim(p,0,20);
// gameView.getBoardHandler().throwPiece(p,Color.CYBER);
// gameView.getBoardHandler().throwPiece(p,Color.AIRFORCE);
gameView.getBoardHandler().throwPiece(p, Color.ARMY);
//gameView.getBoardHandler().movePieceStartAnim(p,0);
}

View File

@@ -1,16 +1,8 @@
package pp.mdga.client;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.server.MdgaServer;
import pp.mdga.client.view.CeremonyView;
import pp.mdga.client.view.GameView;
import pp.mdga.client.view.LobbyView;
import pp.mdga.game.BonusCard;
import pp.mdga.game.Color;
import pp.mdga.message.client.LobbyReadyMessage;
import pp.mdga.notification.AcquireCardNotification;
import pp.mdga.notification.DrawCardNotification;
import pp.mdga.notification.TskSelectNotification;
import java.util.UUID;
import java.util.logging.Level;
@@ -74,10 +66,11 @@ public void selectCard(BonusCard card) {
this.card = card;
GameView gameView = (GameView) app.getView();
if(card != null) {
gameView.needConfirm();
} else {
gameView.noConfirm();
gameView.showNoPower();
}
}
@@ -87,20 +80,19 @@ public void confirm() {
GameView gameView = (GameView) app.getView();
if(a != null && b != null) {
selectPiece(a);
selectPiece(b);
app.getGameLogic().selectPiece(a);
app.getGameLogic().selectPiece(b);
gameView.getBoardHandler().clearSelectable();
} else if (a != null) {
selectPiece(a);
app.getGameLogic().selectPiece(a);
gameView.getBoardHandler().clearSelectable();
} else if (card != null){
selectCard(card);
gameView.getGuiHandler().clearSelectableCards();
} else {
throw new RuntimeException("nothing to confirm");
app.getGameLogic().selectCard(card);
gameView.getGuiHandler().clearSelectableCards();
}
gameView.noConfirm();
gameView.hideNoPower();
}
public void selectTsk(Color color) {

View File

@@ -1,5 +1,6 @@
package pp.mdga.client;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.board.BoardHandler;
import pp.mdga.client.gui.GuiHandler;
import pp.mdga.client.view.CeremonyView;
@@ -15,13 +16,26 @@ public class NotificationSynchronizer {
private ArrayList<Notification> notifications = new ArrayList<>();
private NanoTimer timer = new NanoTimer();
private float delay = 0;
private static final float STANDARD_DELAY = 2.5f;
NotificationSynchronizer(MdgaApp app) {
this.app = app;
}
public void update() {
Notification n = app.getGameLogic().getNotification();
while (n != null) {
while (timer.getTimeInSeconds() >= delay) {
Notification n = app.getGameLogic().getNotification();
if(n == null) {
return;
}
timer.reset();
delay = 0;
if(n instanceof InfoNotification infoNotification) {
app.getView().showInfo(infoNotification.getMessage(), infoNotification.isError());
return;
@@ -89,9 +103,13 @@ private void handleGame(Notification notification) {
if (notification instanceof AcquireCardNotification n) {
guiHandler.addCardOwn(n.getBonusCard());
app.getAcousticHandler().playSound(MdgaSound.BONUS);
delay = STANDARD_DELAY;
} else if (notification instanceof ActivePlayerNotification n) {
gameView.getGuiHandler().setActivePlayer(n.getColor());
if(n.getColor() != ownColor) boardHandler.showDice(n.getColor());
app.getAcousticHandler().playSound(MdgaSound.UI90);
delay = STANDARD_DELAY;
} else if (notification instanceof CeremonyNotification ceremonyNotification) {
app.enter(MdgaState.CEREMONY);
CeremonyView ceremonyView = (CeremonyView) app.getView();
@@ -126,8 +144,8 @@ private void handleGame(Notification notification) {
} else if (notification instanceof HomeMoveNotification home) {
boardHandler.movePieceHomeAnim(home.getPieceId(), home.getHomeIndex());
guiHandler.hideText();
} else if (notification instanceof InterruptNotification) {
gameView.enterInterrupt();
} else if (notification instanceof InterruptNotification notification1) {
gameView.enterInterrupt(notification1.getColor());
} else if (notification instanceof MovePieceNotification n) {
if(n.isMoveStart()) {
//StartMove
@@ -160,8 +178,10 @@ private void handleGame(Notification notification) {
if (n.isTurbo()) guiHandler.showRolledDiceMult(n.getEyes(), n.getMultiplier(), n.getColor());
else guiHandler.showRolledDice(n.getEyes(), n.getColor());
}
delay = 7;
} else if (notification instanceof SelectableCardsNotification n) {
guiHandler.setSelectableCards(n.getCards());
gameView.showNoPower();
} else if (notification instanceof ShieldActiveNotification n) {
boardHandler.shieldPiece(n.getPieceId());
} else if (notification instanceof ShieldSuppressedNotification n) {

View File

@@ -18,12 +18,14 @@ public class AcousticHandler {
private boolean fading = false; // Indicates if a fade is in progress
private NanoTimer fadeTimer = new NanoTimer(); // Timer to track fade progress
private static final float FADE_DURATION = 3.0f; // Duration for outfade
private static final float FADE_DURATION = 2.0f; // Duration for outfade
private static final float CROSSFADE_DURATION = 1.5f; // Duration for infade
private GameMusic playing = null; // Currently playing track
private GameMusic scheduled = null; // Scheduled track to play next
private GameMusic old = null; // Old track being faded out
private GameMusic birds;
private float mainVolume = 0.0f;
private float musicVolume = 1.0f;
private float soundVolume = 1.0f;
@@ -38,6 +40,8 @@ public AcousticHandler(MdgaApp app) {
mainVolume = prefs.getFloat("mainVolume", 1.0f);
musicVolume = prefs.getFloat("musicVolume", 1.0f);
soundVolume = prefs.getFloat("soundVolume", 1.0f);
birds = new GameMusic(app, MusicAsset.BIRDS, getSoundVolumeTotal(), MusicAsset.BIRDS.getSubVolume(), MusicAsset.BIRDS.getLoop(), 0.0f);
}
/**
@@ -60,6 +64,8 @@ public void update() {
iterator.remove();
}
}
birds.update(Math.min(getSoundVolumeTotal(), getMusicVolumeTotal() > 0 ? 0 : 1));
}
/**
@@ -113,9 +119,9 @@ public void playSound(MdgaSound sound) {
assets.add(new SoundAssetDelayVolume(SoundAsset.JET, 1.0f, 0.0f));
break;
case EXPLOSION:
assets.add(new SoundAssetDelayVolume(SoundAsset.EXPLOSION_1, 1.0f, 4f));
assets.add(new SoundAssetDelayVolume(SoundAsset.EXPLOSION_2, 1.0f, 4f));
assets.add(new SoundAssetDelayVolume(SoundAsset.THUNDER, 1.0f, 4f));
assets.add(new SoundAssetDelayVolume(SoundAsset.EXPLOSION_1, 1.0f, 0f));
assets.add(new SoundAssetDelayVolume(SoundAsset.EXPLOSION_2, 1.0f, 0f));
assets.add(new SoundAssetDelayVolume(SoundAsset.THUNDER, 1.0f, 0f));
break;
case LOSE:
assets.add(new SoundAssetDelayVolume(SoundAsset.LOSE, 1.0f, 0.0f));
@@ -123,6 +129,15 @@ public void playSound(MdgaSound sound) {
case BONUS:
assets.add(new SoundAssetDelayVolume(SoundAsset.BONUS, 1.0f, 0.0f));
break;
case UI90:
assets.add(new SoundAssetDelayVolume(SoundAsset.UI90, 1.0f, 0.0f));
break;
case MISSILE:
assets.add(new SoundAssetDelayVolume(SoundAsset.MISSILE, 1.0f, 0.0f));
break;
case MATRIX:
assets.add(new SoundAssetDelayVolume(SoundAsset.MATRIX, 1.0f, 0.0f));
break;
case TURRET_ROTATE:
assets.add(new SoundAssetDelayVolume(SoundAsset.TURRET_ROTATE, 0.7f, 0f));
break;
@@ -153,6 +168,10 @@ public void playState(MdgaState state) {
}
MusicAsset asset = null;
birds.pause();
float pause = 0.0f;
switch (state) {
case MAIN:
playGame = false;
@@ -163,10 +182,12 @@ public void playState(MdgaState state) {
asset = MusicAsset.LOBBY;
break;
case GAME:
birds.play();
addGameTracks();
playGame = true;
assert (!gameTracks.isEmpty()) : "no more game music available";
asset = gameTracks.remove(0);
pause = 2.0f;
break;
case CEREMONY:
playGame = false;
@@ -178,7 +199,7 @@ public void playState(MdgaState state) {
assert (null != asset) : "music sceduling went wrong";
scheduled = new GameMusic(app, asset, getMusicVolumeTotal(), asset.getSubVolume(), asset.getLoop(), 0.0f);
scheduled = new GameMusic(app, asset, getMusicVolumeTotal(), asset.getSubVolume(), asset.getLoop(), pause);
}
/**
@@ -454,7 +475,7 @@ public void setSoundVolume(float soundVolume) {
*/
float getMusicVolumeTotal() {
return getMusicVolume() * getMainVolume();
return getMusicVolume() * getMainVolume() / 2;
}
/**

View File

@@ -35,6 +35,9 @@ public enum MdgaSound {
EXPLOSION,
LOSE,
BONUS,
UI90,
MISSILE,
MATRIX,
TURRET_ROTATE,
TANK_SHOOT,
TANK_EXPLOSION

View File

@@ -10,12 +10,13 @@ enum MusicAsset {
MAIN_MENU("Spaceship.wav", true, 1.0f),
LOBBY("DeadPlanet.wav", true, 1.0f),
CEREMONY("80s,Disco,Life.wav", true, 1.0f),
GAME_1("NeonRoadTrip.wav", 1.0f),
GAME_2("NoPressureTrance.wav", 1.0f),
GAME_3("TheSynthRave.wav", 1.0f),
GAME_4("LaserParty.wav", 1.0f),
GAME_5("RetroNoir.wav", 1.0f),
GAME_6("SpaceInvaders.wav", 1.0f);
GAME_1("NeonRoadTrip.wav", 0.5f),
GAME_2("NoPressureTrance.wav", 0.5f),
GAME_3("TheSynthRave.wav", 0.5f),
GAME_4("LaserParty.wav", 0.5f),
GAME_5("RetroNoir.wav", 0.5f),
GAME_6("SpaceInvaders.wav", 0.5f),
BIRDS("nature-ambience.ogg", true, 1.0f);
private final String path;
private final boolean loop;

View File

@@ -35,6 +35,8 @@ enum SoundAsset {
UI90("ui90.ogg"),
BONUS("bonus.ogg"),
LOSE("lose.ogg"),
MISSILE("missile.ogg"),
MATRIX("matrix.wav"),
CONNECTED("connected.wav"),
TURRET_ROTATE("turret_rotate.ogg"),
TANK_SHOOT("tank_shoot.ogg")

View File

@@ -8,7 +8,12 @@
import com.jme3.scene.Node;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
/**
* The {@code Explosion} class represents an explosion effect in a 3D environment.
* It manages the creation, configuration, and triggering of particle emitters for fire and smoke effects.
*/
public class Explosion {
private final Node rootNode;
@@ -22,11 +27,11 @@ public class Explosion {
private final Material mat;
/**
* Konstruktor r die Explosion.
* Constructor for the {@code Explosion} class.
*
* @param app Die Hauptanwendung.
* @param rootNode Der Root-Knoten, an den die Explosion angefügt wird.
* @param location Der Ort der Explosion in World-Koordinaten.
* @param app The main application managing the explosion.
* @param rootNode The root node to which the explosion effects will be attached.
* @param location The location of the explosion in world coordinates.
*/
public Explosion(MdgaApp app, Node rootNode, Vector3f location) {
this.app = app;
@@ -34,51 +39,54 @@ public Explosion(MdgaApp app, Node rootNode, Vector3f location) {
this.location = location;
this.mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.getAdditionalRenderState().setDepthWrite(false);
mat.getAdditionalRenderState().setDepthTest(false);
mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/flame.png"));
}
/**
* Initialisiert den Partikel-Emitter r die Explosion.
* Initializes the particle emitters for the explosion effect.
* Configures the fire and smoke emitters with appearance, behavior, and lifespan.
*/
private void initializeEmitter() {
fire = new ParticleEmitter("Effect", Type.Triangle,50);
fire.setMaterial(mat);
fire.setImagesX(2);
fire.setImagesY(2);
fire.setStartColor(ColorRGBA.Yellow);
fire.setEndColor(ColorRGBA.Red);
fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0.2f,0.2f,4f));
fire.getParticleInfluencer().setVelocityVariation(0.4f);
fire.setStartSize(0.1f);
fire.setEndSize(0.8f);
fire.setStartSize(0.7f);
fire.setEndSize(1.8f);
fire.setGravity(0, 0, -0.1f);
fire.setLowLife(0.5f);
fire.setHighLife(2.2f);
fire.setParticlesPerSec(0);
fire.setLocalTranslation(location);
fire.move(0, 0, 45);
smoke = new ParticleEmitter("Effect2", Type.Triangle,40);
smoke.setMaterial(mat);
smoke.setImagesX(2);
smoke.setImagesY(2);
smoke.setImagesX(3);
smoke.setImagesY(3);
smoke.setStartColor(ColorRGBA.DarkGray);
smoke.setEndColor(new ColorRGBA(0.05f, 0.05f, 0.05f, 1));
smoke.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f,0.0f,0.7f));
smoke.getParticleInfluencer().setVelocityVariation(0.5f);
smoke.setStartSize(0.2f);
smoke.setEndSize(0.5f);
smoke.setStartSize(0.8f);
smoke.setEndSize(1.5f);
smoke.setGravity(0, 0, -0.3f);
smoke.setLowLife(1.2f);
smoke.setHighLife(5.5f);
smoke.setParticlesPerSec(0);
smoke.setLocalTranslation(location);
smoke.move(0, 0, 45);
app.getAcousticHandler().playSound(MdgaSound.EXPLOSION);
}
/**
* Löst die Explosion aus.
* Triggers the explosion effect by attaching and activating the particle emitters for fire and smoke.
* Both emitters are automatically detached after a predefined duration.
*/
public void trigger() {
if (!triggered) {

View File

@@ -17,31 +17,34 @@
import java.util.UUID;
/**
* The {@code JetAnimation} class handles the animation of a jet model in a 3D environment.
* It creates a jet model, animates its movement along a curved path, triggers an explosion at a target point,
* and performs additional actions upon animation completion.
*/
public class JetAnimation {
private final MdgaApp app; // Referenz auf die Hauptanwendung
private final Node rootNode; // Root-Knoten, an dem die Animation hängt
private Spatial jetModel; // Das Model des "jet"
private final Vector3f spawnPoint; // Spawnpunkt des Jets
private final Vector3f nodePoint; // Punkt des überflogenen Knotens
private final Vector3f despawnPoint; // Punkt, an dem der Jet despawnt
private final float curveHeight; // Maximale Höhe der Kurve
private final float animationDuration; // Dauer der Animation
private final MdgaApp app;
private final Node rootNode;
private Spatial jetModel;
private final Vector3f spawnPoint;
private final Vector3f nodePoint;
private final Vector3f despawnPoint;
private final float curveHeight;
private final float animationDuration;
private Explosion explosion;
private final UUID id;
private Runnable actionAfter;
/**
* Konstruktor r die ThrowAnimation-Klasse.
* Constructor for the {@code JetAnimation} class.
*
* @param app Die Hauptanwendung
* @param rootNode Der Root-Knoten, an dem der Jet angefügt wird
* @param uuid Die UUID des pieces
* @param targetPoint Der Punkt, an dem der Jet spawnt
* @param curveHeight Die maximale he der Flugkurve
* @param animationDuration Die Gesamtdauer der Animation in Sekunden
* @param app The main application managing the jet animation.
* @param rootNode The root node to which the jet model will be attached.
* @param targetPoint The target point where the explosion will occur.
* @param curveHeight The height of the curve for the jet's flight path.
* @param animationDuration The total duration of the jet animation.
*/
public JetAnimation(MdgaApp app, Node rootNode, UUID uuid, Vector3f targetPoint, float curveHeight, float animationDuration, Runnable actionAfter) {
public JetAnimation(MdgaApp app, Node rootNode, Vector3f targetPoint, float curveHeight, float animationDuration, Runnable actionAfter) {
Vector3f spawnPoint = targetPoint.add(170, 50, 50);
Vector3f controlPoint = targetPoint.add(new Vector3f(0, 0, -45));
@@ -56,14 +59,12 @@ public JetAnimation(MdgaApp app, Node rootNode, UUID uuid, Vector3f targetPoint,
this.curveHeight = curveHeight;
this.animationDuration = animationDuration;
id = uuid;
explosion = new Explosion(app, rootNode, nodePoint);
explosion = new Explosion(app, rootNode, targetPoint);
this.actionAfter = actionAfter;
}
/**
* Startet die Animation.
* Starts the jet animation by spawning the jet model and initiating its movement along the predefined path.
*/
public void start() {
app.getAcousticHandler().playSound(MdgaSound.JET);
@@ -72,7 +73,7 @@ public void start() {
}
/**
* Spawnt den Jet an der spezifizierten Position.
* Spawns the jet model at the designated spawn point, applying material, scaling, and rotation.
*/
private void spawnJet() {
jetModel = app.getAssetManager().loadModel(Asset.jet.getModelPath());
@@ -85,12 +86,11 @@ private void spawnJet() {
jetModel.setMaterial(mat);
rootNode.attachChild(jetModel);
app.getAcousticHandler().playSound(MdgaSound.EXPLOSION);
}
/**
* Animiert den Jet entlang einer Kurve und lässt ihn anschließend verschwinden.
/**actionAfter
* Animates the jet along a Bezier curve path, triggers the explosion effect at the appropriate time,
* and performs cleanup operations after the animation completes.
*/
private void animateJet() {
Vector3f controlPoint1 = spawnPoint.add(0, curveHeight, 0);
@@ -127,9 +127,7 @@ protected void controlUpdate(float tpf) {
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
// Wird hier nicht benötigt
}
protected void controlRender(RenderManager rm, ViewPort vp) {}
});
}
@@ -138,11 +136,20 @@ private void endAnim(){
}
/**
* Repräsentiert eine 3D-Bezier-Kurve mit vier Kontrollpunkten.
* The {@code BezierCurve3f} class represents a 3D cubic Bezier curve.
* It provides methods to interpolate positions and derivatives along the curve.
*/
private static class BezierCurve3f {
private final Vector3f p0, p1, p2, p3;
/**
* Constructor for the {@code BezierCurve3f} class.
*
* @param p0 The starting point of the curve.
* @param p1 The first control point influencing the curve's shape.
* @param p2 The second control point influencing the curve's shape.
* @param p3 The endpoint of the curve.
*/
public BezierCurve3f(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) {
this.p0 = p0;
this.p1 = p1;
@@ -150,6 +157,12 @@ public BezierCurve3f(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) {
this.p3 = p3;
}
/**
* Interpolates a position along the curve at a given progress value {@code t}.
*
* @param t The progress value (0.0 to 1.0) along the curve.
* @return The interpolated position on the curve.
*/
public Vector3f interpolate(float t) {
float u = 1 - t;
float tt = t * t;
@@ -164,6 +177,12 @@ public Vector3f interpolate(float t) {
return point;
}
/**
* Computes the derivative at a given progress value {@code t}, representing the direction along the curve.
*
* @param t The progress value (0.0 to 1.0) along the curve.
* @return The derivative (direction vector) at the specified progress.
*/
public Vector3f interpolateDerivative(float t) {
float u = 1 - t;
float tt = t * t;

View File

@@ -0,0 +1,185 @@
package pp.mdga.client.animation;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import pp.mdga.client.Asset;
import pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
import pp.mdga.client.board.BoardHandler;
import java.util.UUID;
/**
* The {@code MissileAnimation} class handles the animation of a missile moving along a parabolic path
* towards a target point in a 3D environment. It also triggers an explosion at the target upon impact.
*/
public class MissileAnimation {
private final Node rootNode;
private final MdgaApp app;
private final Vector3f start;
private final Vector3f target;
private final float flightTime;
private Explosion explosion;
private Spatial missileModel;
private Runnable actionAfter;
private ParticleEmitter smoke;
private Node missileNode = new Node();
private final Material mat;
/**
* Constructor for the {@code MissileAnimation} class.
*
* @param app The main application managing the missile animation.
* @param rootNode The root node to which the missile model will be attached.
* @param target The target point where the missile will explode.
* @param flightTime The total flight time of the missile.
*/
public MissileAnimation(MdgaApp app, Node rootNode, Vector3f target, float flightTime, Runnable actionAfter) {
this.app = app;
this.rootNode = rootNode;
this.flightTime = flightTime;
this.actionAfter = actionAfter;
explosion = new Explosion(app, rootNode, target);
this.target = target.add(new Vector3f(1.5f, -1, 0));
start = BoardHandler.gridToWorld(12, 0);
start.add(new Vector3f(0, 0, 0));
this.mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/vapor_cloud.png"));
smoke = new ParticleEmitter("Effect2", ParticleMesh.Type.Triangle,400);
smoke.setMaterial(mat);
smoke.setImagesX(3);
smoke.setImagesY(3);
smoke.setStartColor(ColorRGBA.DarkGray);
smoke.setEndColor(new ColorRGBA(0.05f, 0.05f, 0.05f, 1));
smoke.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f,0.0f,0.0f));
smoke.getParticleInfluencer().setVelocityVariation(0.1f);
smoke.setStartSize(0.8f);
smoke.setEndSize(1.5f);
smoke.setGravity(0, 0, -0.3f);
smoke.setLowLife(1.2f);
smoke.setHighLife(3.5f);
smoke.setParticlesPerSec(100);
missileNode.attachChild(smoke);
smoke.move(1, 0.85f, 1.0f);
}
/**
* Starts the missile animation by loading the missile model and initiating its parabolic movement.
*/
public void start() {
Smoke s = new Smoke(app, rootNode, start);
s.trigger();
loadMissile();
app.getAcousticHandler().playSound(MdgaSound.MISSILE);
animateMissile();
}
/**
* Loads the missile model into the scene, applies scaling, material, and sets its initial position.
*/
private void loadMissile() {
missileModel = app.getAssetManager().loadModel(Asset.missile.getModelPath());
missileModel.scale(Asset.missile.getSize());
missileModel.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture(Asset.missile.getDiffPath()));
missileModel.setMaterial(mat);
missileNode.setLocalTranslation(start);
missileNode.attachChild(missileModel);
rootNode.attachChild(missileNode);
}
/**
* Animates the missile along a parabolic path, triggers the explosion near the target,
* and removes the missile model after the animation completes.
*/
private void animateMissile() {
missileNode.addControl(new AbstractControl() {
private float elapsedTime = 0;
@Override
protected void controlUpdate(float tpf) {
if(elapsedTime > 6) {
endAnim();
rootNode.detachChild(missileNode);
this.spatial.removeControl(this);
}
elapsedTime += tpf;
float progress = elapsedTime / flightTime;
if (progress >= 0.55) {
smoke.setParticlesPerSec(30);
}
if (progress >= 0.7) {
smoke.setParticlesPerSec(0);
}
if (progress >= 0.95f) {
explosion.trigger();
}
if (progress >= 1) {
explosion.trigger();
missileNode.detachChild(missileModel);
}
Vector3f currentPosition = computeParabolicPath(start, target, progress);
missileNode.setLocalTranslation(currentPosition);
Vector3f direction = computeParabolicPath(start, target, progress + 0.01f)
.subtract(currentPosition)
.normalizeLocal();
missileModel.lookAt(currentPosition.add(direction), Vector3f.UNIT_Y);
missileModel.rotate(0, FastMath.HALF_PI, 0);
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
});
}
private void endAnim(){
actionAfter.run();
}
/**
* Computes a position along a parabolic path at a given progress value {@code t}.
*
* @param start The starting point of the missile's flight.
* @param target The target point of the missile's flight.
* @param t The progress value (0.0 to 1.0) along the flight path.
* @return The interpolated position along the parabolic path.
*/
private Vector3f computeParabolicPath(Vector3f start, Vector3f target, float t) {
Vector3f midPoint = start.add(target).multLocal(0.5f);
midPoint.addLocal(0, 0, 20);
Vector3f startToMid = FastMath.interpolateLinear(t, start, midPoint);
Vector3f midToTarget = FastMath.interpolateLinear(t, midPoint, target);
return FastMath.interpolateLinear(t, startToMid, midToTarget);
}
}

View File

@@ -0,0 +1,127 @@
package pp.mdga.client.animation;
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 pp.mdga.client.MdgaApp;
import pp.mdga.client.acoustic.MdgaSound;
public class Smoke {
private final Node rootNode;
private final MdgaApp app;
private final Vector3f location;
private ParticleEmitter fire;
private ParticleEmitter smoke;
private boolean triggered = false;
private final Material mat;
/**
* Constructor for the {@code Explosion} class.
*
* @param app The main application managing the explosion.
* @param rootNode The root node to which the explosion effects will be attached.
* @param location The location of the explosion in world coordinates.
*/
public Smoke(MdgaApp app, Node rootNode, Vector3f location) {
this.app = app;
this.rootNode = rootNode;
this.location = location;
this.mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", app.getAssetManager().loadTexture("Images/particle/vapor_cloud.png"));
}
/**
* Initializes the particle emitters for the explosion effect.
* Configures the fire and smoke emitters with appearance, behavior, and lifespan.
*/
private void initializeEmitter() {
fire = new ParticleEmitter("Effect", ParticleMesh.Type.Triangle,50);
fire.setMaterial(mat);
fire.setStartColor(ColorRGBA.DarkGray);
fire.setEndColor(ColorRGBA.DarkGray);
fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0.2f,0.2f,4f));
fire.getParticleInfluencer().setVelocityVariation(0.4f);
fire.setStartSize(0.7f);
fire.setEndSize(3.8f);
fire.setGravity(0, 0, -0.1f);
fire.setLowLife(0.5f);
fire.setHighLife(1.2f);
fire.setParticlesPerSec(0);
fire.setLocalTranslation(location);
smoke = new ParticleEmitter("Effect2", ParticleMesh.Type.Triangle,40);
smoke.setMaterial(mat);
smoke.setImagesX(2);
smoke.setImagesY(2);
smoke.setStartColor(ColorRGBA.DarkGray);
smoke.setEndColor(new ColorRGBA(0.05f, 0.05f, 0.05f, 1));
smoke.getParticleInfluencer().setInitialVelocity(new Vector3f(0.0f,0.0f,2f));
smoke.getParticleInfluencer().setVelocityVariation(0.5f);
smoke.setStartSize(0.5f);
smoke.setEndSize(1.5f);
smoke.setGravity(0, 0, -0.3f);
smoke.setLowLife(1.2f);
smoke.setHighLife(2.5f);
smoke.setParticlesPerSec(0);
smoke.setLocalTranslation(location);
app.getAcousticHandler().playSound(MdgaSound.EXPLOSION);
}
/**
* Triggers the explosion effect by attaching and activating the particle emitters for fire and smoke.
* Both emitters are automatically detached after a predefined duration.
*/
public void trigger() {
if (!triggered) {
triggered = true;
initializeEmitter();
}
rootNode.attachChild(fire);
fire.emitAllParticles();
fire.addControl(new AbstractControl() {
private float elapsedTime = 0;
@Override
protected void controlUpdate(float tpf) {
elapsedTime += tpf;
if (elapsedTime > 10f) {
rootNode.detachChild(fire);
fire.removeControl(this);
}
}
@Override
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {}
});
rootNode.attachChild(smoke);
smoke.emitAllParticles();
smoke.addControl(new AbstractControl() {
private float elapsedTime = 0;
@Override
protected void controlUpdate(float tpf) {
elapsedTime += tpf;
if (elapsedTime > 10f) {
rootNode.detachChild(smoke);
smoke.removeControl(this);
}
}
@Override
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {}
});
}
}

View File

@@ -224,7 +224,7 @@ private Spatial createModel(Asset asset, Vector3f pos, float rot) {
* @param y The y-coordinate on the grid
* @return The corresponding world position
*/
private static Vector3f gridToWorld(int x, int y) {
public static Vector3f gridToWorld(int x, int y) {
return new Vector3f(GRID_SIZE * x, GRID_SIZE * y, GRID_ELEVATION);
}
@@ -240,16 +240,38 @@ private Spatial displayAsset(AssetOnMap assetOnMap) {
return createModel(assetOnMap.asset(), gridToWorld(x, y), assetOnMap.rot());
}
/**
* Adds a visual representation of an asset to the scene, attaches a control to it, and returns the control.
*
* @param assetOnMap The asset to be displayed in the 3D environment.
* @param control The control to be added to the spatial representing the asset.
* @param <T> The type of control, extending {@code AbstractControl}.
* @return The control that was added to the spatial.
*/
private <T extends AbstractControl> T displayAndControl(AssetOnMap assetOnMap, T control) {
Spatial spatial = displayAsset(assetOnMap);
spatial.addControl(control);
return control;
}
/**
* Moves a piece in the 3D environment to the location of a specified node.
*
* @param pieceControl The control managing the piece to be moved.
* @param nodeControl The control managing the target node to which the piece will move.
*/
private void movePieceToNode(PieceControl pieceControl, NodeControl nodeControl){
pieceControl.setLocation(nodeControl.getLocation());
}
/**
* Adds a home node for a specific player color, attaching it to the map of home nodes.
*
* @param map The map storing lists of home nodes by player color.
* @param color The color associated with the home nodes to be added.
* @param assetOnMap The asset representing the home node in the 3D environment.
* @throws RuntimeException if more than 4 home nodes are added for a single color.
*/
private void addHomeNode(Map<Color, List<NodeControl>> map, Color color, AssetOnMap assetOnMap){
List<NodeControl> homeNodes = addItemToMapList(map, color, displayAndControl(assetOnMap, new NodeControl(app, fpp)));
if (homeNodes.size() > 4) throw new RuntimeException("too many homeNodes for " + color);
@@ -293,6 +315,16 @@ private void movePieceRek(UUID uuid, int curIndex, int moveIndex){
movePieceRek(uuid, curIndex, moveIndex);
}
/**
* Adds an item to a list in a map. If the key does not exist in the map, a new list is created.
*
* @param map The map containing lists of items.
* @param key The key associated with the list in the map.
* @param item The item to be added to the list.
* @param <T> The type of items in the list.
* @param <E> The type of the key in the map.
* @return The updated list associated with the specified key.
*/
private <T, E> List<T> addItemToMapList(Map<E,List<T>> map, E key, T item){
List<T> list = map.getOrDefault(key, new ArrayList<>());
list.add(item);
@@ -300,12 +332,27 @@ private <T, E> List<T> addItemToMapList(Map<E,List<T>> map, E key, T item){
return list;
}
/**
* Removes an item from a list in a map. If the key does not exist in the map, a new list is created.
*
* @param map The map containing lists of items.
* @param key The key associated with the list in the map.
* @param item The item to be removed from the list.
* @param <T> The type of items in the list.
* @param <E> The type of the key in the map.
*/
private <T, E> void removeItemFromMapList(Map<E,List<T>> map, E key, T item){
List<T> list = map.getOrDefault(key, new ArrayList<>());
list.remove(item);
map.put(key, list);
}
/**
* Calculates the mean position of the waiting nodes for a specific color.
*
* @param color The color associated with the waiting nodes.
* @return The mean position of the waiting nodes as a {@code Vector3f}.
*/
private Vector3f getWaitingPos(Color color){
return getMeanPosition(waitingNodesMap.get(color).stream().map(NodeControl::getLocation).toList());
}
@@ -753,7 +800,7 @@ public void throwPieceAnim(UUID uuid){
public void throwPiece(UUID uuid, Color throwColor){
switch(throwColor){
case ARMY -> throwShell(uuid);
case NAVY -> throwMissle(uuid);
case NAVY -> throwMissile(uuid);
case CYBER -> throwMatrix(uuid);
case AIRFORCE -> throwBomb(uuid);
default -> throw new RuntimeException("invalid color");
@@ -768,7 +815,7 @@ public void throwPiece(UUID uuid, Color throwColor){
private void throwBomb(UUID uuid) {
Vector3f targetPoint = pieces.get(uuid).getLocation();
JetAnimation anim = new JetAnimation(app, rootNode, uuid, targetPoint, 40, 6, ()->throwPieceAnim(uuid));
JetAnimation anim = new JetAnimation(app, rootNode, targetPoint, 40, 6, ()->throwPieceAnim(uuid));
anim.start();
}
@@ -785,8 +832,11 @@ private void throwMatrix(UUID uuid) {
}));
}
private void throwMissle(UUID uuid) {
private void throwMissile(UUID uuid) {
Vector3f targetPoint = pieces.get(uuid).getLocation();
MissileAnimation anim = new MissileAnimation(app, rootNode, targetPoint, 2f, ()->throwPieceAnim(uuid));
anim.start();
}
private void throwShell(UUID uuid) {

View File

@@ -18,17 +18,37 @@ public class OutlineControl extends InitControl {
private static final int THICKNESS_DEFAULT = 6;
private MdgaApp app;
/**
* Constructs an {@code OutlineControl} with default thickness for the object outline.
*
* @param app The main application managing the outline control.
* @param fpp The {@code FilterPostProcessor} used for post-processing effects.
*/
public OutlineControl(MdgaApp app, FilterPostProcessor fpp){
this.app = app;
outlineOwn = new SelectObjectOutliner(THICKNESS_DEFAULT, fpp, app.getRenderManager(), app.getAssetManager(), app.getCamera(), app);
}
/**
* Constructs an {@code OutlineControl} with default thickness, allowing a custom camera to be specified.
*
* @param app The main application managing the outline control.
* @param fpp The {@code FilterPostProcessor} used for post-processing effects.
* @param cam The camera used for rendering the outlined objects.
*/
public OutlineControl(MdgaApp app, FilterPostProcessor fpp, Camera cam){
this.app = app;
outlineOwn = new SelectObjectOutliner(THICKNESS_DEFAULT, fpp, app.getRenderManager(), app.getAssetManager(), cam, app);
}
/**
* Constructs an {@code OutlineControl} with a specified thickness and custom camera.
*
* @param app The main application managing the outline control.
* @param fpp The {@code FilterPostProcessor} used for post-processing effects.
* @param cam The camera used for rendering the outlined objects.
* @param thickness The thickness of the outline.
*/
public OutlineControl(MdgaApp app, FilterPostProcessor fpp, Camera cam, int thickness){
this.app = app;
outlineOwn = new SelectObjectOutliner(thickness, fpp, app.getRenderManager(), app.getAssetManager(), cam, app);
@@ -61,6 +81,11 @@ public void deOutline(){
outlineOwn.deselect(spatial);
}
/**
* Retrieves the instance of the {@code MdgaApp} associated with this control.
*
* @return The {@code MdgaApp} instance.
*/
public MdgaApp getApp() {
return app;
}

View File

@@ -77,12 +77,17 @@ public SliderButton(MdgaApp app, Node node, String label) {
QuadBackgroundComponent background = new QuadBackgroundComponent(BUTTON_NORMAL);
slider.setBackground(background);
// Set label background
QuadBackgroundComponent labelBackground = new QuadBackgroundComponent(BUTTON_NORMAL);
this.label.setBackground(labelBackground);
// Configure the label font
this.label.setFont(font);
this.label.setTextHAlignment(HAlignment.Center);
// Default position and size
pos = new Vector2f(0, 0);
size = new Vector2f(5.5f, 1);
size = new Vector2f(6f, 1);
// Add label and slider to container
container.addChild(this.label);

View File

@@ -7,6 +7,11 @@
import pp.mdga.client.button.SliderButton;
import pp.mdga.client.view.MdgaView;
/**
* The {@code AudioSettingsDialog} class represents a dialog for adjusting audio settings in the application.
* It provides controls for managing main volume, music volume, and sound effect volume, and includes
* a button to return to the previous menu.
*/
public class AudioSettingsDialog extends Dialog {
private final MdgaView view;
@@ -18,6 +23,13 @@ public class AudioSettingsDialog extends Dialog {
private boolean active = false;
/**
* Constructs an {@code AudioSettingsDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param view The current view, used for navigation and interaction with the dialog.
*/
public AudioSettingsDialog(MdgaApp app, Node node, MdgaView view) {
super(app, node);
@@ -42,6 +54,9 @@ public AudioSettingsDialog(MdgaApp app, Node node, MdgaView view) {
backButton.setPos(new Vector2f(0, 1.8f));
}
/**
* Called when the dialog is shown. Initializes and displays the volume controls and back button.
*/
@Override
protected void onShow() {
active = true;
@@ -57,6 +72,9 @@ protected void onShow() {
soundVolume.show();
}
/**
* Called when the dialog is hidden. Hides all volume controls and the back button.
*/
@Override
protected void onHide() {
active = false;
@@ -68,6 +86,10 @@ protected void onHide() {
soundVolume.hide();
}
/**
* Updates the application audio settings based on the current values of the sliders.
* This method is called continuously while the dialog is active.
*/
public void update() {
if(!active) {
return;

View File

@@ -10,17 +10,30 @@
import java.util.ArrayList;
/**
* The {@code CeremonyDialog} class displays a dialog containing statistical data in a tabular format.
* It allows adding rows of statistics and manages their visibility when shown or hidden.
*/
public class CeremonyDialog extends Dialog {
private ArrayList<ArrayList<LabelButton>> labels;
float offsetX;
/**
* Constructs a {@code CeremonyDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
*/
public CeremonyDialog(MdgaApp app, Node node) {
super(app, node);
prepare();
}
/**
* Called when the dialog is shown. Makes all label buttons in the table visible.
*/
@Override
protected void onShow() {
for (ArrayList<LabelButton> row : labels) {
@@ -30,6 +43,9 @@ protected void onShow() {
}
}
/**
* Called when the dialog is hidden. Hides all label buttons in the table.
*/
@Override
protected void onHide() {
for (ArrayList<LabelButton> row : labels) {
@@ -39,6 +55,17 @@ protected void onHide() {
}
}
/**
* Adds a row of statistical data to the dialog.
*
* @param name The name of the player or category for the row.
* @param v1 The value for the first column.
* @param v2 The value for the second column.
* @param v3 The value for the third column.
* @param v4 The value for the fourth column.
* @param v5 The value for the fifth column.
* @param v6 The value for the sixth column.
*/
public void addStatisticsRow(String name, int v1, int v2, int v3, int v4, int v5, int v6) {
float offsetYSmall = 0.5f;
@@ -76,6 +103,9 @@ public void addStatisticsRow(String name, int v1, int v2, int v3, int v4, int v5
labels.add(row);
}
/**
* Prepares the initial layout of the dialog, including header labels.
*/
public void prepare() {
offsetX = 0.5f;

View File

@@ -4,30 +4,53 @@
import com.simsilica.lemur.Container;
import pp.mdga.client.MdgaApp;
/**
* The {@code Dialog} class serves as an abstract base class for dialogs in the application.
* It provides functionality for showing and hiding the dialog and defines abstract methods
* for custom behavior when the dialog is shown or hidden.
*/
public abstract class Dialog {
protected final MdgaApp app;
protected final Node node = new Node();
private final Node root;
/**
* Constructs a {@code Dialog}.
*
* @param app The main application managing the dialog.
* @param node The root node to which the dialog's node will be attached.
*/
Dialog(MdgaApp app, Node node) {
this.app = app;
this.root = node;
}
/**
* Shows the dialog by attaching its node to the root node and invoking the {@code onShow} method.
*/
public void show() {
root.attachChild(node);
onShow();
}
/**
* Hides the dialog by detaching its node from the root node and invoking the {@code onHide} method.
*/
public void hide() {
root.detachChild(node);
onHide();
}
/**
* Called when the dialog is shown. Subclasses must implement this method to define custom behavior.
*/
protected abstract void onShow();
/**
* Called when the dialog is hidden. Subclasses must implement this method to define custom behavior.
*/
protected abstract void onHide();
}

View File

@@ -12,6 +12,10 @@
import java.util.prefs.Preferences;
/**
* The {@code HostDialog} class represents a dialog for hosting a network game session.
* It allows users to input a port number, start hosting a server, and navigate back to the previous view.
*/
public class HostDialog extends NetworkDialog {
private InputButton portInput;
@@ -22,6 +26,13 @@ public class HostDialog extends NetworkDialog {
private Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
/**
* Constructs a {@code HostDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param view The main view used for navigation and interaction with the dialog.
*/
public HostDialog(MdgaApp app, Node node, MainView view) {
super(app, node, (NetworkSupport) app.getNetworkSupport());
@@ -39,6 +50,9 @@ public HostDialog(MdgaApp app, Node node, MainView view) {
offset += 1.5f;
}
/**
* Called when the dialog is shown. Displays all input fields and buttons.
*/
@Override
protected void onShow() {
portInput.show();
@@ -46,6 +60,9 @@ protected void onShow() {
backButton.show();
}
/**
* Called when the dialog is hidden. Hides all input fields and buttons.
*/
@Override
protected void onHide() {
portInput.hide();
@@ -53,27 +70,44 @@ protected void onHide() {
backButton.hide();
}
/**
* Updates the state of the port input field.
* This method is called periodically to synchronize the dialog state.
*/
public void update() {
portInput.update();
}
/**
* Retrieves the currently entered port number, saves it to preferences, and sets it as the active port.
*
* @return The port number as a string.
*/
public String getPort() {
prefs.put("hostPort", portInput.getString());
setPortNumber(Integer.parseInt(portInput.getString()));
return portInput.getString();
}
/**
* Resets the port input field to its default value and updates preferences accordingly.
*/
public void resetPort() {
portInput.reset();
prefs.put("hostPort", "11111");
}
/**
* Starts the server to host a network game.
*/
public void hostServer() {
startServer();
}
/**
* Connects to the server as a client.
*/
public void connectServerAsClient() {
connectServer();
}
}

View File

@@ -8,36 +8,77 @@
import pp.mdga.client.button.LabelButton;
import pp.mdga.client.button.MenuButton;
import pp.mdga.client.view.MdgaView;
import pp.mdga.game.Color;
/**
* The {@code InterruptDialog} class represents a dialog that interrupts the game flow,
* providing a message and the option to force an action if the user is a host.
*/
public class InterruptDialog extends Dialog {
private ButtonRight forceButton;
private LabelButton label;
private String text = "";
/**
* Constructs an {@code InterruptDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
*/
public InterruptDialog(MdgaApp app, Node node) {
super(app, node);
forceButton = new ButtonRight(app, node, () -> app.getModelSynchronize().force(), "Erzwingen", 1);
label = new LabelButton(app, node, "Warte auf Spieler...", new Vector2f(5.5f * 1.5f, 2), new Vector2f(0.5f, 0f), false);
float offset = 2.8f;
label.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
}
/**
* Called when the dialog is shown. Displays the label and optionally the force button if the user is the host.
*/
@Override
protected void onShow() {
if(app.getGameLogic().isHost()) {
forceButton.show();
}
label = new LabelButton(app, node, "Warte auf " + text + "...", new Vector2f(5.5f * 1.5f, 2), new Vector2f(0.5f, 0f), false);
float offset = 2.8f;
label.setPos(new Vector2f(0, MenuButton.VERTICAL - offset));
label.show();
}
/**
* Called when the dialog is hidden. Hides the label and the force button.
*/
@Override
protected void onHide() {
forceButton.hide();
label.hide();
}
/**
* Sets the displayed text based on the specified color.
*
* @param color The color used to determine the text (e.g., "Luftwaffe" for AIRFORCE).
*/
public void setColor(Color color) {
switch (color) {
case AIRFORCE:
text = "Luftwaffe";
break;
case ARMY:
text = "Heer";
break;
case NAVY:
text = "Marine";
break;
case CYBER:
text = "CIR";
break;
}
}
}

View File

@@ -13,6 +13,10 @@
import java.util.prefs.Preferences;
/**
* The {@code JoinDialog} class represents a dialog for joining a network game.
* It allows users to input an IP address and port number, connect to a server, or navigate back to the previous view.
*/
public class JoinDialog extends NetworkDialog {
private InputButton ipInput;
private InputButton portInput;
@@ -24,6 +28,13 @@ public class JoinDialog extends NetworkDialog {
private Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
/**
* Constructs a {@code JoinDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param view The main view used for navigation and interaction with the dialog.
*/
public JoinDialog(MdgaApp app, Node node, MainView view) {
super(app, node, (NetworkSupport) app.getNetworkSupport());
@@ -46,6 +57,9 @@ public JoinDialog(MdgaApp app, Node node, MainView view) {
offset += 1.5f;
}
/**
* Called when the dialog is shown. Displays all input fields and buttons.
*/
@Override
protected void onShow() {
ipInput.show();
@@ -54,6 +68,9 @@ protected void onShow() {
backButton.show();
}
/**
* Called when the dialog is hidden. Hides all input fields and buttons.
*/
@Override
protected void onHide() {
ipInput.hide();
@@ -62,37 +79,62 @@ protected void onHide() {
backButton.hide();
}
/**
* Updates the state of the input fields. This method is called periodically to synchronize the dialog state.
*/
public void update() {
ipInput.update();
portInput.update();
}
/**
* Retrieves the currently entered IP address, saves it to preferences, and sets it as the hostname.
*
* @return The IP address as a string.
*/
public String getIpt() {
prefs.put("joinIp", ipInput.getString());
setHostname(ipInput.getString());
return ipInput.getString();
}
/**
* Resets the IP input field to its default value and updates preferences accordingly.
*/
public void resetIp() {
ipInput.reset();
prefs.put("joinIp", "");
}
/**
* Retrieves the currently entered port number, saves it to preferences, and sets it as the active port.
*
* @return The port number as a string.
*/
public String getPort() {
prefs.put("joinPort", portInput.getString());
setPortNumber(Integer.parseInt(portInput.getString()));
return portInput.getString();
}
/**
* Resets the port input field to its default value and updates preferences accordingly.
*/
public void resetPort() {
portInput.reset();
prefs.put("joinPort", "11111");
}
/**
* Connects to the server using the current IP address and port number.
*/
public void connectToServer() {
connectServer();
}
/**
* Disconnects from the server if a network connection exists.
*/
public void disconnect() {
NetworkSupport network = getNetwork();
if (network != null) {
@@ -104,4 +146,3 @@ public void disconnect() {
}
}
}

View File

@@ -8,6 +8,11 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
* The {@code NetworkDialog} class serves as an abstract base class for dialogs
* that involve network-related functionalities, such as connecting to a server or hosting a game.
* It provides methods for initializing, connecting to, and managing a network server.
*/
public abstract class NetworkDialog extends Dialog {
private NetworkSupport network;
@@ -17,19 +22,41 @@ public abstract class NetworkDialog extends Dialog {
private MdgaServer serverInstance;
private Thread serverThread;
/**
* Constructs a {@code NetworkDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param network The network support instance for managing network interactions.
*/
public NetworkDialog(MdgaApp app, Node node, NetworkSupport network) {
super(app, node);
this.network = network;
}
/**
* Sets the hostname for the network connection.
*
* @param hostname The hostname or IP address of the server.
*/
public void setHostname(String hostname) {
this.hostname = hostname;
}
/**
* Sets the port number for the network connection.
*
* @param portNumber The port number to use for the connection.
*/
public void setPortNumber(int portNumber) {
this.portNumber = portNumber;
}
/**
* Initializes the network connection using the current hostname and port number.
*
* @return {@code null} if successful, otherwise throws an exception.
*/
protected Object initNetwork() {
try {
this.network.initNetwork(this.hostname, this.portNumber);
@@ -39,6 +66,9 @@ protected Object initNetwork() {
}
}
/**
* Starts the process of connecting to a server asynchronously.
*/
protected void connectServer() {
try {
connectionFuture = this.network.getApp().getExecutor().submit(this::initNetwork);
@@ -47,6 +77,9 @@ protected void connectServer() {
}
}
/**
* Starts hosting a server in a separate thread.
*/
protected void startServer() {
serverThread = new Thread(() -> {
try {
@@ -60,6 +93,9 @@ protected void startServer() {
serverThread.start();
}
/**
* Shuts down the hosted server and cleans up resources.
*/
public void shutdownServer() {
try {
@@ -85,6 +121,11 @@ public void shutdownServer() {
}
}
/**
* Updates the state of the connection process.
*
* @param delta The time elapsed since the last update call.
*/
public void update(float delta) {
if (this.connectionFuture != null && this.connectionFuture.isDone()) {
try {
@@ -97,6 +138,11 @@ public void update(float delta) {
}
}
/**
* Retrieves the {@code NetworkSupport} instance associated with this dialog.
*
* @return The {@code NetworkSupport} instance.
*/
public NetworkSupport getNetwork() {
return network;
}

View File

@@ -8,6 +8,10 @@
import pp.mdga.client.view.MainView;
import pp.mdga.client.view.MdgaView;
/**
* The {@code SettingsDialog} class represents a dialog for navigating to various settings sections,
* such as video and audio settings, or returning to the previous view.
*/
public class SettingsDialog extends Dialog {
private MenuButton videoButton;
private MenuButton audioButton;
@@ -15,6 +19,13 @@ public class SettingsDialog extends Dialog {
private final MdgaView view;
/**
* Constructs a {@code SettingsDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param view The view managing navigation and interaction with the settings dialog.
*/
public SettingsDialog(MdgaApp app, Node node, MdgaView view) {
super(app, node);
@@ -34,6 +45,9 @@ public SettingsDialog(MdgaApp app, Node node, MdgaView view) {
backButton.setPos(new Vector2f(0, 1.8f));
}
/**
* Called when the dialog is shown. Displays all buttons for video settings, audio settings, and back navigation.
*/
@Override
protected void onShow() {
videoButton.show();
@@ -41,6 +55,9 @@ protected void onShow() {
backButton.show();
}
/**
* Called when the dialog is hidden. Hides all buttons for video settings, audio settings, and back navigation.
*/
@Override
protected void onHide() {
videoButton.hide();

View File

@@ -14,6 +14,10 @@
import java.util.Random;
import java.util.random.RandomGenerator;
/**
* The {@code StartDialog} class represents the initial dialog in the application,
* allowing the user to input their name, host or join a game, or exit the application.
*/
public class StartDialog extends Dialog {
private InputButton nameInput;
@@ -23,6 +27,13 @@ public class StartDialog extends Dialog {
private final MainView view;
/**
* Constructs a {@code StartDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param view The main view used for navigation and interaction with the dialog.
*/
public StartDialog(MdgaApp app, Node node, MainView view) {
super(app, node);
@@ -48,6 +59,9 @@ public StartDialog(MdgaApp app, Node node, MainView view) {
endButton.setPos(new Vector2f(0, 1.8f));
}
/**
* Called when the dialog is shown. Displays the name input field and all buttons.
*/
@Override
protected void onShow() {
nameInput.show();
@@ -57,6 +71,9 @@ protected void onShow() {
endButton.show();
}
/**
* Called when the dialog is hidden. Hides the name input field and all buttons.
*/
@Override
protected void onHide ()
{
@@ -67,10 +84,18 @@ protected void onHide ()
endButton.hide();
}
/**
* Updates the state of the name input field. This method is called periodically to synchronize the dialog state.
*/
public void update() {
nameInput.update();
}
/**
* Retrieves the name entered by the user. If no name is provided, a random name is generated.
*
* @return The user's name or a randomly generated name.
*/
public String getName() {
String name = nameInput.getString();
@@ -206,7 +231,7 @@ public String getName() {
"FluffiKopf",
"DonutDöner",
"VollpfostenX",
"Schraubenschlüssel",
"Waschlappen",
"Witzepumper",
"ToastTraum",
"FroschFighter",
@@ -263,22 +288,22 @@ public String getName() {
"VulkanKeks",
"WasserToast",
"MenschSalat",
"KampfKohlenhydrate",
"KampfKohl",
"SockenZirkus",
"SchwimmBärchen",
"TanzenderDachgepäckträger",
"TanzenderPudel",
"PizzamarktMensch",
"ZahnarztZocker",
"RollerCoasterTester",
"WaschmaschinenPilot",
"RollerRudi",
"PupsPilot",
"WitzigeZwiebel",
"Pillenschlucker",
"ZwiebelReiter",
"HüpfenderKaktus",
"KochenderAsteroid",
"AsteroidenAlf",
"ChaosKarotte",
"WolkenFurz",
"SchnitzelPartikel",
"Krümelmonster",
"WackelBiene",
};

View File

@@ -11,6 +11,11 @@
import java.util.prefs.Preferences;
/**
* The {@code VideoSettingsDialog} class represents a dialog for configuring video settings,
* such as resolution and fullscreen mode. It also provides an option to restart the application
* when certain settings are changed.
*/
public class VideoSettingsDialog extends Dialog {
private static Preferences prefs = Preferences.userNodeForPackage(JoinDialog.class);
@@ -29,6 +34,13 @@ public class VideoSettingsDialog extends Dialog {
private boolean active = false;
/**
* Constructs a {@code VideoSettingsDialog}.
*
* @param app The main application managing the dialog.
* @param node The root node for attaching UI elements.
* @param view The view managing navigation and interaction with the video settings dialog.
*/
public VideoSettingsDialog(MdgaApp app, Node node, MdgaView view) {
super(app, node);
@@ -67,6 +79,9 @@ public VideoSettingsDialog(MdgaApp app, Node node, MdgaView view) {
backButton.setPos(new Vector2f(0, 1.8f));
}
/**
* Called when the dialog is shown. Displays all buttons and marks the dialog as active.
*/
@Override
protected void onShow() {
active = true;
@@ -83,6 +98,9 @@ protected void onShow() {
backButton.show();
}
/**
* Called when the dialog is hidden. Hides all buttons and marks the dialog as inactive.
*/
@Override
protected void onHide() {
active = false;
@@ -100,12 +118,23 @@ protected void onHide() {
restartButton.hide();
}
/**
* Updates the dialog's state. This method can be used for periodic updates while the dialog is active.
*/
public void update() {
if(!active) {
return;
}
}
/**
* Updates the resolution settings and optionally triggers the restart button if changes are detected.
*
* @param width The width of the resolution.
* @param height The height of the resolution.
* @param imageFactor The scaling factor for the resolution.
* @param isFullscreen {@code true} if fullscreen mode is enabled, {@code false} otherwise.
*/
public void updateResolution(int width, int height, float imageFactor, boolean isFullscreen) {
if(width != prefs.getInt("width", 1280) || height != prefs.getInt("height", 720) || isFullscreen != prefs.getBoolean("fullscreen", false)) {
restartButton.show();

View File

@@ -10,12 +10,25 @@
import pp.mdga.client.animation.ZoomControl;
import pp.mdga.game.Color;
/**
* The {@code ActionTextHandler} class manages the display of animated and stylized text messages in the game's UI.
* It supports dynamic text creation with spacing, color, and effects, such as dice rolls, player actions, and rankings.
*/
class ActionTextHandler {
private Node root;
private BitmapFont font;
private AppSettings appSettings;
private int ranking;
float paddingRanked = 100;
/**
* Constructs an {@code ActionTextHandler}.
*
* @param guiNode The GUI node where the text messages will be displayed.
* @param assetManager The asset manager used to load fonts and other assets.
* @param appSettings The application settings for positioning and sizing.
*/
ActionTextHandler(Node guiNode, AssetManager assetManager, AppSettings appSettings){
root = new Node("actionTextRoot");
guiNode.attachChild(root);
@@ -26,6 +39,16 @@ class ActionTextHandler {
ranking = 0;
}
/**
* Creates a {@code Node} containing text with specified spacing, size, and colors for each segment of the text.
*
* @param textArr An array of strings representing the text to be displayed.
* @param spacing The spacing between individual characters.
* @param size The size of the text.
* @param colorArr An array of {@code ColorRGBA} representing the color for each string in {@code textArr}.
* @return A {@code Node} containing the styled text with spacing and color applied.
* @throws RuntimeException if the lengths of {@code textArr} and {@code colorArr} do not match.
*/
private Node createTextWithSpacing(String[] textArr, float spacing, float size, ColorRGBA[] colorArr) {
if(textArr.length != colorArr.length) throw new RuntimeException("text and color are not the same length");
@@ -52,18 +75,55 @@ private Node createTextWithSpacing(String[] textArr, float spacing, float size,
return textNode;
}
/**
* Creates a {@code Node} containing text with specified spacing, size, and a single color.
*
* @param text The text to be displayed.
* @param spacing The spacing between individual characters.
* @param size The size of the text.
* @param color The color of the text.
* @return A {@code Node} containing the styled text.
*/
private Node createTextWithSpacing(String text, float spacing, float size, ColorRGBA color) {
return createTextWithSpacing(new String[]{text}, spacing, size, new ColorRGBA[]{color});
}
/**
* Calculates the center position of a rectangle given its width, height, and an origin position.
*
* @param width The width of the rectangle.
* @param height The height of the rectangle.
* @param pos The origin position of the rectangle.
* @return A {@code Vector3f} representing the center position.
*/
private Vector3f center(float width, float height, Vector3f pos){
return new Vector3f(pos.x+width/2, pos.y+height/2,0);
}
/**
* Creates and positions a single-line text at the top of the screen with a specified vertical offset.
*
* @param name The text to be displayed.
* @param spacing The spacing between individual characters.
* @param size The size of the text.
* @param color The color of the text.
* @param top The vertical offset from the top of the screen.
* @return A {@code Node} containing the styled text positioned at the top.
*/
private Node createTopText(String name, float spacing, float size, ColorRGBA color, float top){
return createTopText(new String[]{name}, spacing, size, new ColorRGBA[]{color}, top);
}
/**
* Creates and positions multi-line text at the top of the screen with specified vertical offset, spacing, and colors.
*
* @param name An array of strings representing the text to be displayed.
* @param spacing The spacing between individual characters.
* @param size The size of the text.
* @param color An array of {@code ColorRGBA} representing the color for each string in {@code name}.
* @param top The vertical offset from the top of the screen.
* @return A {@code Node} containing the styled text positioned at the top.
*/
private Node createTopText(String[] name, float spacing, float size, ColorRGBA color[], float top){
Node text = createTextWithSpacing(name, spacing, size, color);
text.setLocalTranslation(0, (appSettings.getHeight()/2f)*0.8f-top,0);
@@ -71,18 +131,44 @@ private Node createTopText(String[] name, float spacing, float size, ColorRGBA c
return text;
}
/**
* Calculates the center position of a rectangle with negative width offset.
*
* @param width The negative width of the rectangle.
* @param height The height of the rectangle.
* @param pos The origin position of the rectangle.
* @return A {@code Vector3f} representing the center position.
*/
private Vector3f centerText(float width, float height, Vector3f pos){
return center(-width, height, pos);
}
/**
* Displays a message indicating the active player.
*
* @param name The name of the active player.
* @param color The color representing the player's team.
*/
void activePlayer(String name, Color color){
createTopText(new String[]{name," ist dran"}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
}
/**
* Displays a message indicating that the current player is active.
*
* @param color The color representing the player's team.
*/
void ownActive(Color color){
createTopText(new String[]{"Du"," bist dran"}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
}
/**
* Displays a dice roll result for a player.
*
* @param diceNum The number rolled on the dice.
* @param name The name of the player.
* @param color The color representing the player's team.
*/
void diceNum(int diceNum, String name, Color color){
createTopText(new String[]{name," würfelt:"}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0);
@@ -90,38 +176,84 @@ void diceNum(int diceNum, String name, Color color){
}
/**
* Displays a dice roll result with a multiplier for a player.
*
* @param diceNum The number rolled on the dice.
* @param mult The multiplier applied to the dice result.
* @param name The name of the player.
* @param color The color representing the player's team.
*/
void diceNumMult(int diceNum,int mult, String name, Color color){
createTopText(new String[]{name," würfelt:"}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0);
createTopText(new String[]{String.valueOf(diceNum), " x" + mult + " = " + (diceNum*mult)}, 20, 100, new ColorRGBA[]{ColorRGBA.White,ColorRGBA.Red}, 100);
}
/**
* Displays the dice roll result for the current player.
*
* @param diceNum The number rolled on the dice.
*/
void ownDice(int diceNum){
createTopText(String.valueOf(diceNum), 10, 100, ColorRGBA.White, 0);
}
/**
* Displays the dice roll result with a multiplier for the current player.
*
* @param diceNum The number rolled on the dice.
* @param mult The multiplier applied to the dice result.
*/
void ownDiceMult(int diceNum, int mult){
createTopText(new String[]{String.valueOf(diceNum), " x" + mult + " = " + (diceNum*mult)}, 20, 100, new ColorRGBA[]{ColorRGBA.White,ColorRGBA.Red}, 0);
}
/**
* Displays a message indicating that a specified player received a bonus card.
*
* @param name The name of the player who received the bonus card.
* @param color The color representing the player's team.
*/
void drawCard(String name, Color color){
createTopText(new String[]{name," erhält eine Bonuskarte"}, 7,70, new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
}
/**
* Displays a message indicating that the current player received a bonus card.
*
* @param color The color representing the player's team.
*/
void drawCardOwn(Color color){
createTopText(new String[]{"Du"," erhälst eine Bonuskarte"}, 5,70, new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
}
/**
* Displays a message indicating that a specified player has completed their turn or action.
*
* @param name The name of the player who finished.
* @param color The color representing the player's team.
*/
void finishText(String name, Color color){
createTopText(new String[]{name," ist fertig!"}, 7,70, new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
}
/**
* Displays a message indicating that the current player has completed their turn or action.
*
* @param color The color representing the player's team.
*/
void finishTextOwn(Color color){
createTopText(new String[]{"Du", " bist fertig!"}, 7,70, new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, 0).addControl(new ZoomControl());
}
/**
* Converts a player's team color to a corresponding {@code ColorRGBA}.
*
* @param color The player's team color.
* @return The corresponding {@code ColorRGBA}.
* @throws RuntimeException if the color is invalid.
*/
private ColorRGBA playerColorToColorRGBA(Color color){
return switch (color){
case ARMY -> ColorRGBA.Green;
@@ -132,23 +264,40 @@ private ColorRGBA playerColorToColorRGBA(Color color){
};
}
/**
* Hides all text messages displayed by the handler and resets the ranking counter.
*/
void hide(){
ranking = 0;
root.detachAllChildren();
}
float paddingRanked = 100;
/**
* Displays a ranked dice roll result for a specified player.
*
* @param name The name of the player.
* @param color The color representing the player's team.
* @param eye The dice roll result.
*/
void rollRankingResult(String name, Color color, int eye){
createTopText(new String[]{name,": "+eye}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, paddingRanked*ranking);
ranking++;
}
/**
* Displays a ranked dice roll result for the current player.
*
* @param color The color representing the player's team.
* @param eye The dice roll result.
*/
void rollRankingResultOwn(Color color, int eye){
createTopText(new String[]{"Du",": "+eye}, 10,90,new ColorRGBA[]{playerColorToColorRGBA(color),ColorRGBA.White}, paddingRanked*ranking);
ranking++;
}
/**
* Displays a message prompting the player to roll the dice.
*/
void diceNow(){
createTopText("Klicke zum Würfeln", 5, 80, ColorRGBA.White, 0);
}

View File

@@ -131,6 +131,8 @@ public void selectCard(CardControl cardControl) {
cardControl.select();
cardSelect = getKeyByValue(bonusCardControlMap, cardControl);
}
app.getModelSynchronize().selectCard(cardSelect);
}
public Camera getCardLayerCamera() {

View File

@@ -5,6 +5,7 @@
import com.jme3.network.serializing.serializers.EnumSerializer;
import pp.mdga.Resources;
import pp.mdga.game.*;
import pp.mdga.game.card.*;
import pp.mdga.message.client.*;
import pp.mdga.message.server.*;
import pp.mdga.server.ServerGameLogic;
@@ -124,7 +125,7 @@ private void initializeSerializables() {
Serializer.registerClass(NoTurnMessage.class);
Serializer.registerClass(PauseGameMessage.class);
Serializer.registerClass(PlayCardMessage.class);
Serializer.registerClass(PossibleCardMessage.class);
Serializer.registerClass(PossibleCardsMessage.class);
Serializer.registerClass(PossiblePieceMessage.class);
Serializer.registerClass(RankingResponseMessage.class);
Serializer.registerClass(RankingRollAgainMessage.class);
@@ -144,12 +145,17 @@ private void initializeSerializables() {
Serializer.registerClass(Piece.class);
Serializer.registerClass(BonusNode.class);
Serializer.registerClass(StartNode.class);
Serializer.registerClass(PlayerData.class);
Serializer.registerClass(HomeNode.class);
Serializer.registerClass(PowerCard.class);
Serializer.registerClass(TurboCard.class);
Serializer.registerClass(SwapCard.class);
Serializer.registerClass(ShieldCard.class);
Serializer.registerClass(HiddenCard.class);
Serializer.registerClass(Color.class, new EnumSerializer());
Serializer.registerClass(PieceState.class, new EnumSerializer());
Serializer.registerClass(ShieldState.class, new EnumSerializer());
Serializer.registerClass(BonusCard.class, new EnumSerializer());
}
private void registerListeners() {

View File

@@ -20,6 +20,8 @@ public class GameView extends MdgaView {
private ButtonLeft leaveButton;
private ButtonRight confirmButton;
private ButtonRight noPowerButton;
private Color ownColor = null;
private InterruptDialog interruptDialog;
@@ -35,6 +37,8 @@ public GameView(MdgaApp app) {
confirmButton = new ButtonRight(app, guiNode, () -> app.getModelSynchronize().confirm(), "Bestätigen", 1);
noPowerButton = new ButtonRight(app, guiNode, () -> app.getModelSynchronize().confirm(), "Verzichten", 1);
interruptDialog = new InterruptDialog(app, guiNode);
fpp = new FilterPostProcessor(app.getAssetManager());
@@ -65,6 +69,7 @@ public void onLeave() {
confirmButton.hide();
noPowerButton.hide();
app.getViewPort().removeProcessor(fpp);
}
@@ -109,6 +114,7 @@ public Color getOwnColor() {
}
public void needConfirm() {
noPowerButton.hide();
confirmButton.show();
}
@@ -116,7 +122,16 @@ public void noConfirm() {
confirmButton.hide();
}
public void enterInterrupt() {
public void showNoPower() {
confirmButton.hide();
noPowerButton.show();
}
public void hideNoPower() {
noPowerButton.hide();
}
public void enterInterrupt(Color color) {
enterOverlay(Overlay.INTERRUPT);
guiNode.detachChild(guiHandlerNode);
@@ -124,6 +139,7 @@ public void enterInterrupt() {
app.getInputSynchronize().setClickAllowed(false);
interruptDialog.setColor(color);
interruptDialog.show();
}
@@ -135,6 +151,8 @@ public void leaveInterrupt() {
app.getInputSynchronize().setClickAllowed(true);
app.getAcousticHandler().playSound(MdgaSound.START);
interruptDialog.hide();
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

View File

@@ -1,7 +1,6 @@
// Samplers for textures
uniform sampler2D m_Texture;
uniform sampler2D m_OutlineDepthTexture;
uniform sampler2D m_DepthTexture;
// Input texture coordinates from the vertex shader
in vec2 texCoord;
@@ -15,26 +14,25 @@ uniform float m_OutlineWidth;
out vec4 fragColor;
void main() {
// Sample depth textures
// Sample depth textures at various offsets
vec4 depth = texture(m_OutlineDepthTexture, texCoord);
vec4 depth1 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(m_OutlineWidth, m_OutlineWidth)) / m_Resolution);
vec4 depth2 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(m_OutlineWidth, -m_OutlineWidth)) / m_Resolution);
vec4 depth3 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(-m_OutlineWidth, m_OutlineWidth)) / m_Resolution);
vec4 depth4 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(-m_OutlineWidth, -m_OutlineWidth)) / m_Resolution);
vec4 depth5 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(0.0, m_OutlineWidth)) / m_Resolution);
vec4 depth6 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(0.0, -m_OutlineWidth)) / m_Resolution);
vec4 depth7 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(m_OutlineWidth, 0.0)) / m_Resolution);
vec4 depth8 = texture(m_OutlineDepthTexture, ((texCoord * m_Resolution) + vec2(-m_OutlineWidth, 0.0)) / m_Resolution);
vec4 depth1 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(m_OutlineWidth, m_OutlineWidth)) / m_Resolution);
vec4 depth2 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(m_OutlineWidth, -m_OutlineWidth)) / m_Resolution);
vec4 depth3 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-m_OutlineWidth, m_OutlineWidth)) / m_Resolution);
vec4 depth4 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-m_OutlineWidth, -m_OutlineWidth)) / m_Resolution);
vec4 depth5 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(0.0, m_OutlineWidth)) / m_Resolution);
vec4 depth6 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(0.0, -m_OutlineWidth)) / m_Resolution);
vec4 depth7 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(m_OutlineWidth, 0.0)) / m_Resolution);
vec4 depth8 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-m_OutlineWidth, 0.0)) / m_Resolution);
// Sample the main texture
vec4 color = texture(m_Texture, texCoord);
// Determine whether to apply the outline color
if (depth == vec4(0.0) &&
(depth1 != depth || depth2 != depth || depth3 != depth || depth4 != depth ||
depth5 != depth || depth6 != depth || depth7 != depth || depth8 != depth)) {
fragColor = m_OutlineColor; // Apply outline color
} else {
fragColor = color; // Use the original texture color
}
// Check if an outline should be applied
bool isEdge = (depth == vec4(0.0)) &&
(depth1 != depth || depth2 != depth || depth3 != depth || depth4 != depth ||
depth5 != depth || depth6 != depth || depth7 != depth || depth8 != depth);
// Output the final color
fragColor = isEdge ? m_OutlineColor : color;
}

View File

@@ -1,11 +1,10 @@
// Use 'in' instead of 'varying' for inputs from the vertex shader
// Input texture coordinates from the vertex shader
in vec2 texCoord;
// Declare a custom output variable for the fragment color
// Output variable for the fragment color
out vec4 fragColor;
// Uniform samplers
// Uniform samplers for textures
uniform sampler2D m_Texture;
uniform sampler2D m_NormalsTexture;
uniform sampler2D m_DepthTexture;
@@ -13,6 +12,7 @@ uniform sampler2D m_DepthTexture;
void main() {
// Sample the texture at the given texture coordinates
vec4 color = texture(m_Texture, texCoord);
// Assign the color to the output variable
fragColor = color;
}

View File

@@ -1,109 +1,78 @@
// Uniform samplers
uniform sampler2D m_Texture;
uniform sampler2D m_OutlineDepthTexture;
uniform sampler2D m_DepthTexture;
varying vec2 texCoord;
// Input texture coordinates from the vertex shader
in vec2 texCoord;
// Uniforms for resolution, outline color, and width
uniform vec2 m_Resolution;
uniform vec4 m_OutlineColor;
uniform float m_OutlineWidth;
// Output variable for fragment color
out vec4 fragColor;
void main() {
vec4 depth = texture2D(m_OutlineDepthTexture, texCoord);
vec4 depth1 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(m_OutlineWidth,m_OutlineWidth))/m_Resolution);
vec4 depth2 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(m_OutlineWidth,-m_OutlineWidth))/m_Resolution);
vec4 depth3 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(-m_OutlineWidth,m_OutlineWidth))/m_Resolution);
vec4 depth4 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(-m_OutlineWidth,-m_OutlineWidth))/m_Resolution);
vec4 depth5 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(0.,m_OutlineWidth))/m_Resolution);
vec4 depth6 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(0.,-m_OutlineWidth))/m_Resolution);
vec4 depth7 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(m_OutlineWidth,0.))/m_Resolution);
vec4 depth8 = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(-m_OutlineWidth,0.))/m_Resolution);
vec4 color = texture2D(m_Texture, texCoord);
//如果是背景
float ratio=0.;
if(depth==vec4(0.) && (depth1 != depth || depth2 != depth || depth3 != depth || depth4 != depth||depth5 != depth || depth6 != depth || depth7 != depth || depth8 != depth)){
float dist=m_OutlineWidth;
//距离边的像素
vec4 nearDepth;
if(depth1 != depth){
for(float i=0.;i<m_OutlineWidth;i++){
nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(i,i))/m_Resolution);
if(nearDepth != depth){
dist = i;
break;
}
}
}else
if(depth2 != depth){
for(float i=0.;i<m_OutlineWidth;i++){
nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(i,-i))/m_Resolution);
if(nearDepth != depth){
dist = i;
break;
}
}
}else
if(depth3 != depth){
for(float i=0.;i<m_OutlineWidth;i++){
nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(-i,i))/m_Resolution);
if(nearDepth != depth){
dist = i;
break;
}
}
}else
if(depth4 != depth){
for(float i=0.;i<m_OutlineWidth;i++){
nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(-i,-i))/m_Resolution);
if(nearDepth != depth){
dist = i;
break;
}
}
}else
if(depth5 != depth){
for(float i=0.;i<m_OutlineWidth;i++){
nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(0.,i))/m_Resolution);
if(nearDepth != depth){
dist = i;
break;
}
}
}else
if(depth6 != depth){
for(float i=0.;i<m_OutlineWidth;i++){
nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(0.,-i))/m_Resolution);
if(nearDepth != depth){
dist = i;
break;
}
}
}else
if(depth7 != depth){
for(float i=0.;i<m_OutlineWidth;i++){
nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(i,0.))/m_Resolution);
if(nearDepth != depth){
dist = i;
break;
}
}
}else
if(depth8 != depth){
for(float i=0.;i<m_OutlineWidth;i++){
nearDepth = texture2D(m_OutlineDepthTexture, ((texCoord*m_Resolution)+vec2(-i,0.))/m_Resolution);
if(nearDepth != depth){
dist = i;
break;
}
}
}
//0:场景颜色 1:outline颜色
ratio = clamp(1.- dist/m_OutlineWidth,0.,1.);
//float off = (1.-ratio*ratio)*(1.-ratio*ratio);
gl_FragColor = color*(1.-ratio) +m_OutlineColor*ratio;
//gl_FragColor = m_OutlineColor;
}else{
gl_FragColor = color;
}
//debug
//gl_FragColor = vec4(0.,(1.-ratio),0.,1.);
}
vec4 depth = texture(m_OutlineDepthTexture, texCoord);
vec4 depth1 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(m_OutlineWidth, m_OutlineWidth)) / m_Resolution);
vec4 depth2 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(m_OutlineWidth, -m_OutlineWidth)) / m_Resolution);
vec4 depth3 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-m_OutlineWidth, m_OutlineWidth)) / m_Resolution);
vec4 depth4 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-m_OutlineWidth, -m_OutlineWidth)) / m_Resolution);
vec4 depth5 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(0.0, m_OutlineWidth)) / m_Resolution);
vec4 depth6 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(0.0, -m_OutlineWidth)) / m_Resolution);
vec4 depth7 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(m_OutlineWidth, 0.0)) / m_Resolution);
vec4 depth8 = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-m_OutlineWidth, 0.0)) / m_Resolution);
vec4 color = texture(m_Texture, texCoord);
float ratio = 0.0;
if (depth == vec4(0.0) &&
(depth1 != depth || depth2 != depth || depth3 != depth || depth4 != depth ||
depth5 != depth || depth6 != depth || depth7 != depth || depth8 != depth)) {
float dist = m_OutlineWidth;
vec4 nearDepth;
// Iterate to find the distance to the nearest edge
for (float i = 0.0; i < m_OutlineWidth; i++) {
if (depth1 != depth) {
nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(i, i)) / m_Resolution);
if (nearDepth != depth) { dist = i; break; }
} else if (depth2 != depth) {
nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(i, -i)) / m_Resolution);
if (nearDepth != depth) { dist = i; break; }
} else if (depth3 != depth) {
nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-i, i)) / m_Resolution);
if (nearDepth != depth) { dist = i; break; }
} else if (depth4 != depth) {
nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-i, -i)) / m_Resolution);
if (nearDepth != depth) { dist = i; break; }
} else if (depth5 != depth) {
nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(0.0, i)) / m_Resolution);
if (nearDepth != depth) { dist = i; break; }
} else if (depth6 != depth) {
nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(0.0, -i)) / m_Resolution);
if (nearDepth != depth) { dist = i; break; }
} else if (depth7 != depth) {
nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(i, 0.0)) / m_Resolution);
if (nearDepth != depth) { dist = i; break; }
} else if (depth8 != depth) {
nearDepth = texture(m_OutlineDepthTexture, (texCoord * m_Resolution + vec2(-i, 0.0)) / m_Resolution);
if (nearDepth != depth) { dist = i; break; }
}
}
// Calculate ratio for outline blending
ratio = clamp(1.0 - dist / m_OutlineWidth, 0.0, 1.0);
// Blend the outline color with the base color
fragColor = color * (1.0 - ratio) + m_OutlineColor * ratio;
} else {
// No outline, use the base texture color
fragColor = color;
}
// Optional: Debugging outline visualization
// fragColor = vec4(0.0, 1.0 - ratio, 0.0, 1.0);
}

View File

@@ -1,8 +1,8 @@
// Use 'in' for vertex attributes
// Vertex attributes
in vec4 inPosition;
in vec2 inTexCoord;
// Use 'out' for passing data to the fragment shader
// Output to fragment shader
out vec2 texCoord;
void main() {

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
# Blender MTL File: 'untitled.blend'
# Material Count: 3
newmtl Material.001
Ns 96.078431
Ka 0.000000 0.000000 0.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ni 1.000000
d 1.000000
illum 2
map_Kd untiffftled.jpg
newmtl Material.002
Ns 96.078431
Ka 0.000000 0.000000 0.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ni 1.000000
d 1.000000
illum 2
map_Kd untiffftled.jpg
newmtl Material.004
Ns 96.078431
Ka 0.000000 0.000000 0.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ni 1.000000
d 1.000000
illum 2
map_Kd untiffftled.jpg

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB