[{"data":1,"prerenderedAt":946},["ShallowReactive",2],{"page-blog\u002Fporting-without-pain":3,"article-footer-blog\u002Fporting-without-pain-none-Porting|Console|Godot":936,"richtext-code-i-103e752d-4978-42c3-87fa-094494afec17":938,"richtext-code-i-772fea4c-05e1-4738-a202-eafaa0a08065":939,"richtext-code-i-02fa4b9f-b9d8-48b7-bafd-4dddf5cc5e04":940,"richtext-code-i-a8eb8894-63b6-493e-b9f8-77e02f2ca187":941,"richtext-code-i-2b7b8e05-d9f9-4e2d-8f6a-e46de4ce8c1c":942,"richtext-code-i-4cbe9ab2-82e0-4ed8-9292-0cd0b5e1d9a9":943,"richtext-code-i-766ddf3f-818e-42ce-b6b1-4e6cd4cd4175":944,"richtext-code-i-a1728774-b6f8-46f3-9400-b47cf14e112d":945},{"page":4,"seo":927},{"type":5,"slug":6,"name":7,"title":7,"summary":8,"date":9,"article":10,"image":915,"imageHero":918,"author":919,"tags":920,"footer":924,"seo":925,"seoImage":-1},"article","blog\u002Fporting-without-pain","Porting Without Pain","Rory Thomson, Director of Engineering at Pocket Sized Hands, took to the stage at GodotCon Amsterdam 2026 to share hard-won lessons on getting Godot for consoles right. Structuring your project for multi-platform success from day one.","2026-05-31 00:00",{"nodes":11},[12,22,28,59,76,78,85,101,103,109,115,117,124,139,171,173,179,221,244,246,252,258,304,320,322,328,334,364,366,372,388,390,396,402,440,442,448,454,456,510,512,518,520,530,532,538,544,546,552,558,566,581,583,589,591,597,647,649,655,657,679,681,687,702,704,710,712,727,729,735,737,743,762,764,770,772,778,788,790,796,798,804,806,812,814,836,838,844,850,880,895,897,899,913],{"type":13,"level":14,"text":15,"children":16,"align":21},"heading",2,"Setting Your Godot Game Up for Success. A Godotcon 2026 Talk",[17],{"type":18,"text":15,"marks":19,"styleClasses":20},"text",[],[],"left",{"type":23,"text":8,"children":24,"align":-1},"paragraph",[25],{"type":18,"text":8,"marks":26,"styleClasses":27},[],[],{"type":23,"text":29,"children":30,"align":-1},"Earlier this month our Director of Engineering, Rory Thomson, spoke at GodotCon Amsterdam 2026. The talk was called Porting Without the Pain: Setting Your Godot Game Up for Success, and it draws on what we’ve learned across years of shipping Godot for consoles and PC.",[31,35,46,50,55],{"type":18,"text":32,"marks":33,"styleClasses":34},"Earlier this month our Director of Engineering, Rory Thomson, spoke at ",[],[],{"type":36,"href":37,"target":38,"external":39,"children":40},"link","https:\u002F\u002Fconference.godotengine.org\u002F2026\u002F","_blank",true,[41],{"type":18,"text":42,"marks":43,"styleClasses":45},"GodotCon Amsterdam 2026",[44],"underline",[],{"type":18,"text":47,"marks":48,"styleClasses":49},". The talk was called ",[],[],{"type":18,"text":51,"marks":52,"styleClasses":54},"Porting Without the Pain: Setting Your Godot Game Up for Success",[53],"italic",[],{"type":18,"text":56,"marks":57,"styleClasses":58},", and it draws on what we’ve learned across years of shipping Godot for consoles and PC.",[],[],{"type":60,"blocks":61},"blok",[62],{"id":63,"component":64,"blok":65},"i-a27f4ef2-766f-4b1e-945b-83c376ba2b08","instance_richtext_image",{"_uid":63,"size":66,"align":67,"image":68,"inline":75,"caption":70,"component":64},"full","center",{"id":69,"alt":70,"name":70,"focus":70,"title":70,"source":70,"filename":71,"copyright":70,"fieldtype":72,"meta_data":73,"is_external_url":75},218173125390274,"","https:\u002F\u002Fa.storyblok.com\u002Ff\u002F294481059584474\u002F1600x1204\u002Ff8ca4404e5\u002Fpsh_atgodotcon.jpeg","asset",{"size":74},"1600x1204",false,{"type":23,"text":70,"children":77,"align":-1},[],{"type":13,"level":79,"text":80,"children":81,"align":-1},3,"Think about the end in mind",[82],{"type":18,"text":80,"marks":83,"styleClasses":84},[],[],{"type":23,"text":86,"children":87,"align":-1},"Our central premise is what we call porting perspective the notion that the architectural decisions you make early in development have an outsized impact on how painful (or painless) a port will be later.",[88,92,97],{"type":18,"text":89,"marks":90,"styleClasses":91},"Our central premise is what we call ",[],[],{"type":18,"text":93,"marks":94,"styleClasses":96},"porting perspective",[95],"bold",[],{"type":18,"text":98,"marks":99,"styleClasses":100}," the notion that the architectural decisions you make early in development have an outsized impact on how painful (or painless) a port will be later.",[],[],{"type":23,"text":70,"children":102,"align":-1},[],{"type":13,"level":79,"text":104,"children":105,"align":-1},"The HealthCheck",[106],{"type":18,"text":104,"marks":107,"styleClasses":108},[],[],{"type":23,"text":110,"children":111,"align":-1},"We use an internal project checklist when reviewing games before porting, the HealthCheck. It covers six areas that consistently determine how smoothly a port will go and what may be of concern.",[112],{"type":18,"text":110,"marks":113,"styleClasses":114},[],[],{"type":23,"text":70,"children":116,"align":-1},[],{"type":13,"level":118,"text":119,"children":120,"align":21},4,"1. Optimisation and Performance",[121],{"type":18,"text":119,"marks":122,"styleClasses":123},[],[],{"type":23,"text":125,"children":126,"align":21},"The key question: what is your lowest target? A game that runs fine on PC and even Steam Deck may struggle on Switch or older mobile hardware.",[127,131,135],{"type":18,"text":128,"marks":129,"styleClasses":130},"The key question: ",[],[],{"type":18,"text":132,"marks":133,"styleClasses":134},"what is your lowest target?",[95],[],{"type":18,"text":136,"marks":137,"styleClasses":138}," A game that runs fine on PC and even Steam Deck may struggle on Switch or older mobile hardware.",[],[],{"type":140,"style":141,"items":142},"list","unordered",[143,150,157,164],{"id":144,"text":145,"children":146},"list-item-0-Identify worst-case scenarios early, the most complex scene, the most entities on screen at once","Identify worst-case scenarios early, the most complex scene, the most entities on screen at once",[147],{"type":18,"text":145,"marks":148,"styleClasses":149},[],[],{"id":151,"text":152,"children":153},"list-item-1-Understand memory restrictions on your target platforms","Understand memory restrictions on your target platforms",[154],{"type":18,"text":152,"marks":155,"styleClasses":156},[],[],{"id":158,"text":159,"children":160},"list-item-2-Audit load times in particular one without load screen,","Audit load times in particular one without load screen,",[161],{"type":18,"text":159,"marks":162,"styleClasses":163},[],[],{"id":165,"text":166,"children":167},"list-item-3-Review current frame rate","Review current frame rate",[168],{"type":18,"text":166,"marks":169,"styleClasses":170},[],[],{"type":23,"text":70,"children":172,"align":-1},[],{"type":13,"level":118,"text":174,"children":175,"align":21},"2. Rendering",[176],{"type":18,"text":174,"marks":177,"styleClasses":178},[],[],{"type":23,"text":180,"children":181,"align":-1},"Godot offers three rendering backends: Forward+, Mobile, and Compatibility. Choosing the wrong one for your target platform can mean significant rework later.",[182,186,193,197,201,205,209,213,217],{"type":18,"text":183,"marks":184,"styleClasses":185},"Godot offers three ",[],[],{"type":36,"href":187,"target":38,"external":39,"children":188},"https:\u002F\u002Fdocs.godotengine.org\u002Fen\u002Fstable\u002Fcontributing\u002Fdevelopment\u002Fcore_and_modules\u002Finternal_rendering_architecture.html",[189],{"type":18,"text":190,"marks":191,"styleClasses":192},"rendering backends",[44],[],{"type":18,"text":194,"marks":195,"styleClasses":196},": ",[],[],{"type":18,"text":198,"marks":199,"styleClasses":200},"Forward+",[95],[],{"type":18,"text":202,"marks":203,"styleClasses":204},", ",[],[],{"type":18,"text":206,"marks":207,"styleClasses":208},"Mobile",[95],[],{"type":18,"text":210,"marks":211,"styleClasses":212},", and ",[],[],{"type":18,"text":214,"marks":215,"styleClasses":216},"Compatibility",[95],[],{"type":18,"text":218,"marks":219,"styleClasses":220},". Choosing the wrong one for your target platform can mean significant rework later.",[],[],{"type":140,"style":141,"items":222},[223,230,237],{"id":224,"text":225,"children":226},"list-item-0-Forward+ is the most capable but not suitable for all platforms","Forward+ is the most capable but not suitable for all platforms",[227],{"type":18,"text":225,"marks":228,"styleClasses":229},[],[],{"id":231,"text":232,"children":233},"list-item-1-Mobile and Compatibility modes trade features for broader hardware support","Mobile and Compatibility modes trade features for broader hardware support",[234],{"type":18,"text":232,"marks":235,"styleClasses":236},[],[],{"id":238,"text":239,"children":240},"list-item-2-Plan for fallbacks — if a visual effect isn’t supported on a target platform, does the game still look intentional without it?","Plan for fallbacks — if a visual effect isn’t supported on a target platform, does the game still look intentional without it?",[241],{"type":18,"text":239,"marks":242,"styleClasses":243},[],[],{"type":23,"text":70,"children":245,"align":-1},[],{"type":13,"level":118,"text":247,"children":248,"align":21},"3. Controller and Input",[249],{"type":18,"text":247,"marks":250,"styleClasses":251},[],[],{"type":23,"text":253,"children":254,"align":-1},"Console ports live and die by their input handling.",[255],{"type":18,"text":253,"marks":256,"styleClasses":257},[],[],{"type":140,"style":141,"items":259},[260,267,274,290,297],{"id":261,"text":262,"children":263},"list-item-0-Are your input mechanisms controller-compatible from the start?","Are your input mechanisms controller-compatible from the start?",[264],{"type":18,"text":262,"marks":265,"styleClasses":266},[],[],{"id":268,"text":269,"children":270},"list-item-1-Does your UI support full gamepad navigation?","Does your UI support full gamepad navigation?",[271],{"type":18,"text":269,"marks":272,"styleClasses":273},[],[],{"id":275,"text":276,"children":277},"list-item-2-Are you handling iconography swapping — showing PlayStation glyphs on PS5, Switch button icons on Switch?","Are you handling iconography swapping — showing PlayStation glyphs on PS5, Switch button icons on Switch?",[278,282,286],{"type":18,"text":279,"marks":280,"styleClasses":281},"Are you handling ",[],[],{"type":18,"text":283,"marks":284,"styleClasses":285},"iconography swapping",[95],[],{"type":18,"text":287,"marks":288,"styleClasses":289}," — showing PlayStation glyphs on PS5, Switch button icons on Switch?",[],[],{"id":291,"text":292,"children":293},"list-item-3-Is your input management flexible enough to accommodate different controller schemes per platform?","Is your input management flexible enough to accommodate different controller schemes per platform?",[294],{"type":18,"text":292,"marks":295,"styleClasses":296},[],[],{"id":298,"text":299,"children":300},"list-item-4-Are you also planning on supporting controller remapping?","Are you also planning on supporting controller remapping?",[301],{"type":18,"text":299,"marks":302,"styleClasses":303},[],[],{"type":23,"text":305,"children":306,"align":-1},"Godot’s InputMap is well-suited to this, but only if you’ve built your game to use it cleanly from the start rather than hardcoding keyboard assumptions throughout.",[307,311,316],{"type":18,"text":308,"marks":309,"styleClasses":310},"Godot’s ",[],[],{"type":18,"text":312,"marks":313,"styleClasses":315},"InputMap",[314],"code",[],{"type":18,"text":317,"marks":318,"styleClasses":319}," is well-suited to this, but only if you’ve built your game to use it cleanly from the start rather than hardcoding keyboard assumptions throughout.",[],[],{"type":23,"text":70,"children":321,"align":-1},[],{"type":13,"level":118,"text":323,"children":324,"align":21},"4. Serialisation and Save Systems",[325],{"type":18,"text":323,"marks":326,"styleClasses":327},[],[],{"type":23,"text":329,"children":330,"align":-1},"Save systems are a surprisingly common source of porting pain:",[331],{"type":18,"text":329,"marks":332,"styleClasses":333},[],[],{"type":140,"style":141,"items":335},[336,343,350,357],{"id":337,"text":338,"children":339},"list-item-0-Platforms have different expectations around save locations, permissions, and cloud sync","Platforms have different expectations around save locations, permissions, and cloud sync",[340],{"type":18,"text":338,"marks":341,"styleClasses":342},[],[],{"id":344,"text":345,"children":346},"list-item-1-Avoid assuming instant reads\u002Fwrites — some platforms require async save handling","Avoid assuming instant reads\u002Fwrites — some platforms require async save handling",[347],{"type":18,"text":345,"marks":348,"styleClasses":349},[],[],{"id":351,"text":352,"children":353},"list-item-2-Saving or loading too frequently can cause platform certification failures","Saving or loading too frequently can cause platform certification failures",[354],{"type":18,"text":352,"marks":355,"styleClasses":356},[],[],{"id":358,"text":359,"children":360},"list-item-3-Large numbers of save files can cause issues on platforms with restricted storage","Large numbers of save files can cause issues on platforms with restricted storage",[361],{"type":18,"text":359,"marks":362,"styleClasses":363},[],[],{"type":23,"text":70,"children":365,"align":-1},[],{"type":13,"level":118,"text":367,"children":368,"align":21},"5. Localisation and Terminology",[369],{"type":18,"text":367,"marks":370,"styleClasses":371},[],[],{"type":140,"style":141,"items":373},[374,381],{"id":375,"text":376,"children":377},"list-item-0-Is a localisation system in place from the start, or are strings hardcoded throughout?","Is a localisation system in place from the start, or are strings hardcoded throughout?",[378],{"type":18,"text":376,"marks":379,"styleClasses":380},[],[],{"id":382,"text":383,"children":384},"list-item-1-Platform terminology needs careful attention — “Trophy” on PlayStation, “Achievement” on Xbox for example. Terminology mapping is always going to be a concern if not setup correctly.","Platform terminology needs careful attention — “Trophy” on PlayStation, “Achievement” on Xbox for example. Terminology mapping is always going to be a concern if not setup correctly.",[385],{"type":18,"text":383,"marks":386,"styleClasses":387},[],[],{"type":23,"text":70,"children":389,"align":-1},[],{"type":13,"level":118,"text":391,"children":392,"align":21},"6. Audio, UI, and Everything Else",[393],{"type":18,"text":391,"marks":394,"styleClasses":395},[],[],{"type":23,"text":397,"children":398,"align":-1},"A few final areas that consistently catch studios off guard:",[399],{"type":18,"text":397,"marks":400,"styleClasses":401},[],[],{"type":140,"style":141,"items":403},[404,416,428],{"id":405,"text":406,"children":407},"list-item-0-Audio: Mix loudness requirements differ by platform — certification will fail if you’re too loud. Also check whether any audio middleware you’re using (FMOD, Wwise) is supported on your target platforms at its current sdk verions.","Audio: Mix loudness requirements differ by platform — certification will fail if you’re too loud. Also check whether any audio middleware you’re using (FMOD, Wwise) is supported on your target platforms at its current sdk verions.",[408,412],{"type":18,"text":409,"marks":410,"styleClasses":411},"Audio",[95],[],{"type":18,"text":413,"marks":414,"styleClasses":415},": Mix loudness requirements differ by platform — certification will fail if you’re too loud. Also check whether any audio middleware you’re using (FMOD, Wwise) is supported on your target platforms at its current sdk verions.",[],[],{"id":417,"text":418,"children":419},"list-item-1-UI: Does your UI scale correctly across different resolutions and aspect ratios? Switch for example needs to be readable down to 480p, Console UIs also requires full gamepad navigation and robust state handling","UI: Does your UI scale correctly across different resolutions and aspect ratios? Switch for example needs to be readable down to 480p, Console UIs also requires full gamepad navigation and robust state handling",[420,424],{"type":18,"text":421,"marks":422,"styleClasses":423},"UI",[95],[],{"type":18,"text":425,"marks":426,"styleClasses":427},": Does your UI scale correctly across different resolutions and aspect ratios? Switch for example needs to be readable down to 480p, Console UIs also requires full gamepad navigation and robust state handling",[],[],{"id":429,"text":430,"children":431},"list-item-2-Miscellaneous: DLC, online features, achievements, platform-specific overlays — the list grows quickly. Better to know early which of these are in scope","Miscellaneous: DLC, online features, achievements, platform-specific overlays — the list grows quickly. Better to know early which of these are in scope",[432,436],{"type":18,"text":433,"marks":434,"styleClasses":435},"Miscellaneous",[95],[],{"type":18,"text":437,"marks":438,"styleClasses":439},": DLC, online features, achievements, platform-specific overlays — the list grows quickly. Better to know early which of these are in scope",[],[],{"type":23,"text":70,"children":441,"align":-1},[],{"type":13,"level":79,"text":443,"children":444,"align":21},"Scripting: Choose Wisely",[445],{"type":18,"text":443,"marks":446,"styleClasses":447},[],[],{"type":23,"text":449,"children":450,"align":21},"The scripting approach you choose has direct consequences for which platforms you can target:",[451],{"type":18,"text":449,"marks":452,"styleClasses":453},[],[],{"type":23,"text":70,"children":455,"align":21},[],{"type":60,"blocks":457},[458],{"id":459,"component":460,"blok":461},"i-25f9e8ad-34ba-452d-9312-5e71ba8cc1c2","instance_richtext_table",{"_uid":459,"size":462,"align":67,"table":463,"inline":75,"caption":70,"component":460},"regular",{"tbody":464,"thead":500,"fieldtype":509},[465,478,489],{"_uid":466,"body":467,"component":477},"b622f423-5190-41c6-ae96-e4d85d2b2f05",[468,472,475],{"_uid":469,"value":470,"component":471},"a9b7339c-5e69-4df9-af4a-31703e231034","GDScript","_table_col",{"_uid":473,"value":474,"component":471},"91783105-692b-4c4a-b94f-07c442c446db","Built for the engine, best supported across all platforms",{"_uid":476,"value":70,"component":471},"9e95a2ad-0ca8-437b-b26d-2703d4f092ad","_table_row",{"_uid":479,"body":480,"component":477},"0551b3d3-7841-401b-99ea-57018783ed48",[481,484,487],{"_uid":482,"value":483,"component":471},"72d3963a-77a5-4567-897f-2184516e2e7b","C#",{"_uid":485,"value":486,"component":471},"c5d0d7df-e860-491d-b303-e7d232453fff","Relies on the .NET runtime, which is not available on all platforms",{"_uid":488,"value":70,"component":471},"baf0d0c3-f431-4a54-a676-8f811b025aa8",{"_uid":490,"body":491,"component":477},"e9893252-d99e-428f-8c77-87e9cbf26c6f",[492,495,498],{"_uid":493,"value":494,"component":471},"d6a1a38c-c063-411d-87b9-d9a3a748fb76","GDExtension",{"_uid":496,"value":497,"component":471},"543d7de7-2595-447b-ab79-5912c23f5789","Requires compiling platform-specific binaries for each target",{"_uid":499,"value":70,"component":471},"5485ba59-ac02-4711-980c-a5717ead026e",[501,505,507],{"_uid":502,"value":503,"component":504},"7f36dd86-942e-4e35-b20b-33cbbde480e5","Approach","_table_head",{"_uid":70,"value":506,"component":504},"Notes",{"_uid":508,"value":70,"component":504},"59d26dfd-4cc2-405c-9504-56d894722248","table",{"type":23,"text":70,"children":511,"align":-1},[],{"type":23,"text":513,"children":514,"align":-1},"GDScript is the path of least resistance for getting Godot for consoles. If you’re using C# or GDExtension, verify early that your approach is viable on every platform you’re targeting or understand the tradeoffs. Finding out at the end of development that your scripting choice blocks a platform is an expensive discovery.",[515],{"type":18,"text":513,"marks":516,"styleClasses":517},[],[],{"type":23,"text":70,"children":519,"align":-1},[],{"type":60,"blocks":521},[522],{"id":523,"component":64,"blok":524},"i-de022bae-b0f6-46c1-b832-94874c003868",{"_uid":523,"size":66,"align":67,"image":525,"inline":75,"caption":70,"component":64},{"id":526,"alt":70,"name":70,"focus":70,"title":70,"source":70,"filename":527,"copyright":70,"fieldtype":72,"meta_data":528,"is_external_url":75},218177107088053,"https:\u002F\u002Fa.storyblok.com\u002Ff\u002F294481059584474\u002F1049x565\u002F15484af787\u002Fgodotscriptingplatforms.png",{"size":529},"1049x565",{"type":23,"text":70,"children":531,"align":-1},[],{"type":13,"level":79,"text":533,"children":534,"align":21},"A Practical Pattern: Feature Tags",[535],{"type":18,"text":533,"marks":536,"styleClasses":537},[],[],{"type":23,"text":539,"children":540,"align":-1},"One pattern we use heavily is abstracting platform-specific systems behind an interface, then using Godot’s feature tags to select the right implementation per platform. The talk walked through a complete example using achievements.",[541],{"type":18,"text":539,"marks":542,"styleClasses":543},[],[],{"type":23,"text":70,"children":545,"align":-1},[],{"type":13,"level":79,"text":547,"children":548,"align":21},"The naive approach then and why it breaks",[549],{"type":18,"text":547,"marks":550,"styleClasses":551},[],[],{"type":23,"text":553,"children":554,"align":-1},"The obvious starting point is calling the Steam plugin directly from your game code:",[555],{"type":18,"text":553,"marks":556,"styleClasses":557},[],[],{"type":60,"blocks":559},[560],{"id":561,"component":562,"blok":563},"i-103e752d-4978-42c3-87fa-094494afec17","instance_richtext_code",{"_uid":561,"code":564,"type":565,"component":562},"extends Node\n\nclass_name GameManager\n\nfunc _ready() -> void:\n    Steam.unlock_achievement(\"my_achievement\")","gdscript",{"type":23,"text":567,"children":568,"align":-1},"This works fine in a Steam build. The problem appears the moment you try to export for a platform that doesn’t have the Steam plugin. Godot can’t resolve the Steam class and throws a parse error before your game even runs:",[569,573,577],{"type":18,"text":570,"marks":571,"styleClasses":572},"This works fine in a Steam build. The problem appears the moment you try to export for a platform that doesn’t have the Steam plugin. Godot can’t resolve the ",[],[],{"type":18,"text":574,"marks":575,"styleClasses":576},"Steam",[314],[],{"type":18,"text":578,"marks":579,"styleClasses":580}," class and throws a parse error before your game even runs:",[],[],{"type":23,"text":70,"children":582,"align":-1},[],{"type":60,"blocks":584},[585],{"id":586,"component":562,"blok":587},"i-772fea4c-05e1-4738-a202-eafaa0a08065",{"_uid":586,"code":588,"type":565,"component":562},"SCRIPT ERROR: Parse Error: Could not resolve class \"Steam\", because of a parser error.\n    at: GDScript::reload (res:\u002F\u002Ftest.gd:4)\nERROR: Failed to load script \"res:\u002F\u002Ftest.gd\" with error \"Parse error\".",{"type":23,"text":70,"children":590,"align":-1},[],{"type":13,"level":118,"text":592,"children":593,"align":21},"Step 1: Conditionally load the plugin using feature tags",[594],{"type":18,"text":592,"marks":595,"styleClasses":596},[],[],{"type":23,"text":598,"children":599,"align":-1},"Godot’s export presets have a Features tab where you can define custom feature tags. Create a Windows Desktop - Steam preset and add steam as a custom feature. Then in project.godot, scope the plugin so it only loads when that tag is present:",[600,604,608,612,619,623,627,631,635,639,643],{"type":18,"text":601,"marks":602,"styleClasses":603},"Godot’s export presets have a ",[],[],{"type":18,"text":605,"marks":606,"styleClasses":607},"Features",[95],[],{"type":18,"text":609,"marks":610,"styleClasses":611}," tab where you can define ",[],[],{"type":36,"href":613,"target":38,"external":39,"children":614},"https:\u002F\u002Fdocs.godotengine.org\u002Fen\u002Fstable\u002Ftutorials\u002Fexport\u002Ffeature_tags.html",[615],{"type":18,"text":616,"marks":617,"styleClasses":618},"custom feature tags",[44],[],{"type":18,"text":620,"marks":621,"styleClasses":622},". Create a ",[],[],{"type":18,"text":624,"marks":625,"styleClasses":626},"Windows Desktop - Steam",[314],[],{"type":18,"text":628,"marks":629,"styleClasses":630}," preset and add ",[],[],{"type":18,"text":632,"marks":633,"styleClasses":634},"steam",[314],[],{"type":18,"text":636,"marks":637,"styleClasses":638}," as a custom feature. Then in ",[],[],{"type":18,"text":640,"marks":641,"styleClasses":642},"project.godot",[314],[],{"type":18,"text":644,"marks":645,"styleClasses":646},", scope the plugin so it only loads when that tag is present:",[],[],{"type":23,"text":70,"children":648,"align":-1},[],{"type":60,"blocks":650},[651],{"id":652,"component":562,"blok":653},"i-02fa4b9f-b9d8-48b7-bafd-4dddf5cc5e04",{"_uid":652,"code":654,"type":565,"component":562},"[editor_plugins]\n\nenabled=\"\"\n\nenabled.steam=PackedStringArray(\"res:\u002F\u002Faddons\u002Fsteam\u002Fplugin.cfg\")",{"type":23,"text":70,"children":656,"align":-1},[],{"type":23,"text":658,"children":659,"align":-1},"Now the Steam addon only loads in Steam builds. But your GameManager still references Steam directly — so you need one more step.",[660,664,668,672,675],{"type":18,"text":661,"marks":662,"styleClasses":663},"Now the Steam addon only loads in Steam builds. But your ",[],[],{"type":18,"text":665,"marks":666,"styleClasses":667},"GameManager",[314],[],{"type":18,"text":669,"marks":670,"styleClasses":671}," still references ",[],[],{"type":18,"text":574,"marks":673,"styleClasses":674},[314],[],{"type":18,"text":676,"marks":677,"styleClasses":678}," directly — so you need one more step.",[],[],{"type":23,"text":70,"children":680,"align":-1},[],{"type":13,"level":118,"text":682,"children":683,"align":21},"Step 2: Abstract behind a platform base class",[684],{"type":18,"text":682,"marks":685,"styleClasses":686},[],[],{"type":23,"text":688,"children":689,"align":-1},"Create a PlatformBase script that defines the interface every platform must implement:",[690,694,698],{"type":18,"text":691,"marks":692,"styleClasses":693},"Create a ",[],[],{"type":18,"text":695,"marks":696,"styleClasses":697},"PlatformBase",[314],[],{"type":18,"text":699,"marks":700,"styleClasses":701}," script that defines the interface every platform must implement:",[],[],{"type":23,"text":70,"children":703,"align":-1},[],{"type":60,"blocks":705},[706],{"id":707,"component":562,"blok":708},"i-a8eb8894-63b6-493e-b9f8-77e02f2ca187",{"_uid":707,"code":709,"type":565,"component":562},"extends Node\nclass_name PlatformBase\n\nfunc get_platform_name() -> String:\n    return \"Base\"\n\nfunc unlock_achievement(achievement_name : String) -> void:\n    print(\"Platform Base Unlock Achievement: \" + achievement_name)",{"type":23,"text":70,"children":711,"align":-1},[],{"type":23,"text":713,"children":714,"align":-1},"Then create a PlatformSteam subclass that delegates to the real Steam API:",[715,719,723],{"type":18,"text":716,"marks":717,"styleClasses":718},"Then create a ",[],[],{"type":18,"text":720,"marks":721,"styleClasses":722},"PlatformSteam",[314],[],{"type":18,"text":724,"marks":725,"styleClasses":726}," subclass that delegates to the real Steam API:",[],[],{"type":23,"text":70,"children":728,"align":-1},[],{"type":60,"blocks":730},[731],{"id":732,"component":562,"blok":733},"i-2b7b8e05-d9f9-4e2d-8f6a-e46de4ce8c1c",{"_uid":732,"code":734,"type":565,"component":562},"extends PlatformBase\nclass_name PlatformSteam\n\nfunc get_platform_name() -> String:\n    return \"Steam\"\n\nfunc unlock_achievement(achievement_name : String) -> void:\n    Steam.unlock_achievement(achievement_name)",{"type":23,"text":70,"children":736,"align":-1},[],{"type":13,"level":118,"text":738,"children":739,"align":21},"Step 3: PlatformManager wires it together at runtime",[740],{"type":18,"text":738,"marks":741,"styleClasses":742},[],[],{"type":23,"text":744,"children":745,"align":-1},"PlatformManager uses OS.has_feature() to detect which build is running and loads the right script — no hardcoded platform references anywhere else in the codebase:",[746,750,754,758],{"type":18,"text":747,"marks":748,"styleClasses":749},"PlatformManager",[314],[],{"type":18,"text":751,"marks":752,"styleClasses":753}," uses ",[],[],{"type":18,"text":755,"marks":756,"styleClasses":757},"OS.has_feature()",[314],[],{"type":18,"text":759,"marks":760,"styleClasses":761}," to detect which build is running and loads the right script — no hardcoded platform references anywhere else in the codebase:",[],[],{"type":23,"text":70,"children":763,"align":-1},[],{"type":60,"blocks":765},[766],{"id":767,"component":562,"blok":768},"i-4cbe9ab2-82e0-4ed8-9292-0cd0b5e1d9a9",{"_uid":767,"code":769,"type":565,"component":562},"extends Node\nclass_name PlatformManager\n\nenum PlatformType { NULL, STEAM }\n\nstatic var platform_instance : PlatformBase\n\nstatic func get_platform_type() -> PlatformType:\n    if OS.has_feature(\"steam\"):\n        return PlatformType.STEAM\n    return PlatformType.NULL\n\nfunc _enter_tree() -> void:\n    var platform_script\n    match get_platform_type():\n        PlatformType.STEAM:\n            platform_script = load(\"res:\u002F\u002Fscripts\u002Fplatform\u002Fsteam\u002Fplatform_steam.gd\")\n        _:\n            platform_script = load(\"res:\u002F\u002Fscripts\u002Fplatform\u002Fplatform_base.gd\")\n\n    platform_instance = platform_script.new()\n    add_child(platform_instance)\n    print(\"Initialized Platform: \" + platform_instance.get_platform_name())\n\nstatic func unlock_achievement(achievement_name : String) -> void:\n    if not platform_instance:\n        pass\n\n    platform_instance.unlock_achievement(achievement_name)",{"type":23,"text":70,"children":771,"align":-1},[],{"type":13,"level":118,"text":773,"children":774,"align":21},"Step 4: Game code is now platform-agnostic",[775],{"type":18,"text":773,"marks":776,"styleClasses":777},[],[],{"type":23,"text":779,"children":780,"align":-1},"GameManager no longer knows anything about Steam:",[781,784],{"type":18,"text":665,"marks":782,"styleClasses":783},[314],[],{"type":18,"text":785,"marks":786,"styleClasses":787}," no longer knows anything about Steam:",[],[],{"type":23,"text":70,"children":789,"align":-1},[],{"type":60,"blocks":791},[792],{"id":793,"component":562,"blok":794},"i-766ddf3f-818e-42ce-b6b1-4e6cd4cd4175",{"_uid":793,"code":795,"type":565,"component":562},"extends Node\nclass_name GameManager\n\nfunc _ready() -> void:\n    PlatformManager.unlock_achievement(\"my_achievement\")",{"type":23,"text":70,"children":797,"align":-1},[],{"type":23,"text":799,"children":800,"align":-1},"The output changes automatically based on which export preset was used:",[801],{"type":18,"text":799,"marks":802,"styleClasses":803},[],[],{"type":23,"text":70,"children":805,"align":-1},[],{"type":60,"blocks":807},[808],{"id":809,"component":562,"blok":810},"i-a1728774-b6f8-46f3-9400-b47cf14e112d",{"_uid":809,"code":811,"type":565,"component":562},"# Steam build\nInitialized Platform: Steam\nUnlocking Steam Achievement: my_achievement\n\n# Non-Steam build\nInitialized Platform: Base\nPlatform Base Unlock Achievement: my_achievement",{"type":23,"text":70,"children":813,"align":-1},[],{"type":23,"text":815,"children":816,"align":-1},"Adding a new platform if that’s a console, Epic Games Store, Google Play. All you need to do is create a PlatformX subclass and a new branch in PlatformManager. Nothing else in the codebase needs to change.",[817,821,825,829,832],{"type":18,"text":818,"marks":819,"styleClasses":820},"Adding a new platform if that’s a console, Epic Games Store, Google Play. All you need to do is create a ",[],[],{"type":18,"text":822,"marks":823,"styleClasses":824},"PlatformX",[314],[],{"type":18,"text":826,"marks":827,"styleClasses":828}," subclass and a new branch in ",[],[],{"type":18,"text":747,"marks":830,"styleClasses":831},[314],[],{"type":18,"text":833,"marks":834,"styleClasses":835},". Nothing else in the codebase needs to change.",[],[],{"type":23,"text":70,"children":837,"align":-1},[],{"type":13,"level":79,"text":839,"children":840,"align":21},"The Takeaway",[841],{"type":18,"text":839,"marks":842,"styleClasses":843},[],[],{"type":23,"text":845,"children":846,"align":-1},"The studios that find porting easiest aren’t necessarily the most experienced. They’re the ones that made a few key architectural decisions early:",[847],{"type":18,"text":845,"marks":848,"styleClasses":849},[],[],{"type":140,"style":141,"items":851},[852,859,866,873],{"id":853,"text":854,"children":855},"list-item-0-Know your lowest target platform before you start optimising for your highest","Know your lowest target platform before you start optimising for your highest",[856],{"type":18,"text":854,"marks":857,"styleClasses":858},[],[],{"id":860,"text":861,"children":862},"list-item-1-Use GDScript unless you have a compelling reason not to","Use GDScript unless you have a compelling reason not to",[863],{"type":18,"text":861,"marks":864,"styleClasses":865},[],[],{"id":867,"text":868,"children":869},"list-item-2-Build input handling, save systems, and localisation to be portable by default","Build input handling, save systems, and localisation to be portable by default",[870],{"type":18,"text":868,"marks":871,"styleClasses":872},[],[],{"id":874,"text":875,"children":876},"list-item-3-Know your platform requirements before you hit certification","Know your platform requirements before you hit certification",[877],{"type":18,"text":875,"marks":878,"styleClasses":879},[],[],{"type":23,"text":881,"children":882,"align":-1},"Getting Godot for consoles right isn’t just about the technical lift at the end it’s about the decisions you make on day one.",[883,887,891],{"type":18,"text":884,"marks":885,"styleClasses":886},"Getting ",[],[],{"type":18,"text":888,"marks":889,"styleClasses":890},"Godot for consoles",[95],[],{"type":18,"text":892,"marks":893,"styleClasses":894}," right isn’t just about the technical lift at the end it’s about the decisions you make on day one.",[],[],{"type":23,"text":70,"children":896,"align":-1},[],{"type":898},"horizontal_rule",{"type":23,"text":900,"children":901,"align":-1},"Ready to bring your Godot game to console? Get your free HealthCheck today.",[902,906],{"type":18,"text":903,"marks":904,"styleClasses":905},"Ready to bring your Godot game to console? ",[],[],{"type":36,"href":907,"target":38,"external":39,"children":908},"https:\u002F\u002Fgodotporting.com\u002F#contact-form",[909],{"type":18,"text":910,"marks":911,"styleClasses":912},"Get your free HealthCheck today.",[44],[],{"type":23,"text":70,"children":914,"align":-1},[],{"src":71,"alt":70,"title":70,"focalPoint":70,"width":916,"height":917},1600,1204,{"src":71,"alt":70,"title":70,"focalPoint":70,"width":916,"height":917},"Rory Thomson",[921,922,923],"Porting","Console","Godot","none",{"title":926,"description":70,"ogTitle":-1,"ogDescription":-1,"ogImage":-1,"noIndex":75},"Porting Without Pain | PSH",{"meta":928,"head":930},{"title":926,"description":8,"ogTitle":926,"ogDescription":8,"ogImage":71,"ogType":5,"twitterCard":929},"summary_large_image",{"link":931,"meta":935},[932],{"rel":933,"href":934},"canonical","https:\u002F\u002Fpocketsizedhands.com\u002Fblog\u002Fporting-without-pain",[],{"items":937},[],"\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">extends\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> Node\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">class_name\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> GameManager\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">func\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> _ready\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">->\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">    Steam\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\">unlock_achievement\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"my_achievement\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>","\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">SCRIPT\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\"> ERROR\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\">Parse\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> Error\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\">Could\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\"> not\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> resolve \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">class\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\"> \"Steam\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, because of a parser error.\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    at: \u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\">GDScript\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">::\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\">reload\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> (res:\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">\u002F\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">test.gd:\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">4\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">ERROR\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\">Failed\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> to load script \u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"res:\u002F\u002Ftest.gd\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> with error \u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"Parse error\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">.\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>","\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">[editor_plugins]\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">enabled\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">enabled.steam\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\">PackedStringArray\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"res:\u002F\u002Faddons\u002Fsteam\u002Fplugin.cfg\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>","\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">extends\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> Node\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">class_name\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> PlatformBase\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">func\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> get_platform_name\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">->\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> String\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\"> \"Base\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">func\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> unlock_achievement\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(achievement_name : \u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\">String\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">->\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">    print\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"Platform Base Unlock Achievement: \"\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> achievement_name)\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>","\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">extends\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> PlatformBase\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">class_name\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> PlatformSteam\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">func\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> get_platform_name\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">->\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> String\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\"> \"Steam\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">func\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> unlock_achievement\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(achievement_name : \u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\">String\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">->\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">    Steam\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\">unlock_achievement\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(achievement_name)\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>","\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">extends\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> Node\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">class_name\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> PlatformManager\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">enum\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> PlatformType\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> { \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">NULL\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">STEAM\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">static\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\"> var\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> platform_instance : \u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\">PlatformBase\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">static\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\"> func\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> get_platform_type\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">->\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> PlatformType\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> OS\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\">has_feature\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"steam\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">):\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> PlatformType\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">STEAM\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> PlatformType\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">NULL\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">func\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> _enter_tree\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">->\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    var\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> platform_script\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    match\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> get_platform_type\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">():\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">        PlatformType\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">STEAM\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">            platform_script \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> load\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"res:\u002F\u002Fscripts\u002Fplatform\u002Fsteam\u002Fplatform_steam.gd\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">        _\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">            platform_script \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> load\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"res:\u002F\u002Fscripts\u002Fplatform\u002Fplatform_base.gd\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    platform_instance \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> platform_script\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">    add_child\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(platform_instance)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">    print\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"Initialized Platform: \"\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> platform_instance.\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\">get_platform_name\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">())\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">static\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\"> func\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> unlock_achievement\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(achievement_name : \u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\">String\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">->\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\"> not\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> platform_instance:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        pass\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    platform_instance.\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\">unlock_achievement\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(achievement_name)\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>","\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">extends\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> Node\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">class_name\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> GameManager\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">func\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> _ready\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">->\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">    PlatformManager\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\">unlock_achievement\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"my_achievement\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>","\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Steam build\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">Initialized\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> Platform\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\">Steam\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">Unlocking\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> Steam\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> Achievement\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">: my_achievement\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Non-Steam build\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">Initialized\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> Platform\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\">Base\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">Platform\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> Base\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> Unlock\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> Achievement\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">: my_achievement\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>",1789054763936]