一个里程碑(可以联机了)

This commit is contained in:
2025-09-24 20:07:32 +08:00
parent 5f080713f8
commit 659827bcc3
20 changed files with 282 additions and 288 deletions

View File

@@ -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 &&

View File

@@ -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<TextureRegion> 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();
}
}

View File

@@ -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));
}
}

View File

@@ -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<String, Fighter> 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<String, float[]> positions = NetworkManager.getInstance().getPlayerPositions();
Map<String, String> characters = NetworkManager.getInstance().getPlayerCharacters();
batch.begin();
for (Map.Entry<String, float[]> 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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}

View File

@@ -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<Player> connectedPlayers = new ArrayList<>();
private final List<Socket> 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<String> getPlayerNames() {
List<String> 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();

View File

@@ -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<String> playerPositions = new ArrayList<>();
private String localPlayerId;
private String localCharacter;
private final Map<String, float[]> playerPositions = new HashMap<>();
private final Map<String, String> 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<String> 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<String, float[]> 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<String, String> 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", "已断开连接");
}
}