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