Files
cocos/extensions/level-map-editor/dist/spawn-tools.js
刘宇飞 d393302388 Complete Cocos Creator port with level bundles, themes, and tooling.
Adds level prefabs, theme assets, audio, extensions, and deployment scripts for the Unity WebGL migration.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 15:30:58 +08:00

253 lines
7.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
/** 与 Unity Levels*.cs / levels-database.json spawns 一致 */
const { normalizeSpawnScale, clampScale, DEFAULT_SPAWN_SCALE } = require('./entity-spawn-defaults');
const { normalizeTexturePath } = require('./entity-texture-presets');
const SPAWN_KIND_LABELS = {
player: '玩家',
prop: '可拾取物',
vehicle: '载具',
};
const DIRECTIONS = [
'Direction.North',
'Direction.East',
'Direction.South',
'Direction.West',
];
function ensureSpawns(state) {
if (!state.config) return [];
if (!Array.isArray(state.config.spawns)) state.config.spawns = [];
return state.config.spawns;
}
function spawnsAt(state, x, y) {
return ensureSpawns(state).filter((s) => s.x === x && s.y === y);
}
function findSpawnAt(state, x, y) {
return spawnsAt(state, x, y)[0] || null;
}
function countByKind(state, kind) {
return ensureSpawns(state).filter((s) => s.kind === kind).length;
}
function getPlayerSpawn(state) {
return ensureSpawns(state).find((s) => s.kind === 'player') || null;
}
function getVehicleSpawn(state) {
return ensureSpawns(state).find((s) => s.kind === 'vehicle') || null;
}
function applyScaleField(entry, scale) {
const s = normalizeSpawnScale(scale);
if (s !== undefined) entry.scale = s;
else delete entry.scale;
}
function setPlayerSpawn(state, x, y, direction, scale) {
const prev = getPlayerSpawn(state);
const rest = ensureSpawns(state).filter((s) => s.kind !== 'player');
const entry = {
x,
y,
kind: 'player',
playerDirection: direction || 'Direction.South',
};
const sc = scale !== undefined ? scale : prev?.scale ?? DEFAULT_SPAWN_SCALE.player;
applyScaleField(entry, sc);
rest.push(entry);
state.config.spawns = rest;
return entry;
}
function setVehicleSpawn(state, x, y, direction, scale) {
const prev = getVehicleSpawn(state);
const rest = ensureSpawns(state).filter((s) => s.kind !== 'vehicle');
const entry = {
x,
y,
kind: 'vehicle',
vehicleDirection: direction || 'Direction.North',
};
const sc = scale !== undefined ? scale : prev?.scale ?? DEFAULT_SPAWN_SCALE.vehicle;
applyScaleField(entry, sc);
rest.push(entry);
state.config.spawns = rest;
return entry;
}
function clearVehicleSpawn(state) {
const before = countByKind(state, 'vehicle');
state.config.spawns = ensureSpawns(state).filter((s) => s.kind !== 'vehicle');
return before > 0;
}
/** 载具 0 或 1再次点击已有载具格则清除 */
function vehicleToggleInternal(state, x, y, direction, scale) {
const existing = getVehicleSpawn(state);
if (existing && existing.x === x && existing.y === y) {
clearVehicleSpawn(state);
return { changed: false, spawn: null, removed: true };
}
const spawn = setVehicleSpawn(state, x, y, direction, scale);
return { changed: true, spawn, removed: false };
}
function toggleVehicleSpawn(state, x, y, direction, scale) {
const r = vehicleToggleInternal(state, x, y, direction, scale);
return r.changed;
}
function toggleVehicleSpawnDetailed(state, x, y, direction, scale) {
return vehicleToggleInternal(state, x, y, direction, scale);
}
function togglePropSpawn(state, x, y, scale, propPlacement) {
const spawns = ensureSpawns(state);
const idx = spawns.findIndex((s) => s.x === x && s.y === y && s.kind === 'prop');
if (idx >= 0) {
spawns.splice(idx, 1);
return { changed: false, spawn: null, removed: true };
}
const entry = { x, y, kind: 'prop' };
if (propPlacement === 'ground') entry.propPlacement = 'ground';
const sc = scale !== undefined ? scale : DEFAULT_SPAWN_SCALE.prop;
applyScaleField(entry, sc);
spawns.push(entry);
return { changed: true, spawn: entry, removed: false };
}
function removeSpawnAt(state, x, y) {
const spawns = ensureSpawns(state);
const before = spawns.length;
state.config.spawns = spawns.filter((s) => !(s.x === x && s.y === y));
return state.config.spawns.length < before;
}
function removeSpawnRef(state, spawnRef) {
if (!spawnRef) return false;
const spawns = ensureSpawns(state);
const idx = spawns.indexOf(spawnRef);
if (idx < 0) return false;
spawns.splice(idx, 1);
return true;
}
function pickSpawnAtCell(state, x, y, preferKind) {
const hits = spawnsAt(state, x, y);
if (!hits.length) return null;
if (preferKind) {
const m = hits.find((s) => s.kind === preferKind);
if (m) return m;
}
return hits[0];
}
function spawnStillExists(state, spawnRef) {
if (!spawnRef) return false;
return ensureSpawns(state).includes(spawnRef);
}
function updateSpawnEntry(state, spawnRef, patch) {
const spawns = ensureSpawns(state);
const idx = spawns.indexOf(spawnRef);
if (idx < 0) return false;
const cur = { ...spawns[idx] };
if (patch.x !== undefined) cur.x = parseInt(patch.x, 10);
if (patch.y !== undefined) cur.y = parseInt(patch.y, 10);
if (patch.playerDirection !== undefined) cur.playerDirection = patch.playerDirection;
if (patch.vehicleDirection !== undefined) cur.vehicleDirection = patch.vehicleDirection;
if (patch.scale !== undefined) applyScaleField(cur, patch.scale);
if (patch.texture !== undefined) {
const t = normalizeTexturePath(patch.texture);
if (t) cur.texture = t;
else delete cur.texture;
}
spawns[idx] = cur;
return true;
}
function formatSpawnScale(spawn) {
if (spawn?.scale !== undefined && spawn.scale !== null) return spawn.scale;
return 1;
}
function validateSpawns(state) {
const spawns = ensureSpawns(state);
const players = spawns.filter((s) => s.kind === 'player');
const vehicles = spawns.filter((s) => s.kind === 'vehicle');
const props = spawns.filter((s) => s.kind === 'prop');
const errors = [];
if (players.length !== 1) {
errors.push(`玩家必须恰好 1 个(当前 ${players.length}`);
}
if (vehicles.length > 1) {
errors.push(`载具最多 1 个(当前 ${vehicles.length}`);
}
if (props.length < 1) {
errors.push(`可拾取物至少 1 个(当前 ${props.length}`);
}
return { ok: errors.length === 0, errors, players, vehicles, props };
}
function spawnSummary(state) {
const v = validateSpawns(state);
const player = v.players[0];
const vehicle = v.vehicles[0];
const parts = [];
if (player) {
const sc = formatSpawnScale(player);
parts.push(`玩家 (${player.x},${player.y}) ${player.playerDirection || ''}${sc !== 1 ? ` ×${sc}` : ''}`);
} else {
parts.push('玩家 未设置');
}
parts.push(`载具 ${v.vehicles.length}/1`);
if (vehicle) {
const sc = formatSpawnScale(vehicle);
parts.push(`@(${vehicle.x},${vehicle.y}) ${vehicle.vehicleDirection || ''}${sc !== 1 ? ` ×${sc}` : ''}`);
}
parts.push(`可拾取物 ${v.props.length}`);
if (!v.ok) parts.push('[待完善]');
return parts.join(' · ');
}
function validationHint(state) {
const v = validateSpawns(state);
if (v.ok) return '';
return v.errors.join('');
}
module.exports = {
SPAWN_KIND_LABELS,
DIRECTIONS,
DEFAULT_SPAWN_SCALE,
clampScale,
ensureSpawns,
spawnsAt,
findSpawnAt,
countByKind,
getPlayerSpawn,
getVehicleSpawn,
setPlayerSpawn,
setVehicleSpawn,
clearVehicleSpawn,
toggleVehicleSpawn,
toggleVehicleSpawnDetailed,
togglePropSpawn,
removeSpawnAt,
removeSpawnRef,
pickSpawnAtCell,
spawnStillExists,
updateSpawnEntry,
formatSpawnScale,
validateSpawns,
spawnSummary,
validationHint,
};