4. Unreal Engine 5¶
4.1. Einleitung¶
Die Unreal Engine 5 ist eine leistungsstarke Game-Engine, die von Epic Games entwickelt wurde. Sie ist die neueste Version der Unreal Engine, die für die Erstellung von Videospielen, aber auch für die Entwicklung von Virtual-Reality-Erlebnissen, Architekturvisualisierungen, Filmen und Animationen verwendet wird. Die Logik der Spiele wird in C++ geschrieben. In der Engine selbst kann auch über Blueprints die Logik erstellt werden.
Abb. 1: Unreal Emblem¶
Bemerkung
Für diesen Artikel setzten wir voraus, dass Sie über Grundwissen in der Sprache C++ verfügen.
In unserem Kontext wollen wir ein einfaches kleines Spiel entwickeln, um die Stärken von Unreal aufzuzeigen. Dazu aber mehr in einem späteren Kapitel. Zunächst wollen wir die Vorteile und Alleinstellungsmerkmale der Unreal Engine aufzeigen.
4.2. Alleinstellungsmerkmale der Game Engine¶
Sehen wir uns also die folgenden drei Punkte an. [UNREAL_5_ENGINE]
Nanite
Lumen
World Partition
Dies drei Begriffen sehen harmlos aus, jedoch verstecken sich dahinter die Funktionen, die Unreal gegenüber anderen Game Engines so gut macht. Werfen wir also einen schnellen Blick darauf, um die Schönheit von Unreal zu verdeutlichen und Sie auf auf den Geschmack der Spieleentwicklung zu bringen.
Nanite ist eine wegweisende Technologie innerhalb der Unreal Engine 5, die Entwicklern ermöglicht, extrem detaillierte 3D-Modelle ohne manuelle Optimierung in ihre Spiele und Projekte zu integrieren. Im Wesentlichen erlaubt Nanite die Verwendung von hochaufgelösten Geometrien mit Millionen oder sogar Milliarden von Polygonen, ohne dass dies die Leistung des Spiels beeinträchtigt.
Eine weitere wichtige Funktion ist Lumen. Wie der Name schon vermuten lässt, handelt es sich bei diesem Feature um ein globales Beleuchtungssystem, das dynamische, realistische Beleuchtungseffekte ermöglicht, ohne dass Entwickler Lightmaps erstellen oder statische Beleuchtung verwenden müssen. Dadurch können Spiele eine beeindruckende visuelle Qualität erreichen und gleichzeitig eine immersive und dynamische Umgebung bieten.
Die Unreal Engine 5 bietet auch eine verbesserte Unterstützung für Open World-Spiele, dank eines Systems namens World Partition, das große Spielwelten in kleinere, handhabbare Abschnitte aufteilt und sie bei Bedarf dynamisch lädt und entlädt, um die Leistung zu optimieren.
Es lässt sich also sagen, dass es sich besonders für diese 3 Punkte lohnt die Unreal Engine zu verwenden, um hochwertige Spiele und andere interaktive Erlebnisse zu erstellen, die auf verschiedenen Plattformen, einschließlich Konsolen, PCs und mobilen Geräten, laufen können.
Im nächsten Kapitel wollen wir dann den Projektkontext herstellen und genauer auf unsere konkrete Anwendung eingehen.
4.3. Spielidee¶
Was wäre besser um die Engine vorzustellen, als eine kleine Applikation von Grund auf zu entwickeln. Wir wollen hier also einen kleinen Einblick geben, wie mit der Unreal Engine gearbeitet werden kann. Lassen Sie uns also starten und eintauchen in die süße Welt von CandySearch
Wir entwickeln in diesem kleinen Projekt ein FirstPerson Spiel. Hierbei navigiert der Spieler durch einen realistischen Raum und muss dort verschiedenen Arten von Süßigkeiten finden. In der rechten unteren Ecke ist ein kleiner Counter implementiert, der die aktuell gesammelten Süßigkeiten zählt und gleichzeitig aufzeigt wie viele Süßigkeiten im Level versteckt sind. Gestartet wird das Spiel über ein einfaches Menü. Die nachfolgende Abbildung soll zeigen wie das Spiel aussieht wenn es gestartet wird.
Abb. 2: Das Menu von CandySearch¶
4.4. Ziel des Spiels¶
CandySearch ist ein einfaches Spiel, das vor allem auf Gelegenheitsspieler*innen abzielt. Das Ziel ist demnach schnell zu erreichen. Es müssen alle versteckten Süßigkeiten gefunden und eingesammelt werden. Wer jetzt denk, das Spiel ist sehr einfach, der wird schnell eines Besseren belehrt, da in CandySearch sehr genau hingeschaut werden muss. Damit der Spieler immer nachsehen kann, wie die Süßigkeiten aussehen, hängen diese an einer der vier Wänden innerhalb des Raumes. Das folgende Bild gibt einen Eindruck, wie dies realisiert wurde.
Abb. 3: Süßigkeiten von CandySearch¶
4.5. Die Szene¶
Wie bereits zu Beginn kurz erwähnt, spielt CandySearch in einem realistisch gestalteten Raum, indem man allerlei Dinge finden kann, die man auch in einer gewöhnlichen Wohnung finden würde. Innerhalb der Regale und Schränke befinden sich weitere Assets, die die Szene noch glaubhafter darstellen. Das Spiel enthält auch logische Spielfeldabgrenzungen, die durch die Wände des Raumes bereitgestellt werden. Eine Tür gibt es nicht.
Abb. 4: Spielszene von CandySearch ausgehend vom Startpunkt¶
Abb. 5: Spielszene von CandySearch mit Sicht auf den Startpunkt¶
4.6. Gameplay¶
Da wir nun einen ersten aber prägnanten Eindruck über das Spiel bekommen haben, können wir nun etwas genauer auf die Spielmechanik und das Gameplay eingehen. CandySearch setzt auf eine vertraute Mechanik und Movement-System. Der Spieler kann mit den Tasten W, A, S, D den Avatar in die verschiedenen Richtungen steuern und so unterschiedliche Punkte in der Szene erreichen. Die Leertaste dient zum Springen, um auch auf Objekte gucken zu können. Mit dem Bewegen der Maus manipuliert der Spieler die Kamerasicht und beeinflusst damit die Richtung in die sich der Avatar durch die Szene bewegt. Wird eine Süßigkeit gefunden, so kann diese mit einem einfachen Linksklick durch die Maus aufgesammelt werden. Der Courser muss hierbei auf die jeweilige Süßigkeit zeigen. Um auch unter Objekte zu sehen, kann der Avatar im Spiel auch kriechen, indem die Steuerungstaste gedrückt gehalten wird. Wir können also folgenden Mechaniken dem Charakter zuordnen:
laufen
springen
aufheben von Süßigkeiten
kriechen
Damit der Charakter weiß, dass nun eine Süßigkeit vor ihm liegt, wird ausgehend von seinem Kamera Viewport mittig ein Ray gesendet, welcher in einem gewissen Abstand erkennen kann, welches Objekt er sieht. Hierbei wird geprüft, ob es sich um ein Pick-Up Objekt handelt. Also eine Süßigkeit, welche der Spieler einsammeln kann. Nur wenn dies der Fall ist, kann der Spieler diese aufsammeln.
4.7. Die Spiellogik¶
Die Spiellogik umfasst hauptsächlich die Süßigkeiten, also die Objekte, die der Spieler oder die Spielerin aufsammeln kann. Wir wollen uns also demnach etwas genauer mit dieser Logik befassen. Hierzu betrachen wir den PickUpManager und die PickUps.
PickUp-Objekte:
Diese umfassen alle Süßigkeiten, die der Spieler einsammeln kann. Alle sind Actors der Klasse PickUp und dadurch eindeutig identifizierbar. Zu Beginn sind alle PickUps im Header inaktiv gesetzt. Zudem werden sie unsichbar geschalten und ihre Kollisions- möglichkeit mit dem Spieler ausgeschalten. Die Pick-Up-Klasse beinhaltet zudem die Funktionalität ein PickUp aktiv, also sichtbar, zu schalten, sowie ein PickUp aufzuheben durch den Spieler. Für diese Interaktion muss jedem PickUp der PickUpManager bekannt sein.
Block 1 Funktionen, der Pick-Up-Klasse¶void APickUp::PickUp() { if(!bActive) return; if(!IsValid(PickUpManagerRef)) return; PickUpManagerRef->IncreaseNumPickedUp(); SetActorHiddenInGame(true); SetActorEnableCollision(false); bActive = false; } void APickUp::ActivatePickUp(APickUpManager* PickUpManager) { SetActorHiddenInGame(false); SetActorEnableCollision(true); bActive = true; PickUpManagerRef = PickUpManager; }PickUp-Manager:
Der PickUp-Manager ist dafür zuständig den Fortschritt des Aussammeln zu halten und auszuwählen welche PickUps in der Szene aktiv sein sollen. Um eine gewisse randomisierte Erfahrung zu bieten, wurden jede Menge PickUp-Objekte an feste Orte gesetzt. Hierbei sind die 6 unterschiedlichen Arten von Süßigkeiten jeweils in einer Gruppe bzw. an einem ähnlichen Ort platziert. Diese 6 Candys erhalten dieselbe GruppenID. Zudem wird eine Zahl festgelegt, wie viele Süßigkeiten zu finden sein sollen. Der Algorithmus sucht sich unter all diesen Objekten zufällig die entsprechende Anzahl an Süßigkeiten heraus. Hierbei werden zuerst die GruppenIDs gezogen und in diesen Gruppen zufällig eine Süßigkeitenart ausgewählt. Dieses ausgewählte Objekt wird dann über die Funktion ActivatePickUp() sichtbar gestellt und auch die Interaktion mit dem Charakter wieder eingeschaltet.
Block2: PickUpManager::ChoosePickUps()¶int32 max = PickUps.Num(); TArray<int32> PosGroupIDs; for (int i = 1; i <= max; i++) { PosGroupIDs.Add(i); } for (int32 i = max - 1; i > 0; i--) { int32 RandomIndex = FMath::RandRange(0, i); PosGroupIDs.Swap(i, RandomIndex); } TArray<int32> ChoosenGroupIDs; for (int i = 0; i < NumPickUpsCollect; i++) { int32 Choosen = PosGroupIDs[i]; ChoosenGroupIDs.Add(Choosen); } for (int i = 0; i < ChoosenGroupIDs.Num(); i++) { TArray<APickUp*> PickUpsByGroupID = PickUps[ChoosenGroupIDs[i] - 1]; int32 RandomIndex = FMath::RandRange(0, PickUpsByGroupID.Num()-1); APickUp* ChoosenPickUp = PickUpsByGroupID[RandomIndex]; ActivatePickUp(ChoosenPickUp); }PickUp-Component:
Die PickUp-Component liest die Kameraposition des Charakters aus und sendet dort vom Mittelpunkt aus eine LineTrace mit einer gewissen Distanz. Trifft diese auf ein Objekt in der Szene, kann ausgelesen werden, ob es sich um ein PickUp handelt. Nur wenn dies der Fall ist und der Spieler dann das PickUp aufheben möchte, ist das auch möglich. Es wird also stetig ausgelesen worauf der Charakter gerade schaut.
Block 3 UPickUpComponent¶FHitResult HitResult; //ECC_GameTraceChannel2 == TC_Pickup if(World->LineTraceSingleByChannel(HitResult, CameraLocation, LineTraceEndLocation, ECC_GameTraceChannel2 , CollisionQueryParams)) { AActor* HitActor = HitResult.GetActor(); if(!IsValid(HitActor)) return; if(HitActor->IsA(APickUp::StaticClass())) { GEngine->AddOnScreenDebugMessage(1,1.0f,FColor::Green,TEXT("Hit something ") + HitActor->GetName()); PickUpReference = Cast<APickUp>(HitActor); } } else { PickUpReference = nullptr; }
4.8. Besondere Herausforderungen¶
Eine große Herausforderung war zu Beginn neben dem Einarbeiten in die Engine selbst, die eine wahnsinnige Vielfalt an Optionen bereithält, das Einarbeiten in eine neue Programmiersprache C++. Die Aufteilung des Source Codes in eine header- und eine cpp-Datei war uns neu. Jede Funktion und Variable, muss hierbei im Header vorab deklariert werden. Hier kann allerdings auch den Funktionen und Variablen Eigenschaften hinzugefügt werden, sodass diese dann in der Engine selbst verändert und angesprochen werden können. Zum Beispiel kann man in der Engine die Zahl der zu findenden Süßigkeiten anpassen ohne den Code zu verändern.
Nach dem Packaging trat das Problem auf, dass gar keine PickUp sichtbar waren, obwohl in der Editor zuvor das Spiel einwandfrei gelaufen ist. Nach ein wenig Recherche kamen wir darauf, dass die Reihenfolge, in der alle Objekte in Spiel gespawnt werden, verändert war im Vergleich zum Editor. Einen schnellen Fix erreichten wir durch das Unsichtbarstellen der PickUp-Objekte bereits im Konstruktor, zuvor war dies in der Funktion beginPlay() umgesetzt.
APickUp::APickUp()
PrimaryActorTick.bCanEverTick = false;
SetActorHiddenInGame(true);
SetActorEnableCollision(false);
}
Eine weitere Herausforderung war der Algorithmus zum Heraussuchen der zu findenen Süßigkeiten. Hierbei haben wir Arrays in einem Array gearbeitet. Dadurch war ein wenig Gehirnschmalz notwendig um den passenden Algorithmus zu finden. Die Lösung ist im Folgenden dargestellt.
TArray<AActor*> AllPickUps;
UGameplayStatics::GetAllActorsOfClass(this, APickUp::StaticClass(), AllPickUps);
int32 max = 1;
for (int i = 0; i < AllPickUps.Num(); i++)
{
APickUp* PickUp = Cast<APickUp>(AllPickUps[i]);
if (!IsValid(PickUp))
continue;
int32 GroupID = PickUp->GetGroupID();
if (GroupID > max)
{
max = GroupID;
}
}
for (int i = 0; i < max; i++)
{
PickUps.Add(TArray<APickUp*>());
}
for (int i = 0; i < AllPickUps.Num(); i++)
{
APickUp* PickUp = Cast<APickUp>(AllPickUps[i]);
if (!IsValid(PickUp))
continue;
int32 GroupID = PickUp->GetGroupID();
if (GroupID > 0)
{
PickUps[GroupID-1].Add(PickUp);
}
}
4.9. Schlusswort¶
Das Arbeiten mit der Unreal-Engine 5 war eine spannende Erfahrung, die allerdings einiges mehr an Einarbeitungszeit benötigen würde, um sich mit mehr Möglichkeiten vertraut machen zu können. Die Bandbreite, welche Projekte mit der Engine umgesetzt werden können, ist grenzenlos aber eben genauso erlebnisreich.
CandySearch wurde ein süßes kleines Spiel, wo einiges an Potenzial zu finden ist. Mit mehr Zeit und Arbeit kann daraus ein tolles Spiel geschaffen werden. Auch hierbei könne man mit mehr Erfahrung vor allem im Bereich der Lichtgestaltung bestimmt noch einiges herausholen.
4.10. Literatur¶
Dokumentation: (abgerufen am 14.05.2024): https://www.unrealengine.com/en-US/unreal-engine-5