We shipped SDKs for both Godot and Roblox, along with a working multiplayer sample game, in one week.
For most backend platforms, supporting a new game engine is a 6–12 month project.
Engineers manually implement hundreds of API clients, adapt them to the engine's networking model and runtime, and keep everything aligned as backend services evolve. The challenge compounds when engines use completely different ecosystems: Godot uses GDScript, Roblox uses Luau, Unity uses C#, and Unreal uses C++. Each brings its own runtime model, networking patterns, and tooling.
At AccelByte, we built a different approach.
By combining the capabilities of AccelByte CodeGen with AI-assisted development, we automatically generate the SDK foundation from our backend API specifications and focus engineering effort only on what actually differs between engines. What traditionally takes months now takes days.
Why SDK Development Normally Takes Months
Building a backend SDK is not just wrapping a few API calls. A modern game backend like AccelByte Gaming Services exposes hundreds of endpoints across:
For every engine and language, engineers traditionally implement the full client layer by hand:
That work is not just repetitive. It also has to be redone for every new engine, and it has to stay current as the backend evolves.
When we started looking for ways to speed this up, the pattern was clear: most of the work was not engine-specific at all. It was the same task repeated across different languages, translating backend API specifications into client code.
That insight led us to build AccelByte CodeGen.
What is AccelByte CodeGen and How it Helps
AccelByte CodeGen is an internal system that generates the SDK client layer directly from backend API specifications. Every service in AccelByte Gaming Services is defined using OpenAPI specs. CodeGen reads those specs and produces ready-to-use client code for any target engine or language.
From the moment a new SDK project begins, the entire backend surface area is already available:
Engineers no longer spend time reimplementing hundreds of backend APIs. They focus on what actually varies between engines: networking integration, runtime behavior, and developer-facing utilities.
A Modular Pipeline Built for Any Engine
To make this possible across any engine or language, CodeGen was designed to be fully extensible.
Rather than hardcoding generation for a fixed set of languages, CodeGen is built as a modular pipeline. Each stage — processors, template engines, filters, renderers, and writers — can be extended or replaced independently.
Each engine is defined as a template pack: a set of templates that describe how generated code should look for that platform. The generator reads the API specs, runs them through a chain of processors and over 200 custom Jinja2 filters, and produces client code for the target engine.
This means supporting a new engine does not require rebuilding the SDK or modifying the generator. Engineers simply create a new template pack, point it at the same API specifications, and generate a fully functional SDK with complete backend coverage.
The Render Pipeline
One Spec, Many Engines
SDK Architecture
AccelByte SDKs are structured to separate the engine-specific layer from the generated API layer, allowing CodeGen to handle the API layer while core components are reused across engines.
| Component | Description |
| AccelByte Instance | Main entry point for the game to interact with AccelByte services |
| Core | Engine-native layer handling HTTP, token management, WebSocket, and caching |
| API Client | Generated layer wrapping all AccelByte REST APIs, communicating through Core |
| P2P (Optional) | Separate networking component for peer-to-peer multiplayer. Not needed on platforms like Roblox that manage their own multiplayer infrastructure |
Because the API client layer is generated and the core components are reusable, supporting a new engine primarily requires adapting the engine-specific integration layer rather than rebuilding the SDK from scratch. This architecture also solves several long-standing challenges in SDK development:
Together, these capabilities allow us to support new engines and maintain existing SDKs without the months of manual implementation typically required.
Where AI Fits into the Workflow
CodeGen eliminates the most repetitive part of SDK development. But adapting the generated foundation to a specific engine still requires real engineering work.
Our engineers used AI tools like Claude to accelerate the engine-specific integration:
Rather than spending hours reading documentation or running small experiments, engineers could test ideas quickly and move on. CodeGen handled API generation; AI accelerated everything around it.
Here are some example prompts and code snippets from our Godot and Roblox SDK development:
You are helping me extend our AccelByte CodeGen system to support a new game engine by creating a reusable SDK template pack and a thin engine-specific runtime layer. Context (architecture and tooling): - The project is a Jinja2-based code generator described in `CLAUDE.md` at the repo root, with template authoring details in `templates/CLAUDE.md`. - It reads OpenAPI specs for our backend services and renders templates using an OpenAPI processor/renderer stack plus a set of Jinja2 extensions and filters. - Each SDK “template pack” lives under `templates/<name>/` and typically contains: - `config.yaml` (render config: processor, renderer, extensions, loaders) - `macros.j2` (shared Jinja macros) - `version.txt`, `README.md` - `sdk/` or `templates/` subfolders with per-model, per-operation, and per-wrapper templates. - We already have mature packs for several non-engine targets (e.g., Unreal, Unity, Go, Python, C#, Java, etc.). - Now we want to add new **engine-focused packs** (such as Godot/GDScript or Roblox/Luau) that follow the same architectural principles: - For a Godot/GDScript-style engine, we want per-service scripts that call a shared HTTP client object and return a dictionary-like result (for example, a map with a `success` flag and response data). - For a Roblox/Luau-style engine, we want per-operation modules that call an injected `httpClient:request(...)` and return a `(boolean, ResultType?)` pair, plus tag-based wrapper classes that delegate to those operations. Goal (what you should design): For a new engine (or language) target, help me design the **core templates and macros** that: 1. **Turn OpenAPI operations into engine-friendly functions/methods** - For each operation, generate a function/method whose: - Name is derived from `operationId` using language-appropriate casing rules. - Parameters come from OpenAPI path, query, body, and form fields, with: - Required vs optional handled explicitly in the signature. - Appropriate type hints (or doc comments) based on our FieldType enums. - Reasonable defaults for optional parameters (e.g., sentinel values for numerics in GDScript, optional types in Luau). - Inside the function: - Build the URL path from the template (e.g. `"/users/{userId}"`) and substitute path parameters with properly encoded values. - In Godot, we build `url_path` as a `String` and call `.replace(...)` and `uri_encode()` on values. - In Roblox, we use a backtick string and `config` + `params` interpolation via a helper macro like `luau_path(...)`. - Optionally build a query string from query params: - Only include provided values. - Apply URL encoding (e.g. `HttpService:UrlEncode` in Luau; `AccelbyteHttp.build_query_string` in GDScript). - Optionally build a request body: - JSON (e.g. `JSON.stringify(body)` in GDScript; `body` table in Luau). - `application/x-www-form-urlencoded` (e.g. form dictionary → encoded string). - Choose auth behavior based on operation security: - Basic auth vs bearer tokens vs no auth. - The function should not own credentials; it should either: - Use an injected token/basicauth provider (Luau), or - Use an injected HTTP client plus an `_auth_token` field and/or client ID (GDScript). - Make a single call into an injected HTTP client / transport abstraction, for example: - Godot: `await _http.request(url, AccelbyteHttp.Method.<VERB>, headers, request_body)` returning a `Dictionary`. - Roblox: `return httpClient:request(method, url, tokenOrNil, bodyOrNil, contentTypeOrNil)` returning `(boolean, ResultType?)`. - Match the existing pattern for the chosen engine: - For Godot-style targets, return a `Dictionary` with `success` and payload fields that callers will inspect. - For Roblox-style targets, return a `(boolean, data?)` pair where `boolean` indicates HTTP/transport success. 2. **Generate a thin wrapper/facade per service/tag** - For engines like Godot: - Generate one service class per spec (`*_service.gd`) with: - Engine-native fields (base URL, service URL, namespace, auth token, client ID). - Setter methods called by a higher-level SDK (`set_base_url`, `set_namespace`, `set_auth_token`, `set_client_id`, etc.). - One method per operation, implemented using the per-operation logic described above and calling the shared HTTP client. - For engines like Roblox: - Generate a wrapper type per tag that: - Stores references to the engine’s HTTP client, token repository, and SDK config. - Exposes one method per operation, with a typed `params` table. - Internally delegates to a generated per-operation function module: `return _OperationName(self._http, self._tokens, self._config, params)`. 3. **Share as much logic as possible via macros** - Put reusable logic in `macros.j2`, including: - Mapping our FieldType/container types to engine types (e.g., `int`/`float`/`String`/`Dictionary` in GDScript, Luau type aliases for models, etc.). - Building path strings with substituted parameters (`namespace` taken from config where appropriate). - Building query strings from param lists (with optional vs required behavior). - Building form bodies from `form_data_params`. - Emitting a shared parameter annotation/signature block so both per-operation templates and wrappers can reuse the same param shape. 4. **Clean separation between generated code and engine runtime** - Assume a small, hand-written engine runtime will exist that provides: - An HTTP client abstraction (AccelbyteHttp in Godot, httpClient in Roblox) with a stable request API. - A token provider or token repository (`tokenProvider`/`_tokens`) that exposes operations like `getAccessToken()` and stores expiry metadata. - A config object for base URLs, namespaces, and engine-specific settings. - Generated code must: - Not hardcode credentials. - Use only these injected abstractions to access network and auth. - Be safe to regenerate from specs without overwriting engine-specific logic. 5. **Documentation and comments** - At the top of each generated file, include: - A “DO NOT EDIT – generated from OpenAPI spec” notice. - A short, engine-appropriate usage example: - Godot: `var service = sdk.get_service(MyService); var result = await service.my_method(...)`. - Roblox: `local ok, result = MyTag:MyOperation({ ... })`. - Keep comments focused on non-obvious behavior (e.g., how auth is inferred from OpenAPI security, how numeric sentinel defaults work in GDScript, or how config-injected `namespace` behaves in Luau). What to produce in your answer: 1. A proposed `macros.j2` skeleton for this new engine target, modeled on what we already do for GDScript and Luau: - Type mapping macro(s). - Path/query/body builders. - A shared parameter annotation macro. 2. A per-operation template that shows: - The full function signature (with param types/defaults). - URL/query/body construction. - The final HTTP client call, returning either a `Dictionary` (Godot-style) or `(boolean, ResultType?)` (Roblox-style), depending on the engine you’re targeting. 3. A per-service or per-tag wrapper template that: - Defines the wrapper/service type. - Wires in the engine’s HTTP client, token store, and config. - Exposes one method per operation that simply delegates to the generated operation logic
Godot: per-operation GDScript service method
{# godot-custom-sdk/templates/service/service.gd.j2 #} {% for op in operations %} {% if op.type_ in ['get', 'post', 'put', 'patch', 'delete'] %} {%- set method_name_raw = op.name | to_snake -%} {%- ... name normalization and param classification ... -%} ## {{ (op.summary | default(op.name, true) | truncate(70)) }} ## {{ op.type_ | upper }} {{ op.path }} func {{ method_name }}( {%- if total_params > 0 %} {{ param_lines | join("\n") }} {%- endif %} ) -> Dictionary: # Build URL path var url_path: String = "{{ op.path }}" ... # Replace path params with encoded values ... {%- if query_params | length > 0 %} # Build query parameters var query_params: Dictionary = {} ... if not query_params.is_empty(): url_path += "?" + AccelbyteHttp.build_query_string(query_params) {%- endif %} var url: String = _service_url + url_path {%- if has_form and is_form_urlencoded %} # Build form body var form_data: Dictionary = {} ... var request_body: String = AccelbyteHttp.build_form_body(form_data) # Build headers (basic vs bearer) ... {%- else %} {%- if has_body %} var request_body: String = JSON.stringify(body) {%- else %} var request_body: String = "" {%- endif %} var headers: PackedStringArray = AccelbyteHttp.get_bearer_headers(_auth_token) {%- endif %} {%- if _grant == 'True' %} var _result: Dictionary = await _http.request(url, AccelbyteHttp.Method.{{ op.type_ | upper }}, headers, request_body) ... return _result {%- elif _revoke == 'True' %} var _result: Dictionary = await _http.request(url, AccelbyteHttp.Method.{{ op.type_ | upper }}, headers, request_body) ... return _result {%- else %} return await _http.request(url, AccelbyteHttp.Method.{{ op.type_ | upper }}, headers, request_body) {%- endif %} {% endif %} {% endfor %}
Roblox: per-operation Luau function (HTTP call generator)
{# roblox-sdk/sdk/operation.j2 #} local function {{ name | luau_var }}(httpClient, tokenProvider, config, params: { {{- macs.luau_params_annotation(path_params, query_params, body_params, form_data_params) }} }): (boolean, {{ macs.luau_return_type(first_success_response) }}?) {%- if query_params %} {{ macs.luau_query_string(query_params) }} {%- endif %} {%- if form_data_params %} {{ macs.luau_form_body(form_data_params) }} {%- endif %} {%- if body_params %} local body = params.{{ body_params[0].name | luau_var }} {%- endif %} return httpClient:request( "{{ type_ | upper }}", {{ macs.luau_path(url, path_params) }}{% if query_params %} .. query{% endif %}, {% if has_security_scheme("basic") %} nil {% else %} tokenProvider:getAccessToken() {% endif %} {%- if form_data_params %}, formBody, "application/x-www-form-urlencoded" {%- elif body_params %}, body {%- endif %} ) end return {{ name | luau_var }}
Proof: Two SDKs and a Sample Game in One Week
Using this workflow, our team recently added support for two new engines. Within one week, we shipped:
The Godot SDK connects Godot projects to AccelByte Gaming Services, with support for:
The team first explored how Godot handles HTTP and WebSocket communication, using the AccelByte Unity SDK as a reference. They then created a GDScript CodeGen template, adapting an existing engine template with AI assistance, to generate the full API client layer automatically.
What once took months to implement can now be generated in minutes.
A few engine-specific challenges came up during development:
To validate the integration, the team also built a sample multiplayer 'Stacking' game where the block length reduces if placed inaccurately. With AI, they fully integrated the SDK and completed the sample game development within just two days.
Roblox enforces a strict networking architecture:
Because of this architecture, the Roblox SDK focuses on HTTP-based services such as player auth, leaderboards, and player data and progression that developers can use to connect their project to AccelByte’s backend services without workarounds.
Engineers first implemented the SDK core, including:
From there, CodeGen generated Luau API wrappers from the same OpenAPI specifications used by other AccelByte SDKs.
To validate the SDK, the team built a two-player competitive orb collection game. Despite no prior experience with Roblox Studio or Luau, AI assistance helped the team quickly learn the platform and complete the SDK integration using Roblox Studio assets.
What This Means for Studios on Custom or Non-Standard Engines
Most studios use Unity or Unreal. Some don't. Larger studios may run heavily modified engine forks. Others build proprietary engines from the ground up for specific technical requirements.
Traditionally, getting backend SDK support for those engines meant a 6–12 month project.
With AccelByte CodeGen and AI-assisted development, we can generate a complete SDK foundation for any new engine automatically. Engine-specific integration work takes days or weeks, not months, regardless of the engine or language.
If your studio is building on a custom or non-standard engine and needs backend services support, get in touch. We can explore how quickly we can get you integrated.