Debugování her a aplikací v Unreal Engine
Při vývoji jakékoli funkcionality se můžeme dostat do fáze, kdy napsaný kód negeneruje přesně takové výstupy, jaké bychom očekávali. Zjednodušeně, vyvíjený software nefunguje tak, jak by měl.
Postupu odhalování chyb (bugs) se říká debugging, česky známé rovněž jako ladění. Při ladění se zjišťují aktuální hodnoty proměnných, správné vyhodnocování podmínek, design logiky a podobně.
Vyjma samotného vyhledávání chyb v problematických částí kódu můžeme debugování využít i při návrhu tížené funkcionality, jakožto jakéhosi pomocníka - vodící prvek, díky němuž víme, jakým směrem se tížené hodnoty a vlastnosti ubírají.
V tomto článku Vám ukážu principy debugování ve vývojovém prostředí Unreal Engine. Pod debugovaným softwarem si můžete představit jakoukoli real-time 3D aplikaci, jakou je třeba počítačová hra nebo simulace chování těles v prostoru.
Výpis informací do logu v Unreal Engine
Obdobně jako v jiných softwarech, i Unreal Engine má dialogové okno, do nějž lze v reálném čase zapisovat jakékoliv informace. Nazývá se Output Log (Window -> Output Log). Samozřejmostí je i možnost zpětného přístupu k logu vyvíjené aplikace, a to ze souboru uloženého při složce projektu.
Nejjednodušší zápis do logu lze provést v C++ následujícím syntaxí:
UE_LOG(LogTemp, Display, TEXT(“Text information”));
kdy:
UE_LOG
je makro, které odesílá zprávu protokolu do souboru protokolu. Toto makro važaduje nastavení minimálně 3 následujících parametrů:- Použitá hodnota
LogTemp
se váže ke kategorii logu. Použitý LogTemp definuje uložení zpráv do dočasného protokolu) - Použitá hodnota
Display
se váže k úrovni protokolu logu. Možnosti jsou definovány pomocí enum ELogVerbosity Type. - Hodnota
Text()
konečně definuje text samotné zprávy, která má být vypsána v logu.
- Použitá hodnota
Formátování zprávy logu
Log formát vyžaduje specifický formát výpisu (unicode), kterého se dosáhne použitím makra TEXT()
.
V rámci samotného makra TEXT()
lze používat libovolnou strukturu pro výpis jedné či několika informací, např:UE_LOG(LogTemp, Warning, TEXT("Text, %d %f %s"), intVar, floatVar, *stringVar );
Vypisovanou zprávu v makru Text() lze formátovat a díky tomu dosáhnout snadno strukturovaného výpisu. K dispozici je hned několik specifikátorů proměnných.
Je-li pro vás však výhodnější nejjednodušší možná struktura - výpis pouze stringových hodnot, lze použít UE_LOG(LogTemp, Warning, TEXT(“%s"), *stringVar );
, přičemž si všechny proměnné převést pomocí převodních funkcí pro FString do stringVar před voláním makra UE_LOG. Příklad by mohl vypadat následovně:
Int32 intVar = 5;
Float floatVar = 5.5;
FString stringVar = “Integer is: ”+FString::FromInt(integer)+” and float is: ”+FString::SanitizeFloat(floatVar);
UE_LOG(LogTemp, Warning, TEXT(“%s"), *stringVar );
Deklarace vlastní kategorie logu
Vytvoření vlastních kategorií prokolového logu usnadňuje analýzu logu, jelikož usnadní snadnou filtraci logů v okně Output log dle definovaných kategorií. Filtraci najdete v okně Output log pod nabídkou Filters → Categories.
Novou kategorii lze definovat jednoduchým #include
příkazem v header souboru:DECLARE_LOG_CATEGORY_EXTERN(CategoryName, DefaultVerbosity, CompileTimeVerbosity);
, kde:
- CategoryName je název kategorie logu, kterou nově definujete
- DefaultVerbosity je úroveň výřečnosti, která se použije, není-li uvedena v souborech ini nebo na příkazovém řádku. Vypisují se přitom jen zprávy o nižší podrobnosti.
- CompileTimeVerbosity je maximální upovídanost pro kompilaci v kódu. Nic podrobnějšího než toto nebude zkompilováno.
V praxi se tedy do .h header souboru classy vloží např: DECLARE_LOG_CATEGORY_EXTERN(LogCustom, Log, All);
, přičemž daná kategorie se poté už jen registruje v .cpp souboru pomocí DEFINE_LOG_CATEGORY(LogCustom);
a následně může v rámci oné class používat skrze UE_Log
makro. Příkaz může vypadat následovně:
UE_LOG(LogCustom, Warning, TEXT("Text, %d %f %s"), intVar, floatVar, *stringVar );
Výpis zpráv na obrazovku
Výhodou výpisu na obrazovku namísto do logu je lepší viditelnost - zpráva je viditelná přímo ve hře. Nevýhodou poté fakt, že takový log není ukládán. V praxi se tedy používá kombinace obojího.
Výpis do obrazovky lze provést pomocí globálního příkazu (není n utné jej v rámci class definovat) GEngine->AddOnScreenDebugMessage
. Příklad:
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Black, FString::Printf(TEXT("Some variable values: x: %f, y: %f"), x, y));
kde:
- První parametr je klíč, k němuž je zpráva vedena. Je-li nastaven na
-1
, s každou exekucí příkazu je na obrazovku přidán jeden další řádek. Je-li klíč definován, nová zpráva pod identickým klíčem nahrazuje zprávu předchozí. Hodnota klíče může nabývat hodnot datového typuuint64
. - Druhý parametr je čas v sekundách, definovaný jako
float
. Jde o hodnotu času, po kterou zpráva na obrazovce zůstává viditelná. - Třetí parametr definuje barvu výpisu textu zprávy
- Čtvrtým parametrem je samotná zpráva. Z definice funkce musí jít pouze o jeden parametr, pracujeme-li s více parametry, je nutné je v rámci zápisu sdružit / transformovat “do jednoho” pomocí příkazu
FString::Printf()
. - Pátý, dobrovolný parametr typu hodnoty
boolean
je funkční pouze při hodnotě klíče-1
(parametr 1). Definuje poté místo výpisu zpráv (true shora, false zdola). - Pomocí 6. úarametru, který je typu
FVector2D
a rovněž dobrovolný, lze nastavit měřítko výpisu textu.
Při GEngine->AddOnScreenDebugMessage
si lze nadefinovat i převod do print: #define print(text) if (GEngine) GEngine->AddOnScreenDebugMessage(-1, 1.5, FColor::White,text)
, kdy následně lze výpis na obrazovku provádět oblíbeným příkazem print(text)
.
Vkládání zpráv do podsystému Viewport Stats
Vedle GEngine->AddOnScreenDebugMessage
existuje ještě další způsob výpisu zpráv na obrazovku, a to je s použitím subsystému UViewportStats
. Příkladem použití jsou do viewportu vypisované zprávy Unreal Enginem jako je "LIGHTING NEEDS TO BE REBUILT" nebo "BLUEPRINT COMPILE ERROR".
Princip použití je srozumitelný z dokumentace a classy UViewportStatsSubsystem.
Debug Helpers
V předchozích krocích jsme pracovali pouze s výpisem hodnot. Pomocí nástrojů z knihovny DrawDebugHelpers.h je pak možné si libovolně do prostoru aplikace vkládat debugovací obrazce, jakými mohou být linky, koule, krychle a pod.
Výběr funkcí z DrawDebugHelpers.h knihovny:
- DrawDebugPoint
- DrawDebugSphere
- DrawDebugLine
- DrawDebugCircle
- DrawDebugSolidBox
- DrawDebugBox
- DrawDebugDirectionalArrow
- DrawDebugCrosshairs
- DrawDebug2DDonut
- DrawDebugCamera
- DrawDebugMesh
- ...
Kompletní seznam funkcí naleznete v rámci výpisu Engine Runtime, kde jednoduše hledejte na stránce (ctrl+f) spojení “DrawDebug”.
Debug obrazce lze rovněž smazat, a to příkazem FlushPersistentDebugLines(GetWorld());
To se hodí zejména při používání debug obrazců v rámci on construction script
funkcí.
Kreativita vývojáře
Standardní debugovací funkce jsou skvělým pomocníkem, avšak při jakémkoli vývoji by měl platit princip otevřeného přístupu k možným řešením - nespoléhat se pouze na standardní funkce a namísto toho pracovat s vlastní kreativitou.
Unreal Engine je otevřené prostředí, s mnoha nástroji a funkcemi, které lze parametrově řídit v závislosti na situaci. Všechny tyto nástroje lze současně používat i při debugování.
Tento přístup používám u řady komponent, které postupně uvolňuji v rámci Vrealmatic knihovny komponent pro Unreal Engine.
Tento článek je pouze velmi základním vhledem do debugování. O tom, jak rozsáhlé toto téma je, co vše se dá v rámci něj řešit, a jak s jeho pomocí celkově aplikace ladit pro co nejefektivnější běh někdy v dalším článku :).