BertaDevKit is a personal C++ plugin for Unreal Engine 5.6, built to consolidate reusable development tools across projects without rewriting them each time. It is structured as two modules: a Runtime module covering debug utilities, math helpers, and world queries; and an Editor module covering asset naming enforcement and world validation. All systems are configurable from Project Settings without touching code, and the entire debug surface is stripped from Shipping builds via DevelopmentOnly.
The Settings System (UBertaDevKitSettings) extends UDeveloperSettings to expose plugin configuration under Project Settings → Plugins → BertaDevKit, persisted in Config/DefaultBertaDevKit.ini. Every runtime system checks a master switch here before executing — disabling a system costs a single GetDefault<> call with no downstream overhead. UBertaDevKitSettings::Get() wraps GetDefault<UBertaDevKitSettings>() and returns the CDO — always valid, never null.
The Debug Logging System (UBertaDebugUtils) is a UBlueprintFunctionLibrary with four DevelopmentOnly functions. All functions accept a bEnabled parameter for per-call gating on top of the global master switch. PrintLog routes output to screen, Output Log, or both via EBertaLogOutput. PrintLogWithContext prepends [ObjectName] from a DefaultToSelf world context. PrintLogToNamedCategory routes to an arbitrary log category at runtime via FMsg::Logf — the only viable path when the category name is a string rather than a compile-time symbol. The LogToOutput helper uses a switch with a default: ensureMsgf(false, ...) to catch unhandled EBertaLogVerbosity values at development time.
The Debug Draw System (UBertaDebugDraw) provides twelve DevelopmentOnly functions for in-world shape drawing. Three private helpers eliminate boilerplate across all functions: ResolveWorld validates the context object and resolves UWorld*; ResolveLifeTime maps the public Duration <= 0.0f contract to the -1.0f persistent sentinel expected by DrawDebug*; ToFColor converts FLinearColor to the FColor that the pre-FLinearColor DrawDebug* API requires. FromComponent variants (DrawSphereFromComponent, DrawBoxFromComponent, DrawCapsuleFromComponent) extract location, extent, and orientation directly from the component, eliminating the query boilerplate at every call site. DrawCoordinateSystem passes bPersistentLines = false as a workaround for a known engine bug where passing true makes the lines ignore LifeTime and prevents flushing.
The Screen Stats Panel (UBertaScreenStats) maintains a persistent named key-value panel rendered via GEngine->AddOnScreenDebugMessage with stable keys derived from GetTypeHash(FName). Calling Set* with the same name updates the existing line rather than appending a duplicate. Registration is lazy: the RenderStats callback binds to FCoreDelegates::OnBeginFrame only on the first Set* call, using a static local flag inside EnsureRegistered() — safer than a class-level static member because initialization is guaranteed to occur on first call. Entry storage uses a static local TMap<FName, FBertaStatEntry> for the same reason, avoiding the static initialization order fiasco. DisplayName is converted from FName to FString once on FindOrAdd, never per frame. SetFloat and SetVector use FCString::Snprintf with %.*f for runtime-dynamic decimal precision — FString::Printf with a dynamic format string fails in UE 5.4+ due to TCheckedFormatString.
The Math Utilities (UBertaMathUtils) provide fifteen stateless, BlueprintThreadSafe functions across five categories. The remap functions use NormalizeToRange as a shared building block — its internal FMath::Clamp on the alpha is what makes RemapClamped stay within [OutMin, OutMax], not a separate clamp on the output. The easing functions implement quadratic and cubic ease-in, ease-out, and ease-in-out curves, plus SmoothStep — cubic Hermite with f'(0) = f'(1) = 0, equivalent to GLSL/HLSL smoothstep. The angular functions use FMath::UnwindDegrees to handle wrap-around correctly without branching. ProjectileImpactPoint solves the quadratic for intersection of a ballistic arc with a horizontal plane, handling the zero-gravity linear case separately and returning false for no-real-solution and past-only-solution cases.
The World Utilities (UBertaWorldUtils) cover actor queries, traces, player access, and one-shot timers. A private GetWorldChecked helper centralizes WorldContextObject validation and error logging. Actor query functions iterate via TActorIterator, compute distance in squared space to avoid sqrt per actor, and support optional tag filtering via a NAME_None sentinel — no overloads needed. SetDelayedAction validates both Callback.IsBound() and Delay > 0.0f before setting the timer. Camera access functions guard on PlayerCameraManager validity and return FVector::ZeroVector / FVector::ForwardVector as safe fallbacks.
The Asset Naming System consists of three classes with distinct responsibilities. UBertaAssetNamingUtils is the sole owner of the TMap<UClass*, FString> prefix map — 27 entries, stored as a static local to avoid the static initialization order fiasco. RenameAssetWithPrefix walks the class hierarchy via GetSuperClass() to handle Blueprint subclasses of known types, and strips engine-generated M_ and _Inst from material instances before applying MI_. UBertaAssetNamingActions is a UAssetActionUtility that exposes AddPrefixes to the Content Browser context menu via CallInEditor, delegating all logic to UBertaAssetNamingUtils. UBertaAssetAuditor provides RunAudit (reports violations without modifying assets) and RunAuditAndFix (renames violators). Both use ResolveAssetScope to prefer the current Content Browser selection, falling back to a full /Game/ scan via the Asset Registry without loading assets into memory — GetAsset() is called only on confirmed violators.
The World Validation System (UBertaWorldValidation) iterates all actors in the open level via TActorIterator<AActor> and runs four configurable checks: static mesh components with no mesh assigned, actors beyond a configurable world bounds threshold on any axis, light actors whose mobility does not match the expected project setting, and actors with zero or negative scale. Settings are read once before the loop to avoid redundant GetDefault<> calls per actor. Results are reported to LogBertaDevKitEditor and summarized in a FNotificationInfo toast with CS_Success or CS_Fail completion state.
The Editor Toolbar (FBertaEditorToolbar) is a non-UObject F class owned by FBertaDevKitEditorModule via TUniquePtr. Registration is deferred to FCoreDelegates::OnPostEngineInit via a named member callback because UToolMenus is not guaranteed to exist at StartupModule time. Each FToolMenuEntry has its Owner field set to "BertaDevKit" so that UToolMenus::Get()->UnregisterOwner() in ShutdownModule correctly removes all entries on hot-reload and plugin unload.
Tech Stack
- Unreal Engine 5.6
- C++
- JetBrains Rider
Leave a comment