修正又被穿透、不能拖曳也不能右鍵:點穿改用人物方框判定

根本原因:
點穿是逐像素讀 canvas 該點的 alpha 來決定,而 2.0.7 起每一幀都會重驗。
人物的手腳與邊緣會隨動畫在透明/不透明之間切換(實測小念 hover 時
idle+jumping 共 11 幀,只有 44% 的人物像素每一幀都不透明;running 也只有
56%),游標停在邊緣時點穿位元就跟著動畫高速切換,按下去那一瞬間剛好是
透明幀,滑鼠事件就穿到底下的視窗去了。hover 會觸發跳三下,正好是使用者
伸手要抓寵物的時候,最容易中。

影響:
游標明明在寵物身上,左鍵拖不動、右鍵沒有選單,點擊落到底下的視窗。

修法:
點穿與 hover 一樣改用人物方框(canvas 矩形)或氣泡矩形判定,狀態只取決於
游標位置,不再與動畫幀連動;移除 overSprite 與 tick 內的每幀重驗。這也是
Codex 的做法。README 對應段落與對照表同步更新。版號 2.0.10。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 10:06:32 +08:00
co-authored by Claude Fable 5
parent 4e4563e861
commit a9497cc123
4 changed files with 16 additions and 29 deletions
+8 -21
View File
@@ -149,7 +149,7 @@ function startAnim(anim, now) {
let bubbleTimer = null;
let bubbleSticky = false;
let ignoringMouse = true;
let hovered = false; // 游標是否在人物的不透明像素上
let hovered = false; // 游標是否在人物方框內
let lastPoint = null; // 最後已知的游標位置(視窗相對),主程序輪詢或 mousemove 都會更新
let pressed = null;
let lastReported = "";
@@ -555,9 +555,6 @@ function tick(now) {
prevBase = base;
draw(anim);
// 換格會改變游標底下的像素。游標沒移動時主程序不會再送座標,
// 所以這裡自己重驗一次,否則會卡在點穿狀態動不了。
if (lastPoint && overPetArea(lastPoint.x, lastPoint.y)) refreshHitTest();
requestAnimationFrame(tick);
}
@@ -630,21 +627,10 @@ function overBubble(x, y) {
return x >= r.left && x <= r.right && y >= r.top && y <= r.bottom;
}
// 壓在人物的不透明像素上(透明處要讓滑鼠穿過去)
function overSprite(x, y) {
const r = canvas.getBoundingClientRect();
if (x < r.left || x >= r.right || y < r.top || y >= r.bottom) return false;
const px = Math.floor((x - r.left) * (canvas.width / r.width));
const py = Math.floor((y - r.top) * (canvas.height / r.height));
try {
return ctx.getImageData(px, py, 1, 1).data[3] > 20;
} catch {
return true;
}
}
// hover 判定用人物方框,不用逐像素 alpha:sprite 每一格的輪廓不同,逐像素會讓游標停著不動時
// 也在 true/false 之間高速抖動。Codex 的 pet-area-hover 同樣是用矩形算的。
// hover 與點穿都用人物方框,不用逐像素 alpha:sprite 每一格的輪廓不同,人物的手腳與邊緣
// 會隨動畫在透明/不透明之間切換,逐像素會讓游標停著不動時也在 true/false 之間高速抖動;
// 點穿跟著抖,按下去那一瞬間剛好是透明格,滑鼠事件就穿到底下的視窗去了(看起來就是
// 「又被穿透了、不能拖曳也不能右鍵」)。Codex 的 pet-area-hover 同樣是用矩形算的。
function overPetArea(x, y) {
const r = canvas.getBoundingClientRect();
return x >= r.left && x < r.right && y >= r.top && y < r.bottom;
@@ -674,8 +660,9 @@ function setHovered(on) {
function refreshHitTest() {
if (!lastPoint || pressed || drag) return; // 拖曳中不要動點穿狀態
const { x, y } = lastPoint;
setHovered(overPetArea(x, y));
setIgnore(!(overSprite(x, y) || overBubble(x, y)));
const inArea = overPetArea(x, y);
setHovered(inArea);
setIgnore(!(inArea || overBubble(x, y)));
}
function setIgnore(ignore) {