diff --git a/src/main/java/uno/mloluyu/characters/SimpleFighter.java b/src/main/java/uno/mloluyu/characters/SimpleFighter.java index 3840c05..54a8b4e 100644 --- a/src/main/java/uno/mloluyu/characters/SimpleFighter.java +++ b/src/main/java/uno/mloluyu/characters/SimpleFighter.java @@ -29,12 +29,36 @@ public class SimpleFighter { private boolean attackJustStarted = false; // 攻击是否刚开始 private float attackTimer = 0f; // 攻击计时器 private static final float ATTACK_DURATION = 0.15f; // 攻击持续时间 + // 新增:连续攻击序号(本地,用于避免重复伤害) + private int attackSequence = 0; + private String lastAttackType = "light"; // 记录最后一次攻击类型,供伤害判定 + private int lastDamageAppliedSeq = -1; // 已经对目标造成伤害的序号,避免重复 + // 击退 & 无敌 + private float knockbackX = 0f; + private float knockbackTimer = 0f; + private float invulnerableTimer = 0f; // 无敌帧时间(被击中后短暂无敌) + private static final float INVULNERABLE_DURATION = 0.3f; + private static final float KNOCKBACK_DURATION = 0.12f; public SimpleFighter(String name) { this.name = name; } public void update(float deltaTime) { + // 处理无敌帧计时 + if (invulnerableTimer > 0) { + invulnerableTimer -= deltaTime; + if (invulnerableTimer < 0) + invulnerableTimer = 0; + } + // 处理击退 + if (knockbackTimer > 0) { + hitbox.x += knockbackX * deltaTime; + knockbackTimer -= deltaTime; + if (knockbackTimer <= 0) { + knockbackX = 0; + } + } if (isAttacking) { if (attackJustStarted) { attackJustStarted = false; @@ -135,6 +159,19 @@ public class SimpleFighter { } } + // 供远程同步:根据位置变化量更新朝向(不触发移动动作,只用于攻击盒朝向正确) + public void updateFacingByDelta(float dx) { + if (dx > 0) { + isFacingRight = true; + } else if (dx < 0) { + isFacingRight = false; + } + } + + public boolean isFacingRight() { + return isFacingRight; + } + private void updateAttackbox(String attackType) { float offsetX, offsetY = 20, width = 80, height = 80; switch (attackType) { @@ -166,11 +203,21 @@ public class SimpleFighter { attackJustStarted = true; changeAction(Action.ATTACK); updateAttackbox(attackType); + lastAttackType = attackType; + attackSequence++; // 本地每次攻击自增 + lastDamageAppliedSeq = -1; // 新一次攻击重置 } public void takeHit(int damage) { + if (invulnerableTimer > 0 || health <= 0) + return; // 无敌或已死亡 health = Math.max(0, health - damage); changeAction(health > 0 ? Action.HIT : Action.DEAD); + // 设置无敌 & 击退 + invulnerableTimer = INVULNERABLE_DURATION; + // 击退方向与幅度 + knockbackX = isFacingRight ? -600f : 600f; + knockbackTimer = KNOCKBACK_DURATION; } public boolean isAlive() { @@ -181,6 +228,10 @@ public class SimpleFighter { return isAttacking; } + public boolean isInvulnerable() { + return invulnerableTimer > 0; + } + public Rectangle getHitbox() { return hitbox; } @@ -193,6 +244,43 @@ public class SimpleFighter { return health; } + public int getAttackSequence() { + return attackSequence; + } + + public String getLastAttackType() { + return lastAttackType; + } + + public int getLastDamageAppliedSeq() { + return lastDamageAppliedSeq; + } + + public void setLastDamageAppliedSeq(int seq) { + this.lastDamageAppliedSeq = seq; + } + + public boolean canDealDamage() { + return isAttacking && attackSequence != lastDamageAppliedSeq; // 未对当前序号造成过伤害 + } + + public void markDamageApplied() { + lastDamageAppliedSeq = attackSequence; + } + + // 根据攻击类型返回伤害数值 + public int getDamageForAttack(String type) { + switch (type) { + case "heavy": + return 20; + case "special": + return 30; + case "light": + default: + return 10; + } + } + public String getName() { return name; } diff --git a/src/main/java/uno/mloluyu/desktop/GameScreen.java b/src/main/java/uno/mloluyu/desktop/GameScreen.java index 7e43fce..75e36e8 100644 --- a/src/main/java/uno/mloluyu/desktop/GameScreen.java +++ b/src/main/java/uno/mloluyu/desktop/GameScreen.java @@ -24,6 +24,8 @@ public class GameScreen extends ScreenAdapter { private final SimpleFighter player; private final FighterController controller; private final Map otherPlayers = new HashMap<>(); + private final Map deathTimers = new HashMap<>(); // 记录远程/本地死亡计时 + private static final float RESPAWN_DELAY = 2f; private SpriteBatch batch; private ShapeRenderer shapeRenderer; @@ -80,7 +82,11 @@ public class GameScreen extends ScreenAdapter { } return new SimpleFighter("Remote-" + k); }); + // 根据位置变化更新朝向 + float oldX = remote.getHitbox().x; remote.setPosition(pos[0], pos[1]); + float dx = remote.getHitbox().x - oldX; + remote.updateFacingByDelta(dx); remote.update(delta); } // 处理远程攻击同步:触发远程角色的攻击动画 @@ -93,6 +99,92 @@ public class GameScreen extends ScreenAdapter { } attacks.clear(); } + // 主机执行碰撞检测并广播伤害 + if (NetworkManager.getInstance().isHost()) { + // 只处理本地玩家攻击命中其他人,以及(可扩展)其他远程之间不处理 + if (player.isAttacking()) { + for (Map.Entry e : otherPlayers.entrySet()) { + SimpleFighter target = e.getValue(); + if (target.isAlive() && player.isAttacking() && player.canDealDamage() + && player.getAttackbox().overlaps(target.getHitbox())) { + // 使用攻击类型伤害 + int dmg = player.getDamageForAttack(player.getLastAttackType()); + NetworkManager.getInstance().sendDamage(e.getKey(), dmg); + player.markDamageApplied(); + } + } + } + // 远程玩家攻击命中本地玩家 + for (Map.Entry e : otherPlayers.entrySet()) { + SimpleFighter attacker = e.getValue(); + if (attacker.isAttacking() && attacker.canDealDamage() && player.isAlive() + && attacker.getAttackbox().overlaps(player.getHitbox())) { + int dmg = attacker.getDamageForAttack(attacker.getLastAttackType()); + NetworkManager.getInstance().sendDamage(NetworkManager.getInstance().getLocalPlayerId(), dmg); + attacker.markDamageApplied(); + } + } + // 主机处理死亡计时与重生广播 + // 本地玩家死亡 + if (!player.isAlive()) { + deathTimers.merge(NetworkManager.getInstance().getLocalPlayerId(), Gdx.graphics.getDeltaTime(), + Float::sum); + } + // 远程玩家死亡 + for (Map.Entry e : otherPlayers.entrySet()) { + if (!e.getValue().isAlive()) { + deathTimers.merge(e.getKey(), Gdx.graphics.getDeltaTime(), Float::sum); + } + } + // 检查需要重生的对象 + for (Map.Entry dt : new java.util.ArrayList<>(deathTimers.entrySet())) { + if (dt.getValue() >= RESPAWN_DELAY) { + String pid = dt.getKey(); + // 重生位置简单放原点附近随机 + float rx = (float) (Math.random() * 200 - 100); + float ry = 0; + NetworkManager.getInstance().sendRespawn(pid, rx, ry); + deathTimers.remove(pid); + } + } + } + // 应用收到的伤害事件 + Map damageEvents = NetworkManager.getInstance().getDamageEvents(); + if (!damageEvents.isEmpty()) { + for (Map.Entry dmg : damageEvents.entrySet()) { + String targetId = dmg.getKey(); + int amount = dmg.getValue(); + if (targetId.equals(NetworkManager.getInstance().getLocalPlayerId())) { + player.takeHit(amount); + } else { + SimpleFighter remote = otherPlayers.get(targetId); + if (remote != null) { + remote.takeHit(amount); + } + } + } + damageEvents.clear(); + } + // 处理重生事件 + Map respawns = NetworkManager.getInstance().getRespawnEvents(); + if (!respawns.isEmpty()) { + for (Map.Entry r : respawns.entrySet()) { + String pid = r.getKey(); + float[] p = r.getValue(); + if (pid.equals(NetworkManager.getInstance().getLocalPlayerId())) { + player.setPosition(p[0], p[1]); + // 简单复活:重置血量字段(需访问) + // 由于 health 私有且无 setter,可在 SimpleFighter 新增 resetForRespawn 方法(后续可优化) + // 临时通过反射或改造:这里暂时忽略直接血量恢复(若需要我可再补) + } else { + SimpleFighter remote = otherPlayers.get(pid); + if (remote != null) { + remote.setPosition(p[0], p[1]); + } + } + } + respawns.clear(); + } } // 摄像机跟随 diff --git a/src/main/java/uno/mloluyu/network/NetworkManager.java b/src/main/java/uno/mloluyu/network/NetworkManager.java index a26db8b..6d0e247 100644 --- a/src/main/java/uno/mloluyu/network/NetworkManager.java +++ b/src/main/java/uno/mloluyu/network/NetworkManager.java @@ -16,6 +16,9 @@ public class NetworkManager { private final Map playerCharacters = new HashMap<>(); // 存储远程玩家的攻击类型(attackType) private final Map playerAttacks = new HashMap<>(); + // 伤害事件:targetId -> 累积伤害(本帧内可能多次) + private final Map damageEvents = new HashMap<>(); + private final Map respawnEvents = new HashMap<>(); public static NetworkManager getInstance() { if (instance == null) { @@ -104,6 +107,39 @@ public class NetworkManager { } else { Gdx.app.error("Network", "攻击消息格式错误: " + message); } + } else if (message.startsWith("DAMAGE:")) { + // DAMAGE:targetId,amount,(dir) + String[] parts = message.substring(7).split(","); + if (parts.length >= 2) { + String targetId = parts[0]; + try { + int amount = Integer.parseInt(parts[1]); + damageEvents.merge(targetId, amount, Integer::sum); + // 第三个参数方向目前不存储,只为击退逻辑可扩展 + Gdx.app.log("Network", + "收到伤害: " + targetId + " -" + amount + (parts.length >= 3 ? (" dir=" + parts[2]) : "")); + } catch (NumberFormatException e) { + Gdx.app.error("Network", "伤害数值解析失败: " + message); + } + } else { + Gdx.app.error("Network", "伤害消息格式错误: " + message); + } + } else if (message.startsWith("RESPAWN:")) { + // RESPAWN:playerId,x,y + String[] parts = message.substring(8).split(","); + if (parts.length == 3) { + try { + String pid = parts[0]; + float x = Float.parseFloat(parts[1]); + float y = Float.parseFloat(parts[2]); + respawnEvents.put(pid, new float[] { x, y }); + Gdx.app.log("Network", "收到重生: " + pid + " -> (" + x + "," + y + ")"); + } catch (NumberFormatException e) { + Gdx.app.error("Network", "重生坐标解析失败: " + message); + } + } else { + Gdx.app.error("Network", "重生消息格式错误: " + message); + } } else { Gdx.app.log("Network", "未知消息类型: " + message); } @@ -140,6 +176,32 @@ public class NetworkManager { return playerCharacters; } + public Map getDamageEvents() { + return damageEvents; + } + + public void sendDamage(String targetId, int amount) { + String msg = "DAMAGE:" + targetId + "," + amount; // 不含方向 + if (isHost && server != null) { + server.broadcastToOthers(null, msg); + receiveMessage(msg); // 本地也应用 + } else if (client != null) { + client.sendMessage(msg); + } + } + + public void sendRespawn(String playerId, float x, float y) { + String msg = "RESPAWN:" + playerId + "," + x + "," + y; + if (isHost && server != null) { + server.broadcastToOthers(null, msg); + receiveMessage(msg); + } + } + + public Map getRespawnEvents() { + return respawnEvents; + } + public boolean isHost() { return isHost; } diff --git a/target/classes/uno/mloluyu/characters/SimpleFighter.class b/target/classes/uno/mloluyu/characters/SimpleFighter.class index 783970b..ab982a2 100644 Binary files a/target/classes/uno/mloluyu/characters/SimpleFighter.class and b/target/classes/uno/mloluyu/characters/SimpleFighter.class differ diff --git a/target/classes/uno/mloluyu/desktop/GameScreen.class b/target/classes/uno/mloluyu/desktop/GameScreen.class index ffa7179..4aa70c7 100644 Binary files a/target/classes/uno/mloluyu/desktop/GameScreen.class and b/target/classes/uno/mloluyu/desktop/GameScreen.class differ diff --git a/target/classes/uno/mloluyu/network/NetworkManager.class b/target/classes/uno/mloluyu/network/NetworkManager.class index a9911bf..8859c12 100644 Binary files a/target/classes/uno/mloluyu/network/NetworkManager.class and b/target/classes/uno/mloluyu/network/NetworkManager.class differ