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>
This commit is contained in:
2026-06-16 15:30:58 +08:00
parent cba5105908
commit d393302388
6248 changed files with 17322729 additions and 11036 deletions

View File

@@ -0,0 +1,252 @@
'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,
};