一个里程碑(可以联机了)
This commit is contained in:
@@ -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 &&
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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", "已断开连接");
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user