Improve map editor image previews
This commit is contained in:
+84
-4
@@ -7,6 +7,8 @@ const state = {
|
||||
gridSize: 10,
|
||||
hitFilter: "all",
|
||||
imagePreviews: new Map(),
|
||||
imageLoading: new Set(),
|
||||
imageFailures: new Set(),
|
||||
history: [],
|
||||
dragging: null,
|
||||
};
|
||||
@@ -121,6 +123,7 @@ async function importGraphicAsset(event) {
|
||||
const image = await loadImage(previewUrl);
|
||||
const texturePath = defaultGraphicTexturePath(file.name);
|
||||
state.imagePreviews.set(texturePath, image);
|
||||
state.imageFailures.delete(texturePath);
|
||||
pushUndo("导入图形");
|
||||
addGraphicSprite(texturePath, image.naturalWidth || image.width || 64, image.naturalHeight || image.height || 64);
|
||||
setStatus(`已导入图形 ${file.name},并自动创建同尺寸碰撞块。请把素材放到 ${texturePath}`);
|
||||
@@ -140,11 +143,58 @@ function loadImage(url) {
|
||||
});
|
||||
}
|
||||
|
||||
function preloadMapImages(map) {
|
||||
for (const layer of map.layers) {
|
||||
for (const rect of layer.rects) {
|
||||
if (rect.texture) queueImagePreview(rect.texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function queueImagePreview(texturePath) {
|
||||
if (!texturePath ||
|
||||
state.imagePreviews.has(texturePath) ||
|
||||
state.imageLoading.has(texturePath) ||
|
||||
state.imageFailures.has(texturePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = previewUrlForTexture(texturePath);
|
||||
if (!url) {
|
||||
state.imageFailures.add(texturePath);
|
||||
return;
|
||||
}
|
||||
|
||||
state.imageLoading.add(texturePath);
|
||||
try {
|
||||
const image = await loadImage(url);
|
||||
state.imagePreviews.set(texturePath, image);
|
||||
state.imageFailures.delete(texturePath);
|
||||
refresh();
|
||||
} catch (error) {
|
||||
state.imageFailures.add(texturePath);
|
||||
setStatus(`贴图预览加载失败:${texturePath}`);
|
||||
} finally {
|
||||
state.imageLoading.delete(texturePath);
|
||||
}
|
||||
}
|
||||
|
||||
function previewUrlForTexture(texturePath) {
|
||||
const clean = String(texturePath || "").replace(/\\/g, "/").replace(/^\/+/, "");
|
||||
if (!clean) return "";
|
||||
if (/^(blob:|data:|https?:\/\/)/i.test(clean)) return clean;
|
||||
if (clean.startsWith("assets/")) return `../../game/${clean}`;
|
||||
if (clean.startsWith("game/assets/")) return `../../${clean}`;
|
||||
return clean;
|
||||
}
|
||||
|
||||
function loadMapText(text, status) {
|
||||
try {
|
||||
state.map = parseMap(text);
|
||||
state.selected = null;
|
||||
state.history = [];
|
||||
state.imageFailures.clear();
|
||||
preloadMapImages(state.map);
|
||||
fitWorldToCanvas();
|
||||
setStatus(status);
|
||||
refresh();
|
||||
@@ -489,6 +539,7 @@ function drawLayerRect(ctx, rect) {
|
||||
ctx.strokeRect(rect.bounds.x, rect.bounds.y, rect.bounds.width, rect.bounds.height);
|
||||
return;
|
||||
}
|
||||
if (rect.texture) queueImagePreview(rect.texture);
|
||||
drawFilledRect(ctx, rect.bounds, rect.color, 0.55, "#7d8794");
|
||||
}
|
||||
|
||||
@@ -1108,7 +1159,7 @@ function rectFields(rect) {
|
||||
}
|
||||
|
||||
function layerRectFields(item) {
|
||||
return [
|
||||
const fields = [
|
||||
numberField("X", () => item.bounds.x, (v) => {
|
||||
const next = Number.isFinite(v) ? v : item.bounds.x;
|
||||
moveLinkedCollision(item, next - item.bounds.x, 0);
|
||||
@@ -1129,11 +1180,27 @@ function layerRectFields(item) {
|
||||
}),
|
||||
textField("贴图路径", () => item.texture || "", (v) => {
|
||||
const next = String(v || "").trim();
|
||||
if (next) item.texture = next;
|
||||
else delete item.texture;
|
||||
if (next) {
|
||||
item.texture = next;
|
||||
state.imageFailures.delete(next);
|
||||
queueImagePreview(next);
|
||||
} else {
|
||||
delete item.texture;
|
||||
}
|
||||
}),
|
||||
...colorFields(item.color, true),
|
||||
];
|
||||
|
||||
if (item.texture && item.source) {
|
||||
fields.push(
|
||||
numberField("源 X", () => sourceRectFor(item).x, (v) => { sourceRectFor(item).x = Math.max(0, v); }),
|
||||
numberField("源 Y", () => sourceRectFor(item).y, (v) => { sourceRectFor(item).y = Math.max(0, v); }),
|
||||
numberField("源 W", () => sourceRectFor(item).width, (v) => { sourceRectFor(item).width = Math.max(1, v); }),
|
||||
numberField("源 H", () => sourceRectFor(item).height, (v) => { sourceRectFor(item).height = Math.max(1, v); })
|
||||
);
|
||||
}
|
||||
|
||||
fields.push(...colorFields(item.color, true));
|
||||
return fields;
|
||||
}
|
||||
|
||||
function pointFields(point) {
|
||||
@@ -1161,6 +1228,19 @@ function textField(label, get, set, options = null) {
|
||||
return { label, type: "text", options, get, set };
|
||||
}
|
||||
|
||||
function sourceRectFor(item) {
|
||||
if (!item.source) {
|
||||
const image = item.texture ? state.imagePreviews.get(item.texture) : null;
|
||||
item.source = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: image ? (image.naturalWidth || image.width || Math.max(1, item.bounds.width)) : Math.max(1, item.bounds.width),
|
||||
height: image ? (image.naturalHeight || image.height || Math.max(1, item.bounds.height)) : Math.max(1, item.bounds.height),
|
||||
};
|
||||
}
|
||||
return item.source;
|
||||
}
|
||||
|
||||
function ensureLevelVisualLayer() {
|
||||
if (!state.map.layers.some((layer) => layer.role === "LevelVisual")) {
|
||||
state.map.layers.push({ id: "level_visual", role: "LevelVisual", parallax: 1, rects: [], tileRects: [] });
|
||||
|
||||
Reference in New Issue
Block a user