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