diff --git a/src/main/java/uno/mloluyu/characters/Alice.java b/src/main/java/uno/mloluyu/characters/Alice.java index 3ca864e..c2109fa 100644 --- a/src/main/java/uno/mloluyu/characters/Alice.java +++ b/src/main/java/uno/mloluyu/characters/Alice.java @@ -9,7 +9,7 @@ import com.badlogic.gdx.graphics.g2d.TextureAtlas; public class Alice extends Fighter { public Alice() { - super(new TextureAtlas(Gdx.files.internal("src/main/resources/character/alice/精灵1.2.atlas"))); + super("Alice", new TextureAtlas(Gdx.files.internal("src/main/resources/character/alice/精灵1.2.atlas"))); // 设置角色属性 speed = 350f; // 更快的移动速度 @@ -20,22 +20,27 @@ public class Alice extends Fighter { @Override protected void loadAnimations() { - // 加载基础动作动画 - loadAnimationFromAtlas(Action.IDLE, "stand/stand", 15, true); - loadAnimationFromAtlas(Action.WALK, "walkFront/walkFront", 9, true); - loadAnimationFromAtlas(Action.JUMP, "jump/jump", 8, false); - loadAnimationFromAtlas(Action.FALL, "hitSpin/hitSpin", 5, false); + // 基础动作 + loadLoopingAnimation(Action.IDLE, "stand/stand", 15); + loadLoopingAnimation(Action.WALK, "walkFront/walkFront", 9); + loadOneShotAnimation(Action.JUMP, "jump/jump", 8); + loadOneShotAnimation(Action.FALL, "hitSpin/hitSpin", 5); - // 加载攻击动作动画 - loadAnimationFromAtlas(Action.ATTACK1, "attackAa/attackAa", 6, false); - loadAnimationFromAtlas(Action.ATTACK2, "attackAb/attackAb", 6, false); - loadAnimationFromAtlas(Action.ATTACK3, "attackAc/attackAc", 6, false); - loadAnimationFromAtlas(Action.ATTACK4, "attackAd/attackAd", 6, false); + // 攻击动作 + loadOneShotAnimation(Action.ATTACK1, "attackAa/attackAa", 6); + loadOneShotAnimation(Action.ATTACK2, "attackAb/attackAb", 6); + loadOneShotAnimation(Action.ATTACK3, "attackAc/attackAc", 6); + loadOneShotAnimation(Action.ATTACK4, "attackAd/attackAd", 6); - // 加载受击动画 - loadAnimationFromAtlas(Action.HIT, "hitSpin/hitSpin", 5, false); + // // 特殊动作(可扩展) + // loadOneShotAnimation(Action.SPECIAL1, "special/special1", 6); + // loadOneShotAnimation(Action.SPECIAL2, "special/special2", 6); - // 设置帧间隔(动作速度) + // 受击与死亡 + loadOneShotAnimation(Action.HIT, "hitSpin/hitSpin", 5); + // loadOneShotAnimation(Action.DEATH, "death/death", 8); + + // 帧速率调整 setFrameDuration(Action.IDLE, 0.04f); setFrameDuration(Action.WALK, 0.08f); setFrameDuration(Action.ATTACK1, 0.07f); @@ -50,6 +55,7 @@ public class Alice extends Fighter { if (currentAction != Action.ATTACK1 && currentAction != Action.ATTACK2 && currentAction != Action.ATTACK3 && + currentAction != Action.ATTACK4 && currentAction != Action.SPECIAL1 && currentAction != Action.SPECIAL2 && currentAction != Action.DEFEND && diff --git a/src/main/java/uno/mloluyu/characters/Fighter.java b/src/main/java/uno/mloluyu/characters/Fighter.java index 3e9e589..a8f0791 100644 --- a/src/main/java/uno/mloluyu/characters/Fighter.java +++ b/src/main/java/uno/mloluyu/characters/Fighter.java @@ -1,21 +1,14 @@ package uno.mloluyu.characters; -import uno.mloluyu.util.SimpleFormatter; - import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.graphics.g2d.Animation; -import com.badlogic.gdx.graphics.g2d.SpriteBatch; -import com.badlogic.gdx.graphics.g2d.TextureAtlas; -import com.badlogic.gdx.graphics.g2d.TextureRegion; -import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.graphics.g2d.*; import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Disposable; +import uno.mloluyu.util.SimpleFormatter; import java.util.EnumMap; -/** - * 格斗角色父类,封装所有角色共有的动画和状态管理逻辑 - */ public abstract class Fighter implements Disposable { public enum Action { @@ -51,10 +44,10 @@ public abstract class Fighter implements Disposable { protected float scaleX = 1.0f; protected float scaleY = 1.0f; - public Fighter() { - } + public Fighter() {} - public Fighter(TextureAtlas atlas) { + public Fighter(String name, TextureAtlas atlas) { + this.name = name; this.atlas = atlas; for (Action action : Action.values()) { frameDurations.put(action, DEFAULT_FRAME_DURATION); @@ -85,6 +78,14 @@ public abstract class Fighter implements Disposable { animations.put(action, animation); } + protected void loadLoopingAnimation(Action action, String prefix, int count) { + loadAnimationFromAtlas(action, prefix, count, true); + } + + protected void loadOneShotAnimation(Action action, String prefix, int count) { + loadAnimationFromAtlas(action, prefix, count, false); + } + protected void setFrameDuration(Action action, float duration) { frameDurations.put(action, duration); Animation anim = animations.get(action); @@ -103,23 +104,12 @@ public abstract class Fighter implements Disposable { } protected void handleAnimationTransitions() { - if (!isAnimationFinished) - return; + if (!isAnimationFinished) return; switch (currentAction) { - case ATTACK1: - case ATTACK2: - case ATTACK3: - case SPECIAL1: - case SPECIAL2: - case HIT: - changeAction(Action.IDLE); - break; - case JUMP: - changeAction(Action.FALL); - break; - default: - break; + case ATTACK1, ATTACK2, ATTACK3, SPECIAL1, SPECIAL2, HIT -> changeAction(Action.IDLE); + case JUMP -> changeAction(Action.FALL); + default -> {} } } @@ -150,8 +140,7 @@ public abstract class Fighter implements Disposable { } public boolean changeAction(Action newAction) { - if (isActionUninterruptible(currentAction)) - return false; + if (isActionUninterruptible(currentAction)) return false; if (currentAction != newAction) { currentAction = newAction; stateTime = 0f; @@ -182,18 +171,17 @@ public abstract class Fighter implements Disposable { protected void handleMoveState() { if (!isActionUninterruptible(currentAction) && - currentAction != Action.JUMP && - currentAction != Action.FALL && - currentAction != Action.DEFEND && - !currentAction.name().startsWith("ATTACK") && - !currentAction.name().startsWith("SPECIAL")) { + currentAction != Action.JUMP && + currentAction != Action.FALL && + currentAction != Action.DEFEND && + !currentAction.name().startsWith("ATTACK") && + !currentAction.name().startsWith("SPECIAL")) { changeAction(Action.WALK); } } public boolean attack(int attackType) { - if (!canAttack()) - return false; + if (!canAttack()) return false; Action attackAction = switch (attackType) { case 1 -> Action.ATTACK1; @@ -212,13 +200,24 @@ public abstract class Fighter implements Disposable { } public void takeHit(int damage) { - if (currentAction == Action.DEATH) - return; + if (currentAction == Action.DEATH) return; health = Math.max(0, health - damage); changeAction(health == 0 ? Action.DEATH : Action.HIT); } + public void setPosition(float x, float y) { + hitbox.setPosition(x, y); + } + + public void setFacingRight(boolean facingRight) { + this.isFacingRight = facingRight; + } + + public String getName() { + return name; + } + public Rectangle getHitbox() { return hitbox; } @@ -235,12 +234,6 @@ public abstract class Fighter implements Disposable { return health; } - @Override - public void dispose() { - if (atlas != null) - atlas.dispose(); - } - public Action getCurrentAction() { return currentAction; } @@ -260,4 +253,9 @@ public abstract class Fighter implements Disposable { public float getCenterY() { return hitbox.y + hitbox.height / 2; } + + @Override + public void dispose() { + if (atlas != null) atlas.dispose(); + } } diff --git a/src/main/java/uno/mloluyu/desktop/CharacterSelectScreen.java b/src/main/java/uno/mloluyu/desktop/CharacterSelectScreen.java index 365f128..ea414ed 100644 --- a/src/main/java/uno/mloluyu/desktop/CharacterSelectScreen.java +++ b/src/main/java/uno/mloluyu/desktop/CharacterSelectScreen.java @@ -1,5 +1,8 @@ package uno.mloluyu.desktop; +import java.util.UUID; +import uno.mloluyu.network.NetworkManager; + import com.badlogic.gdx.Gdx; import com.badlogic.gdx.ScreenAdapter; import com.badlogic.gdx.graphics.Color; @@ -141,6 +144,7 @@ public class CharacterSelectScreen extends ScreenAdapter { } } + // 点击确认按钮 // 点击确认按钮 if (isHovered(mouseX, mouseY, BUTTON_X, CONFIRM_Y, BUTTON_WIDTH, BUTTON_HEIGHT)) { if (selectedIndex != -1) { @@ -155,12 +159,19 @@ public class CharacterSelectScreen extends ScreenAdapter { case "Reimu": fighter = new Reimu(); break; - // case "弓箭手": - // fighter = new Archer(); - // break; } if (fighter != null) { + if (multiplayerMode) { + // 设置唯一玩家 ID 并发送角色选择 + if (NetworkManager.getInstance().getLocalPlayerId() == null) { + String playerId = UUID.randomUUID().toString(); + NetworkManager.getInstance().setLocalPlayerId(playerId); + Gdx.app.log("Network", "设置玩家ID: " + playerId); + } + NetworkManager.getInstance().sendCharacterSelection(selectedCharacter); + } + game.setScreen(new GameScreen(game, fighter)); } } diff --git a/src/main/java/uno/mloluyu/desktop/GameScreen.java b/src/main/java/uno/mloluyu/desktop/GameScreen.java index 21f3ab9..45d26c3 100644 --- a/src/main/java/uno/mloluyu/desktop/GameScreen.java +++ b/src/main/java/uno/mloluyu/desktop/GameScreen.java @@ -1,30 +1,41 @@ package uno.mloluyu.desktop; +import java.util.HashMap; +import java.util.Map; + import com.badlogic.gdx.Gdx; import com.badlogic.gdx.ScreenAdapter; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; + +import uno.mloluyu.characters.Alice; import uno.mloluyu.characters.Fighter; +import uno.mloluyu.characters.Reimu; import uno.mloluyu.network.NetworkManager; import uno.mloluyu.util.ClearScreen; +import uno.mloluyu.versatile.FighterController; public class GameScreen extends ScreenAdapter { private final MainGame game; private final Fighter player; private SpriteBatch batch; private ShapeRenderer shapeRenderer; - + private FighterController controller; + private final Map otherPlayers = new HashMap<>(); + public GameScreen(MainGame game, Fighter player) { this.game = game; this.player = player; + this.controller = new FighterController(player); } @Override public void show() { batch = new SpriteBatch(); shapeRenderer = new ShapeRenderer(); + Gdx.input.setInputProcessor(controller); } @Override @@ -33,7 +44,7 @@ public class GameScreen extends ScreenAdapter { // 更新角色状态 player.update(delta); - + controller.update(delta); // 发送本机玩家位置 if (NetworkManager.getInstance().isConnected()) { NetworkManager.getInstance().sendPosition(player.getX(), player.getY()); @@ -42,40 +53,56 @@ public class GameScreen extends ScreenAdapter { // 渲染角色 batch.begin(); player.render(batch); - + batch.end(); // 渲染其他玩家位置(联机模式) if (NetworkManager.getInstance().isConnected()) { renderOtherPlayers(); } - - batch.end(); } - + private void renderOtherPlayers() { - // 获取其他玩家位置并渲染 - for (String position : NetworkManager.getInstance().getOtherPlayerPositions()) { - String[] coords = position.split(","); - if (coords.length == 2) { - try { - float x = Float.parseFloat(coords[0]); - float y = Float.parseFloat(coords[1]); - - // 使用形状渲染器绘制其他玩家标记 - shapeRenderer.begin(ShapeRenderer.ShapeType.Filled); - shapeRenderer.setColor(Color.RED); - shapeRenderer.circle(x, y, 20); // 红色圆圈表示其他玩家 - shapeRenderer.end(); - - // 在批处理中绘制文字 - batch.begin(); - // 这里可以添加玩家名字显示 - batch.end(); - - } catch (NumberFormatException e) { - Gdx.app.error("GameScreen", "解析玩家位置失败: " + position); + Map positions = NetworkManager.getInstance().getPlayerPositions(); + Map characters = NetworkManager.getInstance().getPlayerCharacters(); + + batch.begin(); + for (Map.Entry entry : positions.entrySet()) { + String playerId = entry.getKey(); + float[] pos = entry.getValue(); + + // 跳过本机玩家(可选) + // if (playerId.equals(NetworkManager.getInstance().getLocalPlayerId())) + // continue; + + // 获取角色名 + String characterName = characters.get(playerId); + if (characterName == null || pos == null) + continue; + + // 获取或创建 Fighter 实例 + Fighter fighter = otherPlayers.get(playerId); + if (fighter == null) { + switch (characterName) { + case "Alice": + fighter = new Alice(); + break; + case "Reimu": + fighter = new Reimu(); + default: + fighter = new Alice(); + break; + }; + if (fighter != null) { + otherPlayers.put(playerId, fighter); } } + + // 设置位置并渲染 + if (fighter != null) { + fighter.setPosition(pos[0], pos[1]); + fighter.render(batch); + } } + batch.end(); } @Override @@ -86,4 +113,4 @@ public class GameScreen extends ScreenAdapter { // 断开网络连接 NetworkManager.getInstance().disconnect(); } -} +} \ No newline at end of file diff --git a/src/main/java/uno/mloluyu/desktop/NetworkSettingsScreen.java b/src/main/java/uno/mloluyu/desktop/NetworkSettingsScreen.java index 1f31de7..5bd640a 100644 --- a/src/main/java/uno/mloluyu/desktop/NetworkSettingsScreen.java +++ b/src/main/java/uno/mloluyu/desktop/NetworkSettingsScreen.java @@ -8,8 +8,7 @@ import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.SpriteBatch; -import uno.mloluyu.network.ConnectClient; -import uno.mloluyu.network.ConnectServer; +import uno.mloluyu.network.NetworkManager; import static uno.mloluyu.util.Font.loadChineseFont; import uno.mloluyu.util.ClearScreen; @@ -24,7 +23,7 @@ public class NetworkSettingsScreen extends ScreenAdapter { private static final int BUTTON_X = 760; private static final int CREATE_ROOM_Y = 500; private static final int JOIN_ROOM_Y = 380; - private static final int EXIT_Y = 260; // 退出按钮位置 + private static final int EXIT_Y = 260; public NetworkSettingsScreen(MainGame game) { this.game = game; @@ -48,7 +47,6 @@ public class NetworkSettingsScreen extends ScreenAdapter { renderButtons(mouseX, mouseY); renderTexts(); - handleInput(mouseX, mouseY); } @@ -56,7 +54,7 @@ public class NetworkSettingsScreen extends ScreenAdapter { shapeRenderer.begin(ShapeRenderer.ShapeType.Filled); drawButton(CREATE_ROOM_Y, mouseX, mouseY); drawButton(JOIN_ROOM_Y, mouseX, mouseY); - drawButton(EXIT_Y, mouseX, mouseY); // 新增退出按钮 + drawButton(EXIT_Y, mouseX, mouseY); shapeRenderer.end(); } @@ -65,37 +63,36 @@ public class NetworkSettingsScreen extends ScreenAdapter { font.draw(batch, "联机设置", BUTTON_X + 100, 650); drawButtonText(CREATE_ROOM_Y, "创建房间"); drawButtonText(JOIN_ROOM_Y, "加入房间"); - drawButtonText(EXIT_Y, "返回"); // 新增退出按钮文字 + drawButtonText(EXIT_Y, "返回"); batch.end(); } private void handleInput(int mouseX, int mouseY) { if (Gdx.input.justTouched()) { + // 创建房间 if (isHovered(mouseX, mouseY, BUTTON_X, CREATE_ROOM_Y)) { Gdx.app.log("Network", "创建房间按钮被点击!"); - new Thread(new ConnectServer(11455)).start(); - // 添加服务器启动提示信息 - Gdx.app.log("Network", "服务器已启动,等待玩家连接..."); - System.out.println("服务器已启动,等待玩家连接..."); + NetworkManager.getInstance().createRoom(); + NetworkManager.getInstance().joinRoom("127.0.0.1"); - // 创建联机模式标识并传递到角色选择界面 + Gdx.app.log("Network", "已连接到本地服务器,等待其他玩家加入..."); CharacterSelectScreen characterSelectScreen = new CharacterSelectScreen(game); - characterSelectScreen.setMultiplayerMode(true); // 设置为联机模式 + characterSelectScreen.setMultiplayerMode(true); game.setScreen(characterSelectScreen); + } - } else if (isHovered(mouseX, mouseY, BUTTON_X, JOIN_ROOM_Y)) { + // 加入房间 + else if (isHovered(mouseX, mouseY, BUTTON_X, JOIN_ROOM_Y)) { Gdx.app.log("Network", "加入房间按钮被点击!"); - // 使用LibGDX的输入对话框避免AWT线程问题 + Gdx.input.getTextInput(new com.badlogic.gdx.Input.TextInputListener() { @Override public void input(String ip) { if (ip != null && !ip.trim().isEmpty()) { - new Thread(() -> new ConnectClient(ip.trim(), 11455)).start(); + NetworkManager.getInstance().joinRoom(ip.trim()); Gdx.app.log("Network", "正在连接到服务器 " + ip.trim() + "..."); - System.out.println("正在连接到服务器 " + ip.trim() + "..."); - // 使用postRunnable确保在主线程中执行屏幕切换 Gdx.app.postRunnable(() -> { CharacterSelectScreen characterSelectScreen = new CharacterSelectScreen(game); characterSelectScreen.setMultiplayerMode(true); @@ -109,10 +106,12 @@ public class NetworkSettingsScreen extends ScreenAdapter { Gdx.app.log("Network", "用户取消输入 IP"); } }, "请输入服务器 IP 地址", "", "加入房间"); + } - } else if (isHovered(mouseX, mouseY, BUTTON_X, EXIT_Y)) { + // 返回主菜单 + else if (isHovered(mouseX, mouseY, BUTTON_X, EXIT_Y)) { Gdx.app.log("Network", "退出按钮被点击!"); - game.setScreen(new MainMenuScreen(game)); // 或者 Gdx.app.exit(); 直接退出游戏 + game.setScreen(new MainMenuScreen(game)); } } } @@ -139,5 +138,4 @@ public class NetworkSettingsScreen extends ScreenAdapter { font.dispose(); shapeRenderer.dispose(); } - -} \ No newline at end of file +} diff --git a/src/main/java/uno/mloluyu/network/ConnectClient.java b/src/main/java/uno/mloluyu/network/ConnectClient.java index 105739d..a06dbe8 100644 --- a/src/main/java/uno/mloluyu/network/ConnectClient.java +++ b/src/main/java/uno/mloluyu/network/ConnectClient.java @@ -8,7 +8,7 @@ import java.io.OutputStream; import java.nio.charset.StandardCharsets; /** - * 客户端连接类(实例方式) + * 客户端连接类 */ public class ConnectClient { private Socket socket; @@ -20,48 +20,40 @@ public class ConnectClient { // 启动接收线程 new Thread(this::receiveMessages).start(); - - // 示例:发送一条欢迎消息 - sendMessage("你好,我是客户端玩家"); - } catch (Exception e) { Gdx.app.error("Client", "连接失败: " + e.getMessage(), e); } } - // ... 现有代码 ... + private void receiveMessages() { + try { + byte[] buffer = new byte[1024]; + while (true) { + int read = socket.getInputStream().read(buffer); + if (read == -1) + break; -private void receiveMessages() { - try { - byte[] buffer = new byte[1024]; - while (true) { - int read = socket.getInputStream().read(buffer); - if (read == -1) break; - String message = new String(buffer, 0, read, StandardCharsets.UTF_8); - Gdx.app.log("Client", "收到服务器消息: " + message); - - // 处理位置消息 - if (message.startsWith("POS:")) { - String positionData = message.substring(4); - // 通知网络管理器更新其他玩家位置 - NetworkManager.getInstance().updatePlayerPosition(positionData); + String message = new String(buffer, 0, read, StandardCharsets.UTF_8); + Gdx.app.log("Client", "收到服务器消息: " + message); + + // 主线程处理消息,避免线程冲突 + Gdx.app.postRunnable(() -> { + NetworkManager.getInstance().receiveMessage(message); + }); } + } catch (Exception e) { + Gdx.app.error("Client", "接收消息异常: " + e.getMessage(), e); + } finally { + disconnect(); } - } catch (Exception e) { - Gdx.app.error("Client", "接收消息异常: " + e.getMessage(), e); - } finally { - disconnect(); } -} - -// ... 现有代码 ... - public void sendMessage(String message) { try { OutputStream out = socket.getOutputStream(); out.write(message.getBytes(StandardCharsets.UTF_8)); out.flush(); + Gdx.app.log("Client", "发送消息: " + message); } catch (Exception e) { Gdx.app.error("Client", "发送消息失败: " + e.getMessage(), e); } diff --git a/src/main/java/uno/mloluyu/network/ConnectServer.java b/src/main/java/uno/mloluyu/network/ConnectServer.java index 2f16cb6..6919971 100644 --- a/src/main/java/uno/mloluyu/network/ConnectServer.java +++ b/src/main/java/uno/mloluyu/network/ConnectServer.java @@ -6,41 +6,16 @@ import com.badlogic.gdx.net.ServerSocket; import com.badlogic.gdx.net.Socket; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -/** - * 支持两个玩家连接的服务器类 - */ public class ConnectServer implements Runnable { private final int port; private ServerSocket serverSocket; - private final List connectedPlayers = new ArrayList<>(); + private final List connectedSockets = new ArrayList<>(); private static final int MAX_PLAYERS = 2; - // 玩家内部类,存储Socket和玩家名字 - private static class Player { - Socket socket; - String playerName; - - Player(Socket socket) { - this.socket = socket; - this.playerName = "玩家" + (int) (Math.random() * 1000); // 默认随机名字 - } - - String getPlayerName() { - return playerName; - } - - void setPlayerName(String name) { - this.playerName = name; - } - - Socket getSocket() { - return socket; - } - } - public ConnectServer(int port) { this.port = port; } @@ -51,106 +26,65 @@ public class ConnectServer implements Runnable { Gdx.app.log("Server", "服务器已启动,等待玩家连接..."); try { - while (connectedPlayers.size() < MAX_PLAYERS) { + while (connectedSockets.size() < MAX_PLAYERS) { Socket socket = serverSocket.accept(null); - Player player = new Player(socket); - connectedPlayers.add(player); - Gdx.app.log("Server", "玩家连接成功: " + socket.getRemoteAddress() + ",默认名字: " + player.getPlayerName()); - new Thread(() -> handlePlayer(player)).start(); + connectedSockets.add(socket); + Gdx.app.log("Server", "玩家连接成功: " + socket.getRemoteAddress()); + new Thread(() -> handlePlayer(socket)).start(); } Gdx.app.log("Server", "已连接两个玩家,游戏准备开始!"); - // 输出当前所有玩家名字 - for (int i = 0; i < connectedPlayers.size(); i++) { - Gdx.app.log("Server", "玩家" + (i + 1) + ": " + connectedPlayers.get(i).getPlayerName()); - } - } catch (Exception e) { Gdx.app.error("Server", "连接异常: " + e.getMessage(), e); } } - // ... 现有代码 ... - - private void handlePlayer(Player player) { + private void handlePlayer(Socket socket) { try { byte[] buffer = new byte[1024]; while (true) { - int read = player.getSocket().getInputStream().read(buffer); + int read = socket.getInputStream().read(buffer); if (read == -1) break; - String message = new String(buffer, 0, read); - Gdx.app.log("Server", "收到玩家" + player.getPlayerName() + "的消息: " + message); - // 处理设置玩家名字的消息 - if (message.startsWith("SET_NAME:")) { - String newName = message.substring(8); - player.setPlayerName(newName); - Gdx.app.log("Server", "玩家名字已更新为: " + newName); - } - // 处理位置消息并广播 - else if (message.startsWith("POS:")) { - String positionData = message.substring(4); - // 广播给其他玩家 - broadcastToOtherPlayers(player, "POS:" + positionData); - } + String message = new String(buffer, 0, read, StandardCharsets.UTF_8); + Gdx.app.log("Server", "收到消息: " + message); + + broadcastToOthers(socket, message); + + Gdx.app.postRunnable(() -> { + NetworkManager.getInstance().receiveMessage(message); + }); } } catch (Exception e) { Gdx.app.error("Server", "玩家通信异常: " + e.getMessage(), e); } finally { - player.getSocket().dispose(); - connectedPlayers.remove(player); - Gdx.app.log("Server", "玩家" + player.getPlayerName() + "断开连接"); + socket.dispose(); + connectedSockets.remove(socket); + Gdx.app.log("Server", "玩家断开连接"); } } - // 新增广播方法 - private void broadcastToOtherPlayers(Player sender, String message) { - for (Player player : connectedPlayers) { - if (player != sender) { + public void broadcastToOthers(Socket sender, String message) { + for (Socket socket : connectedSockets) { + if (socket != sender) { try { - OutputStream out = player.getSocket().getOutputStream(); - out.write(message.getBytes(java.nio.charset.StandardCharsets.UTF_8)); + OutputStream out = socket.getOutputStream(); + out.write(message.getBytes(StandardCharsets.UTF_8)); out.flush(); + Gdx.app.log("Server", "广播消息到 " + socket.getRemoteAddress() + ": " + message); } catch (Exception e) { - Gdx.app.error("Server", "广播消息失败: " + e.getMessage(), e); + Gdx.app.error("Server", "广播失败: " + e.getMessage(), e); } } } } - // ... 现有代码 ... - - // 获取当前服务器所有玩家名字的方法 - public List getPlayerNames() { - List names = new ArrayList<>(); - for (Player player : connectedPlayers) { - names.add(player.getPlayerName()); - } - return names; - } - - // 根据索引获取特定玩家名字的方法 - public String getPlayerName(int index) { - if (index >= 0 && index < connectedPlayers.size()) { - return connectedPlayers.get(index).getPlayerName(); - } - return null; - } - - // 设置玩家名字的方法 - public void setPlayerName(int index, String name) { - if (index >= 0 && index < connectedPlayers.size()) { - connectedPlayers.get(index).setPlayerName(name); - Gdx.app.log("Server", "玩家" + index + "名字设置为: " + name); - } - } - public void dispose() { - for (Player player : connectedPlayers) { - player.getSocket().dispose(); + for (Socket socket : connectedSockets) { + socket.dispose(); } - connectedPlayers.clear(); + connectedSockets.clear(); if (serverSocket != null) { serverSocket.dispose(); diff --git a/src/main/java/uno/mloluyu/network/NetworkManager.java b/src/main/java/uno/mloluyu/network/NetworkManager.java index 453c044..405dd53 100644 --- a/src/main/java/uno/mloluyu/network/NetworkManager.java +++ b/src/main/java/uno/mloluyu/network/NetworkManager.java @@ -1,18 +1,20 @@ package uno.mloluyu.network; import com.badlogic.gdx.Gdx; -import java.util.ArrayList; -import java.util.List; -/** - * 网络管理器,协调服务器和客户端通信 - */ +import java.util.HashMap; +import java.util.Map; + public class NetworkManager { private static NetworkManager instance; private ConnectServer server; private ConnectClient client; private boolean isHost = false; - private List playerPositions = new ArrayList<>(); + + private String localPlayerId; + private String localCharacter; + private final Map playerPositions = new HashMap<>(); + private final Map playerCharacters = new HashMap<>(); public static NetworkManager getInstance() { if (instance == null) { @@ -21,67 +23,91 @@ public class NetworkManager { return instance; } - /** - * 创建房间(作为主机) - */ + public void setLocalPlayerId(String id) { + this.localPlayerId = id; + } + + public String getLocalPlayerId() { + return localPlayerId; + } + public void createRoom() { isHost = true; server = new ConnectServer(11455); new Thread(server).start(); - Gdx.app.log("Network", "房间创建成功,等待其他玩家加入..."); + Gdx.app.log("Network", "房主模式:服务器已启动"); } - /** - * 加入房间(作为客户端) - */ public void joinRoom(String ip) { isHost = false; client = new ConnectClient(ip, 11455); - Gdx.app.log("Network", "正在加入房间: " + ip); + Gdx.app.log("Network", "客户端模式:连接到房主 " + ip); } - /** - * 发送玩家位置信息 - */ public void sendPosition(float x, float y) { + String msg = "POS:" + localPlayerId + "," + x + "," + y; + Gdx.app.log("Network", "发送位置消息: " + msg); if (isHost && server != null) { - // 主机直接广播位置 - broadcastMessage("POS:" + x + "," + y); + server.broadcastToOthers(null, msg); + receiveMessage(msg); // 房主自己也处理 } else if (client != null) { - // 客户端发送位置到服务器 - client.sendMessage("POS:" + x + "," + y); + client.sendMessage(msg); } } - /** - * 广播消息(主机使用) - */ - private void broadcastMessage(String message) { - // 这里需要实现广播逻辑 - Gdx.app.log("Network", "广播消息: " + message); + public void sendCharacterSelection(String character) { + this.localCharacter = character; + String msg = "SELECT:" + localPlayerId + "," + character; + Gdx.app.log("Network", "发送角色选择消息: " + msg); + if (isHost && server != null) { + server.broadcastToOthers(null, msg); + receiveMessage(msg); + } else if (client != null) { + client.sendMessage(msg); + } } - /** - * 获取其他玩家位置 - */ - public List getOtherPlayerPositions() { + public void receiveMessage(String message) { + Gdx.app.log("Network", "收到消息: " + message); + + if (message.startsWith("POS:")) { + String[] parts = message.substring(4).split(","); + if (parts.length == 3) { + String playerId = parts[0]; + try { + float x = Float.parseFloat(parts[1]); + float y = Float.parseFloat(parts[2]); + playerPositions.put(playerId, new float[]{x, y}); + Gdx.app.log("Network", "位置更新: " + playerId + " -> " + x + "," + y); + } catch (NumberFormatException e) { + Gdx.app.error("Network", "位置解析失败: " + message); + } + } else { + Gdx.app.error("Network", "位置消息格式错误: " + message); + } + } else if (message.startsWith("SELECT:")) { + String[] parts = message.substring(7).split(","); + if (parts.length == 2) { + String playerId = parts[0]; + String character = parts[1]; + playerCharacters.put(playerId, character); + Gdx.app.log("Network", "角色选择: " + playerId + " -> " + character); + } else { + Gdx.app.error("Network", "角色选择消息格式错误: " + message); + } + } else if (message.equals("READY")) { + Gdx.app.log("Network", "收到准备信号"); + } else { + Gdx.app.log("Network", "未知消息类型: " + message); + } + } + + public Map getPlayerPositions() { return playerPositions; } - /** - * 断开连接 - */ - public void disconnect() { - if (server != null) { - server.dispose(); - server = null; - } - if (client != null) { - client.disconnect(); - client = null; - } - playerPositions.clear(); - Gdx.app.log("Network", "网络连接已断开"); + public Map getPlayerCharacters() { + return playerCharacters; } public boolean isHost() { @@ -92,15 +118,17 @@ public class NetworkManager { return server != null || client != null; } - public void updatePlayerPosition(String positionData) { - // 简单实现:直接存储位置数据 - playerPositions.add(positionData); - - // 限制位置列表大小,避免内存泄漏 - if (playerPositions.size() > 10) { - playerPositions.remove(0); + public void disconnect() { + if (server != null) { + server.dispose(); + server = null; } - - Gdx.app.log("Network", "更新其他玩家位置: " + positionData); + if (client != null) { + client.disconnect(); + client = null; + } + playerPositions.clear(); + playerCharacters.clear(); + Gdx.app.log("Network", "已断开连接"); } } diff --git a/target/classes/uno/mloluyu/characters/Alice.class b/target/classes/uno/mloluyu/characters/Alice.class index e44b5e9..3e5e476 100644 Binary files a/target/classes/uno/mloluyu/characters/Alice.class and b/target/classes/uno/mloluyu/characters/Alice.class differ diff --git a/target/classes/uno/mloluyu/characters/Fighter$Action.class b/target/classes/uno/mloluyu/characters/Fighter$Action.class index 454b83d..f122b81 100644 Binary files a/target/classes/uno/mloluyu/characters/Fighter$Action.class and b/target/classes/uno/mloluyu/characters/Fighter$Action.class differ diff --git a/target/classes/uno/mloluyu/characters/Fighter.class b/target/classes/uno/mloluyu/characters/Fighter.class index 8e0b0e0..5a0857f 100644 Binary files a/target/classes/uno/mloluyu/characters/Fighter.class and b/target/classes/uno/mloluyu/characters/Fighter.class differ diff --git a/target/classes/uno/mloluyu/characters/Reimu.class b/target/classes/uno/mloluyu/characters/Reimu.class index a5c5562..f4bac0d 100644 Binary files a/target/classes/uno/mloluyu/characters/Reimu.class and b/target/classes/uno/mloluyu/characters/Reimu.class differ diff --git a/target/classes/uno/mloluyu/desktop/CharacterSelectScreen.class b/target/classes/uno/mloluyu/desktop/CharacterSelectScreen.class index a06ab3f..1727893 100644 Binary files a/target/classes/uno/mloluyu/desktop/CharacterSelectScreen.class and b/target/classes/uno/mloluyu/desktop/CharacterSelectScreen.class differ diff --git a/target/classes/uno/mloluyu/desktop/GameScreen.class b/target/classes/uno/mloluyu/desktop/GameScreen.class index 3e2cfd7..7b329b6 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/desktop/NetworkSettingsScreen$1.class b/target/classes/uno/mloluyu/desktop/NetworkSettingsScreen$1.class index 4da5691..4e28c53 100644 Binary files a/target/classes/uno/mloluyu/desktop/NetworkSettingsScreen$1.class and b/target/classes/uno/mloluyu/desktop/NetworkSettingsScreen$1.class differ diff --git a/target/classes/uno/mloluyu/desktop/NetworkSettingsScreen.class b/target/classes/uno/mloluyu/desktop/NetworkSettingsScreen.class index 7f00b39..c15a4d2 100644 Binary files a/target/classes/uno/mloluyu/desktop/NetworkSettingsScreen.class and b/target/classes/uno/mloluyu/desktop/NetworkSettingsScreen.class differ diff --git a/target/classes/uno/mloluyu/network/ConnectClient.class b/target/classes/uno/mloluyu/network/ConnectClient.class index b31de5d..3e28e9f 100644 Binary files a/target/classes/uno/mloluyu/network/ConnectClient.class and b/target/classes/uno/mloluyu/network/ConnectClient.class differ diff --git a/target/classes/uno/mloluyu/network/ConnectServer$Player.class b/target/classes/uno/mloluyu/network/ConnectServer$Player.class deleted file mode 100644 index 7c95197..0000000 Binary files a/target/classes/uno/mloluyu/network/ConnectServer$Player.class and /dev/null differ diff --git a/target/classes/uno/mloluyu/network/ConnectServer.class b/target/classes/uno/mloluyu/network/ConnectServer.class index 19a0529..23e2460 100644 Binary files a/target/classes/uno/mloluyu/network/ConnectServer.class and b/target/classes/uno/mloluyu/network/ConnectServer.class differ diff --git a/target/classes/uno/mloluyu/network/NetworkManager.class b/target/classes/uno/mloluyu/network/NetworkManager.class index 6ba8002..78ea3ba 100644 Binary files a/target/classes/uno/mloluyu/network/NetworkManager.class and b/target/classes/uno/mloluyu/network/NetworkManager.class differ