diff --git a/backend/pom.xml b/backend/pom.xml
index 0d23ef9..f63a2f2 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -32,6 +32,11 @@
gson
2.10.1
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.15.2
+
org.slf4j
slf4j-simple
diff --git a/backend/src/main/java/com/zombie/game/GameServerMain.java b/backend/src/main/java/com/zombie/game/GameServerMain.java
index e4a2d7e..54d4101 100644
--- a/backend/src/main/java/com/zombie/game/GameServerMain.java
+++ b/backend/src/main/java/com/zombie/game/GameServerMain.java
@@ -1,16 +1,21 @@
package com.zombie.game;
import com.zombie.game.server.GameWebSocketServer;
+import com.zombie.game.server.MapDesignerApiServer;
+import com.zombie.game.server.MapStorage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.IOException;
+
/**
* 游戏服务器主入口类
*
* 职责:
* 1. 解析命令行参数(端口号)
* 2. 启动 WebSocket 游戏服务器
- * 3. 注册 JVM 关闭钩子,确保服务器优雅退出
+ * 3. 启动地图设计器 API 服务器
+ * 4. 注册 JVM 关闭钩子,确保服务器优雅退出
*/
public class GameServerMain {
private static final Logger logger = LoggerFactory.getLogger(GameServerMain.class);
@@ -20,7 +25,7 @@ public class GameServerMain {
*
* @param args 命令行参数,第一个参数可选为端口号(默认 8080)
*/
- public static void main(String[] args) {
+ public static void main(String[] args) throws IOException {
// 默认监听端口
int port = 8080;
@@ -37,13 +42,20 @@ public class GameServerMain {
GameWebSocketServer server = new GameWebSocketServer(port);
server.start();
+ // 创建并启动地图设计器 API 服务器
+ MapStorage mapStorage = new MapStorage();
+ MapDesignerApiServer apiServer = new MapDesignerApiServer(mapStorage);
+ apiServer.start();
+
logger.info("Zombie Crisis 3 Server started on port {}", port);
+ logger.info("Map Designer API Server started on port 8081");
logger.info("Press Ctrl+C to stop the server");
// 注册关闭钩子:当 JVM 退出时(Ctrl+C 或系统信号),优雅地停止服务器
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
logger.info("Shutting down server...");
try {
+ apiServer.stop();
server.stop();
} catch (Exception e) {
logger.error("Error during server shutdown", e);
diff --git a/backend/src/main/java/com/zombie/game/model/Bullet.java b/backend/src/main/java/com/zombie/game/model/Bullet.java
index c233106..e18a419 100644
--- a/backend/src/main/java/com/zombie/game/model/Bullet.java
+++ b/backend/src/main/java/com/zombie/game/model/Bullet.java
@@ -3,6 +3,8 @@ package com.zombie.game.model;
import lombok.Getter;
import java.util.*;
+import static com.zombie.game.model.Constants.*;
+
/**
* 子弹/投掷物类
*
@@ -123,7 +125,9 @@ public class Bullet {
int gx = (int) Math.floor(x);
int gy = (int) Math.floor(y);
- if (map.isWall(gx, gy)) return false;
+ // 只有碰到静态墙壁才销毁子弹,碰到坚果墙体让碰撞检测处理
+ Wall wall = map.getWall(gx, gy);
+ if (wall instanceof StaticWall) return false;
return true;
}
diff --git a/backend/src/main/java/com/zombie/game/model/Constants.java b/backend/src/main/java/com/zombie/game/model/Constants.java
index aece0d9..f0fa99e 100644
--- a/backend/src/main/java/com/zombie/game/model/Constants.java
+++ b/backend/src/main/java/com/zombie/game/model/Constants.java
@@ -47,7 +47,7 @@ public class Constants {
/** 僵尸生成间隔基础值(秒),难度提升后会逐渐缩短 */
public static final float ZOMBIE_SPAWN_INTERVAL_BASE = 3.0f;
/** 僵尸生成间隔最小值(秒),防止生成过快 */
- public static final float ZOMBIE_SPAWN_INTERVAL_MIN = 0.5f;
+ public static final float ZOMBIE_SPAWN_INTERVAL_MIN = 0.2f;
/** 每次难度提升增加的僵尸生命值 */
public static final float ZOMBIE_HEALTH_INCREASE = 20;
/** 每次难度提升增加的僵尸速度 */
diff --git a/backend/src/main/java/com/zombie/game/model/GameMap.java b/backend/src/main/java/com/zombie/game/model/GameMap.java
index d58273f..0a42b79 100644
--- a/backend/src/main/java/com/zombie/game/model/GameMap.java
+++ b/backend/src/main/java/com/zombie/game/model/GameMap.java
@@ -1,6 +1,9 @@
package com.zombie.game.model;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
+import java.io.File;
import java.util.*;
/**
@@ -23,99 +26,97 @@ public class GameMap {
private final Map walls;
/** 流场导航 */
private final FlowField flowField;
+ /** 玩家出生点 */
+ private final List spawnPoints;
+ /** 僵尸出生点 */
+ private final List zombieSpawnPoints;
/**
- * 构造函数 - 初始化地图并生成默认布局
+ * 构造函数 - 从 JSON 文件加载地图
+ *
+ * @param mapFilePath 地图 JSON 文件路径
*/
- public GameMap() {
- this.width = Constants.GRID_SIZE;
- this.height = Constants.GRID_SIZE;
+ public GameMap(String mapFilePath) {
+ JsonNode root = loadMapFile(mapFilePath);
+ this.width = root.get("width").asInt();
+ this.height = root.get("height").asInt();
this.cells = new int[height][width];
this.walls = new HashMap<>();
this.flowField = new FlowField(width, height);
- generateDefaultMap();
+ this.spawnPoints = new ArrayList<>();
+ this.zombieSpawnPoints = new ArrayList<>();
+ loadFromJson(root);
}
/**
- * 生成默认地图布局
- *
- * 创建边界墙壁、内部障碍物、玩家出生点和僵尸出生点
+ * 加载地图 JSON 文件
*/
- private void generateDefaultMap() {
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
- if (x == 0 || x == width - 1 || y == 0 || y == height - 1) {
- cells[y][x] = 0;
+ private JsonNode loadMapFile(String mapFilePath) {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.readTree(new File(mapFilePath));
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load map file: " + mapFilePath, e);
+ }
+ }
+
+ /**
+ * 从 JSON 数据加载地图
+ */
+ private void loadFromJson(JsonNode root) {
+ // 加载墙体
+ JsonNode wallsNode = root.get("walls");
+ if (wallsNode != null && wallsNode.isArray()) {
+ for (JsonNode wallNode : wallsNode) {
+ int x = (int) wallNode.get("x").asDouble();
+ int y = (int) wallNode.get("y").asDouble();
+ String type = wallNode.get("type").asText();
+
+ if (x < 0 || x >= width || y < 0 || y >= height) continue;
+
+ if ("static".equals(type)) {
walls.put(key(x, y), new StaticWall(x, y));
- } else {
- cells[y][x] = 0;
+ } else if ("nut".equals(type)) {
+ walls.put(key(x, y), new NutWall(x, y));
}
}
}
- int[][] wallSegments = {
- {5, 5, 5, 10}, {10, 3, 15, 3}, {20, 5, 20, 12},
- {8, 15, 14, 15}, {25, 10, 25, 18}, {3, 20, 8, 20},
- {15, 20, 15, 26}, {22, 22, 28, 22}, {5, 25, 10, 25},
- {12, 8, 12, 12}, {18, 16, 22, 16}, {27, 5, 27, 9},
- {8, 27, 13, 27}, {18, 26, 18, 30}
- };
-
- for (int[] seg : wallSegments) {
- if (seg[0] == seg[2]) {
- for (int y = seg[1]; y <= seg[3]; y++) {
- if (seg[0] > 0 && seg[0] < width - 1 && y > 0 && y < height - 1) {
- walls.put(key(seg[0], y), new StaticWall(seg[0], y));
- }
- }
- } else {
- for (int x = seg[0]; x <= seg[2]; x++) {
- if (x > 0 && x < width - 1 && seg[1] > 0 && seg[1] < height - 1) {
- walls.put(key(x, seg[1]), new StaticWall(x, seg[1]));
- }
+ // 加载玩家出生点
+ JsonNode playerSpawns = root.get("playerSpawns");
+ if (playerSpawns != null && playerSpawns.isArray()) {
+ for (JsonNode spawn : playerSpawns) {
+ int x = spawn.get("x").asInt();
+ int y = spawn.get("y").asInt();
+ if (x >= 0 && x < width && y >= 0 && y < height) {
+ cells[y][x] = 2;
+ spawnPoints.add(new int[]{x, y});
}
}
}
- int[][] spawnPoints = {{2, 2}, {29, 2}, {2, 29}, {29, 29}};
- for (int[] sp : spawnPoints) {
- cells[sp[1]][sp[0]] = 2;
- for (int dy = -1; dy <= 1; dy++) {
- for (int dx = -1; dx <= 1; dx++) {
- int ny = sp[1] + dy, nx = sp[0] + dx;
- if (ny > 0 && ny < height - 1 && nx > 0 && nx < width - 1) {
- walls.remove(key(nx, ny));
- }
- }
- }
- }
-
- int[][] zombieSpawnAreas = {{16, 2}, {2, 16}, {29, 16}, {16, 29}, {16, 16}};
- for (int[] sp : zombieSpawnAreas) {
- for (int dy = -1; dy <= 1; dy++) {
- for (int dx = -1; dx <= 1; dx++) {
- int ny = sp[1] + dy, nx = sp[0] + dx;
- if (ny > 0 && ny < height - 1 && nx > 0 && nx < width - 1) {
- walls.remove(key(nx, ny));
- }
- }
- }
- }
-
- int[][] zombieSpawnPoints = {{8, 8}, {24, 24}};
- for (int[] sp : zombieSpawnPoints) {
- cells[sp[1]][sp[0]] = 3;
- for (int dy = -1; dy <= 1; dy++) {
- for (int dx = -1; dx <= 1; dx++) {
- int ny = sp[1] + dy, nx = sp[0] + dx;
- if (ny > 0 && ny < height - 1 && nx > 0 && nx < width - 1) {
- walls.remove(key(nx, ny));
- }
+ // 加载僵尸出生点
+ JsonNode zombieSpawns = root.get("zombieSpawns");
+ if (zombieSpawns != null && zombieSpawns.isArray()) {
+ for (JsonNode spawn : zombieSpawns) {
+ int x = spawn.get("x").asInt();
+ int y = spawn.get("y").asInt();
+ if (x >= 0 && x < width && y >= 0 && y < height) {
+ cells[y][x] = 3;
+ zombieSpawnPoints.add(new int[]{x, y});
}
}
}
}
+ /**
+ * 添加坚果墙体
+ *
+ * @param gx 格子X坐标
+ * @param gy 格子Y坐标
+ * @return true 表示添加成功
+ */
+
/**
* 添加坚果墙体
*
@@ -201,8 +202,53 @@ public class GameMap {
return true;
}
+ /**
+ * 获取地图数据(包含墙体信息)
+ *
+ * 返回的二维数组格式与前端兼容:
+ * - 0 = 空地
+ * - 1 = 静态墙壁
+ * - 2 = 玩家出生点
+ * - 3 = 僵尸出生点
+ * - 4 = 坚果墙体
+ *
+ * @return 地图格子数据
+ */
public int[][] getCells() {
- return cells;
+ int[][] result = new int[height][width];
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ Wall wall = walls.get(key(x, y));
+ if (wall instanceof StaticWall) {
+ result[y][x] = 1;
+ } else if (wall instanceof NutWall) {
+ result[y][x] = 4;
+ } else {
+ result[y][x] = cells[y][x];
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * 获取所有坚果墙体的状态(用于同步给前端)
+ *
+ * @return 坚果墙体状态列表,每个元素包含 x, y, health, maxHealth
+ */
+ public List