1
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
/** 每次难度提升增加的僵尸速度 */
|
||||
|
||||
@@ -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<String, Wall> walls;
|
||||
/** 流场导航 */
|
||||
private final FlowField flowField;
|
||||
/** 玩家出生点 */
|
||||
private final List<int[]> spawnPoints;
|
||||
/** 僵尸出生点 */
|
||||
private final List<int[]> 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<Map<String, Object>> getNutWallStates() {
|
||||
List<Map<String, Object>> states = new ArrayList<>();
|
||||
for (Wall wall : walls.values()) {
|
||||
if (wall instanceof NutWall && !wall.isDestroyed()) {
|
||||
Map<String, Object> state = new LinkedHashMap<>();
|
||||
state.put("x", wall.getGridX());
|
||||
state.put("y", wall.getGridY());
|
||||
state.put("health", wall.getHealth());
|
||||
state.put("maxHealth", NutWall.MAX_HEALTH);
|
||||
states.add(state);
|
||||
}
|
||||
}
|
||||
return states;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,12 +274,7 @@ public class GameMap {
|
||||
* @return 僵尸出生点坐标列表
|
||||
*/
|
||||
public List<int[]> getZombieSpawnPoints() {
|
||||
List<int[]> points = new ArrayList<>();
|
||||
int[][] zombieSpawns = {{8, 8}, {24, 24}};
|
||||
for (int[] sp : zombieSpawns) {
|
||||
points.add(sp);
|
||||
}
|
||||
return points;
|
||||
return zombieSpawnPoints;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.zombie.game.model;
|
||||
import lombok.Getter;
|
||||
import java.util.*;
|
||||
|
||||
import static com.zombie.game.model.Constants.*;
|
||||
|
||||
/**
|
||||
* 游戏世界类
|
||||
*
|
||||
@@ -39,7 +41,11 @@ public class GameWorld {
|
||||
private List<Integer> removedZombieBullets;
|
||||
|
||||
public GameWorld() {
|
||||
this.map = new GameMap();
|
||||
this("/Users/wfz/workspace/zp1/maps/d540209a.json");
|
||||
}
|
||||
|
||||
public GameWorld(String mapFilePath) {
|
||||
this.map = new GameMap(mapFilePath);
|
||||
this.players = new LinkedHashMap<>();
|
||||
this.zombies = new LinkedHashMap<>();
|
||||
this.bullets = new LinkedHashMap<>();
|
||||
@@ -206,17 +212,17 @@ public class GameWorld {
|
||||
|
||||
/**
|
||||
* 更新所有僵尸
|
||||
*
|
||||
*
|
||||
* 处理僵尸移动、攻击和死亡
|
||||
*
|
||||
*
|
||||
* @param dt 时间增量(秒)
|
||||
*/
|
||||
private void updateZombies(float dt) {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
|
||||
List<Zombie> sortedZombies = new ArrayList<>(zombies.values());
|
||||
sortedZombies.sort((a, b) -> Integer.compare(a.getId(), b.getId()));
|
||||
|
||||
|
||||
for (Zombie z : sortedZombies) {
|
||||
if (!z.isAlive()) {
|
||||
onZombieKilled(z);
|
||||
@@ -235,7 +241,14 @@ public class GameWorld {
|
||||
}
|
||||
}
|
||||
|
||||
z.move(map, dt, zombies.values());
|
||||
Wall attackedWall = z.move(map, dt, zombies.values(), now);
|
||||
if (attackedWall != null && z.canAttack(now)) {
|
||||
attackedWall.takeDamage(1.0f); // 每次攻击造成1点伤害
|
||||
z.attack(now);
|
||||
if (attackedWall.isDestroyed()) {
|
||||
map.removeWall(attackedWall.getGridX(), attackedWall.getGridY());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,19 +291,40 @@ public class GameWorld {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测僵尸子弹与玩家的碰撞
|
||||
* 检测僵尸子弹与玩家/墙体的碰撞
|
||||
*/
|
||||
private void checkZombieBulletCollisions() {
|
||||
List<Integer> bulletsToRemove = new ArrayList<>();
|
||||
for (Bullet b : new ArrayList<>(zombieBullets.values())) {
|
||||
boolean hit = false;
|
||||
|
||||
// 检测是否命中玩家
|
||||
for (Player p : new ArrayList<>(players.values())) {
|
||||
if (!p.isAlive()) continue;
|
||||
if (b.hitsEntity(p.getX(), p.getY(), Constants.PLAYER_SIZE)) {
|
||||
p.takeDamage(b.getDamage());
|
||||
bulletsToRemove.add(b.getId());
|
||||
hit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 检测是否命中坚果墙体
|
||||
if (!hit) {
|
||||
int gx = (int) Math.floor(b.getX());
|
||||
int gy = (int) Math.floor(b.getY());
|
||||
Wall wall = map.getWall(gx, gy);
|
||||
if (wall instanceof NutWall && !wall.isDestroyed()) {
|
||||
wall.takeDamage(b.getDamage());
|
||||
hit = true;
|
||||
if (wall.isDestroyed()) {
|
||||
map.removeWall(gx, gy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hit) {
|
||||
bulletsToRemove.add(b.getId());
|
||||
}
|
||||
}
|
||||
for (int id : bulletsToRemove) {
|
||||
zombieBullets.remove(id);
|
||||
@@ -352,20 +386,41 @@ public class GameWorld {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测玩家子弹与僵尸的碰撞
|
||||
* 检测玩家子弹与僵尸/墙体的碰撞
|
||||
*/
|
||||
private void checkBulletCollisions() {
|
||||
List<Integer> bulletsToRemove = new ArrayList<>();
|
||||
for (Bullet b : new ArrayList<>(bullets.values())) {
|
||||
if (b.isGrenade()) continue;
|
||||
|
||||
// 检测是否命中僵尸
|
||||
boolean hit = false;
|
||||
for (Zombie z : new ArrayList<>(zombies.values())) {
|
||||
if (!z.isAlive()) continue;
|
||||
if (b.hitsEntity(z.getX(), z.getY(), Constants.ZOMBIE_SIZE)) {
|
||||
z.takeDamage(b.getDamage());
|
||||
bulletsToRemove.add(b.getId());
|
||||
hit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 检测是否命中坚果墙体
|
||||
if (!hit) {
|
||||
int gx = (int) Math.floor(b.getX());
|
||||
int gy = (int) Math.floor(b.getY());
|
||||
Wall wall = map.getWall(gx, gy);
|
||||
if (wall instanceof NutWall && !wall.isDestroyed()) {
|
||||
wall.takeDamage(b.getDamage());
|
||||
hit = true;
|
||||
if (wall.isDestroyed()) {
|
||||
map.removeWall(gx, gy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hit) {
|
||||
bulletsToRemove.add(b.getId());
|
||||
}
|
||||
}
|
||||
for (int id : bulletsToRemove) {
|
||||
bullets.remove(id);
|
||||
@@ -615,6 +670,7 @@ public class GameWorld {
|
||||
|
||||
state.put("explosions", new ArrayList<>(explosions));
|
||||
state.put("removedBullets", new ArrayList<>(removedBullets));
|
||||
state.put("nutWalls", map.getNutWallStates());
|
||||
state.put("gameTime", gameTime);
|
||||
state.put("waveNumber", waveNumber);
|
||||
state.put("score", score);
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.zombie.game.model;
|
||||
import lombok.Getter;
|
||||
import java.util.*;
|
||||
|
||||
import static com.zombie.game.model.Constants.*;
|
||||
|
||||
/**
|
||||
* 掉落物类
|
||||
*
|
||||
|
||||
120
backend/src/main/java/com/zombie/game/model/MapData.java
Normal file
120
backend/src/main/java/com/zombie/game/model/MapData.java
Normal file
@@ -0,0 +1,120 @@
|
||||
package com.zombie.game.model;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* 地图数据类
|
||||
*
|
||||
* 用于JSON序列化和反序列化
|
||||
*/
|
||||
public class MapData {
|
||||
private String id;
|
||||
private String name;
|
||||
private int width;
|
||||
private int height;
|
||||
private List<Map<String, Object>> walls;
|
||||
private List<Map<String, Integer>> playerSpawns;
|
||||
private List<Map<String, Integer>> zombieSpawns;
|
||||
|
||||
public MapData() {
|
||||
this.walls = new ArrayList<>();
|
||||
this.playerSpawns = new ArrayList<>();
|
||||
this.zombieSpawns = new ArrayList<>();
|
||||
}
|
||||
|
||||
public MapData(String id, String name, int width, int height,
|
||||
List<Map<String, Object>> walls,
|
||||
List<Map<String, Integer>> playerSpawns,
|
||||
List<Map<String, Integer>> zombieSpawns) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.walls = walls;
|
||||
this.playerSpawns = playerSpawns;
|
||||
this.zombieSpawns = zombieSpawns;
|
||||
}
|
||||
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public int getWidth() { return width; }
|
||||
public void setWidth(int width) { this.width = width; }
|
||||
public int getHeight() { return height; }
|
||||
public void setHeight(int height) { this.height = height; }
|
||||
public List<Map<String, Object>> getWalls() { return walls; }
|
||||
public void setWalls(List<Map<String, Object>> walls) { this.walls = walls; }
|
||||
public List<Map<String, Integer>> getPlayerSpawns() { return playerSpawns; }
|
||||
public void setPlayerSpawns(List<Map<String, Integer>> playerSpawns) { this.playerSpawns = playerSpawns; }
|
||||
public List<Map<String, Integer>> getZombieSpawns() { return zombieSpawns; }
|
||||
public void setZombieSpawns(List<Map<String, Integer>> zombieSpawns) { this.zombieSpawns = zombieSpawns; }
|
||||
|
||||
public int[][] toCells() {
|
||||
int[][] cells = new int[height][width];
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
cells[y][x] = 0;
|
||||
}
|
||||
}
|
||||
for (Map<String, Object> wall : walls) {
|
||||
int x = (Integer) wall.get("x");
|
||||
int y = (Integer) wall.get("y");
|
||||
if (x >= 0 && x < width && y >= 0 && y < height) {
|
||||
cells[y][x] = "nut".equals(wall.get("type")) ? 2 : 1;
|
||||
}
|
||||
}
|
||||
for (Map<String, Integer> spawn : playerSpawns) {
|
||||
int x = spawn.get("x");
|
||||
int y = spawn.get("y");
|
||||
if (x >= 0 && x < width && y >= 0 && y < height) {
|
||||
cells[y][x] = 2;
|
||||
}
|
||||
}
|
||||
for (Map<String, Integer> spawn : zombieSpawns) {
|
||||
int x = spawn.get("x");
|
||||
int y = spawn.get("y");
|
||||
if (x >= 0 && x < width && y >= 0 && y < height) {
|
||||
cells[y][x] = 3;
|
||||
}
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
|
||||
public static MapData fromGameMap(String id, String name, GameMap map) {
|
||||
List<Map<String, Object>> walls = new ArrayList<>();
|
||||
List<Map<String, Integer>> playerSpawns = new ArrayList<>();
|
||||
List<Map<String, Integer>> zombieSpawns = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, Wall> entry : map.getWalls().entrySet()) {
|
||||
String[] parts = entry.getKey().split(",");
|
||||
int x = Integer.parseInt(parts[0]);
|
||||
int y = Integer.parseInt(parts[1]);
|
||||
Wall wall = entry.getValue();
|
||||
Map<String, Object> wallData = new HashMap<>();
|
||||
wallData.put("x", x);
|
||||
wallData.put("y", y);
|
||||
wallData.put("type", wall instanceof NutWall ? "nut" : "static");
|
||||
walls.add(wallData);
|
||||
}
|
||||
|
||||
for (int[] spawn : map.getSpawnPoints()) {
|
||||
Map<String, Integer> sp = new HashMap<>();
|
||||
sp.put("x", spawn[0]);
|
||||
sp.put("y", spawn[1]);
|
||||
playerSpawns.add(sp);
|
||||
}
|
||||
|
||||
for (int[] spawn : map.getZombieSpawnPoints()) {
|
||||
Map<String, Integer> sp = new HashMap<>();
|
||||
sp.put("x", spawn[0]);
|
||||
sp.put("y", spawn[1]);
|
||||
zombieSpawns.add(sp);
|
||||
}
|
||||
|
||||
return new MapData(id, name, map.getWidth(), map.getHeight(), walls, playerSpawns, zombieSpawns);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package com.zombie.game.model;
|
||||
import lombok.Getter;
|
||||
import java.util.*;
|
||||
|
||||
import static com.zombie.game.model.Constants.*;
|
||||
|
||||
/**
|
||||
* 玩家类
|
||||
*
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.zombie.game.model;
|
||||
import lombok.Getter;
|
||||
import java.util.*;
|
||||
|
||||
import static com.zombie.game.model.Constants.*;
|
||||
|
||||
/**
|
||||
* 游戏房间类
|
||||
*
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.zombie.game.model;
|
||||
import lombok.Getter;
|
||||
import java.util.*;
|
||||
|
||||
import static com.zombie.game.model.Constants.*;
|
||||
|
||||
/**
|
||||
* 僵尸类
|
||||
*
|
||||
@@ -80,49 +82,84 @@ public class Zombie {
|
||||
|
||||
/**
|
||||
* 移动僵尸
|
||||
*
|
||||
*
|
||||
* 基于流场导航移动,包含:
|
||||
* - 路径规划
|
||||
* - 路径规划(支持加权障碍物,自动权衡绕道 vs 摧毁)
|
||||
* - 避免与其他僵尸重叠
|
||||
* - 墙壁碰撞检测
|
||||
*
|
||||
* - 攻击坚果墙体逻辑
|
||||
*
|
||||
* @param map 游戏地图
|
||||
* @param dt 时间增量(秒)
|
||||
* @param otherZombies 其他僵尸集合
|
||||
* @param now 当前时间戳
|
||||
* @return 如果正在攻击墙体,返回被攻击的墙体对象;否则返回 null
|
||||
*/
|
||||
public void move(GameMap map, float dt, Collection<Zombie> otherZombies) {
|
||||
if (!map.isFlowFieldValid()) return;
|
||||
|
||||
public Wall move(GameMap map, float dt, Collection<Zombie> otherZombies, long now) {
|
||||
if (!map.isFlowFieldValid()) return null;
|
||||
|
||||
int currentGridX = (int) Math.floor(x);
|
||||
int currentGridY = (int) Math.floor(y);
|
||||
|
||||
|
||||
// 如果正在攻击墙体,检查是否到达攻击位置
|
||||
if (attackingWall && attackingWallGridX >= 0) {
|
||||
float wallCenterX = attackingWallGridX + 0.5f;
|
||||
float wallCenterY = attackingWallGridY + 0.5f;
|
||||
float distToWall = distanceTo(wallCenterX, wallCenterY);
|
||||
|
||||
if (distToWall < 0.8f) {
|
||||
// 到达攻击位置,返回要攻击的墙体
|
||||
Wall wall = map.getWall(attackingWallGridX, attackingWallGridY);
|
||||
if (wall != null && !wall.isDestroyed()) {
|
||||
return wall;
|
||||
}
|
||||
// 墙体已被破坏或被移除,停止攻击
|
||||
attackingWall = false;
|
||||
attackingWallGridX = -1;
|
||||
attackingWallGridY = -1;
|
||||
}
|
||||
}
|
||||
|
||||
float centerDist = Float.MAX_VALUE;
|
||||
if (hasTarget) {
|
||||
float dx = targetX - x;
|
||||
float dy = targetY - y;
|
||||
centerDist = (float) Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
|
||||
if (!hasTarget || centerDist < 0.15f) {
|
||||
float[] flowDir = map.getFlowDirection(x, y);
|
||||
float dirX = flowDir[0];
|
||||
float dirY = flowDir[1];
|
||||
|
||||
if (dirX == 0 && dirY == 0) return;
|
||||
|
||||
|
||||
if (dirX == 0 && dirY == 0) return null;
|
||||
|
||||
float len = (float) Math.sqrt(dirX * dirX + dirY * dirY);
|
||||
if (len > 0) {
|
||||
dirX /= len;
|
||||
dirY /= len;
|
||||
}
|
||||
|
||||
|
||||
int nextGridX = currentGridX + (int) Math.round(dirX);
|
||||
int nextGridY = currentGridY + (int) Math.round(dirY);
|
||||
|
||||
if (map.isWall(nextGridX, nextGridY)) {
|
||||
|
||||
// 检查下一个格子是否是坚果墙体
|
||||
if (map.isNutWall(nextGridX, nextGridY)) {
|
||||
// 流场指引我们走向坚果,说明摧毁代价比绕道低
|
||||
// 设置攻击状态
|
||||
attackingWall = true;
|
||||
attackingWallGridX = nextGridX;
|
||||
attackingWallGridY = nextGridY;
|
||||
reservedGridX = nextGridX;
|
||||
reservedGridY = nextGridY;
|
||||
reservation = true;
|
||||
targetX = nextGridX + 0.5f;
|
||||
targetY = nextGridY + 0.5f;
|
||||
hasTarget = true;
|
||||
} else if (map.isWall(nextGridX, nextGridY)) {
|
||||
nextGridX = currentGridX + (int) Math.signum(dirX);
|
||||
nextGridY = currentGridY + (int) Math.signum(dirY);
|
||||
|
||||
|
||||
if (map.isWall(nextGridX, nextGridY)) {
|
||||
if (!map.isWall(currentGridX + (int) Math.signum(dirX), currentGridY)) {
|
||||
nextGridX = currentGridX + (int) Math.signum(dirX);
|
||||
@@ -132,67 +169,86 @@ public class Zombie {
|
||||
nextGridY = currentGridY + (int) Math.signum(dirY);
|
||||
} else {
|
||||
reservation = false;
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isGridOccupiedOrReserved(nextGridX, nextGridY, otherZombies)) {
|
||||
int[] altDirs = findAlternativeDirection(currentGridX, currentGridY, dirX, dirY, map, otherZombies);
|
||||
if (altDirs != null) {
|
||||
nextGridX = altDirs[0];
|
||||
nextGridY = altDirs[1];
|
||||
} else {
|
||||
reservation = false;
|
||||
return;
|
||||
|
||||
if (isGridOccupiedOrReserved(nextGridX, nextGridY, otherZombies)) {
|
||||
int[] altDirs = findAlternativeDirection(currentGridX, currentGridY, dirX, dirY, map, otherZombies);
|
||||
if (altDirs != null) {
|
||||
nextGridX = altDirs[0];
|
||||
nextGridY = altDirs[1];
|
||||
} else {
|
||||
reservation = false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
reservedGridX = nextGridX;
|
||||
reservedGridY = nextGridY;
|
||||
reservation = true;
|
||||
|
||||
targetX = nextGridX + 0.5f;
|
||||
targetY = nextGridY + 0.5f;
|
||||
hasTarget = true;
|
||||
} else {
|
||||
if (isGridOccupiedOrReserved(nextGridX, nextGridY, otherZombies)) {
|
||||
int[] altDirs = findAlternativeDirection(currentGridX, currentGridY, dirX, dirY, map, otherZombies);
|
||||
if (altDirs != null) {
|
||||
nextGridX = altDirs[0];
|
||||
nextGridY = altDirs[1];
|
||||
} else {
|
||||
reservation = false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
reservedGridX = nextGridX;
|
||||
reservedGridY = nextGridY;
|
||||
reservation = true;
|
||||
|
||||
targetX = nextGridX + 0.5f;
|
||||
targetY = nextGridY + 0.5f;
|
||||
hasTarget = true;
|
||||
}
|
||||
|
||||
reservedGridX = nextGridX;
|
||||
reservedGridY = nextGridY;
|
||||
reservation = true;
|
||||
|
||||
targetX = nextGridX + 0.5f;
|
||||
targetY = nextGridY + 0.5f;
|
||||
hasTarget = true;
|
||||
}
|
||||
|
||||
|
||||
float dx = targetX - x;
|
||||
float dy = targetY - y;
|
||||
float dist = (float) Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
|
||||
if (dist < 0.01f) {
|
||||
hasTarget = false;
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
float dirX = dx / dist;
|
||||
float dirY = dy / dist;
|
||||
|
||||
|
||||
float moveX = dirX * speed * dt;
|
||||
float moveY = dirY * speed * dt;
|
||||
|
||||
|
||||
float newX = x + moveX;
|
||||
float newY = y + moveY;
|
||||
|
||||
|
||||
boolean canMoveX = map.isWalkable(newX, y, Constants.ZOMBIE_SIZE);
|
||||
boolean canMoveY = map.isWalkable(x, newY, Constants.ZOMBIE_SIZE);
|
||||
boolean canMoveDiagonal = map.isWalkable(newX, newY, Constants.ZOMBIE_SIZE);
|
||||
|
||||
|
||||
if (moveX != 0 && moveY != 0) {
|
||||
int checkX = (int) Math.floor(newX);
|
||||
int checkY = (int) Math.floor(newY);
|
||||
int checkCurrentX = (int) Math.floor(x);
|
||||
int checkCurrentY = (int) Math.floor(y);
|
||||
|
||||
|
||||
boolean blockedByCorner = false;
|
||||
if (checkX != checkCurrentX && checkY != checkCurrentY) {
|
||||
boolean wallInX = map.isWall(checkX, checkCurrentY);
|
||||
boolean wallInY = map.isWall(checkCurrentX, checkY);
|
||||
|
||||
|
||||
if (wallInX || wallInY) {
|
||||
blockedByCorner = true;
|
||||
|
||||
|
||||
if (!wallInX && canMoveX) {
|
||||
x = newX;
|
||||
canMoveY = map.isWalkable(x, newY, Constants.ZOMBIE_SIZE);
|
||||
@@ -202,7 +258,7 @@ public class Zombie {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!blockedByCorner && canMoveDiagonal) {
|
||||
x = newX;
|
||||
y = newY;
|
||||
@@ -214,26 +270,26 @@ public class Zombie {
|
||||
if (canMoveX) x = newX;
|
||||
if (canMoveY) y = newY;
|
||||
}
|
||||
|
||||
|
||||
float minSeparationDist = Constants.ZOMBIE_SIZE;
|
||||
for (Zombie other : otherZombies) {
|
||||
if (other.getId() == this.id) continue;
|
||||
if (!other.isAlive()) continue;
|
||||
|
||||
|
||||
float ox = other.getX();
|
||||
float oy = other.getY();
|
||||
float sepDx = x - ox;
|
||||
float sepDy = y - oy;
|
||||
float sepDist = (float) Math.sqrt(sepDx * sepDx + sepDy * sepDy);
|
||||
|
||||
|
||||
if (sepDist < minSeparationDist && sepDist > 0.01f) {
|
||||
float overlap = minSeparationDist - sepDist;
|
||||
float pushX = (sepDx / sepDist) * overlap * 0.5f;
|
||||
float pushY = (sepDy / sepDist) * overlap * 0.5f;
|
||||
|
||||
|
||||
float pushedX = x + pushX;
|
||||
float pushedY = y + pushY;
|
||||
|
||||
|
||||
if (map.isWalkable(pushedX, pushedY, Constants.ZOMBIE_SIZE)) {
|
||||
x = pushedX;
|
||||
y = pushedY;
|
||||
@@ -247,10 +303,12 @@ public class Zombie {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (dirX != 0 || dirY != 0) {
|
||||
angle = (float) Math.atan2(dirX, dirY);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user