[{"data":1,"prerenderedAt":9157},["ShallowReactive",2],{"\u002Fdevlog\u002Fxggame-bird\u002F09-export":3,"devlog-chapters-xggame-bird":886},{"id":4,"title":5,"body":6,"cover":876,"date":877,"description":878,"extension":879,"game":410,"github":880,"icon":876,"meta":881,"navigation":319,"path":882,"seo":883,"stem":884,"toc":319,"__hash__":885},"devlog\u002Fdevlog\u002Fxggame-bird\u002F09-export.md","导出 + 发布到 itch.io",{"type":7,"value":8,"toc":857},"minimark",[9,18,21,44,59,64,67,74,77,84,88,91,102,108,112,202,205,238,247,250,260,263,269,338,345,351,355,360,368,371,377,383,386,476,480,487,526,537,552,558,564,568,574,618,624,627,633,660,667,671,674,738,742,749,769,776,780,783,807,810,813,817,820,841,850,853],[10,11,12,13,17],"p",{},"游戏做完了！最激动人心的一步 — ",[14,15,16],"strong",{},"让别人玩到你的游戏","。",[10,19,20],{},"我们要做两件事：",[22,23,24,31],"ol",{},[25,26,27,30],"li",{},[14,28,29],{},"导出为 Web 版（HTML5）"," — 在浏览器里就能玩",[25,32,33,43],{},[14,34,35,36],{},"发布到 ",[37,38,42],"a",{"href":39,"rel":40},"https:\u002F\u002Fitch.io",[41],"nofollow","itch.io"," — 全世界都能搜到、能玩",[45,46,47],"blockquote",{},[10,48,49,50,54,55,58],{},"还记得我们在 ",[37,51,53],{"href":52},"\u002Fdevlog\u002Fxggame-bird\u002F01-getting-started","第 1 章","选了「兼容」渲染器吗？就是为了",[14,56,57],{},"这一步","！🎉",[60,61,63],"h2",{"id":62},"安装-web-导出模板","安装 Web 导出模板",[10,65,66],{},"第一次导出 Web 版需要下载导出模板：",[10,68,69,70,73],{},"路径：编辑器 → ",[14,71,72],{},"管理导出模板"," (Manage Export Templates)",[10,75,76],{},"如果是首次安装，点击「下载并安装」，等几分钟下载完成即可。",[10,78,79],{},[80,81],"img",{"alt":82,"src":83},"09-export-导出模板","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F09-export-%E5%AF%BC%E5%87%BA%E6%A8%A1%E6%9D%BF.png",[60,85,87],{"id":86},"配置-web-导出","配置 Web 导出",[10,89,90],{},"路径：项目 → 导出 (Export)",[10,92,93,94,97,98,101],{},"点击「",[14,95,96],{},"添加","」→ 选择 ",[14,99,100],{},"Web"," 平台。",[10,103,104],{},[80,105],{"alt":106,"src":107},"09-export-配置","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F09-export-%E9%85%8D%E7%BD%AE.png",[109,110,111],"h3",{"id":111},"关键设置",[113,114,115,131],"table",{},[116,117,118],"thead",{},[119,120,121,125,128],"tr",{},[122,123,124],"th",{},"项",[122,126,127],{},"推荐值",[122,129,130],{},"说明",[132,133,134,149,161,176,189],"tbody",{},[119,135,136,143,146],{},[137,138,139],"td",{},[140,141,142],"code",{},"Custom HTML Shell",[137,144,145],{},"留空",[137,147,148],{},"用 Godot 默认模板即可",[119,150,151,156,158],{},[137,152,153],{},[140,154,155],{},"Head Include",[137,157,145],{},[137,159,160],{},"如果想自定义页面 meta，可以加",[119,162,163,168,173],{},[137,164,165],{},[140,166,167],{},"Canvas Resize Policy",[137,169,170],{},[140,171,172],{},"Adaptive",[137,174,175],{},"自适应窗口大小",[119,177,178,183,186],{},[137,179,180],{},[140,181,182],{},"Focus Canvas on Start",[137,184,185],{},"✅ 启用",[137,187,188],{},"启动就聚焦，方便按键响应",[119,190,191,196,199],{},[137,192,193],{},[140,194,195],{},"Experimental Virtual Keyboard",[137,197,198],{},"看需求",[137,200,201],{},"移动端用得上",[60,203,204],{"id":204},"导出游戏",[22,206,207,213,220,231],{},[25,208,209,210,212],{},"在【导出】面板选中 ",[14,211,100],{}," 配置",[25,214,215,216,219],{},"点击右下角「",[14,217,218],{},"导出项目","」",[25,221,222,223,226,227,230],{},"选择一个",[14,224,225],{},"空文件夹","保存（比如 ",[140,228,229],{},"build\u002Fweb\u002F","）",[25,232,233,234,237],{},"文件名填 ",[140,235,236],{},"index.html","（itch.io 上传约定）",[45,239,240],{},[10,241,242,243,246],{},"⚠️ ",[14,244,245],{},"必须导出到空文件夹"," — 否则会和已有文件混在一起。",[10,248,249],{},"导出后这个目录会出现一堆文件：",[251,252,257],"pre",{"className":253,"code":255,"language":256},[254],"language-text","build\u002Fweb\u002F\n├── index.html\n├── index.js\n├── index.pck       ← 游戏资源\n├── index.wasm      ← 游戏引擎（WebAssembly）\n├── index.audio.worklet.js\n└── index.icon.png  ← 图标（可选）\n","text",[140,258,255],{"__ignoreMap":259},"",[60,261,262],{"id":262},"本地测试",[10,264,265,268],{},[14,266,267],{},"Web 导出无法直接双击 index.html 打开","（浏览器安全策略会阻止 wasm 加载）。需要起一个本地 server：",[251,270,274],{"className":271,"code":272,"language":273,"meta":259,"style":259},"language-bash shiki shiki-themes vitesse-light vitesse-dark","# Python 自带 server（最方便）\ncd build\u002Fweb\npython -m http.server 8000\n\n# 或者 Node.js\nnpx serve build\u002Fweb\n","bash",[140,275,276,285,296,314,321,327],{"__ignoreMap":259},[277,278,281],"span",{"class":279,"line":280},"line",1,[277,282,284],{"class":283},"snYqZ","# Python 自带 server（最方便）\n",[277,286,288,292],{"class":279,"line":287},2,[277,289,291],{"class":290},"sHLBJ","cd",[277,293,295],{"class":294},"spP0B"," build\u002Fweb\n",[277,297,299,303,307,310],{"class":279,"line":298},3,[277,300,302],{"class":301},"s_xSY","python",[277,304,306],{"class":305},"sfsYZ"," -m",[277,308,309],{"class":294}," http.server",[277,311,313],{"class":312},"sqbOQ"," 8000\n",[277,315,317],{"class":279,"line":316},4,[277,318,320],{"emptyLinePlaceholder":319},true,"\n",[277,322,324],{"class":279,"line":323},5,[277,325,326],{"class":283},"# 或者 Node.js\n",[277,328,330,333,336],{"class":279,"line":329},6,[277,331,332],{"class":301},"npx",[277,334,335],{"class":294}," serve",[277,337,295],{"class":294},[10,339,340,341,344],{},"打开浏览器访问 ",[140,342,343],{},"http:\u002F\u002Flocalhost:8000","，能玩就说明导出成功。",[10,346,347],{},[80,348],{"alt":349,"src":350},"09-export-本地测试","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F09-export-%E6%9C%AC%E5%9C%B0%E6%B5%8B%E8%AF%95.png",[60,352,354],{"id":353},"注册-itchio","注册 itch.io",[45,356,357],{},[10,358,359],{},"已有账号可以跳过这一步。",[10,361,362,363,367],{},"去 ",[37,364,42],{"href":365,"rel":366},"https:\u002F\u002Fitch.io\u002Fregister",[41]," 注册账号，确认邮箱即可。",[60,369,370],{"id":370},"上传游戏",[10,372,373,374,17],{},"登录后，右上角头像 → ",[14,375,376],{},"Upload new project",[10,378,379],{},[80,380],{"alt":381,"src":382},"09-export-上传页面","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F09-export-%E4%B8%8A%E4%BC%A0%E9%A1%B5%E9%9D%A2.png",[109,384,385],{"id":385},"填写项目信息",[113,387,388,398],{},[116,389,390],{},[119,391,392,395],{},[122,393,394],{},"字段",[122,396,397],{},"填写建议",[132,399,400,411,419,427,438,451,465],{},[119,401,402,405],{},[137,403,404],{},"Title",[137,406,407,408,230],{},"游戏名（如 ",[140,409,410],{},"XGGame-Bird",[119,412,413,416],{},[137,414,415],{},"Project URL",[137,417,418],{},"URL slug（自动生成，可改）",[119,420,421,424],{},[137,422,423],{},"Short description",[137,425,426],{},"一句话简介（搜索结果会显示）",[119,428,429,432],{},[137,430,431],{},"Classification",[137,433,434,435],{},"选 ",[140,436,437],{},"Games",[119,439,440,443],{},[137,441,442],{},"Kind of project",[137,444,445,450],{},[14,446,434,447],{},[140,448,449],{},"HTML"," ← 关键！",[119,452,453,456],{},[137,454,455],{},"Release status",[137,457,458,461,462],{},[140,459,460],{},"Released"," 或 ",[140,463,464],{},"Prototype",[119,466,467,470],{},[137,468,469],{},"Pricing",[137,471,472,475],{},[140,473,474],{},"$0 or donate"," （免费 \u002F 自愿打赏）",[60,477,479],{"id":478},"上传文件-打包成-zip","上传文件 — 打包成 zip",[10,481,482,483,486],{},"itch.io 要求 Web 游戏",[14,484,485],{},"打包成 zip 上传","：",[251,488,490],{"className":271,"code":489,"language":273,"meta":259,"style":259},"# 进入 build\u002Fweb 目录\ncd build\u002Fweb\n\n# 打包成 zip（不要有外层文件夹，要 index.html 在 zip 根目录）\nzip -r ..\u002Fxggame-bird-web.zip .\n",[140,491,492,497,503,507,512],{"__ignoreMap":259},[277,493,494],{"class":279,"line":280},[277,495,496],{"class":283},"# 进入 build\u002Fweb 目录\n",[277,498,499,501],{"class":279,"line":287},[277,500,291],{"class":290},[277,502,295],{"class":294},[277,504,505],{"class":279,"line":298},[277,506,320],{"emptyLinePlaceholder":319},[277,508,509],{"class":279,"line":316},[277,510,511],{"class":283},"# 打包成 zip（不要有外层文件夹，要 index.html 在 zip 根目录）\n",[277,513,514,517,520,523],{"class":279,"line":323},[277,515,516],{"class":301},"zip",[277,518,519],{"class":305}," -r",[277,521,522],{"class":294}," ..\u002Fxggame-bird-web.zip",[277,524,525],{"class":294}," .\n",[10,527,528,529,532,533,536],{},"或者直接在文件管理器里：选中 ",[140,530,531],{},"build\u002Fweb"," 里的",[14,534,535],{},"所有文件","（注意是文件，不是文件夹）→ 右键 → 压缩为 zip。",[45,538,539],{},[10,540,242,541,544,545,547,548,551],{},[14,542,543],{},"常见坑","：zip 解压后必须直接看到 ",[140,546,236],{},"，",[14,549,550],{},"不能有外层文件夹","。否则 itch.io 加载不出来。",[10,553,554,555,17],{},"上传 zip 后，勾选 ",[14,556,557],{},"「This file will be played in the browser」",[10,559,560],{},[80,561],{"alt":562,"src":563},"09-export-上传文件","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F09-export-%E4%B8%8A%E4%BC%A0%E6%96%87%E4%BB%B6.png",[60,565,567],{"id":566},"配置嵌入设置embed-options","配置嵌入设置（Embed Options）",[10,569,570,571,486],{},"下拉找到 ",[14,572,573],{},"Embed options",[113,575,576,585],{},[116,577,578],{},[119,579,580,582],{},[122,581,124],{},[122,583,584],{},"值",[132,586,587,598,610],{},[119,588,589,592],{},[137,590,591],{},"Viewport dimensions",[137,593,594,595,230],{},"你游戏的窗口大小（如 ",[140,596,597],{},"608 × 1080",[119,599,600,603],{},[137,601,602],{},"Frame options",[137,604,605,606,609],{},"一般勾 ",[140,607,608],{},"Fullscreen button"," 让玩家可全屏",[119,611,612,615],{},[137,613,614],{},"Mobile friendly",[137,616,617],{},"如果适配了移动端，勾上",[10,619,620],{},[80,621],{"alt":622,"src":623},"09-export-嵌入设置","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F09-export-%E5%B5%8C%E5%85%A5%E8%AE%BE%E7%BD%AE.png",[60,625,626],{"id":626},"上传封面图和截图",[10,628,629,630,486],{},"往下翻到 ",[14,631,632],{},"Cover image",[634,635,636,648,654],"ul",{},[25,637,638,640,641,461,644,647],{},[14,639,632],{}," — 主图，建议 ",[14,642,643],{},"630 × 500",[14,645,646],{},"315 × 250","，会出现在 itch.io 列表里",[25,649,650,653],{},[14,651,652],{},"Screenshots"," — 游戏截图，2-4 张就够了",[25,655,656,659],{},[14,657,658],{},"Banner"," — 横幅图（可选）",[10,661,662,663,666],{},"封面图建议用游戏的",[14,664,665],{},"最高光时刻","做截图 + 加点文字，吸引点击。",[60,668,670],{"id":669},"设置标签-分类","设置标签 + 分类",[10,672,673],{},"帮助玩家发现你的游戏：",[634,675,676,685,709,717,732],{},[25,677,678,681,682],{},[14,679,680],{},"Genre",": ",[140,683,684],{},"Action",[25,686,687,681,690,693,694,693,697,693,700,693,703,693,706],{},[14,688,689],{},"Tags",[140,691,692],{},"flappy",", ",[140,695,696],{},"casual",[140,698,699],{},"pixel-art",[140,701,702],{},"arcade",[140,704,705],{},"endless",[140,707,708],{},"godot",[25,710,711,681,714],{},[14,712,713],{},"Average session",[140,715,716],{},"A few minutes",[25,718,719,681,722,693,725,693,728,731],{},[14,720,721],{},"Inputs",[140,723,724],{},"Mouse",[140,726,727],{},"Keyboard",[140,729,730],{},"Touchscreen","（如果支持）",[25,733,734,737],{},[14,735,736],{},"Accessibility",": 看实际情况",[60,739,741],{"id":740},"发布","发布！",[10,743,744,745,748],{},"设置好后，把 ",[14,746,747],{},"Visibility"," 改成：",[634,750,751,757,763],{},[25,752,753,756],{},[14,754,755],{},"Public"," — 完全公开，任何人都能搜到",[25,758,759,762],{},[14,760,761],{},"Restricted"," — 只有有链接的人能访问（适合先给朋友测试）",[25,764,765,768],{},[14,766,767],{},"Draft"," — 草稿，只有自己能看",[10,770,771,772,775],{},"点击右下角 ",[14,773,774],{},"Save & view page","，欣赏一下自己的成果 🎉",[60,777,779],{"id":778},"分享出去","分享出去！",[10,781,782],{},"把 itch.io 的链接发到：",[634,784,785,788,796,804],{},[25,786,787],{},"朋友圈、Twitter、小红书、抖音视频简介",[25,789,790,795],{},[37,791,794],{"href":792,"rel":793},"https:\u002F\u002Freddit.com\u002Fr\u002Fgodot",[41],"r\u002Fgodot"," — Godot 社区",[25,797,798,803],{},[37,799,802],{"href":800,"rel":801},"https:\u002F\u002Fitch.io\u002Fboard\u002F4\u002Frelease-announcements",[41],"Itch.io 论坛"," — Release Announcements",[25,805,806],{},"Discord 游戏开发群",[10,808,809],{},"记得在我的博客评论区也贴一下你的 itch.io 链接，互相玩一下！🎮",[811,812],"hr",{},[60,814,816],{"id":815},"整个系列到这就完结了","整个系列到这就完结了 🎉",[10,818,819],{},"回顾一下我们完成的事：",[634,821,822,825,828,831,834],{},[25,823,824],{},"✅ 从零搭建一个 Flappy Bird 类游戏",[25,826,827],{},"✅ 学会了 Godot 的核心概念：节点、场景、信号、Autoload、Parallax",[25,829,830],{},"✅ 实现了完整的游戏循环：菜单 → 游戏 → 结束 → 重开",[25,832,833],{},"✅ 加上美术 + 音效，让游戏有了\"灵魂\"",[25,835,836,837,840],{},"✅ ",[14,838,839],{},"公开发布","，全世界都能玩",[10,842,843,844,849],{},"如果你跟着做完了整个流程，请一定告诉我！可以发我作品链接 → ",[37,845,848],{"href":846,"rel":847},"https:\u002F\u002Ftwitter.com\u002FX_XXGGG",[41],"Twitter @X_XXGGG"," \u002F 或博客评论。",[10,851,852],{},"下一个游戏，我们再战 💪",[854,855,856],"style",{},"html pre.shiki code .snYqZ, html code.shiki .snYqZ{--shiki-default:#A0ADA0;--shiki-dark:#758575DD}html pre.shiki code .sHLBJ, html code.shiki .sHLBJ{--shiki-default:#998418;--shiki-dark:#B8A965}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .sfsYZ, html code.shiki .sfsYZ{--shiki-default:#A65E2B;--shiki-dark:#C99076}html pre.shiki code .sqbOQ, html code.shiki .sqbOQ{--shiki-default:#2F798A;--shiki-dark:#4C9A91}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":259,"searchDepth":287,"depth":287,"links":858},[859,860,863,864,865,866,869,870,871,872,873,874,875],{"id":62,"depth":287,"text":63},{"id":86,"depth":287,"text":87,"children":861},[862],{"id":111,"depth":298,"text":111},{"id":204,"depth":287,"text":204},{"id":262,"depth":287,"text":262},{"id":353,"depth":287,"text":354},{"id":370,"depth":287,"text":370,"children":867},[868],{"id":385,"depth":298,"text":385},{"id":478,"depth":287,"text":479},{"id":566,"depth":287,"text":567},{"id":626,"depth":287,"text":626},{"id":669,"depth":287,"text":670},{"id":740,"depth":287,"text":741},{"id":778,"depth":287,"text":779},{"id":815,"depth":287,"text":816},null,"2026-05-12","XGGame-Bird Godot","md","https:\u002F\u002Fgithub.com\u002FXXGGG\u002FXGGame-Bird",{},"\u002Fdevlog\u002Fxggame-bird\u002F09-export",{"title":5,"description":878},"devlog\u002Fxggame-bird\u002F09-export","ljlAqloJGlO7Zma_Ci8OgRQJmwaOGyMsS93o62Z1Htk",[887,1095,2166,2862,4404,5875,7151,7741,8573],{"id":888,"title":889,"body":890,"cover":876,"date":877,"description":878,"extension":879,"game":410,"github":880,"icon":876,"meta":1091,"navigation":319,"path":52,"seo":1092,"stem":1093,"toc":319,"__hash__":1094},"devlog\u002Fdevlog\u002Fxggame-bird\u002F01-getting-started.md","安装 Godot 及项目设置",{"type":7,"value":891,"toc":1083},[892,896,905,908,914,917,923,930,936,939,945,948,951,954,960,963,969,1002,1007,1011,1017,1023,1064,1069,1078,1080],[60,893,895],{"id":894},"下载与安装-godot","下载与安装 Godot",[45,897,898],{},[10,899,900,901],{},"官网：",[37,902,903],{"href":903,"rel":904},"https:\u002F\u002Fgodotengine.org\u002F",[41],[10,906,907],{},"下载后解压即可使用，无需安装。",[10,909,910],{},[80,911],{"alt":912,"src":913},"01-getting-started-Godot","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F01-getting-started-Godot.png",[60,915,916],{"id":916},"新建项目",[10,918,919,920,17],{},"打开 Godot，新建一个项目，取名 ",[140,921,922],{},"Bird",[10,924,925,926,929],{},"渲染器选择 ",[14,927,928],{},"【兼容】","— 这样后续可以导出为网页版在浏览器里玩。",[10,931,932],{},[80,933],{"alt":934,"src":935},"01-getting-started-Godot-Bird","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F01-getting-started-Godot-Bird.png",[10,937,938],{},"创建完成后进入编辑器：",[10,940,941],{},[80,942],{"alt":943,"src":944},"01-getting-started-界面","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F01-getting-started-%E7%95%8C%E9%9D%A2.png",[60,946,947],{"id":947},"项目设置",[10,949,950],{},"因为这是一个手机竖屏游戏，所以需要先调整窗口尺寸。",[10,952,953],{},"路径：项目 → 项目设置 → 显示 → 窗口",[10,955,956],{},[80,957],{"alt":958,"src":959},"01-getting-started-项目设置位置","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F01-getting-started-%E9%A1%B9%E7%9B%AE%E8%AE%BE%E7%BD%AE%E4%BD%8D%E7%BD%AE.png",[109,961,962],{"id":962},"窗口尺寸",[10,964,965],{},[80,966],{"alt":967,"src":968},"01-getting-started-项目设置","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F01-getting-started-%E9%A1%B9%E7%9B%AE%E8%AE%BE%E7%BD%AE.png",[113,970,971,980],{},[116,972,973],{},[119,974,975,978],{},[122,976,977],{},"设置项",[122,979,584],{},[132,981,982,992],{},[119,983,984,987],{},[137,985,986],{},"视口宽度 (Viewport Width)",[137,988,989],{},[140,990,991],{},"720",[119,993,994,997],{},[137,995,996],{},"视口高度 (Viewport Height)",[137,998,999],{},[140,1000,1001],{},"1280",[45,1003,1004],{},[10,1005,1006],{},"视口是游戏的实际分辨率。",[109,1008,1010],{"id":1009},"拉伸适配-与-强制竖屏","拉伸适配 与 强制竖屏",[10,1012,1013],{},[80,1014],{"alt":1015,"src":1016},"01-getting-started-项目设置2","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F01-getting-started-%E9%A1%B9%E7%9B%AE%E8%AE%BE%E7%BD%AE2.png",[10,1018,1019,1022],{},[14,1020,1021],{},"「拉伸」"," 部分：",[113,1024,1025,1036],{},[116,1026,1027],{},[119,1028,1029,1031,1033],{},[122,1030,977],{},[122,1032,584],{},[122,1034,1035],{},"作用",[132,1037,1038,1051],{},[119,1039,1040,1043,1048],{},[137,1041,1042],{},"模式 (Mode)",[137,1044,1045],{},[140,1046,1047],{},"canvas_items",[137,1049,1050],{},"保持 2D 图形清晰",[119,1052,1053,1056,1061],{},[137,1054,1055],{},"比例 (Aspect)",[137,1057,1058],{},[140,1059,1060],{},"keep",[137,1062,1063],{},"保持宽高比不变形",[10,1065,1066,1022],{},[14,1067,1068],{},"「手持」",[634,1070,1071],{},[25,1072,1073,1074,1077],{},"方向 (Orientation)：选择 ",[140,1075,1076],{},"portrait","（竖屏）",[811,1079],{},[10,1081,1082],{},"设置完成！接下来就可以开始创建「小鸟」了。",{"title":259,"searchDepth":287,"depth":287,"links":1084},[1085,1086,1087],{"id":894,"depth":287,"text":895},{"id":916,"depth":287,"text":916},{"id":947,"depth":287,"text":947,"children":1088},[1089,1090],{"id":962,"depth":298,"text":962},{"id":1009,"depth":298,"text":1010},{},{"title":889,"description":878},"devlog\u002Fxggame-bird\u002F01-getting-started","xfXIiVt-cjGYA7F0hi9cWLKLIpkdWG1VTKPqgfQQ7ts",{"id":1096,"title":1097,"body":1098,"cover":876,"date":877,"description":878,"extension":879,"game":410,"github":880,"icon":876,"meta":2161,"navigation":319,"path":2162,"seo":2163,"stem":2164,"toc":319,"__hash__":2165},"devlog\u002Fdevlog\u002Fxggame-bird\u002F02-create-bird.md","创建主角：小鸟",{"type":7,"value":1099,"toc":2150},[1100,1104,1111,1114,1117,1139,1149,1155,1158,1167,1173,1176,1186,1192,1195,1201,1204,1209,1215,1221,1227,1505,1509,1522,1530,1579,1604,1610,1613,1640,1653,1798,1801,1843,1922,1932,1970,1998,2038,2069,2074,2086,2109,2112,2118,2125,2144,2147],[60,1101,1103],{"id":1102},"创建节点的方法","创建节点的方法：",[10,1105,1106,1107],{},"创建节点\u002F创建Node，点击加号「+」，然后输入需要的节点名称即可创建\n",[80,1108],{"alt":1109,"src":1110},"02-create-bird-创建节点的方法","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F02-create-bird-%E5%88%9B%E5%BB%BA%E8%8A%82%E7%82%B9%E7%9A%84%E6%96%B9%E6%B3%95.png",[60,1112,1097],{"id":1113},"创建主角小鸟",[10,1115,1116],{},"我们需要用到 3 个节点来组成小鸟：",[634,1118,1119],{},[25,1120,1121,1124,1125],{},[140,1122,1123],{},"CharacterBody2D"," — 角色节点（控制移动）\n",[634,1126,1127,1133],{},[25,1128,1129,1132],{},[140,1130,1131],{},"Sprite2D"," — 图片节点（显示外观）",[25,1134,1135,1138],{},[140,1136,1137],{},"CollisionShape2D"," — 碰撞体节点（检测碰撞）",[10,1140,1141,1142,1144,1145,1148],{},"先用 Godot 自带的图标当小鸟，把它拖到 ",[140,1143,1131],{}," 的 ",[140,1146,1147],{},"Texture"," 上即可：",[10,1150,1151],{},[80,1152],{"alt":1153,"src":1154},"02-create-bird-创建小鸟","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F02-create-bird-%E5%88%9B%E5%BB%BA%E5%B0%8F%E9%B8%9F.png",[109,1156,1157],{"id":1157},"保存场景",[10,1159,1160,1163,1164],{},[140,1161,1162],{},"Ctrl + S"," 保存场景，建一个文件夹专门放场景文件：",[140,1165,1166],{},"scenes\u002Fbird.tscn",[10,1168,1169],{},[80,1170],{"alt":1171,"src":1172},"02-create-bird-保存场景","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F02-create-bird-%E4%BF%9D%E5%AD%98%E5%9C%BA%E6%99%AF.png",[109,1174,1175],{"id":1175},"设置碰撞体",[10,1177,1178,1179,1181,1182,1185],{},"选中 ",[140,1180,1137],{},"，在右侧面板选一个形状(矩形：",[140,1183,1184],{},"RectangleShape2D",")，缩放到和图片差不多大：",[10,1187,1188],{},[80,1189],{"alt":1190,"src":1191},"02-create-bird-碰撞体1","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F02-create-bird-%E7%A2%B0%E6%92%9E%E4%BD%931.png",[10,1193,1194],{},"这个蓝色矩形就是碰撞体，游戏里小鸟撞到东西靠的就是它：",[10,1196,1197],{},[80,1198],{"alt":1199,"src":1200},"02-create-bird-碰撞体2","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F02-create-bird-%E7%A2%B0%E6%92%9E%E4%BD%932.png",[60,1202,1203],{"id":1203},"编写脚本",[10,1205,1178,1206,1208],{},[140,1207,1123],{}," 节点，点击右上角的 📜 图标创建脚本：",[10,1210,1211],{},[80,1212],{"alt":1213,"src":1214},"02-create-bird-创建脚本","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F02-create-bird-%E5%88%9B%E5%BB%BA%E8%84%9A%E6%9C%AC.png",[10,1216,1217,1218],{},"同样建一个文件夹专门放脚本：",[140,1219,1220],{},"scripts\u002Fbird.gd",[10,1222,1223],{},[80,1224],{"alt":1225,"src":1226},"02-create-bird-脚本","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F02-create-bird-%E8%84%9A%E6%9C%AC.png",[251,1228,1232],{"className":1229,"code":1230,"language":1231,"meta":259,"style":259},"language-gdscript shiki shiki-themes vitesse-light vitesse-dark","extends CharacterBody2D # 创建时自带的\n\n@export var gravity_scale := 2.0       # 重力倍率（控制掉落快慢）\n@export var jump_force := -500.0       # 跳跃力量（负值 = 向上）\n@export var max_fall_speed := 1000.0   # 最大掉落速度\n\nfunc _physics_process(delta: float) -> void:\n    # 1. 施加重力：每帧让小鸟往下掉\n    if not is_on_floor():\n        velocity += get_gravity() * delta * gravity_scale\n\n    # 2. 限速：防止掉太快\n    if velocity.y > max_fall_speed:\n        velocity.y = max_fall_speed\n\n    # 3. 按键跳跃：按下就给一个向上的速度\n    if Input.is_action_just_pressed(\"fly\"):\n        velocity.y = jump_force\n\n    move_and_slide()\n","gdscript",[140,1233,1234,1247,1251,1274,1294,1311,1315,1349,1355,1372,1397,1402,1408,1429,1444,1449,1455,1477,1491,1496],{"__ignoreMap":259},[277,1235,1236,1240,1244],{"class":279,"line":280},[277,1237,1239],{"class":1238},"sTPum","extends",[277,1241,1243],{"class":1242},"s_NWU"," CharacterBody2D",[277,1245,1246],{"class":283}," # 创建时自带的\n",[277,1248,1249],{"class":279,"line":287},[277,1250,320],{"emptyLinePlaceholder":319},[277,1252,1253,1256,1260,1264,1268,1271],{"class":279,"line":298},[277,1254,1255],{"class":301},"@export",[277,1257,1259],{"class":1258},"s5TCs"," var",[277,1261,1263],{"class":1262},"s9nN2"," gravity_scale",[277,1265,1267],{"class":1266},"si6no"," :=",[277,1269,1270],{"class":312}," 2.0",[277,1272,1273],{"class":283},"       # 重力倍率（控制掉落快慢）\n",[277,1275,1276,1278,1280,1283,1285,1288,1291],{"class":279,"line":316},[277,1277,1255],{"class":301},[277,1279,1259],{"class":1258},[277,1281,1282],{"class":1262}," jump_force",[277,1284,1267],{"class":1266},[277,1286,1287],{"class":1258}," -",[277,1289,1290],{"class":312},"500.0",[277,1292,1293],{"class":283},"       # 跳跃力量（负值 = 向上）\n",[277,1295,1296,1298,1300,1303,1305,1308],{"class":279,"line":323},[277,1297,1255],{"class":301},[277,1299,1259],{"class":1258},[277,1301,1302],{"class":1262}," max_fall_speed",[277,1304,1267],{"class":1266},[277,1306,1307],{"class":312}," 1000.0",[277,1309,1310],{"class":283},"   # 最大掉落速度\n",[277,1312,1313],{"class":279,"line":329},[277,1314,320],{"emptyLinePlaceholder":319},[277,1316,1318,1321,1324,1327,1331,1334,1337,1340,1343,1346],{"class":279,"line":1317},7,[277,1319,1320],{"class":1258},"func",[277,1322,1323],{"class":301}," _physics_process",[277,1325,1326],{"class":1266},"(",[277,1328,1330],{"class":1329},"s8w-G","delta",[277,1332,1333],{"class":1266},":",[277,1335,1336],{"class":1242}," float",[277,1338,1339],{"class":1266},")",[277,1341,1342],{"class":1258}," ->",[277,1344,1345],{"class":1242}," void",[277,1347,1348],{"class":1266},":\n",[277,1350,1352],{"class":279,"line":1351},8,[277,1353,1354],{"class":283},"    # 1. 施加重力：每帧让小鸟往下掉\n",[277,1356,1358,1361,1364,1367,1370],{"class":279,"line":1357},9,[277,1359,1360],{"class":1238},"    if",[277,1362,1363],{"class":1258}," not",[277,1365,1366],{"class":301}," is_on_floor",[277,1368,1369],{"class":1266},"()",[277,1371,1348],{"class":1329},[277,1373,1375,1378,1381,1384,1386,1389,1392,1394],{"class":279,"line":1374},10,[277,1376,1377],{"class":1262},"        velocity",[277,1379,1380],{"class":1258}," +=",[277,1382,1383],{"class":301}," get_gravity",[277,1385,1369],{"class":1266},[277,1387,1388],{"class":1258}," *",[277,1390,1391],{"class":1262}," delta",[277,1393,1388],{"class":1258},[277,1395,1396],{"class":1262}," gravity_scale\n",[277,1398,1400],{"class":279,"line":1399},11,[277,1401,320],{"emptyLinePlaceholder":319},[277,1403,1405],{"class":279,"line":1404},12,[277,1406,1407],{"class":283},"    # 2. 限速：防止掉太快\n",[277,1409,1411,1413,1416,1419,1422,1425,1427],{"class":279,"line":1410},13,[277,1412,1360],{"class":1238},[277,1414,1415],{"class":1262}," velocity",[277,1417,1418],{"class":1266},".",[277,1420,1421],{"class":1262},"y",[277,1423,1424],{"class":1258}," >",[277,1426,1302],{"class":1262},[277,1428,1348],{"class":1329},[277,1430,1432,1434,1436,1438,1441],{"class":279,"line":1431},14,[277,1433,1377],{"class":1262},[277,1435,1418],{"class":1266},[277,1437,1421],{"class":1262},[277,1439,1440],{"class":1266}," =",[277,1442,1443],{"class":1262}," max_fall_speed\n",[277,1445,1447],{"class":279,"line":1446},15,[277,1448,320],{"emptyLinePlaceholder":319},[277,1450,1452],{"class":279,"line":1451},16,[277,1453,1454],{"class":283},"    # 3. 按键跳跃：按下就给一个向上的速度\n",[277,1456,1458,1460,1463,1465,1468,1470,1473,1475],{"class":279,"line":1457},17,[277,1459,1360],{"class":1238},[277,1461,1462],{"class":1242}," Input",[277,1464,1418],{"class":1329},[277,1466,1467],{"class":301},"is_action_just_pressed",[277,1469,1326],{"class":1266},[277,1471,1472],{"class":294},"\"fly\"",[277,1474,1339],{"class":1266},[277,1476,1348],{"class":1329},[277,1478,1480,1482,1484,1486,1488],{"class":279,"line":1479},18,[277,1481,1377],{"class":1262},[277,1483,1418],{"class":1266},[277,1485,1421],{"class":1262},[277,1487,1440],{"class":1266},[277,1489,1490],{"class":1262}," jump_force\n",[277,1492,1494],{"class":279,"line":1493},19,[277,1495,320],{"emptyLinePlaceholder":319},[277,1497,1499,1502],{"class":279,"line":1498},20,[277,1500,1501],{"class":301},"    move_and_slide",[277,1503,1504],{"class":1266},"()\n",[109,1506,1508],{"id":1507},"这段代码在干嘛","这段代码在干嘛？",[251,1510,1512],{"className":1229,"code":1511,"language":1231,"meta":259,"style":259},"extends CharacterBody2D # 创建时自带的\n",[140,1513,1514],{"__ignoreMap":259},[277,1515,1516,1518,1520],{"class":279,"line":280},[277,1517,1239],{"class":1238},[277,1519,1243],{"class":1242},[277,1521,1246],{"class":283},[634,1523,1524],{},[25,1525,1526,1527,1529],{},"为 ",[140,1528,1123],{}," 节点创建脚本时会自带的代码。告诉 Godot：这个脚本是给「角色」用的（一般不用理会）。",[251,1531,1533],{"className":1229,"code":1532,"language":1231,"meta":259,"style":259},"@export var gravity_scale := 2.0       # 重力倍率（控制掉落快慢）\n@export var jump_force := -500.0       # 跳跃力量（负值 = 向上）\n@export var max_fall_speed := 1000.0   # 最大掉落速度\n",[140,1534,1535,1549,1565],{"__ignoreMap":259},[277,1536,1537,1539,1541,1543,1545,1547],{"class":279,"line":280},[277,1538,1255],{"class":301},[277,1540,1259],{"class":1258},[277,1542,1263],{"class":1262},[277,1544,1267],{"class":1266},[277,1546,1270],{"class":312},[277,1548,1273],{"class":283},[277,1550,1551,1553,1555,1557,1559,1561,1563],{"class":279,"line":287},[277,1552,1255],{"class":301},[277,1554,1259],{"class":1258},[277,1556,1282],{"class":1262},[277,1558,1267],{"class":1266},[277,1560,1287],{"class":1258},[277,1562,1290],{"class":312},[277,1564,1293],{"class":283},[277,1566,1567,1569,1571,1573,1575,1577],{"class":279,"line":298},[277,1568,1255],{"class":301},[277,1570,1259],{"class":1258},[277,1572,1302],{"class":1262},[277,1574,1267],{"class":1266},[277,1576,1307],{"class":312},[277,1578,1310],{"class":283},[634,1580,1581,1587,1593,1599],{},[25,1582,1583,1586],{},[140,1584,1585],{},"var"," 定义变量",[25,1588,1589,1592],{},[140,1590,1591],{},"jump_force"," 等是自定义的变量名，",[25,1594,1595,1598],{},[140,1596,1597],{},"-500.0"," 是我们定义的数值！",[25,1600,1601,1603],{},[140,1602,1255],{}," 加在前面是为了让 Godot 把变量放到面板上，方便我们后续调整。👇",[10,1605,1606],{},[80,1607],{"alt":1608,"src":1609},"02-create-bird-右侧面板","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F02-create-bird-%E5%8F%B3%E4%BE%A7%E9%9D%A2%E6%9D%BF.png",[10,1611,1612],{},"它就是我们说的「数值」，例如一般游戏中用来定义角色的「攻击力」「血量」......",[251,1614,1616],{"className":1229,"code":1615,"language":1231,"meta":259,"style":259},"func _physics_process(delta: float) -> void:\n",[140,1617,1618],{"__ignoreMap":259},[277,1619,1620,1622,1624,1626,1628,1630,1632,1634,1636,1638],{"class":279,"line":280},[277,1621,1320],{"class":1258},[277,1623,1323],{"class":301},[277,1625,1326],{"class":1266},[277,1627,1330],{"class":1329},[277,1629,1333],{"class":1266},[277,1631,1336],{"class":1242},[277,1633,1339],{"class":1266},[277,1635,1342],{"class":1258},[277,1637,1345],{"class":1242},[277,1639,1348],{"class":1266},[634,1641,1642,1647],{},[25,1643,1644,1646],{},[140,1645,1320],{},"： 用来定义方法",[25,1648,1649,1652],{},[140,1650,1651],{},"_physics_process(delta)","：游戏每一帧都会自动执行这个函数（大约每秒 60 次）",[251,1654,1656],{"className":1229,"code":1655,"language":1231,"meta":259,"style":259},"func _physics_process(delta: float) -> void:\n    # 1. 施加重力：每帧让小鸟往下掉\n    if not is_on_floor():\n        velocity += get_gravity() * delta * gravity_scale\n\n    # 2. 限速：防止掉太快\n    if velocity.y > max_fall_speed:\n        velocity.y = max_fall_speed\n\n    # 3. 按键跳跃：按下就给一个向上的速度\n    if Input.is_action_just_pressed(\"fly\"):\n        velocity.y = jump_force\n\n    move_and_slide()\n",[140,1657,1658,1680,1684,1696,1714,1718,1722,1738,1750,1754,1758,1776,1788,1792],{"__ignoreMap":259},[277,1659,1660,1662,1664,1666,1668,1670,1672,1674,1676,1678],{"class":279,"line":280},[277,1661,1320],{"class":1258},[277,1663,1323],{"class":301},[277,1665,1326],{"class":1266},[277,1667,1330],{"class":1329},[277,1669,1333],{"class":1266},[277,1671,1336],{"class":1242},[277,1673,1339],{"class":1266},[277,1675,1342],{"class":1258},[277,1677,1345],{"class":1242},[277,1679,1348],{"class":1266},[277,1681,1682],{"class":279,"line":287},[277,1683,1354],{"class":283},[277,1685,1686,1688,1690,1692,1694],{"class":279,"line":298},[277,1687,1360],{"class":1238},[277,1689,1363],{"class":1258},[277,1691,1366],{"class":301},[277,1693,1369],{"class":1266},[277,1695,1348],{"class":1329},[277,1697,1698,1700,1702,1704,1706,1708,1710,1712],{"class":279,"line":316},[277,1699,1377],{"class":1262},[277,1701,1380],{"class":1258},[277,1703,1383],{"class":301},[277,1705,1369],{"class":1266},[277,1707,1388],{"class":1258},[277,1709,1391],{"class":1262},[277,1711,1388],{"class":1258},[277,1713,1396],{"class":1262},[277,1715,1716],{"class":279,"line":323},[277,1717,320],{"emptyLinePlaceholder":319},[277,1719,1720],{"class":279,"line":329},[277,1721,1407],{"class":283},[277,1723,1724,1726,1728,1730,1732,1734,1736],{"class":279,"line":1317},[277,1725,1360],{"class":1238},[277,1727,1415],{"class":1262},[277,1729,1418],{"class":1266},[277,1731,1421],{"class":1262},[277,1733,1424],{"class":1258},[277,1735,1302],{"class":1262},[277,1737,1348],{"class":1329},[277,1739,1740,1742,1744,1746,1748],{"class":279,"line":1351},[277,1741,1377],{"class":1262},[277,1743,1418],{"class":1266},[277,1745,1421],{"class":1262},[277,1747,1440],{"class":1266},[277,1749,1443],{"class":1262},[277,1751,1752],{"class":279,"line":1357},[277,1753,320],{"emptyLinePlaceholder":319},[277,1755,1756],{"class":279,"line":1374},[277,1757,1454],{"class":283},[277,1759,1760,1762,1764,1766,1768,1770,1772,1774],{"class":279,"line":1399},[277,1761,1360],{"class":1238},[277,1763,1462],{"class":1242},[277,1765,1418],{"class":1329},[277,1767,1467],{"class":301},[277,1769,1326],{"class":1266},[277,1771,1472],{"class":294},[277,1773,1339],{"class":1266},[277,1775,1348],{"class":1329},[277,1777,1778,1780,1782,1784,1786],{"class":279,"line":1404},[277,1779,1377],{"class":1262},[277,1781,1418],{"class":1266},[277,1783,1421],{"class":1262},[277,1785,1440],{"class":1266},[277,1787,1490],{"class":1262},[277,1789,1790],{"class":279,"line":1410},[277,1791,320],{"emptyLinePlaceholder":319},[277,1793,1794,1796],{"class":279,"line":1431},[277,1795,1501],{"class":301},[277,1797,1504],{"class":1266},[10,1799,1800],{},"其中：",[251,1802,1804],{"className":1229,"code":1803,"language":1231,"meta":259,"style":259},"# 1. 施加重力：每帧让小鸟往下掉\nif not is_on_floor():\n    velocity += get_gravity() * delta * gravity_scale\n",[140,1805,1806,1811,1824],{"__ignoreMap":259},[277,1807,1808],{"class":279,"line":280},[277,1809,1810],{"class":283},"# 1. 施加重力：每帧让小鸟往下掉\n",[277,1812,1813,1816,1818,1820,1822],{"class":279,"line":287},[277,1814,1815],{"class":1238},"if",[277,1817,1363],{"class":1258},[277,1819,1366],{"class":301},[277,1821,1369],{"class":1266},[277,1823,1348],{"class":1329},[277,1825,1826,1829,1831,1833,1835,1837,1839,1841],{"class":279,"line":298},[277,1827,1828],{"class":1262},"    velocity",[277,1830,1380],{"class":1258},[277,1832,1383],{"class":301},[277,1834,1369],{"class":1266},[277,1836,1388],{"class":1258},[277,1838,1391],{"class":1262},[277,1840,1388],{"class":1258},[277,1842,1396],{"class":1262},[634,1844,1845,1873],{},[25,1846,1847,1849,1850],{},[140,1848,1815],{}," 是用来【判断】的\n",[634,1851,1852,1858,1867],{},[25,1853,1854,1857],{},[140,1855,1856],{},"not"," 是否定句",[25,1859,1860,1863,1864,1866],{},[140,1861,1862],{},"is_on_floor()"," 是 Godot引擎 为 ",[140,1865,1123],{},"节点赋予的属性",[25,1868,1869,1872],{},[140,1870,1871],{},"if not is_on_floor():"," 的意思是“如果 ‘角色’ 没有 站在地面时”\n是的，如果角色没有在地面，那就让角色往下掉！但是要以什么速度往下掉呢？",[25,1874,1875,1878],{},[140,1876,1877],{},"velocity += get_gravity() * delta * gravity_scale",[634,1879,1880,1896,1902,1907,1913],{},[25,1881,1882,1885,1886,1888,1889,1892,1893,1895],{},[140,1883,1884],{},"velocity"," 是 Godot 给 ",[140,1887,1123],{}," 内置的「速度」属性。它是一个二维数值，有 ",[140,1890,1891],{},"x","（左右速度）和 ",[140,1894,1421],{},"（上下速度）两个分量。初始值是 0（小鸟不动）",[25,1897,1898,1901],{},[140,1899,1900],{},"get_gravity()"," 是 Godot 内置方法，返回项目设置里的「重力」值（默认向下 980）",[25,1903,1904,1906],{},[140,1905,1330],{}," 是「上一帧到这一帧的时间间隔」（秒）。乘上它能保证不同帧率（60fps \u002F 120fps）下游戏速度一致",[25,1908,1909,1912],{},[140,1910,1911],{},"gravity_scale"," 是我们自己定义的「重力倍率」，越大掉得越快",[25,1914,1915,1918,1919],{},[140,1916,1917],{},"+="," 是「累加」的意思，等价于 ",[140,1920,1921],{},"velocity = velocity + ...",[10,1923,1924,1925,1931],{},"所以这一行的意思是：",[14,1926,1927,1928,1930],{},"每一帧给 ",[140,1929,1884],{}," 累加一点点重力","。速度越累越大 → 小鸟越掉越快，这就是物理课上学过的「自由落体」。",[251,1933,1935],{"className":1229,"code":1934,"language":1231,"meta":259,"style":259},"# 2. 限速：防止掉太快\nif velocity.y > max_fall_speed:\n    velocity.y = max_fall_speed\n",[140,1936,1937,1942,1958],{"__ignoreMap":259},[277,1938,1939],{"class":279,"line":280},[277,1940,1941],{"class":283},"# 2. 限速：防止掉太快\n",[277,1943,1944,1946,1948,1950,1952,1954,1956],{"class":279,"line":287},[277,1945,1815],{"class":1238},[277,1947,1415],{"class":1262},[277,1949,1418],{"class":1266},[277,1951,1421],{"class":1262},[277,1953,1424],{"class":1258},[277,1955,1302],{"class":1262},[277,1957,1348],{"class":1329},[277,1959,1960,1962,1964,1966,1968],{"class":279,"line":298},[277,1961,1828],{"class":1262},[277,1963,1418],{"class":1266},[277,1965,1421],{"class":1262},[277,1967,1440],{"class":1266},[277,1969,1443],{"class":1262},[634,1971,1972,1982,1992],{},[25,1973,1974,1977,1978,1981],{},[140,1975,1976],{},"velocity.y"," 就是上下方向的速度（",[14,1979,1980],{},"往下为正、往上为负","，这是 Godot 的坐标系约定）",[25,1983,1984,1985,1987,1988,1991],{},"如果当前下落速度 ",[140,1986,1976],{}," 超过了我们设的最大值 ",[140,1989,1990],{},"max_fall_speed","，就把它「卡」在最大值上",[25,1993,1994,1997],{},[14,1995,1996],{},"为什么需要？"," 因为重力会一直累加，没有这个限制小鸟会越掉越快，最后快得看不见",[251,1999,2001],{"className":1229,"code":2000,"language":1231,"meta":259,"style":259},"# 3. 按键跳跃：按下就给一个向上的速度\nif Input.is_action_just_pressed(\"fly\"):\n    velocity.y = jump_force\n",[140,2002,2003,2008,2026],{"__ignoreMap":259},[277,2004,2005],{"class":279,"line":280},[277,2006,2007],{"class":283},"# 3. 按键跳跃：按下就给一个向上的速度\n",[277,2009,2010,2012,2014,2016,2018,2020,2022,2024],{"class":279,"line":287},[277,2011,1815],{"class":1238},[277,2013,1462],{"class":1242},[277,2015,1418],{"class":1329},[277,2017,1467],{"class":301},[277,2019,1326],{"class":1266},[277,2021,1472],{"class":294},[277,2023,1339],{"class":1266},[277,2025,1348],{"class":1329},[277,2027,2028,2030,2032,2034,2036],{"class":279,"line":298},[277,2029,1828],{"class":1262},[277,2031,1418],{"class":1266},[277,2033,1421],{"class":1262},[277,2035,1440],{"class":1266},[277,2037,1490],{"class":1262},[634,2039,2040,2050,2062],{},[25,2041,2042,2045,2046,2049],{},[140,2043,2044],{},"Input.is_action_just_pressed(\"fly\")"," 检测「fly」这个按键",[14,2047,2048],{},"刚刚被按下","（一次按下只触发一次，长按不会连续触发）",[25,2051,2052,2053,2055,2056,2058,2059,2061],{},"一旦按下，直接把 ",[140,2054,1976],{}," 设为 ",[140,2057,1591],{},"（前面定义的 ",[140,2060,1597],{},"，负数 = 向上）",[25,2063,2064,2065,2068],{},"这就是「跳跃」：瞬间把速度变成向上，",[14,2066,2067],{},"覆盖掉","之前累加的下落速度",[45,2070,2071],{},[10,2072,2073],{},"「fly」是什么按键？我们还没告诉 Godot，下面就来配置快捷键。",[251,2075,2077],{"className":1229,"code":2076,"language":1231,"meta":259,"style":259},"move_and_slide()\n",[140,2078,2079],{"__ignoreMap":259},[277,2080,2081,2084],{"class":279,"line":280},[277,2082,2083],{"class":301},"move_and_slide",[277,2085,1504],{"class":1266},[634,2087,2088,2094,2103],{},[25,2089,2090,2091,2093],{},"Godot 给 ",[140,2092,1123],{}," 内置的方法",[25,2095,2096,2102],{},[14,2097,2098,2099,2101],{},"根据 ",[140,2100,1884],{}," 真正移动小鸟","，并自动处理碰撞（撞到东西就停下来）",[25,2104,2105,2106,2108],{},"没有这一行，前面算好的 ",[140,2107,1884],{}," 都只是「空想」，小鸟不会真的动起来",[109,2110,2111],{"id":2111},"配置跳跃按键",[10,2113,2114,2115,2117],{},"代码里用了 ",[140,2116,2044],{},"，所以我们需要在项目设置里添加这个按键映射：",[10,2119,2120,2121],{},"路径：项目 -> 项目设置 -> 输入映射 (Input Map)\n",[80,2122],{"alt":2123,"src":2124},"02-create-bird-配置跳跃按键","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F02-create-bird-%E9%85%8D%E7%BD%AE%E8%B7%B3%E8%B7%83%E6%8C%89%E9%94%AE.png",[22,2126,2127,2134],{},[25,2128,2129,2130,2133],{},"在上方输入 ",[140,2131,2132],{},"fly","，点击「添加」",[25,2135,2136,2137,2139,2140,2143],{},"点击 ",[140,2138,2132],{}," 右边的 ",[140,2141,2142],{},"+","，按下你想要的按键（比如空格键）",[10,2145,2146],{},"这样按空格键小鸟就会往上跳了！不过目前这只小鸟还没有舞台，下一章，我们将为它搭建一个可以飞的场景。",[854,2148,2149],{},"html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s_NWU, html code.shiki .s_NWU{--shiki-default:#2E8F82;--shiki-dark:#5DA994}html pre.shiki code .snYqZ, html code.shiki .snYqZ{--shiki-default:#A0ADA0;--shiki-dark:#758575DD}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .s5TCs, html code.shiki .s5TCs{--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .sqbOQ, html code.shiki .sqbOQ{--shiki-default:#2F798A;--shiki-dark:#4C9A91}html pre.shiki code .s8w-G, html code.shiki .s8w-G{--shiki-default:#393A34;--shiki-dark:#DBD7CAEE}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":259,"searchDepth":287,"depth":287,"links":2151},[2152,2153,2157],{"id":1102,"depth":287,"text":1103},{"id":1113,"depth":287,"text":1097,"children":2154},[2155,2156],{"id":1157,"depth":298,"text":1157},{"id":1175,"depth":298,"text":1175},{"id":1203,"depth":287,"text":1203,"children":2158},[2159,2160],{"id":1507,"depth":298,"text":1508},{"id":2111,"depth":298,"text":2111},{},"\u002Fdevlog\u002Fxggame-bird\u002F02-create-bird",{"title":1097,"description":878},"devlog\u002Fxggame-bird\u002F02-create-bird","4ulFauc0NCvEFhdOh3vFco0Uc_Za6ZBezoBXrpjuq6s",{"id":2167,"title":2168,"body":2169,"cover":876,"date":877,"description":878,"extension":879,"game":410,"github":880,"icon":876,"meta":2857,"navigation":319,"path":2858,"seo":2859,"stem":2860,"toc":319,"__hash__":2861},"devlog\u002Fdevlog\u002Fxggame-bird\u002F03-game-scene.md","游戏场景",{"type":7,"value":2170,"toc":2847},[2171,2174,2177,2180,2195,2198,2213,2216,2223,2227,2238,2242,2253,2256,2261,2267,2270,2273,2280,2282,2305,2311,2321,2327,2348,2351,2354,2365,2368,2371,2374,2377,2397,2404,2825,2837,2841,2844],[10,2172,2173],{},"这一章我们来完善游戏世界：搭建舞台、调整摄像机、加边界。",[60,2175,2176],{"id":2176},"创建游戏场景",[10,2178,2179],{},"小鸟做好了，但现在还没有一个「舞台」让它表演。我们需要创建一个游戏主场景，把小鸟放进去，再加一个摄像机来\"看\"它。",[10,2181,2182,2183,2186,2187,2190,2191],{},"新建场景，根节点选 ",[140,2184,2185],{},"Node2D","，保存为 ",[140,2188,2189],{},"scenes\u002Fgame.tscn","：\n",[80,2192],{"alt":2193,"src":2194},"03-game-scene-创建游戏场景","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F03-game-scene-%E5%88%9B%E5%BB%BA%E6%B8%B8%E6%88%8F%E5%9C%BA%E6%99%AF.png",[10,2196,2197],{},"使用到的节点：",[634,2199,2200],{},[25,2201,2202,2204,2205],{},[140,2203,2185],{}," — 游戏主场景\n",[634,2206,2207],{},[25,2208,2209,2212],{},[140,2210,2211],{},"Camera2D"," — 摄像机（决定屏幕看到哪里）",[60,2214,2215],{"id":2215},"调整摄像机位置",[10,2217,2218,2219],{},"调整摄像机（固定）位置：\n",[80,2220],{"alt":2221,"src":2222},"03-game-scene-调整摄像机位置","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F03-game-scene-%E8%B0%83%E6%95%B4%E6%91%84%E5%83%8F%E6%9C%BA%E4%BD%8D%E7%BD%AE.png",[60,2224,2226],{"id":2225},"导入小鸟角色","导入“小鸟”角色",[10,2228,2229,2230,2233,2234],{},"把之前做好的 ",[140,2231,2232],{},"bird.tscn"," 拖进场景中，小鸟就出现了：\n",[80,2235],{"alt":2236,"src":2237},"03-game-scene-导入bird","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F03-game-scene-%E5%AF%BC%E5%85%A5bird.png",[60,2239,2241],{"id":2240},"设置主场景然后测试","设置【主场景】然后测试",[10,2243,2244,2245,2248,2249],{},"然后把 ",[140,2246,2247],{},"game.tscn"," 设置为【主场景】，点击运行游戏：\n",[80,2250],{"alt":2251,"src":2252},"03-game-scene-设置主场景","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F03-game-scene-%E8%AE%BE%E7%BD%AE%E4%B8%BB%E5%9C%BA%E6%99%AF.png",[10,2254,2255],{},"这时候小鸟就在游戏场景里了，点击【鼠标左键】或【空格键】小鸟就会起飞！",[45,2257,2258],{},[10,2259,2260],{},"确保你已经按上一章配置了【fly】按键映射！",[10,2262,2263],{},[80,2264],{"alt":2265,"src":2266},"03-game-scene-运行游戏","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F03-game-scene-%E8%BF%90%E8%A1%8C%E6%B8%B8%E6%88%8F.png",[10,2268,2269],{},"不过现在不按按键的话，小鸟就会一直往下掉，掉到屏幕外面去。别急，接下来我们会给游戏场景加上边界。",[60,2271,2272],{"id":2272},"添加边界",[10,2274,2275,2276,2279],{},"为了让小鸟不会掉出屏幕，我们给游戏世界加上",[14,2277,2278],{},"底部","边界。",[10,2281,2197],{},[634,2283,2284],{},[25,2285,2286,2289,2290],{},[140,2287,2288],{},"StaticBody2D"," — 静态物理体（不动，但能碰撞）\n",[634,2291,2292],{},[25,2293,2294,2296,2297],{},[140,2295,1137],{}," — 碰撞形状\n",[634,2298,2299],{},[25,2300,2301,2304],{},[140,2302,2303],{},"WorldBoundaryShape2D"," — 选择这个形状（世界边界）",[10,2306,2307],{},[80,2308],{"alt":2309,"src":2310},"03-game-scene-边界","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F03-game-scene-%E8%BE%B9%E7%95%8C.png",[10,2312,2313,2314,2316,2317,2320],{},"然后选择 ",[140,2315,2288],{}," 节点往下拖到边缘底部，因为后续要在该节点里",[14,2318,2319],{},"加入","【死亡区域（Killzone）】，这样才能触发游戏结束的代码。",[10,2322,2323],{},[80,2324],{"alt":2325,"src":2326},"03-game-scene-边界2","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F03-game-scene-%E8%BE%B9%E7%95%8C2.png",[22,2328,2329,2335,2345],{},[25,2330,2331,2332,2334],{},"先选择 ",[140,2333,2288],{}," 节点",[25,2336,2337,2338],{},"然后点击上方的【编辑所选节点】\n",[22,2339,2340],{},[25,2341,2342,2343,2334],{},"避免拖拽时不小心选中 ",[140,2344,1137],{},[25,2346,2347],{},"然后往下拖拽到摄像机和画面边缘。",[60,2349,2350],{"id":2350},"测试运行",[10,2352,2353],{},"会发现几个问题，不过都不是问题！",[22,2355,2356,2359,2362],{},[25,2357,2358],{},"小鸟在原地高低移动，没有往前飞！但是这是正常的，就是要小鸟在原地高低移动。后续我们只要设置背景和障碍物往左移动就会让玩家以为【小鸟】在往右👉飞。",[25,2360,2361],{},"小鸟没有点击【fly】就会倒地，但是游戏没有结束，别着急，后面就会完善代码！",[25,2363,2364],{},"与原版的小鸟有所不同，原本的《Flappy Bird》小鸟在飞的时候会有种“头重脚轻”的感觉：就是在飞的时候仰头，不飞的时候头垂下。在玩家连点的时候有种“扑腾”的感觉。",[10,2366,2367],{},"所以接下来我们就先来优化小鸟的代码脚本，让它可以“扑腾”",[60,2369,2370],{"id":2370},"优化小鸟的旋转",[10,2372,2373],{},"在原版 Flappy Bird 里，小鸟不是一直直挺挺的 — 往上飞的时候头会抬起来，往下掉的时候头会低下去，看起来更生动。",[10,2375,2376],{},"我们来实现这个效果，思路很简单：",[634,2378,2379,2385,2391],{},[25,2380,2381,2384],{},[14,2382,2383],{},"往上飞"," → 头朝上（抬头 -30°）",[25,2386,2387,2390],{},[14,2388,2389],{},"往下掉"," → 头朝下（低头 90°）",[25,2392,2393,2396],{},[14,2394,2395],{},"旋转要平滑过渡","，不能一下子就转过去（那样看起来很僵硬）",[10,2398,2399,2400,2403],{},"在 ",[140,2401,2402],{},"bird.gd"," 里加一个旋转函数：",[251,2405,2407],{"className":1229,"code":2406,"language":1231,"meta":259,"style":259},"extends CharacterBody2D\n\n@export var gravity_scale := 2.0\n@export var jump_force := -400.0\n@export var max_fall_speed := 1000.0\n@export var rotate_speed := 4.0   # 旋转过渡速度（越大越快）\n\nfunc _physics_process(delta: float) -> void:\n    # 重力\n    if not is_on_floor():\n        velocity += get_gravity() * delta * gravity_scale\n\n    # 限速\n    if velocity.y > max_fall_speed:\n        velocity.y = max_fall_speed\n\n    # 跳跃\n    if Input.is_action_just_pressed(\"fly\"):\n        velocity.y = 0\n        velocity.y = jump_force\n\n    # 旋转\n    handle_rotation(delta)\n    move_and_slide()\n\nfunc handle_rotation(delta: float) -> void:\n    var target_rotation = 0.0\n\n    if velocity.y \u003C 0:\n        # 往上飞 → 抬头\n        target_rotation = deg_to_rad(-30)\n    else:\n        # 往下掉 → 低头\n        target_rotation = deg_to_rad(90)\n\n    # lerp_angle：让旋转平滑过渡，不会一下子\"弹\"过去\n    rotation = lerp_angle(rotation, target_rotation, rotate_speed * delta)\n",[140,2408,2409,2416,2420,2433,2448,2461,2478,2482,2504,2509,2521,2539,2543,2548,2564,2576,2580,2585,2603,2616,2628,2633,2639,2652,2659,2664,2688,2702,2707,2726,2732,2753,2761,2767,2783,2788,2794],{"__ignoreMap":259},[277,2410,2411,2413],{"class":279,"line":280},[277,2412,1239],{"class":1238},[277,2414,2415],{"class":1242}," CharacterBody2D\n",[277,2417,2418],{"class":279,"line":287},[277,2419,320],{"emptyLinePlaceholder":319},[277,2421,2422,2424,2426,2428,2430],{"class":279,"line":298},[277,2423,1255],{"class":301},[277,2425,1259],{"class":1258},[277,2427,1263],{"class":1262},[277,2429,1267],{"class":1266},[277,2431,2432],{"class":312}," 2.0\n",[277,2434,2435,2437,2439,2441,2443,2445],{"class":279,"line":316},[277,2436,1255],{"class":301},[277,2438,1259],{"class":1258},[277,2440,1282],{"class":1262},[277,2442,1267],{"class":1266},[277,2444,1287],{"class":1258},[277,2446,2447],{"class":312},"400.0\n",[277,2449,2450,2452,2454,2456,2458],{"class":279,"line":323},[277,2451,1255],{"class":301},[277,2453,1259],{"class":1258},[277,2455,1302],{"class":1262},[277,2457,1267],{"class":1266},[277,2459,2460],{"class":312}," 1000.0\n",[277,2462,2463,2465,2467,2470,2472,2475],{"class":279,"line":329},[277,2464,1255],{"class":301},[277,2466,1259],{"class":1258},[277,2468,2469],{"class":1262}," rotate_speed",[277,2471,1267],{"class":1266},[277,2473,2474],{"class":312}," 4.0",[277,2476,2477],{"class":283},"   # 旋转过渡速度（越大越快）\n",[277,2479,2480],{"class":279,"line":1317},[277,2481,320],{"emptyLinePlaceholder":319},[277,2483,2484,2486,2488,2490,2492,2494,2496,2498,2500,2502],{"class":279,"line":1351},[277,2485,1320],{"class":1258},[277,2487,1323],{"class":301},[277,2489,1326],{"class":1266},[277,2491,1330],{"class":1329},[277,2493,1333],{"class":1266},[277,2495,1336],{"class":1242},[277,2497,1339],{"class":1266},[277,2499,1342],{"class":1258},[277,2501,1345],{"class":1242},[277,2503,1348],{"class":1266},[277,2505,2506],{"class":279,"line":1357},[277,2507,2508],{"class":283},"    # 重力\n",[277,2510,2511,2513,2515,2517,2519],{"class":279,"line":1374},[277,2512,1360],{"class":1238},[277,2514,1363],{"class":1258},[277,2516,1366],{"class":301},[277,2518,1369],{"class":1266},[277,2520,1348],{"class":1329},[277,2522,2523,2525,2527,2529,2531,2533,2535,2537],{"class":279,"line":1399},[277,2524,1377],{"class":1262},[277,2526,1380],{"class":1258},[277,2528,1383],{"class":301},[277,2530,1369],{"class":1266},[277,2532,1388],{"class":1258},[277,2534,1391],{"class":1262},[277,2536,1388],{"class":1258},[277,2538,1396],{"class":1262},[277,2540,2541],{"class":279,"line":1404},[277,2542,320],{"emptyLinePlaceholder":319},[277,2544,2545],{"class":279,"line":1410},[277,2546,2547],{"class":283},"    # 限速\n",[277,2549,2550,2552,2554,2556,2558,2560,2562],{"class":279,"line":1431},[277,2551,1360],{"class":1238},[277,2553,1415],{"class":1262},[277,2555,1418],{"class":1266},[277,2557,1421],{"class":1262},[277,2559,1424],{"class":1258},[277,2561,1302],{"class":1262},[277,2563,1348],{"class":1329},[277,2565,2566,2568,2570,2572,2574],{"class":279,"line":1446},[277,2567,1377],{"class":1262},[277,2569,1418],{"class":1266},[277,2571,1421],{"class":1262},[277,2573,1440],{"class":1266},[277,2575,1443],{"class":1262},[277,2577,2578],{"class":279,"line":1451},[277,2579,320],{"emptyLinePlaceholder":319},[277,2581,2582],{"class":279,"line":1457},[277,2583,2584],{"class":283},"    # 跳跃\n",[277,2586,2587,2589,2591,2593,2595,2597,2599,2601],{"class":279,"line":1479},[277,2588,1360],{"class":1238},[277,2590,1462],{"class":1242},[277,2592,1418],{"class":1329},[277,2594,1467],{"class":301},[277,2596,1326],{"class":1266},[277,2598,1472],{"class":294},[277,2600,1339],{"class":1266},[277,2602,1348],{"class":1329},[277,2604,2605,2607,2609,2611,2613],{"class":279,"line":1493},[277,2606,1377],{"class":1262},[277,2608,1418],{"class":1266},[277,2610,1421],{"class":1262},[277,2612,1440],{"class":1266},[277,2614,2615],{"class":312}," 0\n",[277,2617,2618,2620,2622,2624,2626],{"class":279,"line":1498},[277,2619,1377],{"class":1262},[277,2621,1418],{"class":1266},[277,2623,1421],{"class":1262},[277,2625,1440],{"class":1266},[277,2627,1490],{"class":1262},[277,2629,2631],{"class":279,"line":2630},21,[277,2632,320],{"emptyLinePlaceholder":319},[277,2634,2636],{"class":279,"line":2635},22,[277,2637,2638],{"class":283},"    # 旋转\n",[277,2640,2642,2645,2647,2649],{"class":279,"line":2641},23,[277,2643,2644],{"class":301},"    handle_rotation",[277,2646,1326],{"class":1266},[277,2648,1330],{"class":1262},[277,2650,2651],{"class":1266},")\n",[277,2653,2655,2657],{"class":279,"line":2654},24,[277,2656,1501],{"class":301},[277,2658,1504],{"class":1266},[277,2660,2662],{"class":279,"line":2661},25,[277,2663,320],{"emptyLinePlaceholder":319},[277,2665,2667,2669,2672,2674,2676,2678,2680,2682,2684,2686],{"class":279,"line":2666},26,[277,2668,1320],{"class":1258},[277,2670,2671],{"class":301}," handle_rotation",[277,2673,1326],{"class":1266},[277,2675,1330],{"class":1329},[277,2677,1333],{"class":1266},[277,2679,1336],{"class":1242},[277,2681,1339],{"class":1266},[277,2683,1342],{"class":1258},[277,2685,1345],{"class":1242},[277,2687,1348],{"class":1266},[277,2689,2691,2694,2697,2699],{"class":279,"line":2690},27,[277,2692,2693],{"class":1258},"    var",[277,2695,2696],{"class":1262}," target_rotation",[277,2698,1440],{"class":1266},[277,2700,2701],{"class":312}," 0.0\n",[277,2703,2705],{"class":279,"line":2704},28,[277,2706,320],{"emptyLinePlaceholder":319},[277,2708,2710,2712,2714,2716,2718,2721,2724],{"class":279,"line":2709},29,[277,2711,1360],{"class":1238},[277,2713,1415],{"class":1262},[277,2715,1418],{"class":1266},[277,2717,1421],{"class":1262},[277,2719,2720],{"class":1258}," \u003C",[277,2722,2723],{"class":312}," 0",[277,2725,1348],{"class":1329},[277,2727,2729],{"class":279,"line":2728},30,[277,2730,2731],{"class":283},"        # 往上飞 → 抬头\n",[277,2733,2735,2738,2740,2743,2745,2748,2751],{"class":279,"line":2734},31,[277,2736,2737],{"class":1262},"        target_rotation",[277,2739,1440],{"class":1266},[277,2741,2742],{"class":301}," deg_to_rad",[277,2744,1326],{"class":1266},[277,2746,2747],{"class":1258},"-",[277,2749,2750],{"class":312},"30",[277,2752,2651],{"class":1266},[277,2754,2756,2759],{"class":279,"line":2755},32,[277,2757,2758],{"class":1238},"    else",[277,2760,1348],{"class":1329},[277,2762,2764],{"class":279,"line":2763},33,[277,2765,2766],{"class":283},"        # 往下掉 → 低头\n",[277,2768,2770,2772,2774,2776,2778,2781],{"class":279,"line":2769},34,[277,2771,2737],{"class":1262},[277,2773,1440],{"class":1266},[277,2775,2742],{"class":301},[277,2777,1326],{"class":1266},[277,2779,2780],{"class":312},"90",[277,2782,2651],{"class":1266},[277,2784,2786],{"class":279,"line":2785},35,[277,2787,320],{"emptyLinePlaceholder":319},[277,2789,2791],{"class":279,"line":2790},36,[277,2792,2793],{"class":283},"    # lerp_angle：让旋转平滑过渡，不会一下子\"弹\"过去\n",[277,2795,2797,2800,2802,2805,2807,2810,2813,2815,2817,2819,2821,2823],{"class":279,"line":2796},37,[277,2798,2799],{"class":1262},"    rotation",[277,2801,1440],{"class":1266},[277,2803,2804],{"class":301}," lerp_angle",[277,2806,1326],{"class":1266},[277,2808,2809],{"class":1262},"rotation",[277,2811,2812],{"class":1266},",",[277,2814,2696],{"class":1262},[277,2816,2812],{"class":1266},[277,2818,2469],{"class":1262},[277,2820,1388],{"class":1258},[277,2822,1391],{"class":1262},[277,2824,2651],{"class":1266},[45,2826,2827],{},[10,2828,2829,2832,2833,2836],{},[140,2830,2831],{},"lerp_angle"," 的作用就像是给旋转加了\"缓动\"，从当前角度慢慢转到目标角度，而不是瞬间跳过去。",[140,2834,2835],{},"rotate_speed"," 越大转得越快，可以自己调到手感合适为止。",[60,2838,2840],{"id":2839},"再次测试","再次测试！",[10,2842,2843],{},"再次运行游戏，看看小鸟是不是有了那种「扑腾扑腾」的灵动感？是不是和原版《Flappy Bird》一个味儿了 ✨",[854,2845,2846],{},"html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s_NWU, html code.shiki .s_NWU{--shiki-default:#2E8F82;--shiki-dark:#5DA994}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .s5TCs, html code.shiki .s5TCs{--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .sqbOQ, html code.shiki .sqbOQ{--shiki-default:#2F798A;--shiki-dark:#4C9A91}html pre.shiki code .snYqZ, html code.shiki .snYqZ{--shiki-default:#A0ADA0;--shiki-dark:#758575DD}html pre.shiki code .s8w-G, html code.shiki .s8w-G{--shiki-default:#393A34;--shiki-dark:#DBD7CAEE}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":259,"searchDepth":287,"depth":287,"links":2848},[2849,2850,2851,2852,2853,2854,2855,2856],{"id":2176,"depth":287,"text":2176},{"id":2215,"depth":287,"text":2215},{"id":2225,"depth":287,"text":2226},{"id":2240,"depth":287,"text":2241},{"id":2272,"depth":287,"text":2272},{"id":2350,"depth":287,"text":2350},{"id":2370,"depth":287,"text":2370},{"id":2839,"depth":287,"text":2840},{},"\u002Fdevlog\u002Fxggame-bird\u002F03-game-scene",{"title":2168,"description":878},"devlog\u002Fxggame-bird\u002F03-game-scene","8FuNcn0nabe5yIBK8x_umOGAktOPpJxM53aPIKn6ApQ",{"id":2863,"title":2864,"body":2865,"cover":876,"date":877,"description":878,"extension":879,"game":410,"github":880,"icon":876,"meta":4399,"navigation":319,"path":4400,"seo":4401,"stem":4402,"toc":319,"__hash__":4403},"devlog\u002Fdevlog\u002Fxggame-bird\u002F04-obstacles.md","障碍物",{"type":7,"value":2866,"toc":4382},[2867,2870,2890,2908,2912,2915,2937,2943,2952,2955,2962,2981,2988,2993,3059,3065,3077,3083,3086,3092,3099,3105,3109,3115,3128,3201,3228,3352,3356,3363,3369,3373,3376,3396,3402,3633,3639,3643,3688,3696,3701,3707,3712,3986,3988,3999,4001,4025,4031,4034,4040,4051,4057,4067,4073,4076,4277,4280,4333,4348,4350,4353,4367,4373,4379],[10,2868,2869],{},"Flappy Bird 最经典的玩法 — 上下成对的水管，小鸟要从中间缝隙穿过去。这一章我们做：",[22,2871,2872,2878,2884],{},[25,2873,2874,2877],{},[14,2875,2876],{},"障碍物本体"," — 上下两根水管 + 一个得分区",[25,2879,2880,2883],{},[14,2881,2882],{},"死区 Killzone"," — 撞上就死",[25,2885,2886,2889],{},[14,2887,2888],{},"障碍物生成器"," — 定时刷新、自动移动、屏幕外销毁",[45,2891,2892],{},[10,2893,2894,2895,2898,2899,2901,2902,2907],{},"这里用 ",[140,2896,2897],{},"Area2D","（死区）而不是 ",[140,2900,2288],{},"，因为 ",[14,2903,2904,2906],{},[140,2905,2897],{}," 不会真的\"卡住\"小鸟，只会触发信号","。这样我们就能在信号里写\"游戏结束\"的逻辑，而不是把小鸟物理地卡在水管里动弹不得。",[60,2909,2911],{"id":2910},"创建死区-killzone","创建死区 Killzone",[10,2913,2914],{},"先创建死区，使用到的节点：",[634,2916,2917],{},[25,2918,2919,2921,2922,2925,2926],{},[140,2920,2897],{}," — 死区根节点（重命名：",[140,2923,2924],{},"Killzone","）\n",[634,2927,2928],{},[25,2929,2930,2932,2933,2936],{},[140,2931,1137],{}," — 碰撞形状（",[14,2934,2935],{},"先留空","，下面会解释为什么）",[10,2938,2939],{},[80,2940],{"alt":2941,"src":2942},"04-obstacles-死区","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F04-obstacles-%E6%AD%BB%E5%8C%BA.png",[10,2944,2945,2947,2948,2951],{},[140,2946,1137],{}," 暂时",[14,2949,2950],{},"不要选形状","，因为后续这个死区既可以放到\"障碍物\"上，也可以放到\"地板\"上（小鸟没按飞掉地上也会 Game Over）。形状到时候根据位置再单独设置。",[60,2953,2954],{"id":2954},"障碍物的组成",[10,2956,2957,2958,2961],{},"每个障碍物由 ",[14,2959,2960],{},"3 个区域"," 组成：",[634,2963,2964,2970,2975],{},[25,2965,2966,2969],{},[14,2967,2968],{},"上水管","（Killzone — 死区）→ 撞到就死",[25,2971,2972,2969],{},[14,2973,2974],{},"下水管",[25,2976,2977,2980],{},[14,2978,2979],{},"中间的得分区","（Goal）→ 穿过加 1 分",[10,2982,2983,2984,2987],{},"这里我们同样先用 Godot 的默认素材（也就是默认的 ",[140,2985,2986],{},"icon.svg"," 来作为图片精灵图）",[45,2989,2990],{},[10,2991,2992],{},"等后续我们再一起更新美术资源素材！",[634,2994,2995],{},[25,2996,2997,2999,3000,2925,3003],{},[140,2998,2185],{},"（重命名：",[140,3001,3002],{},"PillarPair",[634,3004,3005,3024,3045],{},[25,3006,3007,3009,3010],{},[140,3008,2924],{},"（我们刚刚创建的死区）\n",[634,3011,3012,3019],{},[25,3013,3014,3016,3017,230],{},[140,3015,1131],{}," — 放入 Godot 默认素材（",[140,3018,2986],{},[25,3020,3021,3023],{},[140,3022,1137],{}," — 碰撞形状（矩形，调整到一根水管的大小）",[25,3025,3026,3029,3030,3032,3033],{},[140,3027,3028],{},"Killzone2","（复制 ",[140,3031,2924],{}," 一份，改名即可）\n",[634,3034,3035,3041],{},[25,3036,3037,3016,3039,230],{},[140,3038,1131],{},[140,3040,2986],{},[25,3042,3043,3023],{},[140,3044,1137],{},[25,3046,3047,2999,3049,2925,3052],{},[140,3048,2897],{},[140,3050,3051],{},"Goal",[634,3053,3054],{},[25,3055,3056,3058],{},[140,3057,1137],{}," — 得分区域",[10,3060,3061],{},[80,3062],{"alt":3063,"src":3064},"04-obstacles-阻碍物","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F04-obstacles-%E9%98%BB%E7%A2%8D%E7%89%A9.png",[10,3066,3067,3068,3070,3071,3073,3074,3076],{},"这里可以调整碰撞体的颜色，用红色来区分。\n然后在设置好一个 ",[140,3069,2924],{}," 里的 ",[140,3072,1131],{}," 和 ",[140,3075,1137],{}," 后可以复制",[10,3078,3079],{},[80,3080],{"alt":3081,"src":3082},"04-obstacles-阻碍物2","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F04-obstacles-%E9%98%BB%E7%A2%8D%E7%89%A92.png",[10,3084,3085],{},"然后调整两个的位置，在中间空出一道间隙。",[10,3087,3088],{},[80,3089],{"alt":3090,"src":3091},"04-obstacles-得分区域","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F04-obstacles-%E5%BE%97%E5%88%86%E5%8C%BA%E5%9F%9F.png",[10,3093,3094,3095,3098],{},"然后把得分区域",[14,3096,3097],{},"放在中间偏后","的位置，这样只有完全通过的时候才算得分！",[10,3100,3101,3102],{},"障碍物设置好后，就可以保存该场景为：",[140,3103,3104],{},"scenes\u002Fpillar_pair.tscn",[109,3106,3108],{"id":3107},"给-killzone-挂脚本","给 Killzone 挂脚本",[10,3110,3111],{},[80,3112],{"alt":3113,"src":3114},"04-obstacles-死区脚本","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F04-obstacles-%E6%AD%BB%E5%8C%BA%E8%84%9A%E6%9C%AC.png",[10,3116,1178,3117,3119,3120,3123,3124,3127],{},[140,3118,2897],{}," 节点（Killzone） → 右侧【节点】面板找到 ",[140,3121,3122],{},"body_entered"," 信号 → 双击 → 连接到自己。Godot 会自动生成 ",[140,3125,3126],{},"_on_body_entered"," 函数：",[251,3129,3131],{"className":1229,"code":3130,"language":1231,"meta":259,"style":259},"extends Area2D\n\nfunc _on_body_entered(body: Node2D) -> void:\n    if body.name == \"Bird\":\n        print(\"撞击死亡！\")\n",[140,3132,3133,3140,3144,3169,3189],{"__ignoreMap":259},[277,3134,3135,3137],{"class":279,"line":280},[277,3136,1239],{"class":1238},[277,3138,3139],{"class":1242}," Area2D\n",[277,3141,3142],{"class":279,"line":287},[277,3143,320],{"emptyLinePlaceholder":319},[277,3145,3146,3148,3151,3153,3156,3158,3161,3163,3165,3167],{"class":279,"line":298},[277,3147,1320],{"class":1258},[277,3149,3150],{"class":301}," _on_body_entered",[277,3152,1326],{"class":1266},[277,3154,3155],{"class":1329},"body",[277,3157,1333],{"class":1266},[277,3159,3160],{"class":1242}," Node2D",[277,3162,1339],{"class":1266},[277,3164,1342],{"class":1258},[277,3166,1345],{"class":1242},[277,3168,1348],{"class":1266},[277,3170,3171,3173,3176,3178,3181,3184,3187],{"class":279,"line":316},[277,3172,1360],{"class":1238},[277,3174,3175],{"class":1262}," body",[277,3177,1418],{"class":1266},[277,3179,3180],{"class":1262},"name",[277,3182,3183],{"class":1258}," ==",[277,3185,3186],{"class":294}," \"Bird\"",[277,3188,1348],{"class":1329},[277,3190,3191,3194,3196,3199],{"class":279,"line":323},[277,3192,3193],{"class":301},"        print",[277,3195,1326],{"class":1266},[277,3197,3198],{"class":294},"\"撞击死亡！\"",[277,3200,2651],{"class":1266},[634,3202,3203,3211,3217],{},[25,3204,3205,486,3207,3210],{},[140,3206,3122],{},[14,3208,3209],{},"任何物理体进入这个区域","时触发的信号",[25,3212,3213,3216],{},[140,3214,3215],{},"if body.name == \"Bird\":"," — 判断进来的是不是小鸟（防止其他东西误触发）",[25,3218,3219,3220,3223,3224,3227],{},"暂时用 ",[140,3221,3222],{},"print"," 打印日志，下一章做完 ",[140,3225,3226],{},"GameManager"," 后再换成真正的\"游戏结束\"逻辑",[45,3229,3230,3237,3339],{},[10,3231,3232,3233,3236],{},"💡 ",[14,3234,3235],{},"后续优化预览","（下一章 GameManager + 第 8 章 SoundManager 后会改成这样，现在不要这么写）：",[251,3238,3240],{"className":1229,"code":3239,"language":1231,"meta":259,"style":259},"extends Area2D\n\nfunc _on_body_entered(body: Node2D) -> void:\n    if body.name == \"Bird\" and GameManager.current_state == GameManager.GameState.PLAYING:\n        SoundManager.play_die()\n        GameManager.game_over()\n",[140,3241,3242,3248,3252,3274,3315,3327],{"__ignoreMap":259},[277,3243,3244,3246],{"class":279,"line":280},[277,3245,1239],{"class":1238},[277,3247,3139],{"class":1242},[277,3249,3250],{"class":279,"line":287},[277,3251,320],{"emptyLinePlaceholder":319},[277,3253,3254,3256,3258,3260,3262,3264,3266,3268,3270,3272],{"class":279,"line":298},[277,3255,1320],{"class":1258},[277,3257,3150],{"class":301},[277,3259,1326],{"class":1266},[277,3261,3155],{"class":1329},[277,3263,1333],{"class":1266},[277,3265,3160],{"class":1242},[277,3267,1339],{"class":1266},[277,3269,1342],{"class":1258},[277,3271,1345],{"class":1242},[277,3273,1348],{"class":1266},[277,3275,3276,3278,3280,3282,3284,3286,3288,3291,3294,3296,3299,3301,3303,3305,3308,3310,3313],{"class":279,"line":316},[277,3277,1360],{"class":1238},[277,3279,3175],{"class":1262},[277,3281,1418],{"class":1266},[277,3283,3180],{"class":1262},[277,3285,3183],{"class":1258},[277,3287,3186],{"class":294},[277,3289,3290],{"class":1258}," and",[277,3292,3293],{"class":1242}," GameManager",[277,3295,1418],{"class":1266},[277,3297,3298],{"class":1262},"current_state",[277,3300,3183],{"class":1258},[277,3302,3293],{"class":1242},[277,3304,1418],{"class":1266},[277,3306,3307],{"class":1262},"GameState",[277,3309,1418],{"class":1266},[277,3311,3312],{"class":1238},"PLAYING",[277,3314,1348],{"class":1329},[277,3316,3317,3320,3322,3325],{"class":279,"line":323},[277,3318,3319],{"class":1242},"        SoundManager",[277,3321,1418],{"class":1329},[277,3323,3324],{"class":301},"play_die",[277,3326,1504],{"class":1266},[277,3328,3329,3332,3334,3337],{"class":279,"line":329},[277,3330,3331],{"class":1242},"        GameManager",[277,3333,1418],{"class":1329},[277,3335,3336],{"class":301},"game_over",[277,3338,1504],{"class":1266},[634,3340,3341,3346],{},[25,3342,3343,3345],{},[140,3344,3226],{}," 是整个游戏的整体管理",[25,3347,3348,3351],{},[140,3349,3350],{},"SoundManager"," 是整个游戏的音效管理",[109,3353,3355],{"id":3354},"地板也可以变成-killzone","地板也可以变成 Killzone",[10,3357,3358,3359,3362],{},"之前第 3 章做的底部边界，",[14,3360,3361],{},"也可以变成死区"," — 小鸟掉到地上就算游戏结束。",[10,3364,3365],{},[80,3366],{"alt":3367,"src":3368},"04-obstacles-地板死区","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F04-obstacles-%E5%9C%B0%E6%9D%BF%E6%AD%BB%E5%8C%BA.png",[60,3370,3372],{"id":3371},"障碍物移动-销毁-得分","障碍物移动 + 销毁 + 得分",[10,3374,3375],{},"障碍物根节点要做三件事：",[22,3377,3378,3384,3390],{},[25,3379,3380,3383],{},[14,3381,3382],{},"往左移动"," — 营造小鸟在向右飞的错觉",[25,3385,3386,3389],{},[14,3387,3388],{},"离开屏幕后销毁"," — 不然会一直累积，性能爆炸",[25,3391,3392,3395],{},[14,3393,3394],{},"检测小鸟穿过"," — 加分",[10,3397,3398,3399,3401],{},"这些都写在障碍物根节点（",[140,3400,2185],{},"）的脚本里：",[251,3403,3405],{"className":1229,"code":3404,"language":1231,"meta":259,"style":259},"extends Node2D\n\n@export var speed := 200.0\n@onready var goal: Area2D = $Goal\n\nfunc _physics_process(delta: float) -> void:\n    # 统一向左移动\n    position.x -= speed * delta\n    # 离开屏幕后自动销毁\n    if position.x \u003C -500:\n        queue_free()\n\n# 当物体进入得分区域【得分】\nfunc _on_goal_body_entered(body: Node2D) -> void:\n    # 检查进入的是不是小鸟（防止其他东西误触发）\n    if body.name == \"Bird\":\n        print(\"得分！\")\n        # 重要：得分后立即禁用这个检测区域，防止重复得分\n        # 使用 set_deferred 是因为在碰撞回调中不能直接修改物理属性\n        goal.set_deferred(\"monitoring\", false)\n",[140,3406,3407,3414,3418,3432,3455,3459,3481,3486,3505,3510,3530,3537,3541,3546,3569,3574,3590,3601,3606,3611],{"__ignoreMap":259},[277,3408,3409,3411],{"class":279,"line":280},[277,3410,1239],{"class":1238},[277,3412,3413],{"class":1242}," Node2D\n",[277,3415,3416],{"class":279,"line":287},[277,3417,320],{"emptyLinePlaceholder":319},[277,3419,3420,3422,3424,3427,3429],{"class":279,"line":298},[277,3421,1255],{"class":301},[277,3423,1259],{"class":1258},[277,3425,3426],{"class":1262}," speed",[277,3428,1267],{"class":1266},[277,3430,3431],{"class":312}," 200.0\n",[277,3433,3434,3437,3439,3442,3444,3447,3449,3452],{"class":279,"line":316},[277,3435,3436],{"class":301},"@onready",[277,3438,1259],{"class":1258},[277,3440,3441],{"class":1262}," goal",[277,3443,1333],{"class":1266},[277,3445,3446],{"class":1242}," Area2D",[277,3448,1440],{"class":1266},[277,3450,3451],{"class":1238}," $",[277,3453,3454],{"class":305},"Goal\n",[277,3456,3457],{"class":279,"line":323},[277,3458,320],{"emptyLinePlaceholder":319},[277,3460,3461,3463,3465,3467,3469,3471,3473,3475,3477,3479],{"class":279,"line":329},[277,3462,1320],{"class":1258},[277,3464,1323],{"class":301},[277,3466,1326],{"class":1266},[277,3468,1330],{"class":1329},[277,3470,1333],{"class":1266},[277,3472,1336],{"class":1242},[277,3474,1339],{"class":1266},[277,3476,1342],{"class":1258},[277,3478,1345],{"class":1242},[277,3480,1348],{"class":1266},[277,3482,3483],{"class":279,"line":1317},[277,3484,3485],{"class":283},"    # 统一向左移动\n",[277,3487,3488,3491,3493,3495,3498,3500,3502],{"class":279,"line":1351},[277,3489,3490],{"class":1262},"    position",[277,3492,1418],{"class":1266},[277,3494,1891],{"class":1262},[277,3496,3497],{"class":1258}," -=",[277,3499,3426],{"class":1262},[277,3501,1388],{"class":1258},[277,3503,3504],{"class":1262}," delta\n",[277,3506,3507],{"class":279,"line":1357},[277,3508,3509],{"class":283},"    # 离开屏幕后自动销毁\n",[277,3511,3512,3514,3517,3519,3521,3523,3525,3528],{"class":279,"line":1374},[277,3513,1360],{"class":1238},[277,3515,3516],{"class":1262}," position",[277,3518,1418],{"class":1266},[277,3520,1891],{"class":1262},[277,3522,2720],{"class":1258},[277,3524,1287],{"class":1258},[277,3526,3527],{"class":312},"500",[277,3529,1348],{"class":1329},[277,3531,3532,3535],{"class":279,"line":1399},[277,3533,3534],{"class":301},"        queue_free",[277,3536,1504],{"class":1266},[277,3538,3539],{"class":279,"line":1404},[277,3540,320],{"emptyLinePlaceholder":319},[277,3542,3543],{"class":279,"line":1410},[277,3544,3545],{"class":283},"# 当物体进入得分区域【得分】\n",[277,3547,3548,3550,3553,3555,3557,3559,3561,3563,3565,3567],{"class":279,"line":1431},[277,3549,1320],{"class":1258},[277,3551,3552],{"class":301}," _on_goal_body_entered",[277,3554,1326],{"class":1266},[277,3556,3155],{"class":1329},[277,3558,1333],{"class":1266},[277,3560,3160],{"class":1242},[277,3562,1339],{"class":1266},[277,3564,1342],{"class":1258},[277,3566,1345],{"class":1242},[277,3568,1348],{"class":1266},[277,3570,3571],{"class":279,"line":1446},[277,3572,3573],{"class":283},"    # 检查进入的是不是小鸟（防止其他东西误触发）\n",[277,3575,3576,3578,3580,3582,3584,3586,3588],{"class":279,"line":1451},[277,3577,1360],{"class":1238},[277,3579,3175],{"class":1262},[277,3581,1418],{"class":1266},[277,3583,3180],{"class":1262},[277,3585,3183],{"class":1258},[277,3587,3186],{"class":294},[277,3589,1348],{"class":1329},[277,3591,3592,3594,3596,3599],{"class":279,"line":1457},[277,3593,3193],{"class":301},[277,3595,1326],{"class":1266},[277,3597,3598],{"class":294},"\"得分！\"",[277,3600,2651],{"class":1266},[277,3602,3603],{"class":279,"line":1479},[277,3604,3605],{"class":283},"        # 重要：得分后立即禁用这个检测区域，防止重复得分\n",[277,3607,3608],{"class":279,"line":1493},[277,3609,3610],{"class":283},"        # 使用 set_deferred 是因为在碰撞回调中不能直接修改物理属性\n",[277,3612,3613,3616,3618,3621,3623,3626,3628,3631],{"class":279,"line":1498},[277,3614,3615],{"class":1262},"        goal",[277,3617,1418],{"class":1329},[277,3619,3620],{"class":301},"set_deferred",[277,3622,1326],{"class":1266},[277,3624,3625],{"class":294},"\"monitoring\"",[277,3627,2812],{"class":1266},[277,3629,3630],{"class":1238}," false",[277,3632,2651],{"class":1266},[10,3634,3635],{},[80,3636],{"alt":3637,"src":3638},"04-obstacles-障碍物脚本","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F04-obstacles-%E9%9A%9C%E7%A2%8D%E7%89%A9%E8%84%9A%E6%9C%AC.png",[109,3640,3642],{"id":3641},"这段代码做了啥","这段代码做了啥？",[634,3644,3645,3651,3660,3666,3676],{},[25,3646,3647,3650],{},[140,3648,3649],{},"@export var speed := 200.0"," — 把移动速度暴露到面板，方便实时调",[25,3652,3653,3656,3657,3659],{},[140,3654,3655],{},"@onready var goal: Area2D = $Goal"," — 拿到子节点 ",[140,3658,3051],{}," 的引用（用于得分后禁用它）",[25,3661,3662,3665],{},[140,3663,3664],{},"position.x -= speed * delta"," — 每帧整个障碍物往左移一点",[25,3667,3668,3671,3672,3675],{},[140,3669,3670],{},"if position.x \u003C -500: queue_free()"," — 飞出屏幕外就",[14,3673,3674],{},"销毁自己","，回收内存",[25,3677,3678,3681,3682,3684,3685,230],{},[140,3679,3680],{},"_on_goal_body_entered"," — 小鸟进入 ",[140,3683,3051],{}," 时触发（同样要在编辑器里",[14,3686,3687],{},"连接信号",[109,3689,3691,3692,3695],{"id":3690},"为什么要-set_deferredmonitoring-false","为什么要 ",[140,3693,3694],{},"set_deferred(\"monitoring\", false)","？",[10,3697,3698,17],{},[14,3699,3700],{},"为了防止重复得分",[10,3702,3703,3704,3706],{},"如果不禁用，小鸟身上的碰撞体可能在 Goal 区域里横跨多帧，每帧都触发一次 ",[140,3705,3122],{},"，你就从 1 分跳到 5 分了 😅",[10,3708,3709,3711],{},[140,3710,3620],{}," 是因为在物理回调里不能直接修改物理属性，要\"延迟到下一帧\"再改。",[45,3713,3714,3719,3965,3968],{},[10,3715,3232,3716,3718],{},[14,3717,3235],{},"（下一章 GameManager + 第 8 章 SoundManager 后会改成这样）：",[251,3720,3722],{"className":1229,"code":3721,"language":1231,"meta":259,"style":259},"extends Node2D\n\n@export var speed := 200.0\n@onready var goal: Area2D = $Goal\n\nfunc _physics_process(delta: float) -> void:\n    if GameManager.current_state == GameManager.GameState.PLAYING:\n        position.x -= speed * delta\n        if position.x \u003C -500:\n            queue_free()\n\nfunc _on_goal_body_entered(body: Node2D) -> void:\n    if body.name == \"Bird\":\n        SoundManager.play_score()\n        if GameManager.has_method(\"add_score\"):\n            GameManager.add_score(1)\n        goal.set_deferred(\"monitoring\", false)\n",[140,3723,3724,3730,3734,3746,3764,3768,3790,3814,3831,3850,3857,3861,3883,3899,3910,3930,3947],{"__ignoreMap":259},[277,3725,3726,3728],{"class":279,"line":280},[277,3727,1239],{"class":1238},[277,3729,3413],{"class":1242},[277,3731,3732],{"class":279,"line":287},[277,3733,320],{"emptyLinePlaceholder":319},[277,3735,3736,3738,3740,3742,3744],{"class":279,"line":298},[277,3737,1255],{"class":301},[277,3739,1259],{"class":1258},[277,3741,3426],{"class":1262},[277,3743,1267],{"class":1266},[277,3745,3431],{"class":312},[277,3747,3748,3750,3752,3754,3756,3758,3760,3762],{"class":279,"line":316},[277,3749,3436],{"class":301},[277,3751,1259],{"class":1258},[277,3753,3441],{"class":1262},[277,3755,1333],{"class":1266},[277,3757,3446],{"class":1242},[277,3759,1440],{"class":1266},[277,3761,3451],{"class":1238},[277,3763,3454],{"class":305},[277,3765,3766],{"class":279,"line":323},[277,3767,320],{"emptyLinePlaceholder":319},[277,3769,3770,3772,3774,3776,3778,3780,3782,3784,3786,3788],{"class":279,"line":329},[277,3771,1320],{"class":1258},[277,3773,1323],{"class":301},[277,3775,1326],{"class":1266},[277,3777,1330],{"class":1329},[277,3779,1333],{"class":1266},[277,3781,1336],{"class":1242},[277,3783,1339],{"class":1266},[277,3785,1342],{"class":1258},[277,3787,1345],{"class":1242},[277,3789,1348],{"class":1266},[277,3791,3792,3794,3796,3798,3800,3802,3804,3806,3808,3810,3812],{"class":279,"line":1317},[277,3793,1360],{"class":1238},[277,3795,3293],{"class":1242},[277,3797,1418],{"class":1266},[277,3799,3298],{"class":1262},[277,3801,3183],{"class":1258},[277,3803,3293],{"class":1242},[277,3805,1418],{"class":1266},[277,3807,3307],{"class":1262},[277,3809,1418],{"class":1266},[277,3811,3312],{"class":1238},[277,3813,1348],{"class":1329},[277,3815,3816,3819,3821,3823,3825,3827,3829],{"class":279,"line":1351},[277,3817,3818],{"class":1262},"        position",[277,3820,1418],{"class":1266},[277,3822,1891],{"class":1262},[277,3824,3497],{"class":1258},[277,3826,3426],{"class":1262},[277,3828,1388],{"class":1258},[277,3830,3504],{"class":1262},[277,3832,3833,3836,3838,3840,3842,3844,3846,3848],{"class":279,"line":1357},[277,3834,3835],{"class":1238},"        if",[277,3837,3516],{"class":1262},[277,3839,1418],{"class":1266},[277,3841,1891],{"class":1262},[277,3843,2720],{"class":1258},[277,3845,1287],{"class":1258},[277,3847,3527],{"class":312},[277,3849,1348],{"class":1329},[277,3851,3852,3855],{"class":279,"line":1374},[277,3853,3854],{"class":301},"            queue_free",[277,3856,1504],{"class":1266},[277,3858,3859],{"class":279,"line":1399},[277,3860,320],{"emptyLinePlaceholder":319},[277,3862,3863,3865,3867,3869,3871,3873,3875,3877,3879,3881],{"class":279,"line":1404},[277,3864,1320],{"class":1258},[277,3866,3552],{"class":301},[277,3868,1326],{"class":1266},[277,3870,3155],{"class":1329},[277,3872,1333],{"class":1266},[277,3874,3160],{"class":1242},[277,3876,1339],{"class":1266},[277,3878,1342],{"class":1258},[277,3880,1345],{"class":1242},[277,3882,1348],{"class":1266},[277,3884,3885,3887,3889,3891,3893,3895,3897],{"class":279,"line":1410},[277,3886,1360],{"class":1238},[277,3888,3175],{"class":1262},[277,3890,1418],{"class":1266},[277,3892,3180],{"class":1262},[277,3894,3183],{"class":1258},[277,3896,3186],{"class":294},[277,3898,1348],{"class":1329},[277,3900,3901,3903,3905,3908],{"class":279,"line":1431},[277,3902,3319],{"class":1242},[277,3904,1418],{"class":1329},[277,3906,3907],{"class":301},"play_score",[277,3909,1504],{"class":1266},[277,3911,3912,3914,3916,3918,3921,3923,3926,3928],{"class":279,"line":1446},[277,3913,3835],{"class":1238},[277,3915,3293],{"class":1242},[277,3917,1418],{"class":1329},[277,3919,3920],{"class":301},"has_method",[277,3922,1326],{"class":1266},[277,3924,3925],{"class":294},"\"add_score\"",[277,3927,1339],{"class":1266},[277,3929,1348],{"class":1329},[277,3931,3932,3935,3937,3940,3942,3945],{"class":279,"line":1451},[277,3933,3934],{"class":1242},"            GameManager",[277,3936,1418],{"class":1329},[277,3938,3939],{"class":301},"add_score",[277,3941,1326],{"class":1266},[277,3943,3944],{"class":312},"1",[277,3946,2651],{"class":1266},[277,3948,3949,3951,3953,3955,3957,3959,3961,3963],{"class":279,"line":1457},[277,3950,3615],{"class":1262},[277,3952,1418],{"class":1329},[277,3954,3620],{"class":301},[277,3956,1326],{"class":1266},[277,3958,3625],{"class":294},[277,3960,2812],{"class":1266},[277,3962,3630],{"class":1238},[277,3964,2651],{"class":1266},[10,3966,3967],{},"升级点：",[634,3969,3970,3977],{},[25,3971,3972,3973,3976],{},"加了 ",[140,3974,3975],{},"if GameManager.current_state == PLAYING"," — 游戏暂停\u002F结束时不再移动",[25,3978,3979,3980,3073,3983],{},"得分时调用 ",[140,3981,3982],{},"GameManager.add_score(1)",[140,3984,3985],{},"SoundManager.play_score()",[60,3987,2888],{"id":2888},[10,3989,3990,3991,3994,3995,3998],{},"光有一根水管不够，需要",[14,3992,3993],{},"源源不断的水管","。用 ",[140,3996,3997],{},"Timer"," 节点定时生成：",[10,4000,2197],{},[634,4002,4003],{},[25,4004,4005,4007,4008,2925,4011],{},[140,4006,2185],{}," — 生成器根节点（重命名：",[140,4009,4010],{},"PillarSpawner",[634,4012,4013],{},[25,4014,4015,4017,4018,4021,4022,230],{},[140,4016,3997],{}," — 定时器（",[140,4019,4020],{},"Wait Time = 2.5s","，勾选 ",[140,4023,4024],{},"Autostart",[10,4026,4027],{},[80,4028],{"alt":4029,"src":4030},"04-obstacles-生成器节点","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F04-obstacles-%E7%94%9F%E6%88%90%E5%99%A8%E8%8A%82%E7%82%B9.png",[10,4032,4033],{},"设置 Timer 节点的属性：",[10,4035,4036],{},[80,4037],{"alt":4038,"src":4039},"04-obstacles-生成器节点2","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F04-obstacles-%E7%94%9F%E6%88%90%E5%99%A8%E8%8A%82%E7%82%B92.png",[10,4041,4042,4043,4046,4047,4050],{},"把 ",[140,4044,4045],{},"pillar_pair.tscn"," 拖到 ",[140,4048,4049],{},"Pillar Scene"," 槽里：",[10,4052,4053],{},[80,4054],{"alt":4055,"src":4056},"04-obstacles-生成器节点3","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F04-obstacles-%E7%94%9F%E6%88%90%E5%99%A8%E8%8A%82%E7%82%B93.png",[10,4058,4059,4060,4062,4063,4066],{},"最后，把 ",[140,4061,4010],{}," 整个节点",[14,4064,4065],{},"拖到游戏窗口右边外面","（这样水管才会从屏幕右侧\"飞\"进来）：",[10,4068,4069],{},[80,4070],{"alt":4071,"src":4072},"04-obstacles-生成器节点4","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F04-obstacles-%E7%94%9F%E6%88%90%E5%99%A8%E8%8A%82%E7%82%B94.png",[109,4074,4075],{"id":4075},"生成器脚本",[251,4077,4079],{"className":1229,"code":4078,"language":1231,"meta":259,"style":259},"extends Node2D\n\n@export var pillar_scene: PackedScene\n@export var y_range := 250.0\n\nfunc _on_timer_timeout() -> void:\n    spawn_pillar()\n\nfunc spawn_pillar() -> void:\n    var new_pillar = pillar_scene.instantiate()\n\n    var spawn_pos = global_position\n    spawn_pos.y += randf_range(-y_range, y_range)\n\n    new_pillar.global_position = spawn_pos\n\n    # 重点：加到主场景或者专门的容器里，防止柱子跟着生成器动\n    get_tree().current_scene.add_child(new_pillar)\n",[140,4080,4081,4087,4091,4105,4119,4123,4138,4145,4149,4164,4182,4186,4198,4225,4229,4244,4248,4253],{"__ignoreMap":259},[277,4082,4083,4085],{"class":279,"line":280},[277,4084,1239],{"class":1238},[277,4086,3413],{"class":1242},[277,4088,4089],{"class":279,"line":287},[277,4090,320],{"emptyLinePlaceholder":319},[277,4092,4093,4095,4097,4100,4102],{"class":279,"line":298},[277,4094,1255],{"class":301},[277,4096,1259],{"class":1258},[277,4098,4099],{"class":1262}," pillar_scene",[277,4101,1333],{"class":1266},[277,4103,4104],{"class":1242}," PackedScene\n",[277,4106,4107,4109,4111,4114,4116],{"class":279,"line":316},[277,4108,1255],{"class":301},[277,4110,1259],{"class":1258},[277,4112,4113],{"class":1262}," y_range",[277,4115,1267],{"class":1266},[277,4117,4118],{"class":312}," 250.0\n",[277,4120,4121],{"class":279,"line":323},[277,4122,320],{"emptyLinePlaceholder":319},[277,4124,4125,4127,4130,4132,4134,4136],{"class":279,"line":329},[277,4126,1320],{"class":1258},[277,4128,4129],{"class":301}," _on_timer_timeout",[277,4131,1369],{"class":1266},[277,4133,1342],{"class":1258},[277,4135,1345],{"class":1242},[277,4137,1348],{"class":1266},[277,4139,4140,4143],{"class":279,"line":1317},[277,4141,4142],{"class":301},"    spawn_pillar",[277,4144,1504],{"class":1266},[277,4146,4147],{"class":279,"line":1351},[277,4148,320],{"emptyLinePlaceholder":319},[277,4150,4151,4153,4156,4158,4160,4162],{"class":279,"line":1357},[277,4152,1320],{"class":1258},[277,4154,4155],{"class":301}," spawn_pillar",[277,4157,1369],{"class":1266},[277,4159,1342],{"class":1258},[277,4161,1345],{"class":1242},[277,4163,1348],{"class":1266},[277,4165,4166,4168,4171,4173,4175,4177,4180],{"class":279,"line":1374},[277,4167,2693],{"class":1258},[277,4169,4170],{"class":1262}," new_pillar",[277,4172,1440],{"class":1266},[277,4174,4099],{"class":1262},[277,4176,1418],{"class":1329},[277,4178,4179],{"class":301},"instantiate",[277,4181,1504],{"class":1266},[277,4183,4184],{"class":279,"line":1399},[277,4185,320],{"emptyLinePlaceholder":319},[277,4187,4188,4190,4193,4195],{"class":279,"line":1404},[277,4189,2693],{"class":1258},[277,4191,4192],{"class":1262}," spawn_pos",[277,4194,1440],{"class":1266},[277,4196,4197],{"class":1262}," global_position\n",[277,4199,4200,4203,4205,4207,4209,4212,4214,4216,4219,4221,4223],{"class":279,"line":1410},[277,4201,4202],{"class":1262},"    spawn_pos",[277,4204,1418],{"class":1266},[277,4206,1421],{"class":1262},[277,4208,1380],{"class":1258},[277,4210,4211],{"class":301}," randf_range",[277,4213,1326],{"class":1266},[277,4215,2747],{"class":1258},[277,4217,4218],{"class":1262},"y_range",[277,4220,2812],{"class":1266},[277,4222,4113],{"class":1262},[277,4224,2651],{"class":1266},[277,4226,4227],{"class":279,"line":1431},[277,4228,320],{"emptyLinePlaceholder":319},[277,4230,4231,4234,4236,4239,4241],{"class":279,"line":1446},[277,4232,4233],{"class":1262},"    new_pillar",[277,4235,1418],{"class":1266},[277,4237,4238],{"class":1262},"global_position",[277,4240,1440],{"class":1266},[277,4242,4243],{"class":1262}," spawn_pos\n",[277,4245,4246],{"class":279,"line":1451},[277,4247,320],{"emptyLinePlaceholder":319},[277,4249,4250],{"class":279,"line":1457},[277,4251,4252],{"class":283},"    # 重点：加到主场景或者专门的容器里，防止柱子跟着生成器动\n",[277,4254,4255,4258,4260,4262,4265,4267,4270,4272,4275],{"class":279,"line":1479},[277,4256,4257],{"class":301},"    get_tree",[277,4259,1369],{"class":1266},[277,4261,1418],{"class":1329},[277,4263,4264],{"class":1262},"current_scene",[277,4266,1418],{"class":1329},[277,4268,4269],{"class":301},"add_child",[277,4271,1326],{"class":1266},[277,4273,4274],{"class":1262},"new_pillar",[277,4276,2651],{"class":1266},[109,4278,4279],{"id":4279},"关键点",[634,4281,4282,4295,4301,4309,4315,4321],{},[25,4283,4284,4287,4288],{},[140,4285,4286],{},"@export var pillar_scene: PackedScene"," — 把\"水管场景\"暴露到面板，",[14,4289,4290,4291,4294],{},"你需要在面板上把 ",[140,4292,4293],{},"pillar.tscn"," 拖进这个槽里",[25,4296,4297,4300],{},[140,4298,4299],{},"@export var y_range := 250.0"," — 水管在垂直方向上的随机偏移范围",[25,4302,4303,4306,4307,230],{},[140,4304,4305],{},"_on_timer_timeout"," — 每次 Timer 触发就生成新的（同样要",[14,4308,3687],{},[25,4310,4311,4314],{},[140,4312,4313],{},"pillar_scene.instantiate()"," — 把场景\"实例化\"成一个真正的节点",[25,4316,4317,4320],{},[140,4318,4319],{},"randf_range(-y_range, y_range)"," — 随机一个垂直偏移",[25,4322,4323,4328,4329,4332],{},[14,4324,4325],{},[140,4326,4327],{},"get_tree().current_scene.add_child(new_pillar)"," ⚠️ 重点！把水管加到",[14,4330,4331],{},"主场景","而不是生成器自己。否则如果生成器以后会移动，水管会跟着一起动，整个画面就乱套了。",[45,4334,4335],{},[10,4336,3232,4337,4339,4340,4343,4344,4347],{},[14,4338,3235],{},"（下一章 GameManager 之后）：在 ",[140,4341,4342],{},"spawn_pillar"," 最外层包一个 ",[140,4345,4346],{},"if GameManager.current_state == GameManager.GameState.PLAYING:","，这样游戏结束后就不会继续生成水管了。",[60,4349,2350],{"id":2350},[10,4351,4352],{},"应该能看到：",[634,4354,4355,4358,4361,4364],{},[25,4356,4357],{},"✅ 水管从右往左移动",[25,4359,4360],{},"✅ 每 2.5 秒生成一根，高度随机",[25,4362,4363],{},"✅ 小鸟穿过会在控制台打印「得分！」",[25,4365,4366],{},"✅ 小鸟撞到水管或地面会在控制台打印「撞击死亡！」",[10,4368,4369],{},[80,4370],{"alt":4371,"src":4372},"04-obstacles-尝试运行","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F04-obstacles-%E5%B0%9D%E8%AF%95%E8%BF%90%E8%A1%8C.png",[10,4374,4375,4376,4378],{},"下一章我们来做 ",[14,4377,3226],{},"，把\"撞击死亡\"真正变成\"游戏结束 + 显示分数 + 重新开始\"。",[854,4380,4381],{},"html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s_NWU, html code.shiki .s_NWU{--shiki-default:#2E8F82;--shiki-dark:#5DA994}html pre.shiki code .s5TCs, html code.shiki .s5TCs{--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .s8w-G, html code.shiki .s8w-G{--shiki-default:#393A34;--shiki-dark:#DBD7CAEE}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sqbOQ, html code.shiki .sqbOQ{--shiki-default:#2F798A;--shiki-dark:#4C9A91}html pre.shiki code .sfsYZ, html code.shiki .sfsYZ{--shiki-default:#A65E2B;--shiki-dark:#C99076}html pre.shiki code .snYqZ, html code.shiki .snYqZ{--shiki-default:#A0ADA0;--shiki-dark:#758575DD}",{"title":259,"searchDepth":287,"depth":287,"links":4383},[4384,4385,4389,4394,4398],{"id":2910,"depth":287,"text":2911},{"id":2954,"depth":287,"text":2954,"children":4386},[4387,4388],{"id":3107,"depth":298,"text":3108},{"id":3354,"depth":298,"text":3355},{"id":3371,"depth":287,"text":3372,"children":4390},[4391,4392],{"id":3641,"depth":298,"text":3642},{"id":3690,"depth":298,"text":4393},"为什么要 set_deferred(\"monitoring\", false)？",{"id":2888,"depth":287,"text":2888,"children":4395},[4396,4397],{"id":4075,"depth":298,"text":4075},{"id":4279,"depth":298,"text":4279},{"id":2350,"depth":287,"text":2350},{},"\u002Fdevlog\u002Fxggame-bird\u002F04-obstacles",{"title":2864,"description":878},"devlog\u002Fxggame-bird\u002F04-obstacles","3A4xyMDP63Q0nzX7ZiPr4mConIp3Z60qkzeE1gvT5wM",{"id":4405,"title":3226,"body":4406,"cover":876,"date":877,"description":878,"extension":879,"game":410,"github":880,"icon":876,"meta":5870,"navigation":319,"path":5871,"seo":5872,"stem":5873,"toc":319,"__hash__":5874},"devlog\u002Fdevlog\u002Fxggame-bird\u002F05-game-manager.md",{"type":7,"value":4407,"toc":5845},[4408,4421,4425,4428,4447,4450,4457,4461,4467,4815,4819,4829,4844,4847,4853,4859,4885,4892,4920,4934,4936,4942,4967,4987,5034,5040,5069,5076,5092,5098,5107,5154,5172,5181,5195,5200,5204,5211,5218,5301,5308,5315,5547,5554,5750,5752,5758,5764,5833,5842],[10,4409,4410,4411,4414,4415,4418,4419,17],{},"游戏到这一步已经能玩了，但缺少",[14,4412,4413],{},"游戏状态管理","和",[14,4416,4417],{},"计分","。我们需要一个统一的管理者来掌控全局 — 这就是 ",[14,4420,3226],{},[60,4422,4424],{"id":4423},"为什么需要-gamemanager","为什么需要 GameManager？",[10,4426,4427],{},"想象一下，游戏有 3 种状态：",[634,4429,4430,4436,4441],{},[25,4431,4432,4435],{},[14,4433,4434],{},"READY","（准备）— 显示\"点击开始\"",[25,4437,4438,4440],{},[14,4439,3312],{},"（进行中）— 小鸟能跳、水管在生成",[25,4442,4443,4446],{},[14,4444,4445],{},"GAME_OVER","（结束）— 显示分数、可以重开",[10,4448,4449],{},"如果让每个节点自己判断状态，比如小鸟自己看\"现在能不能跳\"、水管自己看\"现在能不能生成\"，代码会乱成一团 — 改一个地方要同步改很多处。",[10,4451,4452,4453,4456],{},"GameManager 就是",[14,4454,4455],{},"所有节点共同遵守的\"裁判\""," — 状态、分数都由它统一保管，其他节点只问它要、不自己存。",[60,4458,4460],{"id":4459},"创建-gamemanager-脚本","创建 GameManager 脚本",[10,4462,4463,4464,486],{},"新建文件 ",[140,4465,4466],{},"scripts\u002Fautoload\u002Fgame_manager.gd",[251,4468,4470],{"className":1229,"code":4469,"language":1231,"meta":259,"style":259},"extends Node\n\n# 定义游戏状态\nenum GameState { READY, PLAYING, GAME_OVER }\nvar current_state = GameState.READY\n\nvar total_score: int = 0   # 总分\nvar high_score: int = 0    # 最高分\n\n# 信号：通知其他节点\nsignal score_changed(new_score)\nsignal state_changed(new_state)\n\n## 清空分数\nfunc clear_score():\n    total_score = 0\n    score_changed.emit(total_score)\n\n## 添加得分\nfunc add_score(amount: int = 1):\n    total_score += amount\n    score_changed.emit(total_score)\n\n## 设置当前游戏状态\nfunc set_state(new_state: GameState):\n    current_state = new_state\n    state_changed.emit(new_state)\n\n## 游戏结束\nfunc game_over():\n    set_state(GameState.GAME_OVER)\n    if high_score \u003C total_score:\n        high_score = total_score\n",[140,4471,4472,4479,4483,4488,4513,4529,4533,4552,4570,4574,4579,4594,4608,4612,4617,4627,4636,4653,4657,4662,4686,4695,4709,4713,4718,4735,4745,4760,4764,4769,4778,4793,4805],{"__ignoreMap":259},[277,4473,4474,4476],{"class":279,"line":280},[277,4475,1239],{"class":1238},[277,4477,4478],{"class":1242}," Node\n",[277,4480,4481],{"class":279,"line":287},[277,4482,320],{"emptyLinePlaceholder":319},[277,4484,4485],{"class":279,"line":298},[277,4486,4487],{"class":283},"# 定义游戏状态\n",[277,4489,4490,4493,4496,4499,4502,4504,4506,4508,4510],{"class":279,"line":316},[277,4491,4492],{"class":1238},"enum",[277,4494,4495],{"class":1242}," GameState",[277,4497,4498],{"class":1266}," {",[277,4500,4501],{"class":1262}," READY",[277,4503,693],{"class":1329},[277,4505,3312],{"class":1262},[277,4507,693],{"class":1329},[277,4509,4445],{"class":1262},[277,4511,4512],{"class":1266}," }\n",[277,4514,4515,4517,4520,4522,4524,4526],{"class":279,"line":323},[277,4516,1585],{"class":1258},[277,4518,4519],{"class":1262}," current_state",[277,4521,1440],{"class":1266},[277,4523,4495],{"class":1242},[277,4525,1418],{"class":1266},[277,4527,4528],{"class":1238},"READY\n",[277,4530,4531],{"class":279,"line":329},[277,4532,320],{"emptyLinePlaceholder":319},[277,4534,4535,4537,4540,4542,4545,4547,4549],{"class":279,"line":1317},[277,4536,1585],{"class":1258},[277,4538,4539],{"class":1262}," total_score",[277,4541,1333],{"class":1266},[277,4543,4544],{"class":1242}," int",[277,4546,1440],{"class":1266},[277,4548,2723],{"class":312},[277,4550,4551],{"class":283},"   # 总分\n",[277,4553,4554,4556,4559,4561,4563,4565,4567],{"class":279,"line":1351},[277,4555,1585],{"class":1258},[277,4557,4558],{"class":1262}," high_score",[277,4560,1333],{"class":1266},[277,4562,4544],{"class":1242},[277,4564,1440],{"class":1266},[277,4566,2723],{"class":312},[277,4568,4569],{"class":283},"    # 最高分\n",[277,4571,4572],{"class":279,"line":1357},[277,4573,320],{"emptyLinePlaceholder":319},[277,4575,4576],{"class":279,"line":1374},[277,4577,4578],{"class":283},"# 信号：通知其他节点\n",[277,4580,4581,4584,4587,4589,4592],{"class":279,"line":1399},[277,4582,4583],{"class":1258},"signal",[277,4585,4586],{"class":301}," score_changed",[277,4588,1326],{"class":1266},[277,4590,4591],{"class":1329},"new_score",[277,4593,2651],{"class":1266},[277,4595,4596,4598,4601,4603,4606],{"class":279,"line":1404},[277,4597,4583],{"class":1258},[277,4599,4600],{"class":301}," state_changed",[277,4602,1326],{"class":1266},[277,4604,4605],{"class":1329},"new_state",[277,4607,2651],{"class":1266},[277,4609,4610],{"class":279,"line":1410},[277,4611,320],{"emptyLinePlaceholder":319},[277,4613,4614],{"class":279,"line":1431},[277,4615,4616],{"class":283},"## 清空分数\n",[277,4618,4619,4621,4624],{"class":279,"line":1446},[277,4620,1320],{"class":1258},[277,4622,4623],{"class":301}," clear_score",[277,4625,4626],{"class":1266},"():\n",[277,4628,4629,4632,4634],{"class":279,"line":1451},[277,4630,4631],{"class":1262},"    total_score",[277,4633,1440],{"class":1266},[277,4635,2615],{"class":312},[277,4637,4638,4641,4643,4646,4648,4651],{"class":279,"line":1457},[277,4639,4640],{"class":1262},"    score_changed",[277,4642,1418],{"class":1329},[277,4644,4645],{"class":301},"emit",[277,4647,1326],{"class":1266},[277,4649,4650],{"class":1262},"total_score",[277,4652,2651],{"class":1266},[277,4654,4655],{"class":279,"line":1479},[277,4656,320],{"emptyLinePlaceholder":319},[277,4658,4659],{"class":279,"line":1493},[277,4660,4661],{"class":283},"## 添加得分\n",[277,4663,4664,4666,4669,4671,4674,4676,4678,4680,4683],{"class":279,"line":1498},[277,4665,1320],{"class":1258},[277,4667,4668],{"class":301}," add_score",[277,4670,1326],{"class":1266},[277,4672,4673],{"class":1329},"amount",[277,4675,1333],{"class":1266},[277,4677,4544],{"class":1242},[277,4679,1440],{"class":1266},[277,4681,4682],{"class":312}," 1",[277,4684,4685],{"class":1266},"):\n",[277,4687,4688,4690,4692],{"class":279,"line":2630},[277,4689,4631],{"class":1262},[277,4691,1380],{"class":1258},[277,4693,4694],{"class":1262}," amount\n",[277,4696,4697,4699,4701,4703,4705,4707],{"class":279,"line":2635},[277,4698,4640],{"class":1262},[277,4700,1418],{"class":1329},[277,4702,4645],{"class":301},[277,4704,1326],{"class":1266},[277,4706,4650],{"class":1262},[277,4708,2651],{"class":1266},[277,4710,4711],{"class":279,"line":2641},[277,4712,320],{"emptyLinePlaceholder":319},[277,4714,4715],{"class":279,"line":2654},[277,4716,4717],{"class":283},"## 设置当前游戏状态\n",[277,4719,4720,4722,4725,4727,4729,4731,4733],{"class":279,"line":2661},[277,4721,1320],{"class":1258},[277,4723,4724],{"class":301}," set_state",[277,4726,1326],{"class":1266},[277,4728,4605],{"class":1329},[277,4730,1333],{"class":1266},[277,4732,4495],{"class":1242},[277,4734,4685],{"class":1266},[277,4736,4737,4740,4742],{"class":279,"line":2666},[277,4738,4739],{"class":1262},"    current_state",[277,4741,1440],{"class":1266},[277,4743,4744],{"class":1262}," new_state\n",[277,4746,4747,4750,4752,4754,4756,4758],{"class":279,"line":2690},[277,4748,4749],{"class":1262},"    state_changed",[277,4751,1418],{"class":1329},[277,4753,4645],{"class":301},[277,4755,1326],{"class":1266},[277,4757,4605],{"class":1262},[277,4759,2651],{"class":1266},[277,4761,4762],{"class":279,"line":2704},[277,4763,320],{"emptyLinePlaceholder":319},[277,4765,4766],{"class":279,"line":2709},[277,4767,4768],{"class":283},"## 游戏结束\n",[277,4770,4771,4773,4776],{"class":279,"line":2728},[277,4772,1320],{"class":1258},[277,4774,4775],{"class":301}," game_over",[277,4777,4626],{"class":1266},[277,4779,4780,4783,4785,4787,4789,4791],{"class":279,"line":2734},[277,4781,4782],{"class":301},"    set_state",[277,4784,1326],{"class":1266},[277,4786,3307],{"class":1242},[277,4788,1418],{"class":1266},[277,4790,4445],{"class":1238},[277,4792,2651],{"class":1266},[277,4794,4795,4797,4799,4801,4803],{"class":279,"line":2755},[277,4796,1360],{"class":1238},[277,4798,4558],{"class":1262},[277,4800,2720],{"class":1258},[277,4802,4539],{"class":1262},[277,4804,1348],{"class":1329},[277,4806,4807,4810,4812],{"class":279,"line":2763},[277,4808,4809],{"class":1262},"        high_score",[277,4811,1440],{"class":1266},[277,4813,4814],{"class":1262}," total_score\n",[60,4816,4818],{"id":4817},"autoload-全局单例","Autoload — 全局单例",[10,4820,4821,4822,4825,4826,17],{},"脚本写好了，但现在它只是个 ",[140,4823,4824],{},".gd"," 文件，",[14,4827,4828],{},"别的节点没办法访问到它",[10,4830,4831,4832,4835,4836,4839,4840,4843],{},"Godot 提供了 ",[14,4833,4834],{},"Autoload","（自动加载）机制 — 注册之后，这个脚本\u002F场景",[14,4837,4838],{},"全程只有一份","，任何节点都能直接通过名字访问，不需要 ",[140,4841,4842],{},"get_node()"," 找路径。",[109,4845,4846],{"id":4846},"注册步骤",[10,4848,4849,4850],{},"路径：",[14,4851,4852],{},"项目 → 项目设置 → 全局 → 自动加载",[10,4854,4855],{},[80,4856],{"alt":4857,"src":4858},"05-game-manager-全局单列GM","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F05-game-manager-%E5%85%A8%E5%B1%80%E5%8D%95%E5%88%97GM.png",[22,4860,4861,4870,4879,4882],{},[25,4862,4863,4866,4867],{},[14,4864,4865],{},"路径","：选 ",[140,4868,4869],{},"res:\u002F\u002Fscripts\u002Fautoload\u002Fgame_manager.gd",[25,4871,4872,4875,4876,4878],{},[14,4873,4874],{},"节点名称","：自动填好 ",[140,4877,3226],{},"（也可以手动改）",[25,4880,4881],{},"点击「添加」",[25,4883,4884],{},"确认右侧「启用」是勾选状态",[10,4886,4887,4888,4891],{},"注册之后，任何节点的脚本里都能直接写 ",[140,4889,4890],{},"GameManager.xxx"," 来访问它，比如：",[251,4893,4895],{"className":1229,"code":4894,"language":1231,"meta":259,"style":259},"GameManager.add_score(1)\nGameManager.current_state\n",[140,4896,4897,4911],{"__ignoreMap":259},[277,4898,4899,4901,4903,4905,4907,4909],{"class":279,"line":280},[277,4900,3226],{"class":1242},[277,4902,1418],{"class":1329},[277,4904,3939],{"class":301},[277,4906,1326],{"class":1266},[277,4908,3944],{"class":312},[277,4910,2651],{"class":1266},[277,4912,4913,4915,4917],{"class":279,"line":287},[277,4914,3226],{"class":1242},[277,4916,1418],{"class":1266},[277,4918,4919],{"class":1262},"current_state\n",[10,4921,4922,4923,4926,4927,4926,4930,4933],{},"无需 ",[140,4924,4925],{},"preload"," \u002F ",[140,4928,4929],{},"load",[140,4931,4932],{},"get_node","，超方便。",[60,4935,3642],{"id":3641},[109,4937,4939],{"id":4938},"enum-gamestate",[140,4940,4941],{},"enum GameState",[251,4943,4945],{"className":1229,"code":4944,"language":1231,"meta":259,"style":259},"enum GameState { READY, PLAYING, GAME_OVER }\n",[140,4946,4947],{"__ignoreMap":259},[277,4948,4949,4951,4953,4955,4957,4959,4961,4963,4965],{"class":279,"line":280},[277,4950,4492],{"class":1238},[277,4952,4495],{"class":1242},[277,4954,4498],{"class":1266},[277,4956,4501],{"class":1262},[277,4958,693],{"class":1329},[277,4960,3312],{"class":1262},[277,4962,693],{"class":1329},[277,4964,4445],{"class":1262},[277,4966,4512],{"class":1266},[10,4968,4969,4971,4972,4975,4976,4979,4980,4979,4983,4986],{},[140,4970,4492],{},"（枚举）就是给一组\"状态\"取个",[14,4973,4974],{},"好记的名字","。本质上 ",[140,4977,4978],{},"READY=0","、",[140,4981,4982],{},"PLAYING=1",[140,4984,4985],{},"GAME_OVER=2","，但写代码时用名字比写数字清楚得多：",[251,4988,4990],{"className":1229,"code":4989,"language":1231,"meta":259,"style":259},"# ❌ 不直观\nif current_state == 0:\n# ✅ 一眼明白\nif current_state == GameManager.GameState.READY:\n",[140,4991,4992,4997,5009,5014],{"__ignoreMap":259},[277,4993,4994],{"class":279,"line":280},[277,4995,4996],{"class":283},"# ❌ 不直观\n",[277,4998,4999,5001,5003,5005,5007],{"class":279,"line":287},[277,5000,1815],{"class":1238},[277,5002,4519],{"class":1262},[277,5004,3183],{"class":1258},[277,5006,2723],{"class":312},[277,5008,1348],{"class":1329},[277,5010,5011],{"class":279,"line":298},[277,5012,5013],{"class":283},"# ✅ 一眼明白\n",[277,5015,5016,5018,5020,5022,5024,5026,5028,5030,5032],{"class":279,"line":316},[277,5017,1815],{"class":1238},[277,5019,4519],{"class":1262},[277,5021,3183],{"class":1258},[277,5023,3293],{"class":1242},[277,5025,1418],{"class":1266},[277,5027,3307],{"class":1262},[277,5029,1418],{"class":1266},[277,5031,4434],{"class":1238},[277,5033,1348],{"class":1329},[109,5035,5037,5039],{"id":5036},"signal信号",[140,5038,4583],{},"（信号）",[251,5041,5043],{"className":1229,"code":5042,"language":1231,"meta":259,"style":259},"signal score_changed(new_score)\nsignal state_changed(new_state)\n",[140,5044,5045,5057],{"__ignoreMap":259},[277,5046,5047,5049,5051,5053,5055],{"class":279,"line":280},[277,5048,4583],{"class":1258},[277,5050,4586],{"class":301},[277,5052,1326],{"class":1266},[277,5054,4591],{"class":1329},[277,5056,2651],{"class":1266},[277,5058,5059,5061,5063,5065,5067],{"class":279,"line":287},[277,5060,4583],{"class":1258},[277,5062,4600],{"class":301},[277,5064,1326],{"class":1266},[277,5066,4605],{"class":1329},[277,5068,2651],{"class":1266},[10,5070,5071,5072,5075],{},"信号是 Godot 的「事件通知机制」。GameManager 不需要知道",[14,5073,5074],{},"谁","在听 — 它只管\"广播消息\"：",[634,5077,5078,5085],{},[25,5079,5080,5081,5084],{},"分数变了 → 喊一声 ",[140,5082,5083],{},"score_changed","，UI 接到了就更新分数显示",[25,5086,5087,5088,5091],{},"状态变了 → 喊一声 ",[140,5089,5090],{},"state_changed","，所有关心状态的节点都能响应",[10,5093,5094,5097],{},[14,5095,5096],{},"这样 GameManager 不用知道 UI 长啥样，UI 也不用主动来问 GameManager","。两边解耦，互不干扰 — 信号是 Godot 里非常核心的设计思想。",[109,5099,5101,3073,5104],{"id":5100},"add_score-和-score_changedemit",[140,5102,5103],{},"add_score()",[140,5105,5106],{},"score_changed.emit()",[251,5108,5110],{"className":1229,"code":5109,"language":1231,"meta":259,"style":259},"func add_score(amount: int = 1):\n    total_score += amount\n    score_changed.emit(total_score)\n",[140,5111,5112,5132,5140],{"__ignoreMap":259},[277,5113,5114,5116,5118,5120,5122,5124,5126,5128,5130],{"class":279,"line":280},[277,5115,1320],{"class":1258},[277,5117,4668],{"class":301},[277,5119,1326],{"class":1266},[277,5121,4673],{"class":1329},[277,5123,1333],{"class":1266},[277,5125,4544],{"class":1242},[277,5127,1440],{"class":1266},[277,5129,4682],{"class":312},[277,5131,4685],{"class":1266},[277,5133,5134,5136,5138],{"class":279,"line":287},[277,5135,4631],{"class":1262},[277,5137,1380],{"class":1258},[277,5139,4694],{"class":1262},[277,5141,5142,5144,5146,5148,5150,5152],{"class":279,"line":298},[277,5143,4640],{"class":1262},[277,5145,1418],{"class":1329},[277,5147,4645],{"class":301},[277,5149,1326],{"class":1266},[277,5151,4650],{"class":1262},[277,5153,2651],{"class":1266},[634,5155,5156,5162],{},[25,5157,5158,5161],{},[140,5159,5160],{},"amount: int = 1"," — 参数默认值是 1，不传就 +1，传 2 就 +2",[25,5163,5164,5167,5168,5171],{},[140,5165,5166],{},"score_changed.emit(total_score)"," — ",[14,5169,5170],{},"发出信号","，把最新分数传出去",[109,5173,5175,3073,5178],{"id":5174},"set_state-和-game_over",[140,5176,5177],{},"set_state()",[140,5179,5180],{},"game_over()",[10,5182,5183,5186,5187,5190,5191,5194],{},[140,5184,5185],{},"set_state"," 是修改状态的",[14,5188,5189],{},"唯一入口","，每次修改都自动 emit 信号。这样",[14,5192,5193],{},"任何状态改变都会被广播","，不会漏。",[10,5196,5197,5199],{},[140,5198,3336],{}," 不仅切换状态，还顺便更新最高分。",[60,5201,5203],{"id":5202},"把-gamemanager-接进-killzone-和-障碍物","把 GameManager 接进 Killzone 和 障碍物",[10,5205,5206,5207,5210],{},"GameManager 创建好了，现在回到 ",[37,5208,5209],{"href":4400},"上一章"," 里我们留的\"后续优化预览\"代码，把它们正式接入：",[109,5212,5214,5215,230],{"id":5213},"死区脚本killzonegd","死区脚本（",[140,5216,5217],{},"killzone.gd",[251,5219,5221],{"className":1229,"code":5220,"language":1231,"meta":259,"style":259},"extends Area2D\n\nfunc _on_body_entered(body: Node2D) -> void:\n    if body.name == \"Bird\" and GameManager.current_state == GameManager.GameState.PLAYING:\n        GameManager.game_over()\n",[140,5222,5223,5229,5233,5255,5291],{"__ignoreMap":259},[277,5224,5225,5227],{"class":279,"line":280},[277,5226,1239],{"class":1238},[277,5228,3139],{"class":1242},[277,5230,5231],{"class":279,"line":287},[277,5232,320],{"emptyLinePlaceholder":319},[277,5234,5235,5237,5239,5241,5243,5245,5247,5249,5251,5253],{"class":279,"line":298},[277,5236,1320],{"class":1258},[277,5238,3150],{"class":301},[277,5240,1326],{"class":1266},[277,5242,3155],{"class":1329},[277,5244,1333],{"class":1266},[277,5246,3160],{"class":1242},[277,5248,1339],{"class":1266},[277,5250,1342],{"class":1258},[277,5252,1345],{"class":1242},[277,5254,1348],{"class":1266},[277,5256,5257,5259,5261,5263,5265,5267,5269,5271,5273,5275,5277,5279,5281,5283,5285,5287,5289],{"class":279,"line":316},[277,5258,1360],{"class":1238},[277,5260,3175],{"class":1262},[277,5262,1418],{"class":1266},[277,5264,3180],{"class":1262},[277,5266,3183],{"class":1258},[277,5268,3186],{"class":294},[277,5270,3290],{"class":1258},[277,5272,3293],{"class":1242},[277,5274,1418],{"class":1266},[277,5276,3298],{"class":1262},[277,5278,3183],{"class":1258},[277,5280,3293],{"class":1242},[277,5282,1418],{"class":1266},[277,5284,3307],{"class":1262},[277,5286,1418],{"class":1266},[277,5288,3312],{"class":1238},[277,5290,1348],{"class":1329},[277,5292,5293,5295,5297,5299],{"class":279,"line":323},[277,5294,3331],{"class":1242},[277,5296,1418],{"class":1329},[277,5298,3336],{"class":301},[277,5300,1504],{"class":1266},[10,5302,5303,5304,5307],{},"加上 ",[140,5305,5306],{},"current_state == PLAYING"," 判断 — 防止 READY 状态（还没开始）和 GAME_OVER 状态（已经结束）时误触发。",[109,5309,5311,5312,230],{"id":5310},"障碍物脚本pillar_pairgd","障碍物脚本（",[140,5313,5314],{},"pillar_pair.gd",[251,5316,5318],{"className":1229,"code":5317,"language":1231,"meta":259,"style":259},"extends Node2D\n\n@export var speed := 200.0\n@onready var goal: Area2D = $Goal\n\nfunc _physics_process(delta: float) -> void:\n    # 只在游戏进行中才移动\n    if GameManager.current_state == GameManager.GameState.PLAYING:\n        position.x -= speed * delta\n        if position.x \u003C -500:\n            queue_free()\n\nfunc _on_goal_body_entered(body: Node2D) -> void:\n    if body.name == \"Bird\":\n        if GameManager.has_method(\"add_score\"):\n            GameManager.add_score(1)\n        goal.set_deferred(\"monitoring\", false)\n",[140,5319,5320,5326,5330,5342,5360,5364,5386,5391,5415,5431,5449,5455,5459,5481,5497,5515,5529],{"__ignoreMap":259},[277,5321,5322,5324],{"class":279,"line":280},[277,5323,1239],{"class":1238},[277,5325,3413],{"class":1242},[277,5327,5328],{"class":279,"line":287},[277,5329,320],{"emptyLinePlaceholder":319},[277,5331,5332,5334,5336,5338,5340],{"class":279,"line":298},[277,5333,1255],{"class":301},[277,5335,1259],{"class":1258},[277,5337,3426],{"class":1262},[277,5339,1267],{"class":1266},[277,5341,3431],{"class":312},[277,5343,5344,5346,5348,5350,5352,5354,5356,5358],{"class":279,"line":316},[277,5345,3436],{"class":301},[277,5347,1259],{"class":1258},[277,5349,3441],{"class":1262},[277,5351,1333],{"class":1266},[277,5353,3446],{"class":1242},[277,5355,1440],{"class":1266},[277,5357,3451],{"class":1238},[277,5359,3454],{"class":305},[277,5361,5362],{"class":279,"line":323},[277,5363,320],{"emptyLinePlaceholder":319},[277,5365,5366,5368,5370,5372,5374,5376,5378,5380,5382,5384],{"class":279,"line":329},[277,5367,1320],{"class":1258},[277,5369,1323],{"class":301},[277,5371,1326],{"class":1266},[277,5373,1330],{"class":1329},[277,5375,1333],{"class":1266},[277,5377,1336],{"class":1242},[277,5379,1339],{"class":1266},[277,5381,1342],{"class":1258},[277,5383,1345],{"class":1242},[277,5385,1348],{"class":1266},[277,5387,5388],{"class":279,"line":1317},[277,5389,5390],{"class":283},"    # 只在游戏进行中才移动\n",[277,5392,5393,5395,5397,5399,5401,5403,5405,5407,5409,5411,5413],{"class":279,"line":1351},[277,5394,1360],{"class":1238},[277,5396,3293],{"class":1242},[277,5398,1418],{"class":1266},[277,5400,3298],{"class":1262},[277,5402,3183],{"class":1258},[277,5404,3293],{"class":1242},[277,5406,1418],{"class":1266},[277,5408,3307],{"class":1262},[277,5410,1418],{"class":1266},[277,5412,3312],{"class":1238},[277,5414,1348],{"class":1329},[277,5416,5417,5419,5421,5423,5425,5427,5429],{"class":279,"line":1357},[277,5418,3818],{"class":1262},[277,5420,1418],{"class":1266},[277,5422,1891],{"class":1262},[277,5424,3497],{"class":1258},[277,5426,3426],{"class":1262},[277,5428,1388],{"class":1258},[277,5430,3504],{"class":1262},[277,5432,5433,5435,5437,5439,5441,5443,5445,5447],{"class":279,"line":1374},[277,5434,3835],{"class":1238},[277,5436,3516],{"class":1262},[277,5438,1418],{"class":1266},[277,5440,1891],{"class":1262},[277,5442,2720],{"class":1258},[277,5444,1287],{"class":1258},[277,5446,3527],{"class":312},[277,5448,1348],{"class":1329},[277,5450,5451,5453],{"class":279,"line":1399},[277,5452,3854],{"class":301},[277,5454,1504],{"class":1266},[277,5456,5457],{"class":279,"line":1404},[277,5458,320],{"emptyLinePlaceholder":319},[277,5460,5461,5463,5465,5467,5469,5471,5473,5475,5477,5479],{"class":279,"line":1410},[277,5462,1320],{"class":1258},[277,5464,3552],{"class":301},[277,5466,1326],{"class":1266},[277,5468,3155],{"class":1329},[277,5470,1333],{"class":1266},[277,5472,3160],{"class":1242},[277,5474,1339],{"class":1266},[277,5476,1342],{"class":1258},[277,5478,1345],{"class":1242},[277,5480,1348],{"class":1266},[277,5482,5483,5485,5487,5489,5491,5493,5495],{"class":279,"line":1431},[277,5484,1360],{"class":1238},[277,5486,3175],{"class":1262},[277,5488,1418],{"class":1266},[277,5490,3180],{"class":1262},[277,5492,3183],{"class":1258},[277,5494,3186],{"class":294},[277,5496,1348],{"class":1329},[277,5498,5499,5501,5503,5505,5507,5509,5511,5513],{"class":279,"line":1446},[277,5500,3835],{"class":1238},[277,5502,3293],{"class":1242},[277,5504,1418],{"class":1329},[277,5506,3920],{"class":301},[277,5508,1326],{"class":1266},[277,5510,3925],{"class":294},[277,5512,1339],{"class":1266},[277,5514,1348],{"class":1329},[277,5516,5517,5519,5521,5523,5525,5527],{"class":279,"line":1451},[277,5518,3934],{"class":1242},[277,5520,1418],{"class":1329},[277,5522,3939],{"class":301},[277,5524,1326],{"class":1266},[277,5526,3944],{"class":312},[277,5528,2651],{"class":1266},[277,5530,5531,5533,5535,5537,5539,5541,5543,5545],{"class":279,"line":1457},[277,5532,3615],{"class":1262},[277,5534,1418],{"class":1329},[277,5536,3620],{"class":301},[277,5538,1326],{"class":1266},[277,5540,3625],{"class":294},[277,5542,2812],{"class":1266},[277,5544,3630],{"class":1238},[277,5546,2651],{"class":1266},[109,5548,5550,5551,230],{"id":5549},"生成器脚本pillar_spawnergd","生成器脚本（",[140,5552,5553],{},"pillar_spawner.gd",[251,5555,5557],{"className":1229,"code":5556,"language":1231,"meta":259,"style":259},"extends Node2D\n\n@export var pillar_scene: PackedScene\n@export var y_range := 250.0\n\nfunc _on_timer_timeout() -> void:\n    spawn_pillar()\n\nfunc spawn_pillar() -> void:\n    # 只在游戏进行中才生成\n    if GameManager.current_state == GameManager.GameState.PLAYING:\n        var new_pillar = pillar_scene.instantiate()\n        var spawn_pos = global_position\n        spawn_pos.y += randf_range(-y_range, y_range)\n        new_pillar.global_position = spawn_pos\n        get_tree().current_scene.add_child(new_pillar)\n",[140,5558,5559,5565,5569,5581,5593,5597,5611,5617,5621,5635,5640,5664,5681,5691,5716,5729],{"__ignoreMap":259},[277,5560,5561,5563],{"class":279,"line":280},[277,5562,1239],{"class":1238},[277,5564,3413],{"class":1242},[277,5566,5567],{"class":279,"line":287},[277,5568,320],{"emptyLinePlaceholder":319},[277,5570,5571,5573,5575,5577,5579],{"class":279,"line":298},[277,5572,1255],{"class":301},[277,5574,1259],{"class":1258},[277,5576,4099],{"class":1262},[277,5578,1333],{"class":1266},[277,5580,4104],{"class":1242},[277,5582,5583,5585,5587,5589,5591],{"class":279,"line":316},[277,5584,1255],{"class":301},[277,5586,1259],{"class":1258},[277,5588,4113],{"class":1262},[277,5590,1267],{"class":1266},[277,5592,4118],{"class":312},[277,5594,5595],{"class":279,"line":323},[277,5596,320],{"emptyLinePlaceholder":319},[277,5598,5599,5601,5603,5605,5607,5609],{"class":279,"line":329},[277,5600,1320],{"class":1258},[277,5602,4129],{"class":301},[277,5604,1369],{"class":1266},[277,5606,1342],{"class":1258},[277,5608,1345],{"class":1242},[277,5610,1348],{"class":1266},[277,5612,5613,5615],{"class":279,"line":1317},[277,5614,4142],{"class":301},[277,5616,1504],{"class":1266},[277,5618,5619],{"class":279,"line":1351},[277,5620,320],{"emptyLinePlaceholder":319},[277,5622,5623,5625,5627,5629,5631,5633],{"class":279,"line":1357},[277,5624,1320],{"class":1258},[277,5626,4155],{"class":301},[277,5628,1369],{"class":1266},[277,5630,1342],{"class":1258},[277,5632,1345],{"class":1242},[277,5634,1348],{"class":1266},[277,5636,5637],{"class":279,"line":1374},[277,5638,5639],{"class":283},"    # 只在游戏进行中才生成\n",[277,5641,5642,5644,5646,5648,5650,5652,5654,5656,5658,5660,5662],{"class":279,"line":1399},[277,5643,1360],{"class":1238},[277,5645,3293],{"class":1242},[277,5647,1418],{"class":1266},[277,5649,3298],{"class":1262},[277,5651,3183],{"class":1258},[277,5653,3293],{"class":1242},[277,5655,1418],{"class":1266},[277,5657,3307],{"class":1262},[277,5659,1418],{"class":1266},[277,5661,3312],{"class":1238},[277,5663,1348],{"class":1329},[277,5665,5666,5669,5671,5673,5675,5677,5679],{"class":279,"line":1404},[277,5667,5668],{"class":1258},"        var",[277,5670,4170],{"class":1262},[277,5672,1440],{"class":1266},[277,5674,4099],{"class":1262},[277,5676,1418],{"class":1329},[277,5678,4179],{"class":301},[277,5680,1504],{"class":1266},[277,5682,5683,5685,5687,5689],{"class":279,"line":1410},[277,5684,5668],{"class":1258},[277,5686,4192],{"class":1262},[277,5688,1440],{"class":1266},[277,5690,4197],{"class":1262},[277,5692,5693,5696,5698,5700,5702,5704,5706,5708,5710,5712,5714],{"class":279,"line":1431},[277,5694,5695],{"class":1262},"        spawn_pos",[277,5697,1418],{"class":1266},[277,5699,1421],{"class":1262},[277,5701,1380],{"class":1258},[277,5703,4211],{"class":301},[277,5705,1326],{"class":1266},[277,5707,2747],{"class":1258},[277,5709,4218],{"class":1262},[277,5711,2812],{"class":1266},[277,5713,4113],{"class":1262},[277,5715,2651],{"class":1266},[277,5717,5718,5721,5723,5725,5727],{"class":279,"line":1446},[277,5719,5720],{"class":1262},"        new_pillar",[277,5722,1418],{"class":1266},[277,5724,4238],{"class":1262},[277,5726,1440],{"class":1266},[277,5728,4243],{"class":1262},[277,5730,5731,5734,5736,5738,5740,5742,5744,5746,5748],{"class":279,"line":1451},[277,5732,5733],{"class":301},"        get_tree",[277,5735,1369],{"class":1266},[277,5737,1418],{"class":1329},[277,5739,4264],{"class":1262},[277,5741,1418],{"class":1329},[277,5743,4269],{"class":301},[277,5745,1326],{"class":1266},[277,5747,4274],{"class":1262},[277,5749,2651],{"class":1266},[60,5751,2350],{"id":2350},[10,5753,5754,5755,17],{},"现在还没有 UI，所以视觉上看不出区别 — ",[14,5756,5757],{},"但状态系统已经在跑了",[10,5759,5760,5761,5763],{},"你可以在 ",[140,5762,2402],{}," 里临时加个调试：",[251,5765,5767],{"className":1229,"code":5766,"language":1231,"meta":259,"style":259},"func _physics_process(delta: float) -> void:\n    if Input.is_action_just_pressed(\"fly\"):\n        print(\"当前状态:\", GameManager.current_state)\n        # ...\n",[140,5768,5769,5791,5809,5828],{"__ignoreMap":259},[277,5770,5771,5773,5775,5777,5779,5781,5783,5785,5787,5789],{"class":279,"line":280},[277,5772,1320],{"class":1258},[277,5774,1323],{"class":301},[277,5776,1326],{"class":1266},[277,5778,1330],{"class":1329},[277,5780,1333],{"class":1266},[277,5782,1336],{"class":1242},[277,5784,1339],{"class":1266},[277,5786,1342],{"class":1258},[277,5788,1345],{"class":1242},[277,5790,1348],{"class":1266},[277,5792,5793,5795,5797,5799,5801,5803,5805,5807],{"class":279,"line":287},[277,5794,1360],{"class":1238},[277,5796,1462],{"class":1242},[277,5798,1418],{"class":1329},[277,5800,1467],{"class":301},[277,5802,1326],{"class":1266},[277,5804,1472],{"class":294},[277,5806,1339],{"class":1266},[277,5808,1348],{"class":1329},[277,5810,5811,5813,5815,5818,5820,5822,5824,5826],{"class":279,"line":298},[277,5812,3193],{"class":301},[277,5814,1326],{"class":1266},[277,5816,5817],{"class":294},"\"当前状态:\"",[277,5819,2812],{"class":1266},[277,5821,3293],{"class":1242},[277,5823,1418],{"class":1266},[277,5825,3298],{"class":1262},[277,5827,2651],{"class":1266},[277,5829,5830],{"class":279,"line":316},[277,5831,5832],{"class":283},"        # ...\n",[10,5834,5835,5836,5838,5839,5841],{},"不过这时候默认是 ",[140,5837,4434],{}," 状态，水管不会生成、撞死也不会触发 — 因为我们还没有\"开始游戏\"的入口。下一章做 UI 时会加上一个开始按钮，把状态切到 ",[140,5840,3312],{},"，整个流程才会真正跑起来。",[854,5843,5844],{},"html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s_NWU, html code.shiki .s_NWU{--shiki-default:#2E8F82;--shiki-dark:#5DA994}html pre.shiki code .snYqZ, html code.shiki .snYqZ{--shiki-default:#A0ADA0;--shiki-dark:#758575DD}html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .s8w-G, html code.shiki .s8w-G{--shiki-default:#393A34;--shiki-dark:#DBD7CAEE}html pre.shiki code .s5TCs, html code.shiki .s5TCs{--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .sqbOQ, html code.shiki .sqbOQ{--shiki-default:#2F798A;--shiki-dark:#4C9A91}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html pre.shiki code .sfsYZ, html code.shiki .sfsYZ{--shiki-default:#A65E2B;--shiki-dark:#C99076}",{"title":259,"searchDepth":287,"depth":287,"links":5846},[5847,5848,5849,5852,5861,5869],{"id":4423,"depth":287,"text":4424},{"id":4459,"depth":287,"text":4460},{"id":4817,"depth":287,"text":4818,"children":5850},[5851],{"id":4846,"depth":298,"text":4846},{"id":3641,"depth":287,"text":3642,"children":5853},[5854,5855,5857,5859],{"id":4938,"depth":298,"text":4941},{"id":5036,"depth":298,"text":5856},"signal（信号）",{"id":5100,"depth":298,"text":5858},"add_score() 和 score_changed.emit()",{"id":5174,"depth":298,"text":5860},"set_state() 和 game_over()",{"id":5202,"depth":287,"text":5203,"children":5862},[5863,5865,5867],{"id":5213,"depth":298,"text":5864},"死区脚本（killzone.gd）",{"id":5310,"depth":298,"text":5866},"障碍物脚本（pillar_pair.gd）",{"id":5549,"depth":298,"text":5868},"生成器脚本（pillar_spawner.gd）",{"id":2350,"depth":287,"text":2350},{},"\u002Fdevlog\u002Fxggame-bird\u002F05-game-manager",{"title":3226,"description":878},"devlog\u002Fxggame-bird\u002F05-game-manager","FNw8591Sth7D2Iaz9Dt3V0d-rXtMLyEjwyH5oxagnGQ",{"id":5876,"title":5877,"body":5878,"cover":876,"date":877,"description":878,"extension":879,"game":410,"github":880,"icon":876,"meta":7146,"navigation":319,"path":7147,"seo":7148,"stem":7149,"toc":319,"__hash__":7150},"devlog\u002Fdevlog\u002Fxggame-bird\u002F06-ui.md","UI 界面 + 主场景",{"type":7,"value":5879,"toc":7126},[5880,5883,5916,5919,5924,5930,5936,5943,5945,5972,5987,5993,5996,6009,6057,6068,6074,6087,6090,6188,6194,6200,6571,6573,6648,6655,6707,6716,6719,6728,6733,6736,6744,6845,6859,6865,6868,6877,6883,7050,7068,7080,7083,7089,7092,7098,7100,7103,7120,7123],[10,5881,5882],{},"GameManager 把状态和分数管理好了，这一章让玩家\"看得见\"。我们要做：",[22,5884,5885,5895,5904,5910],{},[25,5886,5887,5890,5891,5894],{},[14,5888,5889],{},"开始菜单","（",[140,5892,5893],{},"menu.tscn","）— 一个开始按钮",[25,5896,5897,5890,5900,5903],{},[14,5898,5899],{},"游戏主场景",[140,5901,5902],{},"main.tscn","）— 整合小鸟、水管、UI",[25,5905,5906,5909],{},[14,5907,5908],{},"分数 UI"," — 实时显示当前分数",[25,5911,5912,5915],{},[14,5913,5914],{},"结束画面"," — 显示最终分数 + 重新开始按钮",[60,5917,5918],{"id":5918},"整体流程",[45,5920,5921],{},[10,5922,5923],{},"整理一下我们要搭的\"两个场景\"的关系：",[251,5925,5928],{"className":5926,"code":5927,"language":256},[254],"menu.tscn（开始菜单）\n   ↓ 点击「开始」按钮\nmain.tscn（游戏主场景）\n   ├─ READY 状态：等待玩家按下 fly\n   ├─ PLAYING 状态：正常游戏\n   └─ GAME_OVER 状态：显示分数 + 重新开始按钮\n",[140,5929,5927],{"__ignoreMap":259},[60,5931,5933,5934],{"id":5932},"开始菜单-menutscn","开始菜单 ",[140,5935,5893],{},[10,5937,2182,5938,2186,5940,17],{},[140,5939,2185],{},[140,5941,5942],{},"scenes\u002Fmenu.tscn",[10,5944,2197],{},[634,5946,5947],{},[25,5948,5949,5951,5952],{},[140,5950,2185],{}," — 菜单根节点\n",[634,5953,5954],{},[25,5955,5956,5959,5960],{},[140,5957,5958],{},"CanvasLayer"," — UI 图层（不随摄像机移动）\n",[634,5961,5962],{},[25,5963,5964,5967,5968,5971],{},[140,5965,5966],{},"Button","（重命名 ",[140,5969,5970],{},"StartButton","）— 开始按钮",[45,5973,5974],{},[10,5975,5976,5977,5979,5980,5982,5983,5986],{},"为什么 UI 要放在 ",[140,5978,5958],{}," 下？因为 ",[140,5981,5958],{}," 的内容",[14,5984,5985],{},"不会被摄像机变换影响","，永远固定在屏幕上。",[10,5988,5989],{},[80,5990],{"alt":5991,"src":5992},"06-ui-menu-场景","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F06-ui-menu-%E5%9C%BA%E6%99%AF.png",[109,5994,5995],{"id":5995},"菜单脚本",[10,5997,5998,5999,6002,6003,1144,6005,6008],{},"选中根节点挂脚本 ",[140,6000,6001],{},"scripts\u002Fmenu.gd","，然后在【节点】面板把 ",[140,6004,5970],{},[140,6006,6007],{},"pressed"," 信号连接到根节点：",[251,6010,6012],{"className":1229,"code":6011,"language":1231,"meta":259,"style":259},"extends Node2D\n\nfunc _on_start_button_pressed() -> void:\n    get_tree().change_scene_to_file(\"res:\u002F\u002Fscenes\u002Fmain.tscn\")\n",[140,6013,6014,6020,6024,6039],{"__ignoreMap":259},[277,6015,6016,6018],{"class":279,"line":280},[277,6017,1239],{"class":1238},[277,6019,3413],{"class":1242},[277,6021,6022],{"class":279,"line":287},[277,6023,320],{"emptyLinePlaceholder":319},[277,6025,6026,6028,6031,6033,6035,6037],{"class":279,"line":298},[277,6027,1320],{"class":1258},[277,6029,6030],{"class":301}," _on_start_button_pressed",[277,6032,1369],{"class":1266},[277,6034,1342],{"class":1258},[277,6036,1345],{"class":1242},[277,6038,1348],{"class":1266},[277,6040,6041,6043,6045,6047,6050,6052,6055],{"class":279,"line":316},[277,6042,4257],{"class":301},[277,6044,1369],{"class":1266},[277,6046,1418],{"class":1329},[277,6048,6049],{"class":301},"change_scene_to_file",[277,6051,1326],{"class":1266},[277,6053,6054],{"class":294},"\"res:\u002F\u002Fscenes\u002Fmain.tscn\"",[277,6056,2651],{"class":1266},[634,6058,6059],{},[25,6060,6061,6064,6065,6067],{},[140,6062,6063],{},"change_scene_to_file()"," — 切换到指定场景，路径填你保存 ",[140,6066,5902],{}," 的位置",[60,6069,6071,6072],{"id":6070},"游戏主场景-maintscn","游戏主场景 ",[140,6073,5902],{},[10,6075,6076,6077,6080,6081,6083,6084,6086],{},"现在把之前章节做的所有东西",[14,6078,6079],{},"整合到一起","。如果之前你已经做了 ",[140,6082,2247],{},"，可以直接改名为 ",[140,6085,5902],{},"，或者新建一个。",[10,6088,6089],{},"使用到的节点（核心结构）：",[634,6091,6092],{},[25,6093,6094,6096,6097,2925,6100],{},[140,6095,2185],{}," — 主场景根节点（挂 ",[140,6098,6099],{},"main.gd",[634,6101,6102,6107,6120,6128,6135,6140],{},[25,6103,6104,6106],{},[140,6105,2211],{}," — 摄像机",[25,6108,6109,5967,6112,6115,6116,230],{},[140,6110,6111],{},"Parallax2D",[140,6113,6114],{},"BG","）— 滚动背景（",[37,6117,6119],{"href":6118},"\u002Fdevlog\u002Fxggame-bird\u002F07-art-assets","第 7 章会做",[25,6121,6122,5967,6124,6127],{},[140,6123,6111],{},[140,6125,6126],{},"Progress","）— 滚动地面",[25,6129,6130,6132,6133,230],{},[140,6131,922],{}," — 小鸟（之前做的 ",[140,6134,2232],{},[25,6136,6137,6139],{},[140,6138,4010],{}," — 障碍物生成器",[25,6141,6142,6144,6145],{},[140,6143,5958],{}," — UI 层\n",[634,6146,6147,6156],{},[25,6148,6149,5967,6152,6155],{},[140,6150,6151],{},"Label",[140,6153,6154],{},"ScoreLabel","）— 分数显示",[25,6157,6158,5967,6161,6164,6165],{},[140,6159,6160],{},"Panel",[140,6162,6163],{},"GameOverPanel","）— 结束面板\n",[634,6166,6167,6172,6180],{},[25,6168,6169,6171],{},[140,6170,6151],{}," — \"Game Over\"",[25,6173,6174,5890,6176,6179],{},[140,6175,6151],{},[140,6177,6178],{},"FinalScore","）— 最终分数",[25,6181,6182,5890,6184,6187],{},[140,6183,5966],{},[140,6185,6186],{},"RestartButton","）— 重新开始",[10,6189,6190],{},[80,6191],{"alt":6192,"src":6193},"06-ui-main-场景","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F06-ui-main-%E5%9C%BA%E6%99%AF.png",[60,6195,6197,6198],{"id":6196},"主场景脚本-maingd","主场景脚本 ",[140,6199,6099],{},[251,6201,6203],{"className":1229,"code":6202,"language":1231,"meta":259,"style":259},"extends Node2D\n\nfunc _ready() -> void:\n    # 刚加载场景，设置为准备状态\n    GameManager.set_state(GameManager.GameState.READY)\n    GameManager.state_changed.connect(_on_game_state)  # 监听信号\n    Engine.time_scale = 0  # 冻结游戏\n    GameManager.clear_score()  # 游戏开始前清空分数\n\nfunc _input(event: InputEvent) -> void:\n    # 准备状态下，按一下 fly 就开始游戏\n    if GameManager.current_state == GameManager.GameState.READY:\n        if event.is_action_pressed(\"fly\"):\n            start_game()\n\nfunc start_game():\n    GameManager.set_state(GameManager.GameState.PLAYING)\n    Engine.time_scale = 1  # 解冻游戏\n\n## 重新开始游戏\nfunc _on_restart_button_pressed() -> void:\n    get_tree().reload_current_scene()\n\nfunc _on_game_state(new_state) -> void:\n    # 游戏结束时，让背景和地面停止滚动\n    if new_state == GameManager.GameState.GAME_OVER:\n        $BG.autoscroll.x = 0\n        $Progress.autoscroll.x = 0\n",[140,6204,6205,6211,6215,6230,6235,6258,6281,6298,6312,6316,6341,6346,6370,6390,6397,6401,6410,6432,6447,6451,6456,6471,6484,6488,6507,6512,6533,6553],{"__ignoreMap":259},[277,6206,6207,6209],{"class":279,"line":280},[277,6208,1239],{"class":1238},[277,6210,3413],{"class":1242},[277,6212,6213],{"class":279,"line":287},[277,6214,320],{"emptyLinePlaceholder":319},[277,6216,6217,6219,6222,6224,6226,6228],{"class":279,"line":298},[277,6218,1320],{"class":1258},[277,6220,6221],{"class":301}," _ready",[277,6223,1369],{"class":1266},[277,6225,1342],{"class":1258},[277,6227,1345],{"class":1242},[277,6229,1348],{"class":1266},[277,6231,6232],{"class":279,"line":316},[277,6233,6234],{"class":283},"    # 刚加载场景，设置为准备状态\n",[277,6236,6237,6240,6242,6244,6246,6248,6250,6252,6254,6256],{"class":279,"line":323},[277,6238,6239],{"class":1242},"    GameManager",[277,6241,1418],{"class":1329},[277,6243,5185],{"class":301},[277,6245,1326],{"class":1266},[277,6247,3226],{"class":1242},[277,6249,1418],{"class":1266},[277,6251,3307],{"class":1262},[277,6253,1418],{"class":1266},[277,6255,4434],{"class":1238},[277,6257,2651],{"class":1266},[277,6259,6260,6262,6264,6266,6268,6271,6273,6276,6278],{"class":279,"line":329},[277,6261,6239],{"class":1242},[277,6263,1418],{"class":1266},[277,6265,5090],{"class":1262},[277,6267,1418],{"class":1329},[277,6269,6270],{"class":301},"connect",[277,6272,1326],{"class":1266},[277,6274,6275],{"class":1262},"_on_game_state",[277,6277,1339],{"class":1266},[277,6279,6280],{"class":283},"  # 监听信号\n",[277,6282,6283,6286,6288,6291,6293,6295],{"class":279,"line":1317},[277,6284,6285],{"class":1242},"    Engine",[277,6287,1418],{"class":1266},[277,6289,6290],{"class":1262},"time_scale",[277,6292,1440],{"class":1266},[277,6294,2723],{"class":312},[277,6296,6297],{"class":283},"  # 冻结游戏\n",[277,6299,6300,6302,6304,6307,6309],{"class":279,"line":1351},[277,6301,6239],{"class":1242},[277,6303,1418],{"class":1329},[277,6305,6306],{"class":301},"clear_score",[277,6308,1369],{"class":1266},[277,6310,6311],{"class":283},"  # 游戏开始前清空分数\n",[277,6313,6314],{"class":279,"line":1357},[277,6315,320],{"emptyLinePlaceholder":319},[277,6317,6318,6320,6323,6325,6328,6330,6333,6335,6337,6339],{"class":279,"line":1374},[277,6319,1320],{"class":1258},[277,6321,6322],{"class":301}," _input",[277,6324,1326],{"class":1266},[277,6326,6327],{"class":1329},"event",[277,6329,1333],{"class":1266},[277,6331,6332],{"class":1242}," InputEvent",[277,6334,1339],{"class":1266},[277,6336,1342],{"class":1258},[277,6338,1345],{"class":1242},[277,6340,1348],{"class":1266},[277,6342,6343],{"class":279,"line":1399},[277,6344,6345],{"class":283},"    # 准备状态下，按一下 fly 就开始游戏\n",[277,6347,6348,6350,6352,6354,6356,6358,6360,6362,6364,6366,6368],{"class":279,"line":1404},[277,6349,1360],{"class":1238},[277,6351,3293],{"class":1242},[277,6353,1418],{"class":1266},[277,6355,3298],{"class":1262},[277,6357,3183],{"class":1258},[277,6359,3293],{"class":1242},[277,6361,1418],{"class":1266},[277,6363,3307],{"class":1262},[277,6365,1418],{"class":1266},[277,6367,4434],{"class":1238},[277,6369,1348],{"class":1329},[277,6371,6372,6374,6377,6379,6382,6384,6386,6388],{"class":279,"line":1410},[277,6373,3835],{"class":1238},[277,6375,6376],{"class":1262}," event",[277,6378,1418],{"class":1329},[277,6380,6381],{"class":301},"is_action_pressed",[277,6383,1326],{"class":1266},[277,6385,1472],{"class":294},[277,6387,1339],{"class":1266},[277,6389,1348],{"class":1329},[277,6391,6392,6395],{"class":279,"line":1431},[277,6393,6394],{"class":301},"            start_game",[277,6396,1504],{"class":1266},[277,6398,6399],{"class":279,"line":1446},[277,6400,320],{"emptyLinePlaceholder":319},[277,6402,6403,6405,6408],{"class":279,"line":1451},[277,6404,1320],{"class":1258},[277,6406,6407],{"class":301}," start_game",[277,6409,4626],{"class":1266},[277,6411,6412,6414,6416,6418,6420,6422,6424,6426,6428,6430],{"class":279,"line":1457},[277,6413,6239],{"class":1242},[277,6415,1418],{"class":1329},[277,6417,5185],{"class":301},[277,6419,1326],{"class":1266},[277,6421,3226],{"class":1242},[277,6423,1418],{"class":1266},[277,6425,3307],{"class":1262},[277,6427,1418],{"class":1266},[277,6429,3312],{"class":1238},[277,6431,2651],{"class":1266},[277,6433,6434,6436,6438,6440,6442,6444],{"class":279,"line":1479},[277,6435,6285],{"class":1242},[277,6437,1418],{"class":1266},[277,6439,6290],{"class":1262},[277,6441,1440],{"class":1266},[277,6443,4682],{"class":312},[277,6445,6446],{"class":283},"  # 解冻游戏\n",[277,6448,6449],{"class":279,"line":1493},[277,6450,320],{"emptyLinePlaceholder":319},[277,6452,6453],{"class":279,"line":1498},[277,6454,6455],{"class":283},"## 重新开始游戏\n",[277,6457,6458,6460,6463,6465,6467,6469],{"class":279,"line":2630},[277,6459,1320],{"class":1258},[277,6461,6462],{"class":301}," _on_restart_button_pressed",[277,6464,1369],{"class":1266},[277,6466,1342],{"class":1258},[277,6468,1345],{"class":1242},[277,6470,1348],{"class":1266},[277,6472,6473,6475,6477,6479,6482],{"class":279,"line":2635},[277,6474,4257],{"class":301},[277,6476,1369],{"class":1266},[277,6478,1418],{"class":1329},[277,6480,6481],{"class":301},"reload_current_scene",[277,6483,1504],{"class":1266},[277,6485,6486],{"class":279,"line":2641},[277,6487,320],{"emptyLinePlaceholder":319},[277,6489,6490,6492,6495,6497,6499,6501,6503,6505],{"class":279,"line":2654},[277,6491,1320],{"class":1258},[277,6493,6494],{"class":301}," _on_game_state",[277,6496,1326],{"class":1266},[277,6498,4605],{"class":1329},[277,6500,1339],{"class":1266},[277,6502,1342],{"class":1258},[277,6504,1345],{"class":1242},[277,6506,1348],{"class":1266},[277,6508,6509],{"class":279,"line":2661},[277,6510,6511],{"class":283},"    # 游戏结束时，让背景和地面停止滚动\n",[277,6513,6514,6516,6519,6521,6523,6525,6527,6529,6531],{"class":279,"line":2666},[277,6515,1360],{"class":1238},[277,6517,6518],{"class":1262}," new_state",[277,6520,3183],{"class":1258},[277,6522,3293],{"class":1242},[277,6524,1418],{"class":1266},[277,6526,3307],{"class":1262},[277,6528,1418],{"class":1266},[277,6530,4445],{"class":1238},[277,6532,1348],{"class":1329},[277,6534,6535,6538,6540,6542,6545,6547,6549,6551],{"class":279,"line":2690},[277,6536,6537],{"class":1238},"        $",[277,6539,6114],{"class":305},[277,6541,1418],{"class":1266},[277,6543,6544],{"class":1262},"autoscroll",[277,6546,1418],{"class":1266},[277,6548,1891],{"class":1262},[277,6550,1440],{"class":1266},[277,6552,2615],{"class":312},[277,6554,6555,6557,6559,6561,6563,6565,6567,6569],{"class":279,"line":2704},[277,6556,6537],{"class":1238},[277,6558,6126],{"class":305},[277,6560,1418],{"class":1266},[277,6562,6544],{"class":1262},[277,6564,1418],{"class":1266},[277,6566,1891],{"class":1262},[277,6568,1440],{"class":1266},[277,6570,2615],{"class":312},[109,6572,3642],{"id":3641},[113,6574,6575,6584],{},[116,6576,6577],{},[119,6578,6579,6582],{},[122,6580,6581],{},"函数",[122,6583,1035],{},[132,6585,6586,6600,6613,6625,6635],{},[119,6587,6588,6593],{},[137,6589,6590],{},[140,6591,6592],{},"_ready()",[137,6594,6595,6596,6599],{},"场景一加载就执行：把状态设为 READY、监听状态变化、",[14,6597,6598],{},"冻结整个游戏","、清空分数",[119,6601,6602,6607],{},[137,6603,6604],{},[140,6605,6606],{},"_input()",[137,6608,6609,6610],{},"监听全局输入：在 READY 状态下，按下 fly 就调用 ",[140,6611,6612],{},"start_game()",[119,6614,6615,6619],{},[137,6616,6617],{},[140,6618,6612],{},[137,6620,6621,6622],{},"把状态切到 PLAYING，",[14,6623,6624],{},"解冻游戏",[119,6626,6627,6632],{},[137,6628,6629],{},[140,6630,6631],{},"_on_restart_button_pressed()",[137,6633,6634],{},"重启按钮：直接重新加载当前场景",[119,6636,6637,6642],{},[137,6638,6639],{},[140,6640,6641],{},"_on_game_state()",[137,6643,6644,6645,6647],{},"监听 GameManager 的 ",[140,6646,5090],{}," 信号，GAME_OVER 时停止背景滚动",[109,6649,6651,6654],{"id":6650},"enginetime_scale-全局时间缩放",[140,6652,6653],{},"Engine.time_scale"," — 全局时间缩放",[251,6656,6658],{"className":1229,"code":6657,"language":1231,"meta":259,"style":259},"Engine.time_scale = 0  # 完全冻结\nEngine.time_scale = 1  # 正常速度\nEngine.time_scale = 0.5  # 慢动作\n",[140,6659,6660,6676,6691],{"__ignoreMap":259},[277,6661,6662,6665,6667,6669,6671,6673],{"class":279,"line":280},[277,6663,6664],{"class":1242},"Engine",[277,6666,1418],{"class":1266},[277,6668,6290],{"class":1262},[277,6670,1440],{"class":1266},[277,6672,2723],{"class":312},[277,6674,6675],{"class":283},"  # 完全冻结\n",[277,6677,6678,6680,6682,6684,6686,6688],{"class":279,"line":287},[277,6679,6664],{"class":1242},[277,6681,1418],{"class":1266},[277,6683,6290],{"class":1262},[277,6685,1440],{"class":1266},[277,6687,4682],{"class":312},[277,6689,6690],{"class":283},"  # 正常速度\n",[277,6692,6693,6695,6697,6699,6701,6704],{"class":279,"line":298},[277,6694,6664],{"class":1242},[277,6696,1418],{"class":1266},[277,6698,6290],{"class":1262},[277,6700,1440],{"class":1266},[277,6702,6703],{"class":312}," 0.5",[277,6705,6706],{"class":283},"  # 慢动作\n",[10,6708,6709,6710,6712,6713,17],{},"这是 Godot 的\"全局慢放\u002F暂停\"开关，影响所有用 ",[140,6711,1330],{}," 计算的逻辑（包括小鸟下落、水管移动、Timer 等）。",[14,6714,6715],{},"比一个一个写\"如果暂停就别动\"省心得多",[109,6717,6718],{"id":6718},"信号的连接",[10,6720,6721,6724,6725,17],{},[140,6722,6723],{},"GameManager.state_changed.connect(_on_game_state)"," — 这就是",[14,6726,6727],{},"订阅信号",[45,6729,6730],{},[10,6731,6732],{},"你也可以在编辑器的【节点】面板里手动连接信号，效果一样。代码连接的好处是更明确、便于版本管理。",[60,6734,5908],{"id":6735},"分数-ui",[10,6737,6738,6740,6741,486],{},[140,6739,6154],{}," 挂个脚本 ",[140,6742,6743],{},"score_label.gd",[251,6745,6747],{"className":1229,"code":6746,"language":1231,"meta":259,"style":259},"extends Label\n\nfunc _ready() -> void:\n    text = \"0\"\n    GameManager.score_changed.connect(_on_score_changed)\n\nfunc _on_score_changed(new_score: int) -> void:\n    text = str(new_score)\n",[140,6748,6749,6756,6760,6774,6784,6803,6807,6830],{"__ignoreMap":259},[277,6750,6751,6753],{"class":279,"line":280},[277,6752,1239],{"class":1238},[277,6754,6755],{"class":1242}," Label\n",[277,6757,6758],{"class":279,"line":287},[277,6759,320],{"emptyLinePlaceholder":319},[277,6761,6762,6764,6766,6768,6770,6772],{"class":279,"line":298},[277,6763,1320],{"class":1258},[277,6765,6221],{"class":301},[277,6767,1369],{"class":1266},[277,6769,1342],{"class":1258},[277,6771,1345],{"class":1242},[277,6773,1348],{"class":1266},[277,6775,6776,6779,6781],{"class":279,"line":316},[277,6777,6778],{"class":1262},"    text",[277,6780,1440],{"class":1266},[277,6782,6783],{"class":294}," \"0\"\n",[277,6785,6786,6788,6790,6792,6794,6796,6798,6801],{"class":279,"line":323},[277,6787,6239],{"class":1242},[277,6789,1418],{"class":1266},[277,6791,5083],{"class":1262},[277,6793,1418],{"class":1329},[277,6795,6270],{"class":301},[277,6797,1326],{"class":1266},[277,6799,6800],{"class":1262},"_on_score_changed",[277,6802,2651],{"class":1266},[277,6804,6805],{"class":279,"line":329},[277,6806,320],{"emptyLinePlaceholder":319},[277,6808,6809,6811,6814,6816,6818,6820,6822,6824,6826,6828],{"class":279,"line":1317},[277,6810,1320],{"class":1258},[277,6812,6813],{"class":301}," _on_score_changed",[277,6815,1326],{"class":1266},[277,6817,4591],{"class":1329},[277,6819,1333],{"class":1266},[277,6821,4544],{"class":1242},[277,6823,1339],{"class":1266},[277,6825,1342],{"class":1258},[277,6827,1345],{"class":1242},[277,6829,1348],{"class":1266},[277,6831,6832,6834,6836,6839,6841,6843],{"class":279,"line":1351},[277,6833,6778],{"class":1262},[277,6835,1440],{"class":1266},[277,6837,6838],{"class":301}," str",[277,6840,1326],{"class":1266},[277,6842,4591],{"class":1262},[277,6844,2651],{"class":1266},[634,6846,6847,6856],{},[25,6848,6849,6852,6853,6855],{},[140,6850,6851],{},"_ready"," 里订阅 ",[140,6854,5083],{}," 信号",[25,6857,6858],{},"每次分数变化，自动更新 Label 文本",[10,6860,6861,6864],{},[14,6862,6863],{},"这就是信号解耦的好处"," — Label 不需要主动去问 GameManager 当前几分，GameManager 也不知道有这么个 Label 存在，但分数照样能正确显示。",[60,6866,6867],{"id":6867},"结束面板",[10,6869,6870,6872,6873,6876],{},[140,6871,6163],{}," 默认隐藏（在编辑器把 ",[140,6874,6875],{},"visible"," 取消勾选），监听到 GAME_OVER 状态时才显示。",[10,6878,6879,6880,486],{},"挂脚本 ",[140,6881,6882],{},"game_over_panel.gd",[251,6884,6886],{"className":1229,"code":6885,"language":1231,"meta":259,"style":259},"extends Panel\n\n@onready var final_score: Label = $FinalScore\n\nfunc _ready() -> void:\n    visible = false\n    GameManager.state_changed.connect(_on_state_changed)\n\nfunc _on_state_changed(new_state) -> void:\n    if new_state == GameManager.GameState.GAME_OVER:\n        final_score.text = \"得分：%d\" % GameManager.total_score\n        visible = true\n",[140,6887,6888,6895,6899,6920,6924,6938,6948,6967,6971,6990,7010,7040],{"__ignoreMap":259},[277,6889,6890,6892],{"class":279,"line":280},[277,6891,1239],{"class":1238},[277,6893,6894],{"class":1242}," Panel\n",[277,6896,6897],{"class":279,"line":287},[277,6898,320],{"emptyLinePlaceholder":319},[277,6900,6901,6903,6905,6908,6910,6913,6915,6917],{"class":279,"line":298},[277,6902,3436],{"class":301},[277,6904,1259],{"class":1258},[277,6906,6907],{"class":1262}," final_score",[277,6909,1333],{"class":1266},[277,6911,6912],{"class":1242}," Label",[277,6914,1440],{"class":1266},[277,6916,3451],{"class":1238},[277,6918,6919],{"class":305},"FinalScore\n",[277,6921,6922],{"class":279,"line":316},[277,6923,320],{"emptyLinePlaceholder":319},[277,6925,6926,6928,6930,6932,6934,6936],{"class":279,"line":323},[277,6927,1320],{"class":1258},[277,6929,6221],{"class":301},[277,6931,1369],{"class":1266},[277,6933,1342],{"class":1258},[277,6935,1345],{"class":1242},[277,6937,1348],{"class":1266},[277,6939,6940,6943,6945],{"class":279,"line":329},[277,6941,6942],{"class":1262},"    visible",[277,6944,1440],{"class":1266},[277,6946,6947],{"class":1238}," false\n",[277,6949,6950,6952,6954,6956,6958,6960,6962,6965],{"class":279,"line":1317},[277,6951,6239],{"class":1242},[277,6953,1418],{"class":1266},[277,6955,5090],{"class":1262},[277,6957,1418],{"class":1329},[277,6959,6270],{"class":301},[277,6961,1326],{"class":1266},[277,6963,6964],{"class":1262},"_on_state_changed",[277,6966,2651],{"class":1266},[277,6968,6969],{"class":279,"line":1351},[277,6970,320],{"emptyLinePlaceholder":319},[277,6972,6973,6975,6978,6980,6982,6984,6986,6988],{"class":279,"line":1357},[277,6974,1320],{"class":1258},[277,6976,6977],{"class":301}," _on_state_changed",[277,6979,1326],{"class":1266},[277,6981,4605],{"class":1329},[277,6983,1339],{"class":1266},[277,6985,1342],{"class":1258},[277,6987,1345],{"class":1242},[277,6989,1348],{"class":1266},[277,6991,6992,6994,6996,6998,7000,7002,7004,7006,7008],{"class":279,"line":1374},[277,6993,1360],{"class":1238},[277,6995,6518],{"class":1262},[277,6997,3183],{"class":1258},[277,6999,3293],{"class":1242},[277,7001,1418],{"class":1266},[277,7003,3307],{"class":1262},[277,7005,1418],{"class":1266},[277,7007,4445],{"class":1238},[277,7009,1348],{"class":1329},[277,7011,7012,7015,7017,7019,7021,7024,7027,7030,7033,7035,7037],{"class":279,"line":1399},[277,7013,7014],{"class":1262},"        final_score",[277,7016,1418],{"class":1266},[277,7018,256],{"class":1262},[277,7020,1440],{"class":1266},[277,7022,7023],{"class":294}," \"得分：",[277,7025,7026],{"class":305},"%d",[277,7028,7029],{"class":294},"\"",[277,7031,7032],{"class":1258}," %",[277,7034,3293],{"class":1242},[277,7036,1418],{"class":1266},[277,7038,7039],{"class":1262},"total_score\n",[277,7041,7042,7045,7047],{"class":279,"line":1404},[277,7043,7044],{"class":1262},"        visible",[277,7046,1440],{"class":1266},[277,7048,7049],{"class":1238}," true\n",[634,7051,7052,7057,7060],{},[25,7053,7054,7055],{},"监听 ",[140,7056,5090],{},[25,7058,7059],{},"GAME_OVER 时显示面板 + 填上最终分数",[25,7061,7062,7064,7065],{},[140,7063,7026],{}," 是字符串占位符，等同于 ",[140,7066,7067],{},"str(GameManager.total_score)",[45,7069,7070],{},[10,7071,7072,7073,1144,7075,7077,7078,17],{},"⚠️ 别忘了把 ",[140,7074,6186],{},[140,7076,6007],{}," 信号连接到 main 场景根节点的 ",[140,7079,6631],{},[60,7081,7082],{"id":7082},"设置主场景",[10,7084,7085,7086,7088],{},"最后把【主场景】改成 ",[140,7087,5893],{},"（这样游戏启动直接到菜单）：",[10,7090,7091],{},"路径：项目 → 项目设置 → 常规 → 运行 → 主场景",[10,7093,7094],{},[80,7095],{"alt":7096,"src":7097},"06-ui-主场景设置","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F06-ui-%E4%B8%BB%E5%9C%BA%E6%99%AF%E8%AE%BE%E7%BD%AE.png",[60,7099,2350],{"id":2350},[10,7101,7102],{},"按下运行键，整个流程应该是：",[22,7104,7105,7108,7111,7114,7117],{},[25,7106,7107],{},"✅ 启动 → 显示菜单 + 开始按钮",[25,7109,7110],{},"✅ 点击开始 → 切换到游戏场景，画面冻结，等待按键",[25,7112,7113],{},"✅ 按下 fly → 游戏开始，水管生成，小鸟可控",[25,7115,7116],{},"✅ 撞死 → 显示结束面板 + 最终分数",[25,7118,7119],{},"✅ 点重新开始 → 回到游戏初始状态",[10,7121,7122],{},"下一章我们替换美术资源，把这些灰色矩形变成真正的小鸟和水管 🎨",[854,7124,7125],{},"html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s_NWU, html code.shiki .s_NWU{--shiki-default:#2E8F82;--shiki-dark:#5DA994}html pre.shiki code .s5TCs, html code.shiki .s5TCs{--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .s8w-G, html code.shiki .s8w-G{--shiki-default:#393A34;--shiki-dark:#DBD7CAEE}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .snYqZ, html code.shiki .snYqZ{--shiki-default:#A0ADA0;--shiki-dark:#758575DD}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .sqbOQ, html code.shiki .sqbOQ{--shiki-default:#2F798A;--shiki-dark:#4C9A91}html pre.shiki code .sfsYZ, html code.shiki .sfsYZ{--shiki-default:#A65E2B;--shiki-dark:#C99076}",{"title":259,"searchDepth":287,"depth":287,"links":7127},[7128,7129,7133,7135,7142,7143,7144,7145],{"id":5918,"depth":287,"text":5918},{"id":5932,"depth":287,"text":7130,"children":7131},"开始菜单 menu.tscn",[7132],{"id":5995,"depth":298,"text":5995},{"id":6070,"depth":287,"text":7134},"游戏主场景 main.tscn",{"id":6196,"depth":287,"text":7136,"children":7137},"主场景脚本 main.gd",[7138,7139,7141],{"id":3641,"depth":298,"text":3642},{"id":6650,"depth":298,"text":7140},"Engine.time_scale — 全局时间缩放",{"id":6718,"depth":298,"text":6718},{"id":6735,"depth":287,"text":5908},{"id":6867,"depth":287,"text":6867},{"id":7082,"depth":287,"text":7082},{"id":2350,"depth":287,"text":2350},{},"\u002Fdevlog\u002Fxggame-bird\u002F06-ui",{"title":5877,"description":878},"devlog\u002Fxggame-bird\u002F06-ui","MaW5eGhaEWyzREhq8HxUBpIIw5GmRnx7tB25QX3pkfw",{"id":7152,"title":7153,"body":7154,"cover":876,"date":877,"description":878,"extension":879,"game":410,"github":880,"icon":876,"meta":7737,"navigation":319,"path":6118,"seo":7738,"stem":7739,"toc":319,"__hash__":7740},"devlog\u002Fdevlog\u002Fxggame-bird\u002F07-art-assets.md","替换美术资源",{"type":7,"value":7155,"toc":7723},[7156,7159,7162,7177,7180,7215,7218,7244,7250,7254,7264,7275,7292,7298,7305,7308,7322,7328,7337,7346,7350,7366,7369,7382,7388,7399,7402,7406,7413,7426,7429,7434,7452,7458,7463,7523,7528,7531,7539,7557,7571,7577,7588,7591,7599,7678,7685,7687,7690,7704,7710,7713,7720],[10,7157,7158],{},"到目前为止小鸟还是 Godot 的\"机器人头\"，水管也是个矩形 — 这一章给游戏换上真正的美术资源，从「白板原型」一秒变成「真游戏」。✨",[10,7160,7161],{},"我们要做：",[22,7163,7164,7167,7170],{},[25,7165,7166],{},"准备 + 导入美术素材",[25,7168,7169],{},"替换小鸟、水管的精灵图",[25,7171,7172,7173,7176],{},"加上",[14,7174,7175],{},"滚动","的地面和背景（这是 Flappy Bird \"横向飞行错觉\"的灵魂）",[60,7178,7179],{"id":7179},"准备美术资源",[45,7181,7182,7188,7191],{},[10,7183,7184,7185],{},"我用的素材来源：",[277,7186,7187],{},"TODO 素材链接",[10,7189,7190],{},"你也可以自己画、或者从这些免费站找：",[634,7192,7193,7200,7208],{},[25,7194,7195],{},[37,7196,7199],{"href":7197,"rel":7198},"https:\u002F\u002Fitch.io\u002Fgame-assets\u002Ffree",[41],"itch.io 免费素材",[25,7201,7202,7207],{},[37,7203,7206],{"href":7204,"rel":7205},"https:\u002F\u002Fkenney.nl\u002F",[41],"Kenney.nl","（CC0 免版权）",[25,7209,7210],{},[37,7211,7214],{"href":7212,"rel":7213},"https:\u002F\u002Fopengameart.org\u002F",[41],"OpenGameArt",[10,7216,7217],{},"需要这几样：",[634,7219,7220,7226,7232,7238],{},[25,7221,7222,7225],{},[140,7223,7224],{},"bird.png"," — 小鸟（推荐 3 帧动画，扑翅膀更生动）",[25,7227,7228,7231],{},[140,7229,7230],{},"pipe.png"," — 水管",[25,7233,7234,7237],{},[140,7235,7236],{},"ground.png"," — 地面",[25,7239,7240,7243],{},[140,7241,7242],{},"background.png"," — 背景（最好做成左右无缝拼接的）",[10,7245,7246],{},[80,7247],{"alt":7248,"src":7249},"07-art-assets-素材","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F07-art-assets-%E7%B4%A0%E6%9D%90.png",[60,7251,7253],{"id":7252},"导入设置像素美术别变模糊","导入设置：像素美术别变模糊",[10,7255,4042,7256,7259,7260,7263],{},[140,7257,7258],{},".png"," 文件拖进 Godot 项目，默认导入会用",[14,7261,7262],{},"双线性插值","把像素图变得模糊糊的，这不是我们要的复古感觉。",[45,7265,7266],{},[10,7267,7268,7269,17],{},"⚠️ 像素美术必须把 ",[14,7270,7271,7272],{},"Filter 设为 ",[140,7273,7274],{},"Nearest",[10,7276,7277,7278,7281,7282,2055,7285,7287,7288,7291],{},"选中图片资源 → 上方【导入】标签页 → ",[140,7279,7280],{},"Compress \u002F Mode = Lossless"," + ",[140,7283,7284],{},"Filter",[140,7286,7274],{}," → 点击「",[14,7289,7290],{},"重新导入","」。",[10,7293,7294],{},[80,7295],{"alt":7296,"src":7297},"07-art-assets-导入设置","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F07-art-assets-%E5%AF%BC%E5%85%A5%E8%AE%BE%E7%BD%AE.png",[10,7299,7300,7301,7304],{},"或者一劳永逸：项目设置 → 渲染 → 纹理 → ",[140,7302,7303],{},"Canvas Textures > Default Texture Filter = Nearest","，整个项目所有图片都默认 Nearest。",[60,7306,7307],{"id":7307},"替换小鸟",[10,7309,7310,7311,7313,7314,7316,7317,7319,7320,17],{},"打开 ",[140,7312,2232],{},"，选中 ",[140,7315,1131],{}," 节点，把它的 ",[140,7318,1147],{}," 换成 ",[140,7321,7224],{},[10,7323,7324],{},[80,7325],{"alt":7326,"src":7327},"07-art-assets-小鸟替换","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F07-art-assets-%E5%B0%8F%E9%B8%9F%E6%9B%BF%E6%8D%A2.png",[10,7329,7330,7333,7334,7336],{},[14,7331,7332],{},"碰撞体也要对应调整","：选中 ",[140,7335,1137],{},"，把矩形缩放到正好包住新的小鸟图。",[45,7338,7339],{},[10,7340,7341,7342,7345],{},"💡 小贴士：碰撞体可以",[14,7343,7344],{},"略小于图片","（特别是小鸟）。这样玩家会觉得\"我明明擦边过去了\"的判定更宽容，手感更好。这是 Flappy Bird 类游戏的隐藏秘诀之一。",[109,7347,7349],{"id":7348},"可选小鸟扑翅膀动画","（可选）小鸟扑翅膀动画",[10,7351,7352,7353,7355,7356,7319,7358,7361,7362,7365],{},"如果你的 ",[140,7354,7224],{}," 是 3 帧的 sprite sheet，可以把 ",[140,7357,1131],{},[140,7359,7360],{},"AnimatedSprite2D","，然后用 ",[140,7363,7364],{},"SpriteFrames"," 资源做帧动画。这部分稍微进阶一点，你可以先跳过 — 不影响后续。",[60,7367,7368],{"id":7368},"替换水管",[10,7370,7310,7371,7373,7374,7376,7377,7379,7380,17],{},[140,7372,4045],{},"，选中两个 ",[140,7375,1131],{},"（上下水管），把它们的 ",[140,7378,1147],{}," 都换成 ",[140,7381,7230],{},[10,7383,7384],{},[80,7385],{"alt":7386,"src":7387},"07-art-assets-水管替换","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F07-art-assets-%E6%B0%B4%E7%AE%A1%E6%9B%BF%E6%8D%A2.png",[10,7389,7390,7391,7394,7395,7398],{},"上水管要",[14,7392,7393],{},"垂直翻转","（在 Inspector 里勾选 ",[140,7396,7397],{},"Flip V","）。",[10,7400,7401],{},"碰撞体同样调整到包住水管的大小。",[60,7403,7405],{"id":7404},"滚动地面-背景重头戏","滚动地面 + 背景（重头戏 🎬）",[10,7407,7408,7409,7412],{},"Flappy Bird 的精髓 — 小鸟其实在原地扑腾，",[14,7410,7411],{},"背景和地面在向左移动","，制造出\"鸟在向右飞\"的错觉。",[10,7414,7415,7416,7418,7419,7421,7422,7425],{},"Godot 4.3+ 有专门的 ",[140,7417,6111],{}," 节点，自带 ",[140,7420,6544],{},"（自动滚动）和 ",[140,7423,7424],{},"repeat","（无缝循环），非常好用。",[109,7427,7428],{"id":7428},"添加滚动背景",[10,7430,2399,7431,7433],{},[140,7432,5902],{}," 的根节点下添加：",[634,7435,7436],{},[25,7437,7438,5967,7440,2925,7442],{},[140,7439,6111],{},[140,7441,6114],{},[634,7443,7444],{},[25,7445,7446,5167,7448,2055,7450],{},[140,7447,1131],{},[140,7449,1147],{},[140,7451,7242],{},[10,7453,7454],{},[80,7455],{"alt":7456,"src":7457},"07-art-assets-背景节点","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F07-art-assets-%E8%83%8C%E6%99%AF%E8%8A%82%E7%82%B9.png",[10,7459,1178,7460,7462],{},[140,7461,6114],{},"，在 Inspector 里设置：",[113,7464,7465,7476],{},[116,7466,7467],{},[119,7468,7469,7472,7474],{},[122,7470,7471],{},"属性",[122,7473,584],{},[122,7475,1035],{},[132,7477,7478,7494,7507],{},[119,7479,7480,7485,7491],{},[137,7481,7482],{},[140,7483,7484],{},"Autoscroll > x",[137,7486,7487,7490],{},[140,7488,7489],{},"-50","（或你喜欢的速度）",[137,7492,7493],{},"每秒向左移动多少像素",[119,7495,7496,7501,7504],{},[137,7497,7498],{},[140,7499,7500],{},"Repeat Size > x",[137,7502,7503],{},"背景图宽度",[137,7505,7506],{},"让它无缝循环",[119,7508,7509,7514,7520],{},[137,7510,7511],{},[140,7512,7513],{},"Repeat Times",[137,7515,7516,7519],{},[140,7517,7518],{},"3"," 或更大",[137,7521,7522],{},"重复多少次（保证屏幕外有备用）",[45,7524,7525],{},[10,7526,7527],{},"负值表示向左滚动；正值表示向右。",[109,7529,7530],{"id":7530},"添加滚动地面",[10,7532,7533,7534,5967,7536,7538],{},"同样的方式，加一个 ",[140,7535,6111],{},[140,7537,6126],{},"）：",[634,7540,7541],{},[25,7542,7543,5890,7545,2925,7547],{},[140,7544,6111],{},[140,7546,6126],{},[634,7548,7549],{},[25,7550,7551,5167,7553,2055,7555],{},[140,7552,1131],{},[140,7554,1147],{},[140,7556,7236],{},[10,7558,7559,7562,7563,7566,7567,7570],{},[140,7560,7561],{},"Autoscroll.x"," 可以",[14,7564,7565],{},"比背景快","（比如 ",[140,7568,7569],{},"-150","），制造\"近景快、远景慢\"的视差感（这就是 Parallax 这个词的来源）。",[10,7572,7573],{},[80,7574],{"alt":7575,"src":7576},"07-art-assets-地面节点","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F07-art-assets-%E5%9C%B0%E9%9D%A2%E8%8A%82%E7%82%B9.png",[45,7578,7579],{},[10,7580,7581,7582,7584,7585,7587],{},"💡 这两个节点的 z-index 注意一下：背景 ",[140,7583,6114],{}," 要在最底层（z 最小），地面 ",[140,7586,6126],{}," 在小鸟之上（z 最大，遮住可能的视觉穿帮）。",[60,7589,7590],{"id":7590},"让背景在游戏结束时停下来",[10,7592,7593,7594,7598],{},"还记得 ",[37,7595,7597],{"href":7596},"\u002Fdevlog\u002Fxggame-bird\u002F06-ui#%E4%B8%BB%E5%9C%BA%E6%99%AF%E8%84%9A%E6%9C%AC-maingd","上一章 main.gd"," 里这两行吗？",[251,7600,7602],{"className":1229,"code":7601,"language":1231,"meta":259,"style":259},"func _on_game_state(new_state) -> void:\n    if new_state == GameManager.GameState.GAME_OVER:\n        $BG.autoscroll.x = 0\n        $Progress.autoscroll.x = 0\n",[140,7603,7604,7622,7642,7660],{"__ignoreMap":259},[277,7605,7606,7608,7610,7612,7614,7616,7618,7620],{"class":279,"line":280},[277,7607,1320],{"class":1258},[277,7609,6494],{"class":301},[277,7611,1326],{"class":1266},[277,7613,4605],{"class":1329},[277,7615,1339],{"class":1266},[277,7617,1342],{"class":1258},[277,7619,1345],{"class":1242},[277,7621,1348],{"class":1266},[277,7623,7624,7626,7628,7630,7632,7634,7636,7638,7640],{"class":279,"line":287},[277,7625,1360],{"class":1238},[277,7627,6518],{"class":1262},[277,7629,3183],{"class":1258},[277,7631,3293],{"class":1242},[277,7633,1418],{"class":1266},[277,7635,3307],{"class":1262},[277,7637,1418],{"class":1266},[277,7639,4445],{"class":1238},[277,7641,1348],{"class":1329},[277,7643,7644,7646,7648,7650,7652,7654,7656,7658],{"class":279,"line":298},[277,7645,6537],{"class":1238},[277,7647,6114],{"class":305},[277,7649,1418],{"class":1266},[277,7651,6544],{"class":1262},[277,7653,1418],{"class":1266},[277,7655,1891],{"class":1262},[277,7657,1440],{"class":1266},[277,7659,2615],{"class":312},[277,7661,7662,7664,7666,7668,7670,7672,7674,7676],{"class":279,"line":316},[277,7663,6537],{"class":1238},[277,7665,6126],{"class":305},[277,7667,1418],{"class":1266},[277,7669,6544],{"class":1262},[277,7671,1418],{"class":1266},[277,7673,1891],{"class":1262},[277,7675,1440],{"class":1266},[277,7677,2615],{"class":312},[10,7679,7680,7681,7684],{},"它做的就是：",[14,7682,7683],{},"游戏结束时把背景\u002F地面的滚动速度设为 0","。这样画面整体定住，更有\"撞死\"的仪式感。",[60,7686,2350],{"id":2350},[10,7688,7689],{},"现在游戏应该长这样：",[634,7691,7692,7695,7698,7701],{},[25,7693,7694],{},"✅ 真正的小鸟在画面里扑腾",[25,7696,7697],{},"✅ 水管是绿色（或你的素材颜色）的管子",[25,7699,7700],{},"✅ 背景缓缓向左滚动 + 地面快速向左滚动 → 视差错觉",[25,7702,7703],{},"✅ 撞死瞬间，背景立刻定住",[10,7705,7706],{},[80,7707],{"alt":7708,"src":7709},"07-art-assets-效果","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F07-art-assets-%E6%95%88%E6%9E%9C.png",[10,7711,7712],{},"是不是一瞬间像个游戏了？🎮",[10,7714,7715,7716,7719],{},"下一章给它加上",[14,7717,7718],{},"音效"," — 翅膀扇动、得分、碰撞，最后一点\"灵魂\"。",[854,7721,7722],{},"html pre.shiki code .s5TCs, html code.shiki .s5TCs{--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .s8w-G, html code.shiki .s8w-G{--shiki-default:#393A34;--shiki-dark:#DBD7CAEE}html pre.shiki code .s_NWU, html code.shiki .s_NWU{--shiki-default:#2E8F82;--shiki-dark:#5DA994}html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .sfsYZ, html code.shiki .sfsYZ{--shiki-default:#A65E2B;--shiki-dark:#C99076}html pre.shiki code .sqbOQ, html code.shiki .sqbOQ{--shiki-default:#2F798A;--shiki-dark:#4C9A91}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":259,"searchDepth":287,"depth":287,"links":7724},[7725,7726,7727,7730,7731,7735,7736],{"id":7179,"depth":287,"text":7179},{"id":7252,"depth":287,"text":7253},{"id":7307,"depth":287,"text":7307,"children":7728},[7729],{"id":7348,"depth":298,"text":7349},{"id":7368,"depth":287,"text":7368},{"id":7404,"depth":287,"text":7405,"children":7732},[7733,7734],{"id":7428,"depth":298,"text":7428},{"id":7530,"depth":298,"text":7530},{"id":7590,"depth":287,"text":7590},{"id":2350,"depth":287,"text":2350},{},{"title":7153,"description":878},"devlog\u002Fxggame-bird\u002F07-art-assets","ELNEWo2f5dW9OT0EJ3dApq6f3iJy7cbJJ_itwQbN3ls",{"id":7742,"title":7743,"body":7744,"cover":876,"date":877,"description":878,"extension":879,"game":410,"github":880,"icon":876,"meta":8568,"navigation":319,"path":8569,"seo":8570,"stem":8571,"toc":319,"__hash__":8572},"devlog\u002Fdevlog\u002Fxggame-bird\u002F08-sound.md","音效 SoundManager",{"type":7,"value":7745,"toc":8548},[7746,7749,7772,7775,7810,7817,7837,7843,7856,7864,7871,7893,7897,7903,7905,7948,7954,7958,7961,7980,7986,7990,7996,8150,8165,8169,8174,8197,8203,8212,8215,8218,8224,8274,8280,8385,8391,8476,8478,8481,8492,8504,8508,8518,8539,8542,8545],[10,7747,7748],{},"游戏没声音就像炒菜没盐 — 没那个味儿。这一章加上：",[634,7750,7751,7758,7765],{},[25,7752,7753,7754,7757],{},"🪶 ",[14,7755,7756],{},"翅膀扇动声","（小鸟跳跃时）",[25,7759,7760,7761,7764],{},"⭐ ",[14,7762,7763],{},"得分声","（穿过水管时）",[25,7766,7767,7768,7771],{},"💥 ",[14,7769,7770],{},"碰撞声","（撞死时）",[60,7773,7774],{"id":7774},"准备音效文件",[45,7776,7777,7782,7785],{},[10,7778,7779,7780],{},"我用的音效来源：",[277,7781,7187],{},[10,7783,7784],{},"免费音效推荐：",[634,7786,7787,7795,7802],{},[25,7788,7789,7794],{},[37,7790,7793],{"href":7791,"rel":7792},"https:\u002F\u002Ffreesound.org\u002F",[41],"freesound.org","（CC 许可）",[25,7796,7797],{},[37,7798,7801],{"href":7799,"rel":7800},"https:\u002F\u002Fwww.zapsplat.com\u002F",[41],"zapsplat.com",[25,7803,7804,7809],{},[37,7805,7808],{"href":7806,"rel":7807},"https:\u002F\u002Fsfxr.me\u002F",[41],"sfxr"," — 在线生成复古 8-bit 音效，超适合 Flappy Bird",[10,7811,7812,7813,7816],{},"需要 3 个文件，放到 ",[140,7814,7815],{},"audio\u002F"," 目录下：",[634,7818,7819,7825,7831],{},[25,7820,7821,7824],{},[140,7822,7823],{},"wing.wav"," — 扑翅膀",[25,7826,7827,7830],{},[140,7828,7829],{},"score.wav"," — 得分",[25,7832,7833,7836],{},[140,7834,7835],{},"strike.wav"," — 撞击",[10,7838,7839],{},[80,7840],{"alt":7841,"src":7842},"08-sound-音效文件","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F08-sound-%E9%9F%B3%E6%95%88%E6%96%87%E4%BB%B6.png",[45,7844,7845],{},[10,7846,7847,7848,7851,7852,7855],{},"💡 推荐用 ",[140,7849,7850],{},".wav"," 而不是 ",[140,7853,7854],{},".mp3","：体积大一点，但延迟低、循环不卡顿。短音效用 wav 是标配。",[60,7857,7859,7860,7863],{"id":7858},"soundmanager-是个场景不是脚本","SoundManager 是个",[14,7861,7862],{},"场景","，不是脚本",[10,7865,7866,7867,7870],{},"和 GameManager 不一样，SoundManager 我们用 ",[14,7868,7869],{},"场景（.tscn）"," 来做，而不是单纯的脚本。",[10,7872,7873,7876,7877,7880,7881,7884,7885,7888,7889,7892],{},[14,7874,7875],{},"为什么？"," 因为每个音效都需要一个 ",[140,7878,7879],{},"AudioStreamPlayer"," 节点 — 与其在脚本里 ",[140,7882,7883],{},"new AudioStreamPlayer()","，不如直接在场景里把所有 player 节点摆好，",[14,7886,7887],{},"音效文件也提前在 Inspector 里预设好","，调用时直接 ",[140,7890,7891],{},".play()"," 就行。",[60,7894,7896],{"id":7895},"创建-soundmanager-场景","创建 SoundManager 场景",[10,7898,7899,7900,17],{},"新建场景，保存为 ",[140,7901,7902],{},"scenes\u002Fautoload\u002Fsound_manager.tscn",[10,7904,2197],{},[634,7906,7907],{},[25,7908,7909,5967,7912,7914,7915],{},[140,7910,7911],{},"Node",[140,7913,3350],{},"）— 根节点\n",[634,7916,7917],{},[25,7918,7919,5967,7921,7924,7925],{},[140,7920,7911],{},[140,7922,7923],{},"SFX","）— 音效组\n",[634,7926,7927,7934,7941],{},[25,7928,7929,5967,7931,230],{},[140,7930,7879],{},[140,7932,7933],{},"wing_sfx",[25,7935,7936,5967,7938,230],{},[140,7937,7879],{},[140,7939,7940],{},"score_sfx",[25,7942,7943,5967,7945,230],{},[140,7944,7879],{},[140,7946,7947],{},"strike_sfx",[10,7949,7950],{},[80,7951],{"alt":7952,"src":7953},"08-sound-scene-结构","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F08-sound-scene-%E7%BB%93%E6%9E%84.png",[109,7955,7957],{"id":7956},"每个-audiostreamplayer-的设置","每个 AudioStreamPlayer 的设置",[10,7959,7960],{},"选中每个 player，在右侧 Inspector 里：",[634,7962,7963,7974],{},[25,7964,7965,7968,7969,4046,7971,7973],{},[14,7966,7967],{},"Stream","：拖入对应的音效文件（如 ",[140,7970,7823],{},[140,7972,7933],{}," 的 Stream 槽）",[25,7975,7976,7979],{},[14,7977,7978],{},"Volume dB","：根据音量调整（-6 dB 是温和的衰减，-12 dB 是明显的降低）",[10,7981,7982],{},[80,7983],{"alt":7984,"src":7985},"08-sound-audiostreamplayer","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F08-sound-audiostreamplayer.png",[60,7987,7989],{"id":7988},"soundmanager-脚本","SoundManager 脚本",[10,7991,7992,7993,486],{},"给根节点挂脚本 ",[140,7994,7995],{},"sound_manager.gd",[251,7997,7999],{"className":1229,"code":7998,"language":1231,"meta":259,"style":259},"extends Node\n\n# 在检查器里预载好你的音效文件\n@onready var score_sfx: AudioStreamPlayer = $SFX\u002Fscore_sfx\n@onready var strike_sfx: AudioStreamPlayer = $SFX\u002Fstrike_sfx\n@onready var wing_sfx: AudioStreamPlayer = $SFX\u002Fwing_sfx\n\nfunc play_score():\n    score_sfx.play()\n\nfunc play_die():\n    strike_sfx.play()\n\nfunc play_wing():\n    wing_sfx.play()\n",[140,8000,8001,8007,8011,8016,8037,8057,8077,8081,8090,8102,8106,8115,8126,8130,8139],{"__ignoreMap":259},[277,8002,8003,8005],{"class":279,"line":280},[277,8004,1239],{"class":1238},[277,8006,4478],{"class":1242},[277,8008,8009],{"class":279,"line":287},[277,8010,320],{"emptyLinePlaceholder":319},[277,8012,8013],{"class":279,"line":298},[277,8014,8015],{"class":283},"# 在检查器里预载好你的音效文件\n",[277,8017,8018,8020,8022,8025,8027,8030,8032,8034],{"class":279,"line":316},[277,8019,3436],{"class":301},[277,8021,1259],{"class":1258},[277,8023,8024],{"class":1262}," score_sfx",[277,8026,1333],{"class":1266},[277,8028,8029],{"class":1242}," AudioStreamPlayer",[277,8031,1440],{"class":1266},[277,8033,3451],{"class":1238},[277,8035,8036],{"class":305},"SFX\u002Fscore_sfx\n",[277,8038,8039,8041,8043,8046,8048,8050,8052,8054],{"class":279,"line":323},[277,8040,3436],{"class":301},[277,8042,1259],{"class":1258},[277,8044,8045],{"class":1262}," strike_sfx",[277,8047,1333],{"class":1266},[277,8049,8029],{"class":1242},[277,8051,1440],{"class":1266},[277,8053,3451],{"class":1238},[277,8055,8056],{"class":305},"SFX\u002Fstrike_sfx\n",[277,8058,8059,8061,8063,8066,8068,8070,8072,8074],{"class":279,"line":329},[277,8060,3436],{"class":301},[277,8062,1259],{"class":1258},[277,8064,8065],{"class":1262}," wing_sfx",[277,8067,1333],{"class":1266},[277,8069,8029],{"class":1242},[277,8071,1440],{"class":1266},[277,8073,3451],{"class":1238},[277,8075,8076],{"class":305},"SFX\u002Fwing_sfx\n",[277,8078,8079],{"class":279,"line":1317},[277,8080,320],{"emptyLinePlaceholder":319},[277,8082,8083,8085,8088],{"class":279,"line":1351},[277,8084,1320],{"class":1258},[277,8086,8087],{"class":301}," play_score",[277,8089,4626],{"class":1266},[277,8091,8092,8095,8097,8100],{"class":279,"line":1357},[277,8093,8094],{"class":1262},"    score_sfx",[277,8096,1418],{"class":1329},[277,8098,8099],{"class":301},"play",[277,8101,1504],{"class":1266},[277,8103,8104],{"class":279,"line":1374},[277,8105,320],{"emptyLinePlaceholder":319},[277,8107,8108,8110,8113],{"class":279,"line":1399},[277,8109,1320],{"class":1258},[277,8111,8112],{"class":301}," play_die",[277,8114,4626],{"class":1266},[277,8116,8117,8120,8122,8124],{"class":279,"line":1404},[277,8118,8119],{"class":1262},"    strike_sfx",[277,8121,1418],{"class":1329},[277,8123,8099],{"class":301},[277,8125,1504],{"class":1266},[277,8127,8128],{"class":279,"line":1410},[277,8129,320],{"emptyLinePlaceholder":319},[277,8131,8132,8134,8137],{"class":279,"line":1431},[277,8133,1320],{"class":1258},[277,8135,8136],{"class":301}," play_wing",[277,8138,4626],{"class":1266},[277,8140,8141,8144,8146,8148],{"class":279,"line":1446},[277,8142,8143],{"class":1262},"    wing_sfx",[277,8145,1418],{"class":1329},[277,8147,8099],{"class":301},[277,8149,1504],{"class":1266},[634,8151,8152,8158],{},[25,8153,8154,8157],{},[140,8155,8156],{},"@onready var xxx = $SFX\u002Fxxx"," — 拿到子节点的引用",[25,8159,8160,8161,8164],{},"方法本身就一行：调用对应 player 的 ",[140,8162,8163],{},"play()"," 即可",[60,8166,8168],{"id":8167},"注册为-autoload","注册为 Autoload",[10,8170,8171,8172,486],{},"和 GameManager 一样，",[14,8173,4852],{},[113,8175,8176,8184],{},[116,8177,8178],{},[119,8179,8180,8182],{},[122,8181,4865],{},[122,8183,4874],{},[132,8185,8186],{},[119,8187,8188,8193],{},[137,8189,8190],{},[140,8191,8192],{},"res:\u002F\u002Fscenes\u002Fautoload\u002Fsound_manager.tscn",[137,8194,8195],{},[140,8196,3350],{},[10,8198,8199],{},[80,8200],{"alt":8201,"src":8202},"08-sound-autoload","\u002Fimg\u002Fdevlog\u002Fxggame-bird\u002F08-sound-autoload.png",[45,8204,8205],{},[10,8206,8207,8208,8211],{},"⚠️ 注意这里加载的是 ",[14,8209,8210],{},"场景文件 (.tscn)","，不是脚本 (.gd)。Godot 的 Autoload 同时支持两者。",[60,8213,8214],{"id":8214},"在游戏里调用",[10,8216,8217],{},"现在任何节点都能直接喊 SoundManager 一声 — 在合适的位置加上音效调用：",[109,8219,8221,8222,230],{"id":8220},"小鸟跳跃时birdgd","小鸟跳跃时（",[140,8223,2402],{},[251,8225,8227],{"className":1229,"code":8226,"language":1231,"meta":259,"style":259},"if Input.is_action_just_pressed(\"fly\"):\n    velocity.y = jump_force\n    SoundManager.play_wing()  # 👈 加这一行\n",[140,8228,8229,8247,8259],{"__ignoreMap":259},[277,8230,8231,8233,8235,8237,8239,8241,8243,8245],{"class":279,"line":280},[277,8232,1815],{"class":1238},[277,8234,1462],{"class":1242},[277,8236,1418],{"class":1329},[277,8238,1467],{"class":301},[277,8240,1326],{"class":1266},[277,8242,1472],{"class":294},[277,8244,1339],{"class":1266},[277,8246,1348],{"class":1329},[277,8248,8249,8251,8253,8255,8257],{"class":279,"line":287},[277,8250,1828],{"class":1262},[277,8252,1418],{"class":1266},[277,8254,1421],{"class":1262},[277,8256,1440],{"class":1266},[277,8258,1490],{"class":1262},[277,8260,8261,8264,8266,8269,8271],{"class":279,"line":298},[277,8262,8263],{"class":1242},"    SoundManager",[277,8265,1418],{"class":1329},[277,8267,8268],{"class":301},"play_wing",[277,8270,1369],{"class":1266},[277,8272,8273],{"class":283},"  # 👈 加这一行\n",[109,8275,8277,8278,230],{"id":8276},"穿过水管得分时pillar_pairgd","穿过水管得分时（",[140,8279,5314],{},[251,8281,8283],{"className":1229,"code":8282,"language":1231,"meta":259,"style":259},"func _on_goal_body_entered(body: Node2D) -> void:\n    if body.name == \"Bird\":\n        SoundManager.play_score()  # 👈 加这一行\n        if GameManager.has_method(\"add_score\"):\n            GameManager.add_score(1)\n        goal.set_deferred(\"monitoring\", false)\n",[140,8284,8285,8307,8323,8335,8353,8367],{"__ignoreMap":259},[277,8286,8287,8289,8291,8293,8295,8297,8299,8301,8303,8305],{"class":279,"line":280},[277,8288,1320],{"class":1258},[277,8290,3552],{"class":301},[277,8292,1326],{"class":1266},[277,8294,3155],{"class":1329},[277,8296,1333],{"class":1266},[277,8298,3160],{"class":1242},[277,8300,1339],{"class":1266},[277,8302,1342],{"class":1258},[277,8304,1345],{"class":1242},[277,8306,1348],{"class":1266},[277,8308,8309,8311,8313,8315,8317,8319,8321],{"class":279,"line":287},[277,8310,1360],{"class":1238},[277,8312,3175],{"class":1262},[277,8314,1418],{"class":1266},[277,8316,3180],{"class":1262},[277,8318,3183],{"class":1258},[277,8320,3186],{"class":294},[277,8322,1348],{"class":1329},[277,8324,8325,8327,8329,8331,8333],{"class":279,"line":298},[277,8326,3319],{"class":1242},[277,8328,1418],{"class":1329},[277,8330,3907],{"class":301},[277,8332,1369],{"class":1266},[277,8334,8273],{"class":283},[277,8336,8337,8339,8341,8343,8345,8347,8349,8351],{"class":279,"line":316},[277,8338,3835],{"class":1238},[277,8340,3293],{"class":1242},[277,8342,1418],{"class":1329},[277,8344,3920],{"class":301},[277,8346,1326],{"class":1266},[277,8348,3925],{"class":294},[277,8350,1339],{"class":1266},[277,8352,1348],{"class":1329},[277,8354,8355,8357,8359,8361,8363,8365],{"class":279,"line":323},[277,8356,3934],{"class":1242},[277,8358,1418],{"class":1329},[277,8360,3939],{"class":301},[277,8362,1326],{"class":1266},[277,8364,3944],{"class":312},[277,8366,2651],{"class":1266},[277,8368,8369,8371,8373,8375,8377,8379,8381,8383],{"class":279,"line":329},[277,8370,3615],{"class":1262},[277,8372,1418],{"class":1329},[277,8374,3620],{"class":301},[277,8376,1326],{"class":1266},[277,8378,3625],{"class":294},[277,8380,2812],{"class":1266},[277,8382,3630],{"class":1238},[277,8384,2651],{"class":1266},[109,8386,8388,8389,230],{"id":8387},"撞死时killzonegd","撞死时（",[140,8390,5217],{},[251,8392,8394],{"className":1229,"code":8393,"language":1231,"meta":259,"style":259},"func _on_body_entered(body: Node2D) -> void:\n    if body.name == \"Bird\" and GameManager.current_state == GameManager.GameState.PLAYING:\n        SoundManager.play_die()  # 👈 加这一行\n        GameManager.game_over()\n",[140,8395,8396,8418,8454,8466],{"__ignoreMap":259},[277,8397,8398,8400,8402,8404,8406,8408,8410,8412,8414,8416],{"class":279,"line":280},[277,8399,1320],{"class":1258},[277,8401,3150],{"class":301},[277,8403,1326],{"class":1266},[277,8405,3155],{"class":1329},[277,8407,1333],{"class":1266},[277,8409,3160],{"class":1242},[277,8411,1339],{"class":1266},[277,8413,1342],{"class":1258},[277,8415,1345],{"class":1242},[277,8417,1348],{"class":1266},[277,8419,8420,8422,8424,8426,8428,8430,8432,8434,8436,8438,8440,8442,8444,8446,8448,8450,8452],{"class":279,"line":287},[277,8421,1360],{"class":1238},[277,8423,3175],{"class":1262},[277,8425,1418],{"class":1266},[277,8427,3180],{"class":1262},[277,8429,3183],{"class":1258},[277,8431,3186],{"class":294},[277,8433,3290],{"class":1258},[277,8435,3293],{"class":1242},[277,8437,1418],{"class":1266},[277,8439,3298],{"class":1262},[277,8441,3183],{"class":1258},[277,8443,3293],{"class":1242},[277,8445,1418],{"class":1266},[277,8447,3307],{"class":1262},[277,8449,1418],{"class":1266},[277,8451,3312],{"class":1238},[277,8453,1348],{"class":1329},[277,8455,8456,8458,8460,8462,8464],{"class":279,"line":298},[277,8457,3319],{"class":1242},[277,8459,1418],{"class":1329},[277,8461,3324],{"class":301},[277,8463,1369],{"class":1266},[277,8465,8273],{"class":283},[277,8467,8468,8470,8472,8474],{"class":279,"line":316},[277,8469,3331],{"class":1242},[277,8471,1418],{"class":1329},[277,8473,3336],{"class":301},[277,8475,1504],{"class":1266},[60,8477,2350],{"id":2350},[10,8479,8480],{},"戴上耳机，应该能听到：",[634,8482,8483,8486,8489],{},[25,8484,8485],{},"✅ 按 fly → 翅膀扇动声",[25,8487,8488],{},"✅ 穿过水管 → 清脆的得分音",[25,8490,8491],{},"✅ 撞死 → 一声闷响",[10,8493,8494,8495,8498,8499,547,8501,17],{},"如果声音太大\u002F太小，去 ",[140,8496,8497],{},"sound_manager.tscn"," 里调 ",[140,8500,7978],{},[14,8502,8503],{},"不用改代码",[60,8505,8507],{"id":8506},"可选背景音乐-bgm","（可选）背景音乐 BGM",[10,8509,8510,8511,8513,8514,8517],{},"你可以再加一个 ",[140,8512,7879],{},"（命名 ",[140,8515,8516],{},"BGM","），在 Inspector 里：",[634,8519,8520,8525,8531],{},[25,8521,8522,8524],{},[14,8523,7967],{},"：拖入背景音乐",[25,8526,8527,8530],{},[14,8528,8529],{},"Autoplay","：✅ 勾选（场景一加载就播放）",[25,8532,8533,8536,8537],{},[14,8534,8535],{},"Loop","：在 Stream 的导入设置里勾选 ",[140,8538,8535],{},[10,8540,8541],{},"这样游戏一开始就有背景音乐。",[10,8543,8544],{},"下一章我们打包导出游戏，发布到 itch.io！🚀",[854,8546,8547],{},"html pre.shiki code .sTPum, html code.shiki .sTPum{--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .s_NWU, html code.shiki .s_NWU{--shiki-default:#2E8F82;--shiki-dark:#5DA994}html pre.shiki code .snYqZ, html code.shiki .snYqZ{--shiki-default:#A0ADA0;--shiki-dark:#758575DD}html pre.shiki code .s_xSY, html code.shiki .s_xSY{--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .s5TCs, html code.shiki .s5TCs{--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .s9nN2, html code.shiki .s9nN2{--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .si6no, html code.shiki .si6no{--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .sfsYZ, html code.shiki .sfsYZ{--shiki-default:#A65E2B;--shiki-dark:#C99076}html pre.shiki code .s8w-G, html code.shiki .s8w-G{--shiki-default:#393A34;--shiki-dark:#DBD7CAEE}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .spP0B, html code.shiki .spP0B{--shiki-default:#B56959;--shiki-dark:#C98A7D}html pre.shiki code .sqbOQ, html code.shiki .sqbOQ{--shiki-default:#2F798A;--shiki-dark:#4C9A91}",{"title":259,"searchDepth":287,"depth":287,"links":8549},[8550,8551,8553,8556,8557,8558,8566,8567],{"id":7774,"depth":287,"text":7774},{"id":7858,"depth":287,"text":8552},"SoundManager 是个场景，不是脚本",{"id":7895,"depth":287,"text":7896,"children":8554},[8555],{"id":7956,"depth":298,"text":7957},{"id":7988,"depth":287,"text":7989},{"id":8167,"depth":287,"text":8168},{"id":8214,"depth":287,"text":8214,"children":8559},[8560,8562,8564],{"id":8220,"depth":298,"text":8561},"小鸟跳跃时（bird.gd）",{"id":8276,"depth":298,"text":8563},"穿过水管得分时（pillar_pair.gd）",{"id":8387,"depth":298,"text":8565},"撞死时（killzone.gd）",{"id":2350,"depth":287,"text":2350},{"id":8506,"depth":287,"text":8507},{},"\u002Fdevlog\u002Fxggame-bird\u002F08-sound",{"title":7743,"description":878},"devlog\u002Fxggame-bird\u002F08-sound","-xino61vnvQDm2Uq8TfxIUqUrf99NjngMPZDav9wZr8",{"id":4,"title":5,"body":8574,"cover":876,"date":877,"description":878,"extension":879,"game":410,"github":880,"icon":876,"meta":9155,"navigation":319,"path":882,"seo":9156,"stem":884,"toc":319,"__hash__":885},{"type":7,"value":8575,"toc":9136},[8576,8580,8582,8595,8603,8605,8607,8611,8613,8617,8619,8621,8627,8631,8633,8699,8701,8721,8727,8729,8734,8736,8740,8780,8784,8788,8790,8794,8799,8801,8805,8809,8811,8879,8881,8885,8917,8923,8933,8937,8941,8943,8947,8981,8985,8987,8991,9009,9013,9015,9017,9061,9063,9067,9081,9085,9087,9089,9105,9107,9109,9111,9113,9127,9132,9134],[10,8577,12,8578,17],{},[14,8579,16],{},[10,8581,20],{},[22,8583,8584,8588],{},[25,8585,8586,30],{},[14,8587,29],{},[25,8589,8590,43],{},[14,8591,35,8592],{},[37,8593,42],{"href":39,"rel":8594},[41],[45,8596,8597],{},[10,8598,49,8599,54,8601,58],{},[37,8600,53],{"href":52},[14,8602,57],{},[60,8604,63],{"id":62},[10,8606,66],{},[10,8608,69,8609,73],{},[14,8610,72],{},[10,8612,76],{},[10,8614,8615],{},[80,8616],{"alt":82,"src":83},[60,8618,87],{"id":86},[10,8620,90],{},[10,8622,93,8623,97,8625,101],{},[14,8624,96],{},[14,8626,100],{},[10,8628,8629],{},[80,8630],{"alt":106,"src":107},[109,8632,111],{"id":111},[113,8634,8635,8645],{},[116,8636,8637],{},[119,8638,8639,8641,8643],{},[122,8640,124],{},[122,8642,127],{},[122,8644,130],{},[132,8646,8647,8657,8667,8679,8689],{},[119,8648,8649,8653,8655],{},[137,8650,8651],{},[140,8652,142],{},[137,8654,145],{},[137,8656,148],{},[119,8658,8659,8663,8665],{},[137,8660,8661],{},[140,8662,155],{},[137,8664,145],{},[137,8666,160],{},[119,8668,8669,8673,8677],{},[137,8670,8671],{},[140,8672,167],{},[137,8674,8675],{},[140,8676,172],{},[137,8678,175],{},[119,8680,8681,8685,8687],{},[137,8682,8683],{},[140,8684,182],{},[137,8686,185],{},[137,8688,188],{},[119,8690,8691,8695,8697],{},[137,8692,8693],{},[140,8694,195],{},[137,8696,198],{},[137,8698,201],{},[60,8700,204],{"id":204},[22,8702,8703,8707,8711,8717],{},[25,8704,209,8705,212],{},[14,8706,100],{},[25,8708,215,8709,219],{},[14,8710,218],{},[25,8712,222,8713,226,8715,230],{},[14,8714,225],{},[140,8716,229],{},[25,8718,233,8719,237],{},[140,8720,236],{},[45,8722,8723],{},[10,8724,242,8725,246],{},[14,8726,245],{},[10,8728,249],{},[251,8730,8732],{"className":8731,"code":255,"language":256},[254],[140,8733,255],{"__ignoreMap":259},[60,8735,262],{"id":262},[10,8737,8738,268],{},[14,8739,267],{},[251,8741,8742],{"className":271,"code":272,"language":273,"meta":259,"style":259},[140,8743,8744,8748,8754,8764,8768,8772],{"__ignoreMap":259},[277,8745,8746],{"class":279,"line":280},[277,8747,284],{"class":283},[277,8749,8750,8752],{"class":279,"line":287},[277,8751,291],{"class":290},[277,8753,295],{"class":294},[277,8755,8756,8758,8760,8762],{"class":279,"line":298},[277,8757,302],{"class":301},[277,8759,306],{"class":305},[277,8761,309],{"class":294},[277,8763,313],{"class":312},[277,8765,8766],{"class":279,"line":316},[277,8767,320],{"emptyLinePlaceholder":319},[277,8769,8770],{"class":279,"line":323},[277,8771,326],{"class":283},[277,8773,8774,8776,8778],{"class":279,"line":329},[277,8775,332],{"class":301},[277,8777,335],{"class":294},[277,8779,295],{"class":294},[10,8781,340,8782,344],{},[140,8783,343],{},[10,8785,8786],{},[80,8787],{"alt":349,"src":350},[60,8789,354],{"id":353},[45,8791,8792],{},[10,8793,359],{},[10,8795,362,8796,367],{},[37,8797,42],{"href":365,"rel":8798},[41],[60,8800,370],{"id":370},[10,8802,373,8803,17],{},[14,8804,376],{},[10,8806,8807],{},[80,8808],{"alt":381,"src":382},[109,8810,385],{"id":385},[113,8812,8813,8821],{},[116,8814,8815],{},[119,8816,8817,8819],{},[122,8818,394],{},[122,8820,397],{},[132,8822,8823,8831,8837,8843,8851,8861,8871],{},[119,8824,8825,8827],{},[137,8826,404],{},[137,8828,407,8829,230],{},[140,8830,410],{},[119,8832,8833,8835],{},[137,8834,415],{},[137,8836,418],{},[119,8838,8839,8841],{},[137,8840,423],{},[137,8842,426],{},[119,8844,8845,8847],{},[137,8846,431],{},[137,8848,434,8849],{},[140,8850,437],{},[119,8852,8853,8855],{},[137,8854,442],{},[137,8856,8857,450],{},[14,8858,434,8859],{},[140,8860,449],{},[119,8862,8863,8865],{},[137,8864,455],{},[137,8866,8867,461,8869],{},[140,8868,460],{},[140,8870,464],{},[119,8872,8873,8875],{},[137,8874,469],{},[137,8876,8877,475],{},[140,8878,474],{},[60,8880,479],{"id":478},[10,8882,482,8883,486],{},[14,8884,485],{},[251,8886,8887],{"className":271,"code":489,"language":273,"meta":259,"style":259},[140,8888,8889,8893,8899,8903,8907],{"__ignoreMap":259},[277,8890,8891],{"class":279,"line":280},[277,8892,496],{"class":283},[277,8894,8895,8897],{"class":279,"line":287},[277,8896,291],{"class":290},[277,8898,295],{"class":294},[277,8900,8901],{"class":279,"line":298},[277,8902,320],{"emptyLinePlaceholder":319},[277,8904,8905],{"class":279,"line":316},[277,8906,511],{"class":283},[277,8908,8909,8911,8913,8915],{"class":279,"line":323},[277,8910,516],{"class":301},[277,8912,519],{"class":305},[277,8914,522],{"class":294},[277,8916,525],{"class":294},[10,8918,528,8919,532,8921,536],{},[140,8920,531],{},[14,8922,535],{},[45,8924,8925],{},[10,8926,242,8927,544,8929,547,8931,551],{},[14,8928,543],{},[140,8930,236],{},[14,8932,550],{},[10,8934,554,8935,17],{},[14,8936,557],{},[10,8938,8939],{},[80,8940],{"alt":562,"src":563},[60,8942,567],{"id":566},[10,8944,570,8945,486],{},[14,8946,573],{},[113,8948,8949,8957],{},[116,8950,8951],{},[119,8952,8953,8955],{},[122,8954,124],{},[122,8956,584],{},[132,8958,8959,8967,8975],{},[119,8960,8961,8963],{},[137,8962,591],{},[137,8964,594,8965,230],{},[140,8966,597],{},[119,8968,8969,8971],{},[137,8970,602],{},[137,8972,605,8973,609],{},[140,8974,608],{},[119,8976,8977,8979],{},[137,8978,614],{},[137,8980,617],{},[10,8982,8983],{},[80,8984],{"alt":622,"src":623},[60,8986,626],{"id":626},[10,8988,629,8989,486],{},[14,8990,632],{},[634,8992,8993,9001,9005],{},[25,8994,8995,640,8997,461,8999,647],{},[14,8996,632],{},[14,8998,643],{},[14,9000,646],{},[25,9002,9003,653],{},[14,9004,652],{},[25,9006,9007,659],{},[14,9008,658],{},[10,9010,662,9011,666],{},[14,9012,665],{},[60,9014,670],{"id":669},[10,9016,673],{},[634,9018,9019,9025,9041,9047,9057],{},[25,9020,9021,681,9023],{},[14,9022,680],{},[140,9024,684],{},[25,9026,9027,681,9029,693,9031,693,9033,693,9035,693,9037,693,9039],{},[14,9028,689],{},[140,9030,692],{},[140,9032,696],{},[140,9034,699],{},[140,9036,702],{},[140,9038,705],{},[140,9040,708],{},[25,9042,9043,681,9045],{},[14,9044,713],{},[140,9046,716],{},[25,9048,9049,681,9051,693,9053,693,9055,731],{},[14,9050,721],{},[140,9052,724],{},[140,9054,727],{},[140,9056,730],{},[25,9058,9059,737],{},[14,9060,736],{},[60,9062,741],{"id":740},[10,9064,744,9065,748],{},[14,9066,747],{},[634,9068,9069,9073,9077],{},[25,9070,9071,756],{},[14,9072,755],{},[25,9074,9075,762],{},[14,9076,761],{},[25,9078,9079,768],{},[14,9080,767],{},[10,9082,771,9083,775],{},[14,9084,774],{},[60,9086,779],{"id":778},[10,9088,782],{},[634,9090,9091,9093,9098,9103],{},[25,9092,787],{},[25,9094,9095,795],{},[37,9096,794],{"href":792,"rel":9097},[41],[25,9099,9100,803],{},[37,9101,802],{"href":800,"rel":9102},[41],[25,9104,806],{},[10,9106,809],{},[811,9108],{},[60,9110,816],{"id":815},[10,9112,819],{},[634,9114,9115,9117,9119,9121,9123],{},[25,9116,824],{},[25,9118,827],{},[25,9120,830],{},[25,9122,833],{},[25,9124,836,9125,840],{},[14,9126,839],{},[10,9128,843,9129,849],{},[37,9130,848],{"href":846,"rel":9131},[41],[10,9133,852],{},[854,9135,856],{},{"title":259,"searchDepth":287,"depth":287,"links":9137},[9138,9139,9142,9143,9144,9145,9148,9149,9150,9151,9152,9153,9154],{"id":62,"depth":287,"text":63},{"id":86,"depth":287,"text":87,"children":9140},[9141],{"id":111,"depth":298,"text":111},{"id":204,"depth":287,"text":204},{"id":262,"depth":287,"text":262},{"id":353,"depth":287,"text":354},{"id":370,"depth":287,"text":370,"children":9146},[9147],{"id":385,"depth":298,"text":385},{"id":478,"depth":287,"text":479},{"id":566,"depth":287,"text":567},{"id":626,"depth":287,"text":626},{"id":669,"depth":287,"text":670},{"id":740,"depth":287,"text":741},{"id":778,"depth":287,"text":779},{"id":815,"depth":287,"text":816},{},{"title":5,"description":878},1779499826153]