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