Skip to content

2026-04-16開發日誌

  • 日期:2026-04-16
  • 專案:Cá xấu Duckduck

今天的重點是「場景物件的開關互動」要怎麼做才對,過程中踩了幾個坑,最後找到一個乾淨的模式。

冰箱開關互動

第一次嘗試:TextureRect + anchor 定位

一開始的做法是把 refrigerator.png(裁切版,帶黑邊)放進場景,用 anchor 去手動對齊到背景圖的冰箱位置。結果發現這很難對準——anchor 是百分比,背景圖裡的冰箱是像素位置,兩者之間的換算誤差很大,在不同解析度下還會跑位。

正確做法:全螢幕 Overlay + mouse_filter = IGNORE

refrigerator.png 改成 2560×1080 透明背景,冰箱內容畫在正確的座標上,其他地方全透明。這樣 RefrigeratorOverlay 只要設成:

layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
stretch_mode = KEEP(不縮放)
mouse_filter = 2(IGNORE)

填滿全螢幕、不需要任何手動對齊,透明區域自然對上背景。

mouse_filter = 2 是關鍵:overlay 完全不攔截滑鼠事件,所以冰箱開著的時候,其他所有按鈕照樣可以點。

RefrigeratorBtn 是一個 flat = true 的隱形按鈕,蓋在背景圖的冰箱門上(anchor 估值,在編輯器裡微調)。點一下開、再點一下關,0.2 秒 TRANS_QUAD 淡入淡出。

架構可擴展性

之後有 5-6 個可開關的物件,每一個就是:

  • 一個全螢幕 TextureRect(overlay,mouse_filter = IGNORE)
  • 一個 flat Button(蓋在背景的物件上)
  • 腳本裡一個 bool + 一個 handler

不同物件之間不互相干擾,腳本也不會複雜化。


糖罐背景動態換圖

糖罐場景(chapter1_sugar_prop)有兩張背景圖:

  • suger_prop.png:糖塊還在罐子前面
  • suger_prop_dis.png:糖塊消失了(已被拿走)

進場時根據存檔切換

_ready() 檢查 InventoryManager.has_item("sugar"),已收集就直接用 suger_prop_dis.png

gdscript
_collected = InventoryManager.has_item("sugar")
if _collected:
    _background.texture = _TEXTURE_DIS

收集瞬間即時換圖

一開始把換圖放在動畫結束的 callback 裡,但視覺上感覺太慢。後來改成按下按鈕的瞬間就換圖,和動畫平行執行:

gdscript
func _on_suger_pick_pressed() -> void:
    ...
    _background.texture = _TEXTURE_DIS  # 立即換圖
    _play_collect_animation()            # 動畫同步開始

效果是:背景瞬間切換到「糖塊消失」版,同時糖塊動畫從罐子飛出彈起、再飛進包包。視覺上合理——糖被拿走的那一刻背景就反映了這個狀態。

preload 用於兩張貼圖,在編譯時載入,runtime 不會有找不到檔案的問題:

gdscript
const _TEXTURE_NORMAL := preload("res://Images/chapter1/puzzle/suger_prop.png")
const _TEXTURE_DIS := preload("res://Images/chapter1/puzzle/suger_prop_dis.png")

小結

今天最重要的收穫是確立了「可互動物件的 overlay 模式」:美術輸出全螢幕透明圖、程式直接疊上去、mouse_filter 控制是否擋點擊。這個模式之後所有的開關物件都可以直接套用,不需要每次在編輯器裡手動對齊座標。