更新
This commit is contained in:
@@ -29,12 +29,36 @@ public class SimpleFighter {
|
|||||||
private boolean attackJustStarted = false; // 攻击是否刚开始
|
private boolean attackJustStarted = false; // 攻击是否刚开始
|
||||||
private float attackTimer = 0f; // 攻击计时器
|
private float attackTimer = 0f; // 攻击计时器
|
||||||
private static final float ATTACK_DURATION = 0.15f; // 攻击持续时间
|
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) {
|
public SimpleFighter(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(float deltaTime) {
|
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 (isAttacking) {
|
||||||
if (attackJustStarted) {
|
if (attackJustStarted) {
|
||||||
attackJustStarted = false;
|
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) {
|
private void updateAttackbox(String attackType) {
|
||||||
float offsetX, offsetY = 20, width = 80, height = 80;
|
float offsetX, offsetY = 20, width = 80, height = 80;
|
||||||
switch (attackType) {
|
switch (attackType) {
|
||||||
@@ -166,11 +203,21 @@ public class SimpleFighter {
|
|||||||
attackJustStarted = true;
|
attackJustStarted = true;
|
||||||
changeAction(Action.ATTACK);
|
changeAction(Action.ATTACK);
|
||||||
updateAttackbox(attackType);
|
updateAttackbox(attackType);
|
||||||
|
lastAttackType = attackType;
|
||||||
|
attackSequence++; // 本地每次攻击自增
|
||||||
|
lastDamageAppliedSeq = -1; // 新一次攻击重置
|
||||||
}
|
}
|
||||||
|
|
||||||
public void takeHit(int damage) {
|
public void takeHit(int damage) {
|
||||||
|
if (invulnerableTimer > 0 || health <= 0)
|
||||||
|
return; // 无敌或已死亡
|
||||||
health = Math.max(0, health - damage);
|
health = Math.max(0, health - damage);
|
||||||
changeAction(health > 0 ? Action.HIT : Action.DEAD);
|
changeAction(health > 0 ? Action.HIT : Action.DEAD);
|
||||||
|
// 设置无敌 & 击退
|
||||||
|
invulnerableTimer = INVULNERABLE_DURATION;
|
||||||
|
// 击退方向与幅度
|
||||||
|
knockbackX = isFacingRight ? -600f : 600f;
|
||||||
|
knockbackTimer = KNOCKBACK_DURATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAlive() {
|
public boolean isAlive() {
|
||||||
@@ -181,6 +228,10 @@ public class SimpleFighter {
|
|||||||
return isAttacking;
|
return isAttacking;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isInvulnerable() {
|
||||||
|
return invulnerableTimer > 0;
|
||||||
|
}
|
||||||
|
|
||||||
public Rectangle getHitbox() {
|
public Rectangle getHitbox() {
|
||||||
return hitbox;
|
return hitbox;
|
||||||
}
|
}
|
||||||
@@ -193,6 +244,43 @@ public class SimpleFighter {
|
|||||||
return health;
|
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() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ public class GameScreen extends ScreenAdapter {
|
|||||||
private final SimpleFighter player;
|
private final SimpleFighter player;
|
||||||
private final FighterController controller;
|
private final FighterController controller;
|
||||||
private final Map<String, SimpleFighter> otherPlayers = new HashMap<>();
|
private final Map<String, SimpleFighter> otherPlayers = new HashMap<>();
|
||||||
|
private final Map<String, Float> deathTimers = new HashMap<>(); // 记录远程/本地死亡计时
|
||||||
|
private static final float RESPAWN_DELAY = 2f;
|
||||||
|
|
||||||
private SpriteBatch batch;
|
private SpriteBatch batch;
|
||||||
private ShapeRenderer shapeRenderer;
|
private ShapeRenderer shapeRenderer;
|
||||||
@@ -80,7 +82,11 @@ public class GameScreen extends ScreenAdapter {
|
|||||||
}
|
}
|
||||||
return new SimpleFighter("Remote-" + k);
|
return new SimpleFighter("Remote-" + k);
|
||||||
});
|
});
|
||||||
|
// 根据位置变化更新朝向
|
||||||
|
float oldX = remote.getHitbox().x;
|
||||||
remote.setPosition(pos[0], pos[1]);
|
remote.setPosition(pos[0], pos[1]);
|
||||||
|
float dx = remote.getHitbox().x - oldX;
|
||||||
|
remote.updateFacingByDelta(dx);
|
||||||
remote.update(delta);
|
remote.update(delta);
|
||||||
}
|
}
|
||||||
// 处理远程攻击同步:触发远程角色的攻击动画
|
// 处理远程攻击同步:触发远程角色的攻击动画
|
||||||
@@ -93,6 +99,92 @@ public class GameScreen extends ScreenAdapter {
|
|||||||
}
|
}
|
||||||
attacks.clear();
|
attacks.clear();
|
||||||
}
|
}
|
||||||
|
// 主机执行碰撞检测并广播伤害
|
||||||
|
if (NetworkManager.getInstance().isHost()) {
|
||||||
|
// 只处理本地玩家攻击命中其他人,以及(可扩展)其他远程之间不处理
|
||||||
|
if (player.isAttacking()) {
|
||||||
|
for (Map.Entry<String, SimpleFighter> 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<String, SimpleFighter> 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<String, SimpleFighter> e : otherPlayers.entrySet()) {
|
||||||
|
if (!e.getValue().isAlive()) {
|
||||||
|
deathTimers.merge(e.getKey(), Gdx.graphics.getDeltaTime(), Float::sum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 检查需要重生的对象
|
||||||
|
for (Map.Entry<String, Float> 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<String, Integer> damageEvents = NetworkManager.getInstance().getDamageEvents();
|
||||||
|
if (!damageEvents.isEmpty()) {
|
||||||
|
for (Map.Entry<String, Integer> 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<String, float[]> respawns = NetworkManager.getInstance().getRespawnEvents();
|
||||||
|
if (!respawns.isEmpty()) {
|
||||||
|
for (Map.Entry<String, float[]> 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 摄像机跟随
|
// 摄像机跟随
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ public class NetworkManager {
|
|||||||
private final Map<String, String> playerCharacters = new HashMap<>();
|
private final Map<String, String> playerCharacters = new HashMap<>();
|
||||||
// 存储远程玩家的攻击类型(attackType)
|
// 存储远程玩家的攻击类型(attackType)
|
||||||
private final Map<String, String> playerAttacks = new HashMap<>();
|
private final Map<String, String> playerAttacks = new HashMap<>();
|
||||||
|
// 伤害事件:targetId -> 累积伤害(本帧内可能多次)
|
||||||
|
private final Map<String, Integer> damageEvents = new HashMap<>();
|
||||||
|
private final Map<String, float[]> respawnEvents = new HashMap<>();
|
||||||
|
|
||||||
public static NetworkManager getInstance() {
|
public static NetworkManager getInstance() {
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
@@ -104,6 +107,39 @@ public class NetworkManager {
|
|||||||
} else {
|
} else {
|
||||||
Gdx.app.error("Network", "攻击消息格式错误: " + message);
|
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 {
|
} else {
|
||||||
Gdx.app.log("Network", "未知消息类型: " + message);
|
Gdx.app.log("Network", "未知消息类型: " + message);
|
||||||
}
|
}
|
||||||
@@ -140,6 +176,32 @@ public class NetworkManager {
|
|||||||
return playerCharacters;
|
return playerCharacters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, Integer> 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<String, float[]> getRespawnEvents() {
|
||||||
|
return respawnEvents;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isHost() {
|
public boolean isHost() {
|
||||||
return isHost;
|
return isHost;
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user