The Godot Barn
Sign in
Sign in
Home
News & updates
Explore
Articles
Snippets
Shaders
Themes
Submit content
Sign in to submit content
Give or get help
Tutorials
Questions
Conversations
Coming soon!
GodotSteam Tutorials - Workshop
0
Description
"A hot topic that comes up: Workshop \/ UGC. There are a lot of moving parts but our tutorial is based on [Skillet's UGC Editor](https:\/\/godotsteam.com\/projects\/skillet_ugc_editor.md) which covers creating, editing, and viewing Workshop items. While we don't yet have information on how to use these items, luckily some smart folks have provided information based on their experiences that we can also share!\r\n\r\n??? guide Relevant GodotSteam classes and functions\r\n * [Friends class](https:\/\/godotsteam.com\/classes\/friends)\r\n * [activateGameOverlayToWebPage()](https:\/\/godotsteam.com\/classes\/friends\/#activategameoverlaytowebpage)\r\n * [UGC class](https:\/\/godotsteam.com\/classes\/ugc)\r\n * [createItem()](https:\/\/godotsteam.com\/classes\/ugc#createitem)\r\n * [createQueryUserUGCRequest()](https:\/\/godotsteam.com\/classes\/ugc#createqueryuserugcrequest)\r\n * [getItemInstallInfo()](https:\/\/godotsteam.com\/classes\/ugc#getiteminstallinfo)\r\n * [getSubscribedItems()](https:\/\/godotsteam.com\/classes\/ugc#getsubscribeditems)\r\n * [getQueryUGCResult()](https:\/\/godotsteam.com\/classes\/ugc#getqueryugcresult)\r\n * [releaseQueryUGCRequest()](https:\/\/godotsteam.com\/classes\/ugc#releasequeryugcrequest)\r\n * [sendQueryUGCRequest()](https:\/\/godotsteam.com\/classes\/ugc#sendqueryugcrequest)\r\n * [setItemContent()](https:\/\/godotsteam.com\/classes\/ugc#setitemcontent)\r\n * [setItemDescription()](https:\/\/godotsteam.com\/classes\/ugc#setitemdescription)\r\n * [setItemMetadata()](https:\/\/godotsteam.com\/classes\/ugc#setitemmetadata)\r\n * [setItemPreview()](https:\/\/godotsteam.com\/classes\/ugc#setitempreview)\r\n * [setItemTags()](https:\/\/godotsteam.com\/classes\/ugc#setitemtags)\r\n * [setItemTitle()](https:\/\/godotsteam.com\/classes\/ugc#setitemtitle)\r\n * [setItemUpdateLanguage()](https:\/\/godotsteam.com\/classes\/ugc#setitemupdatelanguage)\r\n * [setItemVisibility()](https:\/\/godotsteam.com\/classes\/ugc#setitemvisibility)\r\n * [setReturnOnlyIDs()](https:\/\/godotsteam.com\/classes\/ugc#setreturnonlyids)\r\n * [startItemUpdate()](https:\/\/godotsteam.com\/classes\/ugc#startitemupdate)\r\n * [submitItemUpdate()](https:\/\/godotsteam.com\/classes\/ugc#submititemupdate)\r\n * [User class](https:\/\/godotsteam.com\/classes\/user)\r\n * [getSteamID()](https:\/\/godotsteam.com\/classes\/user#getsteamid)\r\n???\r\n\r\n!!! warning Notes\r\nYou may want to [double-check our Initialization tutorial](https:\/\/godotsteam.com\/tutorials\/initializing) to set up initialization and callbacks functionality if you haven't done so already.\r\n!!!\r\n\r\n## Preparations{.block}\r\n\r\nBefore anything else, you'll want to read [Valve's write-up on Workshop \/ UGC which will cover a lot of steps that aren't covered in this tutorial.](https:\/\/partner.steamgames.com\/doc\/features\/workshop) Once you get through that, you should also read through [Valve's write-up on the implementation of Workshop \/ UGC so you'll be ready to continue on.](https:\/\/partner.steamgames.com\/doc\/features\/workshop\/implementation)\r\n\r\nAs with our other tutorials, we will add some variables to our **steamworks.gd** global script for later use:\r\n\r\n```gdscript\r\nvar is_new_item: bool = false\r\nvar published_file_id: int = 0\r\nvar query_handle: int = 0\r\nvar update_handle: int = 0\r\nvar workshop_app_id: int = 0\r\n```\r\n\r\nWe are popping these in our global script because we will need them across a few different scenes. However, depending on how you are implementing Workshop, this may not apply to your layout. A bit about our variables here:\r\n\r\n- **is_new_item** - Since our editing scene is used for newly created items and editing already existing ones, this will let us know if we have to query for details or not. We will cover this more in [Newly Created vs Editing Items.](#newly-created-vs-editing-items)\r\n- **published_file_id** - The current item we are interacting with.\r\n- **query_handle** - Used when querying items.\r\n- **update_handle** - The handle for our item updates, whether it is a new or existing item.\r\n- **workshop_app_id** - This may differ from your game's app ID if you are creating a separate Workshop tool.\r\n\r\n## Creating Items{.block}\r\n\r\nWhen creating new items, we will be defaulting to the **Community** file type (shown below as Ready-To-Use) but there are [quite a few other options you can use listed here.](https:\/\/godotsteam.com\/classes\/remote_storage\/#workshopfiletype) This particular file type is defined as \"Normal Workshop item that can be subscribed to.\" While it is not shown here, our drop-down actually has all the available options.\r\n\r\n\r\n\r\nFor our item creation scene, we will hook up the only signal we need, **item_created**, and a button that triggers the **createItem** Steam function like so:\r\n\r\n```gdscript\r\nvar file_type: int = Steam.WORKSHOP_FILE_TYPE_COMMUNITY\r\n\r\n\r\nfunc _ready() -> void:\r\n Steam.item_created.connect(_on_item_created)\r\n\r\n\r\nfunc _on_create_item_pressed() -> void:\r\n print(\"Creating new Workshop item\")\r\n Steam.createItem(Steamworks.workshop_app_id, file_type)\r\n```\r\n\r\nAgain, the **workshop_app_id** will only differ from the game's app ID _if_ you are using a separate app. Our callback _should_ pretty much always succeed but, if not, [there can be a bunch of reasons why.](https:\/\/godotsteam.com\/classes\/ugc\/#item_created) Upon success, if the user has not accepted the Workshop TOS, we'll have overlay direct them there.\r\n\r\n```gdscript\r\nfunc _on_item_created(result: int, new_file_id: int, need_to_accept_tos: bool) -> void:\r\n if result == Steam.RESULT_OK:\r\n # We don't want to block the process but still direct the player to accept the TOS\r\n if need_to_accept_tos:\r\n print(\"User needs to accept Workshop TOS\")\r\n Steam.activateGameOverlayToWebPage(\"steam:\/\/url\/CommunityFilePage\/%s\" % new_file_id)\r\n\r\n Steamworks.published_file_id = new_file_id\r\n\r\n # We will automatically subscribe to this item so it shows up in the View Items section\r\n Steam.subscribeItem(Steamworks.published_file_id)\r\n\r\n print(\"Workshop item %s created successfully.\" % Steamworks.published_file_id)\r\n else:\r\n print(\"Could not create a new Workshop item! %s\" % result)\r\n```\r\n\r\nWe'll store the **published_file_id** so we can actually update and edit the item we just created, since our editor does not directly open the editing form upon success. However, you could make yours directly switch to the editing scene for ease; which is on our to-do list for Skillet's UGC editor.\r\n\r\nYou will also notice we subscribe the user to the item they just created as this is not done automatically. On to editing our item!\r\n\r\n## Editing Items{.block}\r\n\r\nThe real meat of our editor is, well, editing items. This is where you'll probably run into the most issues as parts of this can be real picky.\r\n\r\n\r\n\r\nFirst we'll set up the buttons and signals we need for our editing scene:\r\n\r\n```gdscript\r\n@onready var button_delete: Button = %DeleteButton\r\n@onready var button_update: Button = %UpdateButton\r\n\r\n\r\nfunc _ready() -> void:\r\n button_delete.pressed.connect(_on_delete_pressed)\r\n button_update.pressed.connect(_on_update_pressed)\r\n\r\n Steam.item_updated.connect(_on_item_updated)\r\n Steam.item_deleted.connect(_on_item_deleted)\r\n\r\n\r\nfunc _on_delete_pressed() -> void:\r\n print(\"Deleting Workshop item %s\" % Steamworks.published_file_id)\r\n Steam.deleteItem(Steamworks.published_file_id)\r\n\r\n\r\nfunc _on_item_deleted(this_result: int, this_file_id:int) -> void:\r\n if this_file_id != Steamworks.published_file_id:\r\n print(\"Trying to delete the wrong file ID: %s\" % this_file_id)\r\n return\r\n\r\n if this_result != Steam.RESULT_OK:\r\n print(\"Failed to delete item %s: %s\" % [Steamworks.published_file_id, this_result])\r\n return\r\n\r\n print(\"Successfully deleted item %s\" % Steamworks.published_file_id)\r\n # Show either a modal or move back to the item viewing scene\r\n\r\n\r\nfunc _on_item_updated(result: Steam.Result, needs_to_accept_tos: bool, file_id: int) -> void:\r\n if Steamworks.published_file_id != file_id:\r\n print(\"Got wrong file ID back that does not match %s: %s\" % [Steamworks.published_file_id, file_id])\r\n return\r\n\r\n if needs_to_accept_tos:\r\n print(\"User needs to accept Workshop TOS\")\r\n Steam.activateGameOverlayToWebPage(\"steam:\/\/url\/CommunityFilePage\/%s\" % Steamworks.published_file_id)\r\n \r\n if result == Steam.RESULT_OK:\r\n print(\"Workshop item %s updated successfully.\" % Steamworks.published_file_id)\r\n # Show some confirmation it updated\r\n else:\r\n print(\"Could not update the Workshop item! %s\" % get_result_type(result))\r\n # Show some confirmation it failed\r\n\r\n\r\nfunc _on_update_pressed() -> void:\r\n print(\"Submitting item update to Steam\")\r\n\r\n set_item_title()\r\n set_item_content()\r\n set_item_description()\r\n set_item_metadata()\r\n set_item_preview()\r\n set_item_tags()\r\n set_item_update_language()\r\n set_item_visibility()\r\n\r\n Steam.submitItemUpdate(Steamworks.update_handle, set_item_comment())\r\n```\r\n\r\nOur item deleting functions are pretty straight-forward but our updating item function calls each of our form's fields individually and sets them before submitting the update itself to Steam.\r\n\r\n### Item Details\r\n\r\nLet's take a look at all these different details to update. Most of these will be using LineEdit or TextEdit nodes.\r\n\r\n```gdscript\r\n@onready var item_comment_lineedit: LineEdit = %ItemComment\r\n@onready var item_content_lineedit: LineEdit = %ItemContent\r\n@onready var item_description: TextEdit = %ItemDescription\r\n@onready var item_metadata_lineedit: LineEdit = %ItemMetadata\r\n@onready var item_preview_lineedit: LineEdit = %ItemPreview\r\n@onready var item_tags_lineedit: LineEdit = %ItemTags\r\n@onready var item_title_lineedit: LineEdit = %ItemTitle\r\n@onready var item_update_language_lineedit: LineEdit = %ItemUpdateLanguage\r\n@onready var item_visibility_dropdown: OptionButton = %ItemVisibility\r\n\r\n\r\nfunc set_item_comment() -> String:\r\n return item_comment_lineedit.text\r\n\r\n\r\nfunc set_item_content() -> void:\r\n if not item_content_lineedit.text.is_empty():\r\n if not Steam.setItemContent(Steamworks.update_handle, item_content_lineedit.text):\r\n print(\"Failed to set item content location\")\r\n\r\n\r\nfunc set_item_description() -> void:\r\n if not item_description.text.is_empty():\r\n if not Steam.setItemDescription(Steamworks.update_handle, item_description.text):\r\n print(\"Failed to set item description\")\r\n\r\n\r\nfunc set_item_metadata() -> void:\r\n if not item_metadata_lineedit.text.is_empty():\r\n if not Steam.setItemMetadata(Steamworks.update_handle, item_metadata_lineedit.text):\r\n print(\"Failed to set item metadata\")\r\n\r\n\r\nfunc set_item_preview() -> void:\r\n if not item_preview_lineedit.text.is_empty():\r\n if not Steam.setItemPreview(Steamworks.update_handle, item_preview_lineedit.text):\r\n print(\"Failed to set\")\r\n\r\n\r\n# Tag lists should be comma separated for this to work correctly\r\nfunc set_item_tags() -> void:\r\n if not item_tags_lineedit.text.is_empty():\r\n var these_tags: Array = item_tags_lineedit.text.split(\",\")\r\n print(\"Setting %s item tags: %s\" % [these_tags.size(), these_tags])\r\n if not Steam.setItemTags(Steamworks.update_handle, these_tags, true):\r\n print(\"Failed to set item tags\")\r\n\r\n\r\nfunc set_item_title() -> void:\r\n if not item_title_lineedit.text.is_empty():\r\n if not Steam.setItemTitle(Steamworks.update_handle, item_title_lineedit.text):\r\n print(\"Failed to set item title\")\r\n\r\n\r\nfunc set_item_update_language() -> void:\r\n if not item_update_language_lineedit.text.is_empty():\r\n if not Steam.setItemUpdateLanguage(Steamworks.update_handle, item_update_language_lineedit.text):\r\n print(\"Failed to set item update language\")\r\n\r\n\r\nfunc set_item_visibility() -> void:\r\n if item_visibility_dropdown.selected > -1:\r\n if not Steam.setItemVisibility(Steamworks.update_handle, item_visibility_dropdown.selected):\r\n print(\"Failed to set item visibility\")\r\n```\r\n\r\nMost don't really need much explanation but we do want to go into detail about the [item content](https:\/\/godotsteam.com\/classes\/ugc#setitemcontent) and [item preview](https:\/\/godotsteam.com\/classes\/ugc#setitempreview) fields. These will often be your pain points because they must be **absolute paths** and the item preview image **must** be under 1 MB in size. You may also run into permission errors and, if using containerized Steam or Godot versions in Flatpak or Snap, confusing responses from Steam about being unable to find your files.\r\n\r\nWhen everything is filled out the way you want it, hit the update button to trigger the **submitItemUpdate** function call. Upon success, your Workshop item should now be ready to use by the community!\r\n\r\nBefore we move on to look at browsing \/ viewing items, let's talk a little bit about the different between freshly created items and editing already existing ones.\r\n\r\n### Newly Created vs Editing Items\r\n\r\nNewly created items from our last step will, obviously, have no information to populate for our form but we will have to get that information for our already edited items. So we'll make some additions to our editing scene script.\r\n\r\nOur **reset_form** function will clear the form, just in case, and starts the update process by calling **startItemUpdate**. If this is a newly created item with our **is_new_item** set to true, we'll ignore more data gathering. However, if this is an already edited item, we'll pull details using **createQueryUGCDetailsRequest** to get a new query handle, store it, then make the request with **sendQueryUGCRequest**.\r\n\r\n```gdscript\r\nfunc _ready() -> void:\r\n Steam.ugc_query_completed.connect(_on_ugc_query_completed)\r\n reset_form()\r\n\r\n\r\nfunc reset_form() -> void:\r\n print(\"Resetting editing form\")\r\n Steamworks.update_handle = Steam.startItemUpdate(Steamworks.workshop_app_id, Steamworks.published_file_id)\r\n\r\n title_label.text = \"Editing %s: %s\" % [Steamworks.published_file_id, item_title_lineedit.text]\r\n item_title_lineedit.text = \"\"\r\n item_description.text = \"\"\r\n item_visibility_dropdown.selected = 0\r\n item_tags_lineedit.text = \"\"\r\n item_content_lineedit.text = \"\"\r\n item_preview_lineedit.text = \"\"\r\n \r\n if not Steamworks.is_new_item:\r\n print(\"%s is not new so requesting UGC details\" % Steamworks.published_file_id)\r\n Steamworks.query_handle = Steam.createQueryUGCDetailsRequest([Steamworks.published_file_id])\r\n\r\n if not Steam.setReturnMetadata(Steamworks.query_handle, true):\r\n print(\"Failed to set return only metadata\")\r\n Steam.sendQueryUGCRequest(Steamworks.query_handle)\r\n\r\n Steamworks.is_new_item = false\r\n```\r\n\r\nOur **sendQueryUGCRequest** call will trigger a **ugc_query_completed** callback and fills in all the details:\r\n\r\n```gdscript\r\nfunc _on_ugc_query_completed(query_handle: int, result: int, _results_returned: int, _total_matching: int, _cached: bool, _next_cursor: String) -> void:\r\n if result == Steam.RESULT_OK:\r\n # This must be called if you set setReturnMetadata to true\r\n var this_item_metadata: String = Steam.getQueryUGCMetadata(query_handle, 0)\r\n var this_item_details: Dictionary = Steam.getQueryUGCResult(query_handle, 0)\r\n print(\"Updating data for published item %s\" % this_item_details['file_id'])\r\n\r\n # Oddly, there is no way to get the language set when creating the item\r\n title_label.text = \"Editing %s: %s\" % [this_item_details['file_id'], this_item_details['title']]\r\n item_title_lineedit.text = this_item_details['title']\r\n item_description.text = this_item_details['description']\r\n item_visibility_dropdown.selected = this_item_details['visibility']\r\n item_tags_lineedit.text = this_item_details['tags']\r\n item_metadata_lineedit.text = this_item_metadata\r\n else:\r\n print(\"The query failed, result: %s\" % result)\r\n\r\n Steam.releaseQueryUGCRequest(Steamworks.query_handle)\r\n Steamworks.query_handle = 0 \r\n```\r\n\r\nAfter that, we'll want to release the query with **releaseQueryUGCRequest**. Now let's check out displaying Workshop items.\r\n\r\n## Viewing Items{.block}\r\n\r\nFor our tutorial we are only dealing with items that the local user has created but we'll talk about other types of item queries a bit later. For now, let's set up some variables for our viewing scene:\r\n\r\n```gdscript\r\nconst UGCItemObject: Object = preload(\"res:\/\/src\/entities\/ugc_item.tscn\")\r\n\r\n@onready var button_refresh: Button = %Refresh\r\n@onready var title_label: Label = %Title\r\n\r\n\r\nfunc _ready() -> void:\r\n button_refresh.pressed.connect(_on_refresh_pressed)\r\n Steam.ugc_query_completed.connect(_on_ugc_query_completed)\r\n```\r\n\r\n\r\n\r\nWhen our refresh button is pressed, we make a call to **createQueryUserUGCRequest** with the enum for published items - **USER_UGC_LIST_PUBLISHED**. However, if you want to browser all items or ones the user is subscribed to you can change the [second argument to one of these enums.](https:\/\/godotsteam.com\/classes\/ugc\/#userugclist)\r\n\r\n```gdscript\r\nfunc _on_refresh_items_pressed() -> void:\r\n update_subscribed_items()\r\n\r\n\r\nfunc update_subscribed_items(this_page: int = 1) -> void:\r\n print(\"Updating player's subscribed items for page: %s\" % this_page)\r\n # Clear our grid or list of previous items\r\n\r\n Steamworks.query_handle = Steam.createQueryUserUGCRequest(Steamworks.steam_id, Steam.USER_UGC_LIST_PUBLISHED, Steam.UGC_MATCHING_UGC_TYPE_ALL, Steam.USER_UGC_LIST_SORT_ORDER_CREATION_ORDER_DESC, Steamworks.app_id, Steamworks.workshop_app_id, this_page)\r\n Steam.sendQueryUGCRequest(Steamworks.query_handle)\r\n```\r\n\r\nAs mentioned before, our app ID and Workshop app ID will be the same if you are accessing things from your game but they should be different if doing so from a separate editor.\r\n\r\nThe **update_subscribed_items** function will call **sendQueryUGCRequest** and should trigger our **\\_on_ugc_query_completed** callback.\r\n\r\n```gdscript\r\nfunc _on_ugc_query_completed(query_handle: int, result: int, results_returned: int, total_matching: int, _cached: bool, _next_cursor: String) -> void:\r\n if query_handle == Steamworks.query_handle:\r\n if result == Steam.RESULT_OK:\r\n title_label.text = \"Published Items (%s)\" % total_matching\r\n\r\n for this_item in range(0, results_returned):\r\n var this_item_details: Dictionary = Steam.getQueryUGCResult(Steamworks.query_handle, this_item)\r\n var this_item_preview: String = Steam.getQueryUGCPreviewURL(Steamworks.query_handle, this_item)\r\n\r\n if this_item_details.result != Steam.RESULT_OK:\r\n print(\"Failed to retrieve UGC details: %s\" % this_item_details.result)\r\n\r\n var this_ugc_item: Object = UGCItemObject.instantiate()\r\n item_grid.add_child(this_ugc_item)\r\n this_ugc_item.pressed.connect(_on_ugc_item_pressed.bind(this_item_details['file_id']))\r\n this_ugc_item.setup_button(this_item_preview, this_item_details['title'])\r\n else:\r\n print(\"The query failed, result: %s\" % result)\r\n\r\n Steam.releaseQueryUGCRequest(Steamworks.query_handle)\r\n Steamworks.query_handle = 0\r\n```\r\n\r\nUpon success, we'll loop through the queried items and pull some details like the preview image, file ID, and title. We will instance a child scene to add this information.\r\n\r\n### Preview Items Scene\r\n\r\nOur child scene will be a basic Button node with the following children:\r\n\r\n- HTTPRequest\r\n- TextureRect (our item preview image)\r\n- Label (our item name)\r\n\r\nWe will then attach the following bits of code to it:\r\n\r\n```gdscript\r\n@onready var http_request_node: HTTPRequest = %HTTPRequest\r\n@onready var image_texture_rect: TextureRect = %Image\r\n@onready var title_label: Label = %Title\r\n\r\n\r\nfunc _ready() -> void:\r\n http_request_node.request_completed.connect(_on_request_completed)\r\n\r\n\r\nfunc get_image_from_steam_url(item_preview: String) -> void:\r\n print(\"Preview image uRL: %s\" % item_preview)\r\n var request_error: Error = http_request_node.request(item_preview)\r\n if request_error != OK:\r\n print(\"Failed sending preview image request: %s\" % request_error)\r\n\r\n\r\nfunc _on_request_completed(result: int, _response_code: int, _headers: PackedStringArray, image_buffer: PackedByteArray):\r\n if result != HTTPRequest.RESULT_SUCCESS:\r\n print(\"Failed downloading preview image from Steam: %s\" % result)\r\n return\r\n var preview_image: Image = Image.new()\r\n var load_error: Error = preview_image.load_png_from_buffer(image_buffer)\r\n if load_error != OK:\r\n print(\"Failed loading preview image: %s\" % load_error)\r\n return\r\n image_texture_rect.texture = ImageTexture.create_from_image(preview_image)\r\n\r\n\r\nfunc setup_button(this_item_preview: String, this_title: String) -> void:\r\n if not this_item_preview.is_empty():\r\n get_image_from_steam_url(this_item_preview)\r\n title_label.text = \"Unknown\" if this_title.is_empty() else this_title\r\n```\r\n\r\nLastly, when you press one of these item buttons, it will trigger our **\\_on_ugc_item_pressed** function which will set this item as our global **published_file_id** and [swap our scene back to the editing one](#editing-items):\r\n\r\n```gdscript\r\nfunc _on_ugc_item_pressed(this_file_id: int) -> void:\r\n print(\"Opening item %s\" % this_file_id)\r\n Steamworks.published_file_id = this_file_id\r\n # Swap scenes back to our editing scene\r\n```\r\n\r\nAnd that's what we have so far for our Workshop \/ UGC tutorial. At some point we'll add some examples for using your Workshop items in-game. For now, you can check our some additional information on how to set up Workshop.\r\n\r\n## Additional Implementations{.block}\r\n\r\n### Uploading \/ Downloading In Workshop \/ UGC\r\n\r\n[**KarpPaul of Forgotten Dream Games** has given us this pretty great tutorial on uploading and downloading items in Workshop \/ UGC.](https:\/\/forgottendreamgames.com\/blog\/godotsteam-how-to-upload-and-download-user-generated-content-ugc-repost.html) Since he has written everything, with code examples, there isn't any reason to reiterate it here when you can just click the link and read it all yourself.\r\n\r\n### Using Items In Workshop \/ UGC\r\n\r\n**Lyaaaaaaaaaaaaaaa** submitted some code showing how they use items in Workshop \/ UGC:\r\n\r\n```gdscript\r\nextends Node\r\n\r\nclass_name Steam_Workshop\r\n\r\nsignal query_request_success\r\n\r\nvar published_items : Array\r\nvar steam = SteamAutoload.steam # I don't use the `Steam` directly to avoid scripts errors in non-steam builds.\r\n\r\nvar _app_id : int = ProjectSettings.get_setting(\"global\/steam_app_id\")\r\nvar _query_handler : int\r\nvar _page_number : int = 1\r\nvar _subscribed_items : Dictionary\r\n\r\nfunc _init() -> void:\r\n steam.connect(\"ugc_query_completed\", self, \"_on_query_completed\")\r\n\r\n for item in steam.getSubscribedItems():\r\n var info : Dictionary\r\n info = get_item_install_info(item)\r\n if info[\"ret\"] == true:\r\n _subscribed_items[item] = info\r\n\r\n\r\nstatic func open_tos() -> void:\r\n var steam = SteamAutoload.steam\r\n var tos_url = \"https:\/\/steamcommunity.com\/sharedfiles\/workshoplegalagreement\"\r\n steam.activateGameOverlayToWebPage(tos_url)\r\n\r\n\r\nfunc get_item_install_info(p_item_id : int) -> Dictionary:\r\n var info : Dictionary\r\n info = steam.getItemInstallInfo(p_item_id)\r\n\r\n if info[\"ret\"] == false:\r\n var warning = \"Item \" + String(p_item_id) + \" isn't installed or has no content\"\r\n # Here your code to log\/display errors\r\n\r\n return info\r\n\r\n\r\nfunc get_published_items(p_page : int = 1, p_only_ids : bool = false) -> void:\r\n var user_id : int = steam.getSteamID()\r\n var list : int = steam.USER_UGC_LIST_PUBLISHED\r\n var type : int = steam.WORKSHOP_FILE_TYPE_COMMUNITY\r\n var sort : int = steam.USER_UGC_LIST_SORT_ORDER_CREATION_ORDER_DESC\r\n\r\n _query_handler = steam.createQueryUserUGCRequest(user_id,\r\n list,\r\n type,\r\n sort,\r\n _app_id,\r\n _app_id,\r\n p_page)\r\n steam.setReturnOnlyIDs(_query_handler, p_only_ids)\r\n steam.sendQueryUGCRequest(_query_handler)\r\n\r\n\r\nfunc get_item_folder(p_item_id : int) -> String:\r\n return _subscribed_items[p_item_id][\"folder\"]\r\n\r\n\r\nfunc fetch_query_result(p_number_results : int) -> void:\r\n var result : Dictionary\r\n for i in range(p_number_results):\r\n result = steam.getQueryUGCResult(_query_handler, i)\r\n published_items.append(result)\r\n\r\n steam.releaseQueryUGCRequest(_query_handler)\r\n\r\n\r\nfunc _on_query_completed(p_query_handler : int,\r\n p_result : int,\r\n p_results_returned : int,\r\n p_total_matching : int,\r\n p_cached : bool) -> void:\r\n\r\n if p_result == steam.RESULT_OK:\r\n fetch_query_result(p_results_returned)\r\n else:\r\n var warning = \"Couldn't get published items. Error: \" + String(p_result)\r\n # Here your code to log\/display errors\r\n\r\n if p_result == 50:\r\n _page_number ++ 1\r\n get_published_items(_page_number)\r\n\r\n elif p_result < 50:\r\n emit_signal(\"query_request_success\",\r\n p_results_returned,\r\n _page_number)\r\n```\r\n\r\n### Workshop Items\r\n\r\nFollowing along with Lyaaaaaaaaaaaaaaa's example, here is a class for your Workshop \/ UGC items.\r\n\r\n```gdscript\r\nextends Node\r\n\r\nclass_name UGC_Item\r\n\r\nsignal item_created\r\nsignal item_updated\r\nsignal item_creation_failed\r\nsignal item_update_failed\r\n\r\nvar steam = SteamAutoload.steam # I don't use the `Steam` directly to avoid scripts errors in non-steam builds.\r\n\r\nvar _app_id : int = ProjectSettings.get_setting(\"global\/steam_app_id\")\r\nvar _item_id : int\r\nvar _update_handler\r\n\r\nfunc _init(p_item_id : int = 0,\r\n p_file_type : int = steam.WORKSHOP_FILE_TYPE_COMMUNITY) -> void:\r\n\r\n steam.connect(\"item_created\", self, \"_on_item_created\")\r\n steam.connect(\"item_updated\", self, \"_on_item_updated\")\r\n\r\n if p_item_id == 0:\r\n steam.createItem(_app_id, p_file_type)\r\n else:\r\n _item_id = p_item_id\r\n start_update(p_item_id)\r\n\r\n\r\nfunc start_update(p_item_id : int) -> void:\r\n _update_handler = steam.startItemUpdate(_app_id, p_item_id)\r\n\r\n\r\nfunc update(p_update_description : String = \"Initial commit\") -> void:\r\n steam.submitItemUpdate(_update_handler, p_update_description)\r\n\r\n\r\nfunc set_title(p_title : String) -> void:\r\n if steam.setItemTitle(_update_handler, p_title) == false:\r\n # Here your code to log\/display errors\r\n\r\n\r\nfunc set_description(p_description : String = \"\") -> void:\r\n if steam.setItemDescription(_update_handler, p_description) == false:\r\n # Here your code to log\/display errors\r\n\r\n\r\nfunc set_update_language(p_language : String) -> void:\r\n if steam.setItemUpdateLanguage(_update_handler, p_language) == false:\r\n # Here your code to log\/display errors\r\n\r\n\r\nfunc set_visibility(p_visibility : int = 2) -> void:\r\n if steam.setItemVisibility(_update_handler, p_visibility) == false:\r\n # Here your code to log\/display errors\r\n\r\n\r\nfunc set_tags(p_tags : Array = []) -> void:\r\n if steam.setItemTags(_update_handler, p_tags) == false:\r\n # Here your code to log\/display errors\r\n\r\n\r\nfunc set_content(p_content : String) -> void:\r\n if steam.setItemContent(_update_handler, p_content) == false:\r\n # Here your code to log\/display errors\r\n\r\n\r\nfunc set_preview(p_image_preview : String = \"\") -> void:\r\n if steam.setItemPreview(_update_handler, p_image_preview) == false:\r\n # Here your code to log\/display errors\r\n\r\n\r\nfunc set_metadata(p_metadata : String = \"\") -> void:\r\n if steam.setItemMetadata(_update_handler, p_metadata) == false:\r\n # Here your code to log\/display errors\r\n\r\n\r\nfunc get_id() -> int:\r\n return _item_id\r\n\r\n\r\nfunc _on_item_created(p_result : int, p_file_id : int, p_accept_tos : bool) -> void:\r\n if p_result == steam.RESULT_OK:\r\n _item_id = p_file_id\r\n # Here your code to log\/display success\r\n emit_signal(\"item_created\", p_file_id)\r\n else:\r\n var error = \"Failed creating workshop item. Error: \" + String(p_result)\r\n # Here your code to log\/display errors\r\n emit_signal(\"item_creation_failed\", error)\r\n\r\n if p_accept_tos:\r\n Steam_Workshop.open_tos()\r\n\r\n\r\nfunc _on_item_updated(p_result : int, p_accept_tos : bool) -> void:\r\n if p_result == steam.RESULT_OK:\r\n var item_url = \"steam:\/\/url\/CommunityFilePage\/\" + String(_item_id)\r\n # Here your code to log\/display success\r\n steam.activateGameOverlayToWebPage(item_url)\r\n emit_signal(\"item_updated\")\r\n else:\r\n var error = \"Failed updated workshop item. Error: \" + String(p_result)\r\n # Here your code to log\/display errors\r\n emit_signal(\"item_update_failed\", error)\r\n\r\n if p_accept_tos:\r\n Steam_Workshop.open_tos()\r\n```\r\n\r\n## Weird Issues{.block}\r\n\r\n***KarpPaul*** had some information about getting \"access is denied\" from **getQueryUGCResult**:\r\n\r\n> Regarding the issue that I had with steamworks (Steam.getQueryUGCResult returns \"access is denied\" when the workshop is set visible to developers and customers). I talked to steam support and they were not able to reproduce the error. I checked the ipc log and indeed everything looks normal - no access denied results in steam logs. However, the error is still in the game. No idea why this happens and how, maybe I am doing smth wrong. I wonder if anybody else will encounter this.... anyway, it is not critical, so I just make my workshop visible to everyone and decide to ignore the issue for now.\r\n\r\n## Additional Resources{.block}\r\n\r\n### Example Project\r\n\r\n[You can see this tutorial in action with more in-depth information by checking out our Skillet Workshop \/ UGC Editor on Codeberg.](https:\/\/codeberg.org\/godotsteam\/skillet\/src\/branch\/ugc_editor) There you will be able to view of the code used which can serve as a starting point for you to branch out from."
Comments
Log in to post a comment
Licensed under the CC-BY license
See the full license details
Submitted by
Gramps
Open on Github
Code for this tutorial is also available on Github
Table of contents
Compatibility
Works in Godot
4.x
Tags
GodotSteam
SteamĀ®
steamworks
Share this tutorial
Share on Bluesky
Share on X / Twitter
or share this direct link:
https://thegodotbarn.com/contributions/tutorial/690/godotsteam-tutorials-workshop
Please wait ...
Okay
Okay
No
Yes
Okay