Use when working with 2D-specific systems — TileMaps, parallax scrolling, 2D lights and shadows, canvas layers, particles 2D, custom drawing, and 2D meshes in Godot 4.3+
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when calling web APIs or backends from Godot — HTTPRequest, REST/JSON clients, auth headers, downloads, TLS, WebSocketPeer clients, retries, and keeping secrets out of the build
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
실행 점수 미검증
설치 방법 보기설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
Talk to REST APIs, leaderboards, analytics, login services, and other HTTP/WebSocket backends. All examples target Godot 4.3+ with no deprecated APIs. GDScript is shown first, then C#.
Related skills: multiplayer-basics for real-time game networking with WebSocketMultiplayerPeer, save-load for persisting tokens and cached responses, web-export for CORS and browser limits, mobile-development for Android export permissions, multithreading for heavy response parsing off the main thread.
| Need | Use | Why |
|---|---|---|
| Ordinary REST call, file or image download | HTTPRequest node | Handles connect, redirects, gzip, timeout; emits one signal when done |
| Streaming a response, custom keep-alive, many requests to one host | HTTPClient | Low-level; you call poll() and drive the state machine yourself |
| Persistent two-way channel (chat, live events, lobby service) | WebSocketPeer | Client socket; call poll() every frame |
| Real-time gameplay sync with RPCs | WebSocketMultiplayerPeer | Plugs into MultiplayerAPI — see multiplayer-basics |
One HTTPRequest node runs one request at a time. A second request() while one is in flight returns ERR_BUSY. Create one node per concurrent request (Section 3) rather than queueing on a shared node.
awaitrequest() only starts the request; its return value says whether it could be sent, not whether the server answered. The result arrives on request_completed(result, response_code, headers, body).
func fetch_motd() -> void:
var http := HTTPRequest.new()
http.timeout = 10.0 # Default 0.0 means wait forever.
add_child(http)
var err := http.request("https://api.example.com/v1/motd")
if err != OK:
push_error("Could not start request: %s" % error_string(err))
http.queue_free()
return
var res: Array = await http.request_completed
http.queue_free()
var result: int = res[0]
var code: int = res[1]
var body: PackedByteArray = res[3]
if result != HTTPRequest.RESULT_SUCCESS:
push_warning("Network failure (HTTPRequest.Result %d)" % result)
elif code != 200:
push_warning("Server answered HTTP %d" % code)
else:
print(body.get_string_from_utf8())
public async void FetchMotd()
{
var http = new HttpRequest { Timeout = 10.0 }; // Default 0.0 means wait forever.
AddChild(http);
Error err = http.Request("https://api.example.com/v1/motd");
if (err != Error.Ok)
{
GD.PushError($"Could not start request: {err}");
http.QueueFree();
return;
}
Variant[] res = await ToSignal(http, HttpRequest.SignalName.RequestCompleted);
http.QueueFree();
long result = res[0].AsInt64();
long code = res[1].AsInt64();
byte[] body = res[3].AsByteArray();
if (result != (long)HttpRequest.Result.Success)
GD.PushWarning($"Network failure (HttpRequest.Result {result})");
else if (code != 200)
GD.PushWarning($"Server answered HTTP {code}");
else
GD.Print(body.GetStringFromUtf8());
}
Check both values: result is the transport outcome (DNS, TLS, timeout — RESULT_CANT_RESOLVE, RESULT_TLS_HANDSHAKE_ERROR, RESULT_TIMEOUT, …), response_code is the HTTP status. A 404 or 500 arrives with RESULT_SUCCESS.
Wrap the boilerplate once. Register this as an autoload named Api; every call spawns its own HTTPRequest, so calls can overlap.
# api.gd — autoload "Api"
extends Node
const BASE_URL := "https://api.example.com/v1"
const TIMEOUT_SEC := 10.0
var auth_token: String = ""
## Returns {ok: bool, code: int, data: Variant, error: String}.
func request_json(path: String, method: HTTPClient.Method = HTTPClient.METHOD_GET, payload: Variant = null) -> Dictionary:
var http := HTTPRequest.new()
http.timeout = TIMEOUT_SEC
add_child(http)
var headers := PackedStringArray(["Accept: application/json"])
var body := ""
if payload != null:
headers.append("Content-Type: application/json")
body = JSON.stringify(payload)
if not auth_token.is_empty():
headers.append("Authorization: Bearer " + auth_token)
var err := http.request(BASE_URL + path, headers, method, body)
if err != OK:
http.queue_free()
return {"ok": false, "code": 0, "data": null, "error": "request() failed: " + error_string(err)}
var res: Array = await http.request_completed
http.queue_free()
if res[0] != HTTPRequest.RESULT_SUCCESS:
return {"ok": false, "code": 0, "data": null, "error": "transport error %d" % res[0]}
var code: int = res[1]
var text := (res[3] as PackedByteArray).get_string_from_utf8()
var data: Variant = JSON.parse_string(text) if not text.is_empty() else null
return {"ok": code >= 200 and code < 300, "code": code, "data": data, "error": "" if code < 400 else text}
// Api.cs — autoload "Api"
using System.Collections.Generic;
using System.Threading.Tasks;
using Godot;
public partial class Api : Node
{
private const string BaseUrl = "https://api.example.com/v1";
private const double TimeoutSec = 10.0;
public string AuthToken { get; set; } = "";
public readonly record struct ApiResult(bool Ok, long Code, Variant Data, string Error);
public async Task<ApiResult> RequestJson(string path, HttpClient.Method method = HttpClient.Method.Get, Variant payload = default)
{
var http = new HttpRequest { Timeout = TimeoutSec };
AddChild(http);
var headers = new List<string> { "Accept: application/json" };
string body = "";
if (payload.VariantType != Variant.Type.Nil)
{
headers.Add("Content-Type: application/json");
body = Json.Stringify(payload);
}
if (AuthToken != "")
headers.Add("Authorization: Bearer " + AuthToken);
Error err = http.Request(BaseUrl + path, headers.ToArray(), method, body);
if (err != Error.Ok)
{
http.QueueFree();
return new ApiResult(false, 0, default, $"Request() failed: {err}");
}
Variant[] res = await ToSignal(http, HttpRequest.SignalName.RequestCompleted);
http.QueueFree();
if (res[0].AsInt64() != (long)HttpRequest.Result.Success)
return new ApiResult(false, 0, default, $"Transport error {res[0]}");
long code = res[1].AsInt64();
string text = res[3].AsByteArray().GetStringFromUtf8();
Variant data = text == "" ? default : Json.ParseString(text);
return new ApiResult(code is >= 200 and < 300, code, data, code < 400 ? "" : text);
}
}
Call it from anywhere:
var res := await Api.request_json("/scores", HTTPClient.METHOD_POST, {"name": "Ada", "score": 1200})
if not res.ok:
show_error(res.error)
var api = GetNode<Api>("/root/Api");
var res = await api.RequestJson("/scores", HttpClient.Method.Post, new Godot.Collections.Dictionary { { "name", "Ada" }, { "score", 1200 } });
if (!res.Ok)
ShowError(res.Error);
Retries with exponential backoff, request cancellation on scene change, and an auth refresh flow: references/rest-client-patterns.md.
float. {"id": 5} gives 5.0. Cast before using it as an index, enum, or dictionary key: int(data["id"]) in GDScript, data["id"].AsInt32() in C#. == compares 5.0 == 5 as true, but match patterns are type-strict, and a dictionary keyed by 5 has no entry for 5.0.JSON.parse_string() returns null on bad input and logs an error. When you need the line and message, use an instance: var json := JSON.new(); if json.parse(text) != OK: print(json.get_error_message(), json.get_error_line()).data is Dictionary and data.has("items") — a server error page in HTML parses to null, and an empty array is still ok.stringify itself writes 5, the parse brings back 5.0), and JSON.stringify(obj) only works on Variant types — convert custom objects or Resources to a Dictionary first.| Task | How |
|---|---|
| Save straight to disk | Set http.download_file = "user://cache/pack.zip" before request(); the body is written there, not returned |
| Progress bar | Poll get_downloaded_bytes() against get_body_size() (-1 when the server sends no length, and always -1 on Web) |
| Cap the size | body_size_limit (bytes) → RESULT_BODY_SIZE_LIMIT_EXCEEDED |
| Binary upload | request_raw(url, headers, HTTPClient.METHOD_PUT, bytes) with a Content-Type header |
| Show a remote image | Image.new().load_png_from_buffer(body) (or _jpg_, _webp_), then ImageTexture.create_from_image(image) |
Image, form-encoded, and multipart recipes with C#: references/downloads-and-uploads.md.
.crt and pass TLSOptions.client(cert) via http.set_tls_options(). TLSOptions.client_unsafe() skips validation — never ship it.project.godot, or an encrypted PCK can be pulled out by a determined player. Keep privileged keys (payment, admin, third-party paid APIs) on a server you control and have the game call that server.user://; treat them as revocable, not secret.RESULT_TLS_HANDSHAKE_ERROR, as does any server limited to TLS 1.1 or older.| Platform | Gotcha |
|---|---|
| Android | Enable the INTERNET permission in the export preset, or every request fails |
| Web | Requests go through the browser: the server must send CORS headers (Access-Control-Allow-Origin), and plain http:// from an https:// page is blocked as mixed content. See web-export |
| Dedicated server | Same API; run requests from the server so keys never reach clients — see dedicated-server |
Use WebSocketPeer for a persistent, server-pushed event stream; use HTTPRequest for request/response. Nothing happens unless you call poll() every frame (_process). Never put long-lived tokens in the URL — URLs end up in logs.
See references/websocket-client.md for the GDScript and C# client,
wss://, auth, heartbeat, and reconnect rules.
| Symptom | Cause | Fix |
|---|---|---|
request() returns ERR_BUSY (44) | Reused one HTTPRequest for overlapping calls | One node per in-flight request |
request() returns ERR_UNCONFIGURED | Node not in the tree yet | add_child() before request() |
| Request hangs forever | timeout left at 0.0 | Set timeout (5–15 s) on API calls; big downloads need a stall check instead (timeout covers the whole transfer) |
| Works in editor, fails on Android | Missing INTERNET permission | Tick it in the export preset |
| Works on desktop, fails on Web | CORS or mixed content | Fix server headers; use HTTPS/WSS |
id is 5.0, not 5 | JSON numbers are floats | Cast with int() |
Body empty, file appears in user:// | download_file was set | Clear it for in-memory requests |
| Request never completes after a scene change | The HTTPRequest was a child of a node that got freed, so the coroutine never resumes | Parent requests to an autoload (Section 3); don't touch freed nodes after the await |
HTTPRequest per concurrent request; each is queue_free()d after completiontimeout set on API calls; large downloads use a stall check or cancel button insteadresult and the HTTP response_code are checkedint where integers are expected; parsed data shape validatedTLSOptions.client_unsafe() not used outside local testingpoll() called every frame; reconnects back off아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
Use when working with 2D-specific systems — TileMaps, parallax scrolling, 2D lights and shadows, canvas layers, particles 2D, custom drawing, and 2D meshes in Godot 4.3+
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when working with 3D-specific systems — materials, lighting, shadows, environment, global illumination, fog, LOD, occlusion culling, and decals in Godot 4.3+
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when building character abilities — Resource-based abilities with cost/cooldown/cast, buffs/debuffs, stat modifiers, gameplay tags, and HUD binding
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when creating Godot editor plugins — EditorPlugin, @tool scripts, custom inspectors, and dock panels
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when implementing AI movement — NavigationAgent2D/3D, steering behaviors, behavior trees, and patrol patterns
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when implementing animations — AnimationPlayer, AnimationTree, blend trees, state machines, sprite animation, and code-driven animation
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.