.NET support for Godot is now generally available
We recently released version 2.0.0 of Sentry’s Godot Engine SDK, bringing C#/.NET support and application metrics to general availability. That means you can now capture errors from your managed C# code and track custom metrics across your development and retail builds.
C#/.NET support
Godot supports two fairly distinct worlds of scripting: GDScript and C#. During the 1.x releases, we instrumented the SDK to capture issues from GDScript, the engine itself, and the native C/C++ code, and expanded support to all officially supported platforms. With the 2.0.0 release, we’re introducing first-class error reporting for .NET in Godot Engine.
Errors from your managed code now appear on the same trace alongside GDScript and engine issues, and scope elements like tags, breadcrumbs, and user context are present where they’re currently synchronized.
So, let’s see how this works in practice. Here’s a short version of C# code that throws at runtime in our Godot game:
private List<UpgradePathBase>? TryFetchUpgradesFromServer()
{
var fetchTransaction = SentrySdk.StartTransaction("fetch_upgrades", "http.client");
return ParseUpgradeData(FetchUpgradeDataFromServer(fetchTransaction), fetchTransaction);
}
private List<UpgradePathBase>? ParseUpgradeData(string responseContent, ITransactionTracer transaction)
{
// ...
// Parsing the server response throws JsonException
var serverResponse = JsonSerializer.Deserialize<ServerUpgradeResponse>(responseContent);
var upgradePaths = new List<UpgradePathBase>(serverResponse!.upgrades.Length);
// ...
}The SDK captures the error at runtime and sends an envelope to your Sentry project. This is how the issue looks in Sentry:
Sentry has integrations for most popular SCMs, like GitHub, and can fetch the source code context around each frame right from your repository.
Metrics
To better understand performance across different hardware configurations, you can send metrics to Sentry so you can track performance over time and slice it by custom attributes. In this example, we measure the actual time between frames instead of relying on the engine’s delta time, since delta time is typically clamped and smoothed. We also attach CPU and GPU attributes, making it easy to slice the data by hardware configuration. You can add other attributes to narrow the data down even further, such as the game level where the metric was recorded.
Here we’re looking at the p95 frame time to spot configurations where the game consistently struggles to run smoothly. This makes it easier to identify hardware-specific performance issues, or even level-specific bottlenecks, and focus your optimization efforts where they’ll have the biggest impact.
extends Node
## Samples frame time and sends it to Sentry as metrics.
const FRAME_SAMPLE_INTERVAL_MS := 100.0
var _time_since_sample: float = 0.0
var _last_frame_usec: int = Time.get_ticks_usec()
func _process(_delta: float) -> void:
var now := Time.get_ticks_usec()
var frame_ms := (now - _last_frame_usec) / 1000.0
_last_frame_usec = now
_time_since_sample += frame_ms
# Sample at a fixed interval to avoid producing too many metrics.
if _time_since_sample >= FRAME_SAMPLE_INTERVAL_MS:
_time_since_sample = 0.0
var attributes := {
"gpu.name": RenderingServer.get_video_adapter_name(),
"cpu.name": OS.get_processor_name(),
"cpu.cores": OS.get_processor_count(),
}
SentrySDK.metrics.distribution("perf.frame_time", frame_ms,
SentryUnit.millisecond, attributes)For another example, let’s track how long runs take and what experience level players reach on average in a roguelite/survivors-style game. game.run.time records the total time it took to complete a single run, while game.run.xp_level records the experience level reached during that run.
This example shows that metrics can be used to track more than just performance: they’re also useful for gameplay events. Data like this can help you balance gameplay variables and create a better experience for your players.
extends Node
## Sends gameplay metrics to Sentry by listening for game events
## on the EventBus.
func _ready() -> void:
EventBus.connect("EnemyDestroyed", _on_enemy_destroyed)
EventBus.connect("RunEnded", _on_run_ended)
func _on_enemy_destroyed(_score_value: int) -> void:
SentrySDK.metrics.count("game.enemies_killed", 1)
func _on_run_ended(xp_level: int, time: float) -> void:
SentrySDK.metrics.distribution("game.run.xp_level", xp_level)
SentrySDK.metrics.distribution("game.run.time", time, SentryUnit.second)Try it out
Give it a try! We want to hear your feedback as we continue to improve the SDK. To get started, download Sentry from the Godot Asset Store, or directly from GitHub. Extract the archive into your project, set your DSN in the project settings under Sentry > Options, and let the errors come through with full context. For more details, check out the dedicated C#/.NET guide. Happy debugging!