Fix layered collision selection

This commit is contained in:
2026-06-10 01:30:06 +08:00
parent 8050ab2c5e
commit 8daf5d27aa
3 changed files with 50 additions and 19 deletions
+44 -13
View File
@@ -731,21 +731,22 @@ function getSelectedObject() {
function onCanvasMouseDown(event) {
const world = screenToWorld(event.offsetX, event.offsetY);
const selected = getSelectedObject();
const object = hitTest(world.x, world.y);
const stack = hitStackAt(world.x, world.y);
const object = stack[0] || null;
if (selected && selected.resize && isInsideResizeHandle(selected, world.x, world.y)) {
state.dragging = { resize: true, last: snapPoint(world) };
setCanvasCursor("nwse-resize");
return;
}
if (selected && selected.move && isInsideBounds(selected, world.x, world.y) && canDragSelectedInPlace(selected, object)) {
state.dragging = { last: snapPoint(world) };
if (selected && selected.move && isInsideBounds(selected, world.x, world.y) && canDragSelectedInPlace(selected, object, stack)) {
state.dragging = dragStateFor(selected, stack, world, true);
setCanvasCursor("move");
return;
}
if (object) {
state.selected = object.key;
state.dragging = { last: snapPoint(world) };
state.dragging = dragStateFor(object, stack, world, selected && sameSelection(selected.key, object.key));
setCanvasCursor("move");
refresh();
} else {
@@ -775,6 +776,7 @@ function onCanvasMouseMove(event) {
pushUndo(state.dragging.resize ? "调整对象尺寸" : "移动对象");
state.dragging.undoPushed = true;
}
state.dragging.moved = true;
if (state.dragging.resize && object.resize) {
object.resize(dx, dy);
} else if (object.move) {
@@ -786,7 +788,11 @@ function onCanvasMouseMove(event) {
}
function onCanvasMouseUp() {
const dragging = state.dragging;
state.dragging = null;
if (dragging && dragging.cycleOnClick && !dragging.moved && dragging.cycleKeys && dragging.cycleKeys.length > 1) {
cycleSelection(dragging.cycleKeys, dragging.cycleFrom);
}
setCanvasCursor("crosshair");
}
@@ -802,19 +808,23 @@ function onCanvasWheel(event) {
}
function hitTest(x, y) {
const objects = hitCandidates();
for (const object of objects) {
return hitStackAt(x, y)[0] || null;
}
function hitStackAt(x, y) {
const stack = [];
for (const object of hitCandidates()) {
const bounds = object.bounds && object.bounds();
if (!bounds) continue;
if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) return object;
if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) stack.push(object);
}
return null;
return stack;
}
function hitCandidates() {
const objects = collectObjects().filter(isHitFilterEnabled);
if (state.hitFilter !== "all") return objects.reverse();
const priority = { tile: 0, rect: 1, spawn: 2, collision: 3, zone: 4, player: 5, camera: 6 };
const priority = { collision: 0, rect: 1, tile: 2, spawn: 3, zone: 4, player: 5, camera: 6 };
return objects
.map((object, index) => ({ object, index }))
.sort((a, b) => {
@@ -832,9 +842,29 @@ function isInsideBounds(object, x, y) {
return x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height;
}
function canDragSelectedInPlace(selected, topHit) {
function canDragSelectedInPlace(selected, topHit, stack) {
if (state.hitFilter !== "all") return true;
return topHit && sameSelection(selected.key, topHit.key);
return Boolean(topHit) && stack.some((object) => sameSelection(object.key, selected.key));
}
function dragStateFor(object, stack, world, cycleOnClick) {
return {
last: snapPoint(world),
cycleKeys: stack.map((item) => item.key),
cycleFrom: object.key,
cycleOnClick,
moved: false,
};
}
function cycleSelection(cycleKeys, currentKey) {
const index = cycleKeys.findIndex((key) => sameSelection(key, currentKey));
if (index < 0) return;
const nextKey = cycleKeys[(index + 1) % cycleKeys.length];
if (!sameSelection(nextKey, state.selected)) {
state.selected = nextKey;
refresh();
}
}
function isHitFilterEnabled(object) {
@@ -846,12 +876,13 @@ function isHitFilterEnabled(object) {
function updateCanvasCursor(event) {
const world = screenToWorld(event.offsetX, event.offsetY);
const selected = getSelectedObject();
const object = hitTest(world.x, world.y);
const stack = hitStackAt(world.x, world.y);
const object = stack[0] || null;
if (selected && selected.resize && isInsideResizeHandle(selected, world.x, world.y)) {
setCanvasCursor("nwse-resize");
return;
}
if (selected && selected.move && isInsideBounds(selected, world.x, world.y) && canDragSelectedInPlace(selected, object)) {
if (selected && selected.move && isInsideBounds(selected, world.x, world.y) && canDragSelectedInPlace(selected, object, stack)) {
setCanvasCursor("move");
return;
}