2026-04-15開發日誌
- 日期:2026-04-15
- 專案:Cá xấu Duckduck
今天做的事情比較分散,但每一塊都是紮實的基礎建設:背包的視覺位置調對了、遊戲第一次有了真正的存讀檔,還有道具的狀態現在會被記錄下來。
背包 UI 調整
靠近包包、動態寬度
昨天的背包面板是從畫面左邊延伸過來的,感覺和包包的位置有點脫節。今天把它改成從包包左緣往左展開——兩個 anchor 都釘在 0.88(包包左緣),grow_horizontal = 0 讓它往左長,offset_left 動態計算寬度。
寬度公式:
panel_width = 道具數 × 160 + (道具數 - 1) × 12 + 28- 最少顯示一格(空背包不會縮成一條線)
- 超過螢幕範圍時 ScrollContainer 接手
每次 _refresh_items() 都會重算一次,InventoryPanel 和 PanelBG 的 offset_left 同步更新。
PanelBG 延伸到包包後面
原本 PanelBG 是 InventoryPanel 的子節點,背景只蓋到道具列的範圍。今天把它搬出來變成 Bag 的直接子節點,anchor_right = 1.0 讓背景延伸到螢幕右緣,包包圖示也被包進去了。
打開背包時,PanelBG 和 InventoryPanel 一起淡入;收起時一起淡出。
存檔系統(SaveManager)
架構
建了一個新的 autoload SaveManager,統一管理遊戲進度的讀寫。存檔位置是 user://save.json——Godot 的慣例,對應到 OS 的使用者資料夾,不放在 project 裡面。
存檔格式:
{
"version": 1,
"current_scene": "res://Scene/chapter1_background.tscn",
"inventory": [
{"id": "sugar", "texture_path": "res://Images/chapter1/puzzle/suger.png"}
],
"props": {
"sugar_collected": 1
},
"timestamp": 1744934400
}props 是一個通用的旗標字典,之後每個有狀態的道具或事件都可以往裡面加。
API
save_checkpoint(scene_path)— 存目前場景 + 背包 + props,寫入磁碟set_prop(key, value)— 設單一旗標,立即寫入(不需要完整 checkpoint)get_prop(key, default)— 讀旗標restore_inventory()— 把存檔裡的道具清單還原進InventoryManagerreset()— 清除所有存檔
自動存檔
在 chapter1_background._ready() 加了 SaveManager.save_checkpoint(...)。玩家每次進到這個場景,進度就自動寫入。之後新增場景時,一行就能接上。
Continue 按鈕啟用邏輯
開始畫面的 Continue 按鈕現在有真正的邏輯了:
- 無存檔:半透明、
mouse_filter = IGNORE,完全不可互動 - 有存檔:亮起、
mouse_filter = STOP,可以點
按 Continue 會先呼叫 SaveManager.restore_inventory(),把上次的道具還回背包,再直接跳到存檔記錄的場景。
按 Play 時的確認視窗
如果玩家已有存檔又按 Play,會跳出一個確認視窗:
已有存檔,確定要開始新遊戲? 原本的進度將會消失。
確定 → SaveManager.reset() + InventoryManager.clear() + 進 cutscene 取消 → 關閉視窗,回到開始畫面
道具狀態:sugar_collected
props 字典的第一個用例。糖塊收集後,會呼叫:
SaveManager.set_prop("sugar_collected", 1)這會立刻寫入 JSON,不等下次 checkpoint。
「已經拿過了」提示
如果玩家再回到糖罐場景、再點罐子,不會什麼都沒發生——會出現「已經拿過了」的文字提示,停留約 1.2 秒後淡出。
判斷邏輯在 _ready() 就處理掉了:
_collected = InventoryManager.has_item("sugar")這樣不管是同一個 session 還是 Continue 回來的,狀態都能正確恢復。
小結
今天最重要的事是存檔系統打通了——從「玩家做了什麼」到「下次開啟遊戲還在」,這條路現在是完整的。props 的設計讓之後每個有狀態的事件都可以輕鬆接上,不需要動架構。
