方案一、全量重建
javascript
const LOGIC_WIDTH = 320 // 游戏逻辑宽度
const LOGIC_HEIGHT = 560 // 游戏逻辑高度
const CELL_SIZE = 40 // 格子大小 (320/40 = 8 列,560/40 = 14 行)
// --- 空间网格管理器: 它负责将对象注册到对应的网格中,并提供范围查询功能 ---
const GRID_CELL_SIZE = CELL_SIZE // 网格大小与地图格子一致,40px
const GRID_COLS = Math.ceil(LOGIC_WIDTH / GRID_CELL_SIZE) // 8
const GRID_ROWS = Math.ceil(LOGIC_HEIGHT / GRID_CELL_SIZE) // 14
const spatialGrid = {
cells: new Array(GRID_COLS * GRID_ROWS), // 扁平化数组存储网格
clear() {
for (let i = 0; i < this.cells.length; i++) {
if (this.cells[i] && this.cells[i].length > 0) {
this.cells[i].length = 0
}
}
},
// 将对象插入对应的网格中
insert(obj) {
// 根据对象的中心点计算所在网格索引
const cx = Math.floor(obj.x / GRID_CELL_SIZE)
const cy = Math.floor(obj.y / GRID_CELL_SIZE)
// 边界保护
if (cx < 0 || cx >= GRID_COLS || cy < 0 || cy >= GRID_ROWS) return
const index = cy * GRID_COLS + cx
if (!this.cells[index]) this.cells[index] = []
this.cells[index].push(obj)
},
// 查询某个点周围网格内的所有对象
queryNearby(x, y, targetArray) {
const cx = Math.floor(x / GRID_CELL_SIZE)
const cy = Math.floor(y / GRID_CELL_SIZE)
// 遍历当前格子及周围 8 个格子(共 9 个网格)
for (let i = -1; i <= 1; i++) {
for (let j = -1; j <= 1; j++) {
const gx = cx + i
const gy = cy + j
if (gx < 0 || gx >= GRID_COLS || gy < 0 || gy >= GRID_ROWS) continue
const cell = this.cells[gy * GRID_COLS + gx]
if (cell) {
for (let k = 0; k < cell.length; k++) {
targetArray.push(cell[k])
}
}
}
}
return targetArray
}
}
// 复用的临时数组,避免每次检测都创建新数组
const nearbyTargetsCache = []
javascript
// 游戏循环逻辑
function gameLoop(ticker) {
// 考虑倍速的时间增量; 强制限制单帧最大跨度为 2 帧
const delta = Math.min(ticker.deltaTime * gameDataStore.gameSpeed, 2)
const deltaMS = Math.min(ticker.deltaMS * gameDataStore.gameSpeed, 33.33)
// --- 每帧重建空间网格 ---
spatialGrid.clear()
// 将所有可见的怪物和道具注册到网格中
layers.monsters.children.forEach(monster => {
if (monster.visible) spatialGrid.insert(monster)
})
layers.props.children.forEach(prop => {
if (prop.visible) spatialGrid.insert(prop)
})
// 子弹碰撞检测 - 修改为圆形碰撞检测
function bulletCollisionDetection(bullet) {
if (!bullet.visible) {
removeBullet(bullet)
return
}
const gameData = bullet.gameData
const bulletRadius = gameData.collisionRadius // 子弹碰撞半径(子弹宽度的一半)
// 清空复用数组,避免内存分配
nearbyTargetsCache.length = 0
// 只获取子弹周围 9 个网格内的目标
const nearbyTargets = spatialGrid.queryNearby(bullet.x, bullet.y, nearbyTargetsCache)
for (let i = 0; i < nearbyTargets.length; i++) {
const item = nearbyTargets[i]
if (!item.visible) continue
// 防止同一帧对同一怪物/道具重复伤害
if (gameData.hitMonsterIds.has(item.gameData.id)) continue
const dx = item.x - bullet.x
const dy = item.y - bullet.y
// 计算距离的平方
const distSq = dx * dx + dy * dy
// 使用圆形碰撞检测
const targetRadius = item.gameData.collisionRadius || (item.width / 2) // 获取目标的碰撞半径
const combinedRadius = bulletRadius + targetRadius // 计算组合碰撞半径:两个圆形碰撞检测时,需要将两个圆的半径相加
const combinedRadiusSq = combinedRadius * combinedRadius // 计算组合半径的平方
// 判断是否发生碰撞:如果两个圆心之间的距离 ≤ 两个圆半径之和,则发生碰撞。使用平方比较避免开方
if (distSq <= combinedRadiusSq) {
gameData.hitMonsterIds.add(item.gameData.id)
gameData.target = item
hitTarget(bullet)
return
}
}
}
方案二、增量更新
javascript
const LOGIC_WIDTH = 320 // 游戏逻辑宽度
const LOGIC_HEIGHT = 560 // 游戏逻辑高度
const CELL_SIZE = 40 // 格子大小 (320/40 = 8 列,560/40 = 14 行)
// --- 空间网格管理器: 它负责将对象注册到对应的网格中,并提供范围查询功能 ---
const GRID_CELL_SIZE = CELL_SIZE // 网格大小与地图格子一致,40px
const GRID_COLS = Math.ceil(LOGIC_WIDTH / GRID_CELL_SIZE) // 8
const GRID_ROWS = Math.ceil(LOGIC_HEIGHT / GRID_CELL_SIZE) // 14
const spatialGrid = {
cells: new Array(GRID_COLS * GRID_ROWS),
// 存储每个对象所在的网格索引,用于快速定位
objectGridMap: new Map(),
// 标记哪些网格有变化,用于批量更新
dirtyCells: new Set(),
// 初始化网格
init() {
for (let i = 0; i < this.cells.length; i++) {
this.cells[i] = []
}
},
// 清空网格(仅在游戏重启时使用)
clear() {
for (let i = 0; i < this.cells.length; i++) {
if (this.cells[i] && this.cells[i].length > 0) {
this.cells[i].length = 0
}
}
this.objectGridMap.clear()
this.dirtyCells.clear()
},
// 计算对象所在的网格索引
getGridIndex(x, y) {
const cx = Math.floor(x / GRID_CELL_SIZE)
const cy = Math.floor(y / GRID_CELL_SIZE)
if (cx < 0 || cx >= GRID_COLS || cy < 0 || cy >= GRID_ROWS) {
return -1
}
return cy * GRID_COLS + cx
},
// 插入对象到网格
insert(obj) {
const index = this.getGridIndex(obj.x, obj.y)
if (index === -1) return
// 确保网格存在
if (!this.cells[index]) {
this.cells[index] = []
}
this.cells[index].push(obj)
this.objectGridMap.set(obj.gameData.id, index)
this.dirtyCells.add(index)
},
// 从网格中移除对象
remove(obj) {
const oldIndex = this.objectGridMap.get(obj.gameData.id)
if (oldIndex === undefined) return
const cell = this.cells[oldIndex]
if (cell) {
const idx = cell.indexOf(obj)
if (idx !== -1) {
cell.splice(idx, 1)
this.dirtyCells.add(oldIndex)
}
}
this.objectGridMap.delete(obj.gameData.id)
},
// 更新对象位置(仅在跨网格时更新)
updateObject(obj, newX, newY) {
const oldIndex = this.objectGridMap.get(obj.gameData.id)
const newIndex = this.getGridIndex(newX, newY)
// 如果对象不在网格中,或者索引无效,直接插入
if (oldIndex === undefined || newIndex === -1) {
this.insert(obj)
return
}
// 如果网格索引没有变化,不需要更新
if (oldIndex === newIndex) return
// 从旧网格移除
const oldCell = this.cells[oldIndex]
if (oldCell) {
const idx = oldCell.indexOf(obj)
if (idx !== -1) {
oldCell.splice(idx, 1)
this.dirtyCells.add(oldIndex)
}
}
// 插入新网格
if (!this.cells[newIndex]) {
this.cells[newIndex] = []
}
this.cells[newIndex].push(obj)
this.objectGridMap.set(obj.gameData.id, newIndex)
this.dirtyCells.add(newIndex)
},
/**
* 查询某个点周围网格内的所有对象
* @param x 查询点的x坐标
* @param y 查询点的y坐标
* @param targetArray 用于存储查询结果的数组
* @param n 查询范围(网格数): n = 1查询周围3*3网格;n = 2查询周围5*5网格;n = 3查询周围7*7网格;
*/
queryNearby(x, y, targetArray, n = 1) {
const cx = Math.floor(x / GRID_CELL_SIZE)
const cy = Math.floor(y / GRID_CELL_SIZE)
// 遍历当前格子及周围格子
for (let i = -n; i <= n; i++) {
for (let j = -n; j <= n; j++) {
const gx = cx + i
const gy = cy + j
if (gx < 0 || gx >= GRID_COLS || gy < 0 || gy >= GRID_ROWS) continue
const cell = this.cells[gy * GRID_COLS + gx]
if (cell) {
for (let k = 0; k < cell.length; k++) {
targetArray.push(cell[k])
}
}
}
}
return targetArray
},
// 获取调试信息
getStats() {
let totalObjects = 0
let nonEmptyCells = 0
for (let i = 0; i < this.cells.length; i++) {
if (this.cells[i] && this.cells[i].length > 0) {
totalObjects += this.cells[i].length
nonEmptyCells++
}
}
return {
totalObjects,
nonEmptyCells,
totalCells: this.cells.length,
mappedObjects: this.objectGridMap.size
}
}
}
// 初始化网格
spatialGrid.init()
// 复用的临时数组,避免每次检测都创建新数组
const nearbyTargetsCache = []