Inhoud
- Wat vooraf ging
- Inleiding
- Control Flow
- Sequentie
- Booleaanse vergelijkingen
- Selectie
- Iteratie
- Extra – Control Flow technieken eigen aan Unreal Engine
Wat vooraf ging
- U hebt de Blueprint Visual Scripting introductie doorgenomen.
- U hebt weet van variabelen.
- U hebt basiskennis van Flow Control.
- U weet wat het DRY-principe en functies zijn.
Inleiding
Deze handleiding herneemt de basishandleiding en wordt uitgebreid met specifieke Flow Control voor Unreal Engine. U zou zich dus kunnen beperken tot volgende onderdelen:
Situering van deze handleiding binnen Unreal Engine
Control Flow
Control Flow (of Flow Control, beiden zijn goed) is de bepaling in welke volgorde de instructies van een programma moeten uitgevoerd worden. In grote lijnen hebt u 3 vormen:
- Sequentie
- Selectie
- Iteratie
In deze handleiding maken we kennis hoe de Control Flow verloopt in Unreal Engine, ik ga me echter beperken tot de algemene technieken, technieken die u ook in de meest andere programmeertalen tegenkomt. Specifieke technieken van Unreal Engine laat ik hier even terzijde, zij zullen ten gepaste tijden aan bod komen.
Hoewel we verder bouwen op vorige handleidingen verkies ik toch met een nieuw project te starten.
- Start een nieuw Third Person Desktop/Console project van Maximum Quality With Starters Content en geef het een passende naam.
We gaan programmeren vanuit de Level Blueprint.
- Om de Level Blueprint te openen klikt u in de knoppenbalk van de Viewport op Blueprints – Open Level Blueprint.
De Level Blueprint wordt geopend in de zogenaamde Event Graph. De Level Blueprint kent enkel maar deze Event Graph.
In de Events Graph worden de events geprogrammeerd.
Ik herneem ook nog even hoe u Toetsenbord-events kunt aanmaken.
De verschillende code die we gaan maken gaan we elk aan een toets koppelen. Hiertoe moeten we toetsenbord-events gebruiken.
- Klik met de rechtermuisknop ingedrukt ergens in de Blueprint en zoek naar Keyboard.
- Vervolgens zoekt u naar de gewenste toets (bv. de F-toets) en klikt u deze aan.
U ziet de Exec-pins Pressed en Released staan. Deze kunnen we gebruiken voor het indrukken (Pressed) en weer loslaten van een toets (Released). Key is een Output-parameter die informatie over de ingedrukte toets bevat.
- Compile, Save en Play om dit uit te testen.
We gaan deze techniek in onderstaande voorbeelden gebruiken.
Om een overzicht te krijgen van alle beschikbare Flow Control-technieken klikt u met de rechtermuisknop in de Blueprint en zoekt u naar Flow Control.
Hieronder bespreek ik de meest algemene, de technieken die ook voorkomen in klassieke programmeercode, een volledig overzicht voor Unreal Engine vindt u hier (sommige voorbeelden zijn misschien nog wat te complex op dit ogenblik).
BELANGRIJK, in Unreal Engine Blueprints Visual Scripting wordt de Control Flow (of Flow Control) bepaald door de witte lijnen die de Exec-pins verbinden. Volg dus steeds de witte lijnen!
Sequentie
Sequentie betekent zoveel als “op volgorde”, de ene instructie na de andere.
In Unreal Engine komt dit duidelijk tot uiting door de verbinding van de nodes.
Onderstaande code zal achtereenvolgend A B C D en E afdrukken op het scherm.
Hebt u de code toch liever onder mekaar dan de Sequence-node gebruiken. Beide structuren zijn gelijkwaardig, aan u de keuze.
Uiteraard kunnen er na iedere Exec-pin meerdere instructies komen en niet enkel één Print String.
Onderstaande variant toont in volgorde A D E B C.
Booleaanse vergelijkingen
Booleaanse vergelijkingen zijn vergelijkingen die als uitkomst enkel True of False (waar of onwaar) kunnen hebben.
Onderstaande video legt nog even heel eenvoudig uit wat een Boolean is.
In de handleiding Start to Program – Blueprints Visual Scripting – Basisbegrippen – Eenvoudige bewerkingen hebben we reeds verschillende Booleaanse vergelijkingen bekeken maar nog niets mee gedaan. Nu gaan we zien hoe de uitkomst van een booleaanse vergelijking de Control Flow bepaalt.
De reeds aangehaalde Booleaanse vergelijkingen zijn:
String
Integer
Float
Een voorbeeld van een Booleaanse vergelijking is bv.:
Een punt moet groter dan of gelijk zijn aan 0
De code is dan als volgt (stel dat het punt een geheel getal (Integer) is).
Ik ga nog even stap voor stap uitleggen hoe u dit kunt aanmaken.
- Indien u er nog niet bent, ga naar Level Blueprint.
- Zoek naar >=.
- Selecteer Integer >= Integer (omdat ons Punt van het type Integer is, wilt u een decimaal punt, kies dan Float (en ja Byte (een waarde tussen 0 en 255) kan ook)).
Nu wil ik als invoer een variabele met de naam Punt, gewoon, omdat dit duidelijker is dan gewoon een cijfer, het duidt het cijfer als zijnde een “punt” en ik kan die variabele Punt eventueel later opnieuw gebruiken.
U kunt gewoon een variabele aanmaken en die eraan koppelen maar ik kies ervoor om de invoerpin te “promoten tot variabele”.
- Klik met de rechtermuisknop ingedrukt op de bovenste invoerpin en kies voor Promote to Variable.
U krijgt een nieuw variabele van het overeenkomende datatype van de pin (hier Integer).
Vervolgens kunt u de naam wijzigen in het Detail-panel bij Variable Name en na het klikken op Compile kunt u ook een Default Value geven (het is deze Default Value die gebruikt wordt om de Booleaanse vergelijking te testen).
Dit is het resultaat.
Booleaanse vergelijkingen combineren
Maar stel, het punt moet niet enkel groter dan of gelijk zijn aan 0, het moet ook kleiner dan of gelijk zijn aan 10.
We hebben dus 2 condities:
Groter dan of gelijk aan 0 en kleiner dan of gelijk aan 10
Condities combineren kan met volgende logische operatoren (Booleaanse operatoren).
De meest gebruikte zijn:
Merk op dat alle pinnen (in- en uitvoer) van datatype Boolean zijn en dat u via Add pin meerdere pinnen kunt toevoegen.
Onderstaande tabel toont de resultaten (waar of onwaar) van de combinaties.
Vergelijking 1 | Vergelijking 2 | 1 AND 2 (1 && 2) | 1 OR 2 (1 || 2) | NOT 1 (!1) | 1 XOR 2 (1 ^ 2) |
true | true | true | true | false | false |
true | false | false | true | false | true |
false | true | false | true | true | true |
false | false | false | false | true | false |
De oplossing voor “Een punt moet groter dan of gelijk zijn aan 0 en moet kleiner dan of gelijk aan 10” is:
- Maak bovenstaande code en test deze uit door de Default Value van de variabele Punt te wijzigen.
De And-operator en de OR-operator zijn steeds beiden mogelijk als oplossing maar werken juist het omgekeerde. Dus het probleem “Een punt moet groter dan of gelijk zijn aan 0 en moet kleiner dan of gelijk aan 10” zou ook als volgt kunnen benaderd worden:
- Maak eveneens bovenstaande code en test deze uit door de Default Value van de variabele Punt te wijzigen.
Merk op hoe dit eigenlijk juist een tegenovergestelde benadering is. Hoewel beide bovenstaande oplossingen mogelijk zijn voelt u de onderste misschien als minder “goed” of minder “natuurlijk” aan.
Maar stel, u wilt het iets complexer namelijk:
Het punt moet groter dan of gelijk zijn aan 0 en kleiner dan of gelijk aan 10 of het punt mag gelijk zijn aan 20 en het mag niet gelijk zijn aan 5
De code zal dus moeten bestaan uit meerdere Booleaanse operatoren.
Oké, hier komt een nadeel van Blueprints naar boven, waar alles tot nu vrij overzichtelijk en duidelijk was, hoop ik, kan een Blueprint al snel vrij complex worden. Als het een troost mag zijn, de code in C++ is niet veel beter: (Punt >= 0 && Punt <= 10) && Punt != 5 || Punt == 20
Organization
Gelukkig kent Unreal een aantal trukjes om de code overzichtelijker te maken.
- Selecteer de Booleaanse vergelijkingen en klik in de selectie met de rechtermuisknop.
Onder Organization ziet u een aantal opties staan.
- Klik op Collapse Nodes en geef het een passende naam (bv. TestPunt).
Alle nodes zijn samengevouwen tot één enkele node, in onderstaand voorbeeld ziet u eveneens de Tooltip die verschijnt als u er de muis over beweegt.
Merk op dat ik eventueel ook de variabele Punt had kunnen mee selecteren en mee opnemen in de samenvoeging.
- Als u de samengevouwen node dubbelklikt om te openen ziet u de volledige code staan.
Merk op dat u in het tabblad zit met de naam van de samengevouwen nodes en dat u met het pijltje naar links kunt terugkeren naar Level Blueprint.
- Keer terug naar Level Blueprint door op het pijltje naar link te klikken.
Oké, onze code is overzichtelijker maar, het kan beter.
Wanneer u deze code nergens anders meer herhaalt is de bovenstaande oplossing voldoende, maar stel dat u op een andere plaats in de code het punt opnieuw wilt testen, dan zal u de code opnieuw moeten maken, tenzij… u er een functie van maakt.
Eerst moeten de samengevouwen node weer uitgeklapt worden.
- Klik met de rechtermuisknop in de samengevouwen node TestPunt en kies voor Expand Node.
U ziet weer de originele code uitgevouwen.
- Selecteer de Booleaanse vergelijkingen en klik in de selectie met de rechtermuisknop.
Ik zou kunnen kiezen voor Collapse to Function, maar ik ga dit niet doen, omdat ik niet echt het gewenste resultaat krijg. Ik ga immers, net zoals daarnet met Collapse to Node, 4 verbindingen behouden vanuit de variabele Punt en hoewel dit niet verkeerd is, is dit toch ook niet echt handig.
Ik verkies om de functie volledig zelf te bouwen.
Functie TestPunt
- Maak een nieuwe functie aan via Add Function in My Blueprint-panel.
- Geef het de gewenste naam (bv. TestPunt).
Als invoer voor de functie moeten we een Punt hebben van het type Integer. Als uitvoer een Resultaat van het type Boolean.
- Voeg in het Details-panel een Input-parameter toe met als naam Punt (mag ook een andere naam zijn) en als datatype Integer.
- Voeg in het Details-panel een Output-parameter toe met als naam Resultaat (mag ook een andere naam zijn) en als datatype Boolean.
- Ga opnieuw naar de Event Graph (via het tabblad).
- Knip en plak de Booleaanse vergelijkingen van de Event Graph naar de functie TestPunt.
- Maak de nodige verbindingen binnen de functie TestPunt.
- Ga opnieuw naar de Event Graph (via het tabblad).
- Klik met de rechtermuisknop in de Level Blueprint en zoek de functie TestPunt.
- Maak de nodige verbindingen binnen de functie TestPunt. De variabele Punt gaat naar de Input-pin Punt en de Output-pin Resultaat gaat naar Print String. Vergeet niet de Exec-pinnen mee te verbinden.
De code oogt weer overzichtelijk en we hebben nu een functie TestPunt die we kunnen en gaan hergebruiken.
Nu we wat meer inzicht hebben in wat Booleaanse vergelijkingen zijn gaan we nu zien hoe ze de Control Flow kunnen beïnvloeden.
Selectie
Een selectie stuurt het programma een specifieke richting uit dit kan zijn op basis van de uitkomst van een Booleaanse expressie (Branch), op basis van de waarde van een input (Switch) of gewoon al flip floppend (FlipFlop).
Branch
Een Branch laat twee wegen toe, een True en een False, naargelang de uitkomst van een Booleaanse vergelijking.
Hebt u al enige programmeerervaring dan zal u dit misschien herkennen als een IF-statement, en dit klopt volledig.
In bovenstaand voorbeeld hebben we een punt getest en het resultaat getoond. Het getoonde resultaat was echter simpelweg het woordje True of False. Hieronder ziet u de versie met de aangemaakte functie.
Heel handig is dit niet en heel vaak zal u naargelang de het resultaat (True of False) iets anders willen uitvoeren.
Bijvoorbeeld, bij True moet de tekst komen “U hebt een correct punt gekregen.”, bij False de tekst “U hebt een ongeldig punt gekregen!”.
Om dit op te vangen hebt u een Branch nodig. U ziet de oplossing hieronder.
Merk op dat als u het uitgevoerde programma en de code samen in beeld brengt u effectief de flow kunt zien.
Dit is heel handig om te Debuggen (fouten in de code te achterhalen).
Weet dat u altijd één Branch minder zal hebben dan het aantal mogelijke uitkomsten.
Stel u wilt weten of een punt positief, negatief of Nul is. Dit zijn dus 3 mogelijke uitkomsten, u zal dus 2 Branches nodig hebben. De tweede Branch wordt vaak verbonden aan de False kant van de eerste Branch. U ziet de oplossing hieronder.
Nog een voorbeeld, we simuleren een dobbelsteen worp.
- Bij 1 moet de tekst “Dramatische worp!” komen.
- Bij 2 moet de tekst “Slechte worp.” komen.
- Bij 3 4 of 5 moet de tekst “Normale worp.” komen.
- Bij 6 komt de tekst “Schitterende worp.”.
4 mogelijke uitkomsten dus 3 branches. De oplossing is als volgt.
Merk op dat ik de Random Integer in Range functie gebruik om de dobbelsteenrol te simuleren.
Merk ook op dat code al heel snel, en hier zijn er nog maar 4 opties, vrij complex kan worden, is er dan echt geen betere methode dan Branches om dit aan te pakken? Ja, maak gebruik van een Switch.
Switch
Switch is eveneens een selectie die te complexe Branches kan vereenvoudigen. Switch komt in vele vormen.
We beperken ons hier tot de Switch on Int en de Switch on String.
Switch on Int
Laten we bovenstaand voorbeeld (de dobbelsteenworp) hernemen en uitwerken gebruikmakend van Switch on Int.
Herwerken we dit met een Switch on Int dan wordt het:
Merk op dat deze Switch on Int beperkt is in zijn flexibiliteit. Hij is enkel in staat opeenvolgende getallen, startend bij een Start Index, die u kunt ingeven in het Details-panel, als opties aan te bieden. De Default-pin kan gebruikt worden om gelijk welke andere waarde op te vangen, als een soort foutmelding (in ons voorbeeld is dit niet van toepassing omdat de waarden hoedanook beperkt zijn tussen 1 en 6).
Desalniettemin is de code toch al een heel stuk overzichtelijker.
In bovenstaand voorbeeld weten we echter niet hoeveel we geworpen hebben. Ik heb nu de code herwerkt om ook het resultaat van onze dobbelsteenworp te kunnen weergeven. Ik ga hiervoor 2 variabelen gebruiken. Een variabele Dobbelsteen van het type Integer om de dobbelsteenworp bij te houden en een variabele Worpomschrijving van het type String.
Laten we dit even ontleden.
- Random Integer in Range bepaalt een willekeurig getal tussen 1 en 6.
- Dit willekeurige getal wordt toegewezen (SET) aan de variabele Dobbelsteen.
- Deze variabele Dobbelsteen wordt vervolgens gebruikt om via Switch on Int de gewenste tekst te bepalen.
- Deze gewenste tekst wordt dan toegewezen (SET) aan de variabele Worpomschrijving. Dit wordt gehaald voor iedere mogelijk setting (hier dus 4 keer).
- De beiden variabelen Dobbelsteen en Worpomschrijving worden samengevoegd tot een zin via Append.
- Deze zin wordt geprint via Print String.
Verbind ook alle nodige Exec-pins.
Switch on String
Switch on String werkt zoals Switch on Integer maar gebruikt een String als input.
Een belangrijk verschil is dat de pinnen niet automatisch worden aangemaakt op basis van een index maar dat u deze zelf moet ingeven via Pin Names – Adds Element in het Details-panel (of via Add pin).
Hieronder heb ik de namen van mijn kinderen toegevoegd aan een Switch on String. Ik verkies dat de vergelijking niet “Is Case Sensitive” is (niet hoofdletter gevoelig).
Onderstaande code demonstreert dit door de waarde van een variabele MijnKind te gebruiken om de gewenste uitvoer te bepalen.
Merk op dat de Default hier gebruikt wordt voor alle andere mogelijke waarden van de variabele MijnKind.
FlipFlop
FlipFlop is simpel, de eerste keer wordt A uitgevoerd, de volgende keer B, de volgende keer weer A, dan weer B,…. Bijkomend kan via de Boolean-pin Is A gecontroleerd worden welke richting A of B ingeslagen is.
Onderstaande video toont een praktisch gebruik om via FlipFlop een lamp aan en uit te doen.
Onderstaande FlipFlop verandert het materiaal.
Iteratie
Een Iteratie is een lus, een herhaling.
Een lus kan je een vast aantal keer uitvoeren of kan uitgevoerd zolang een Booleaanse vergelijking True is.
De ForLoop, ForLoopWithBreak en WhileLoop zijn algemeen en komen ook in andere programmeertalen aan voor. Zij worden hier dan ook besproken.
DoN en DoOnce zijn eigen aan Unreal Engine, ik ga me dan ook beperken tot het tonen van een voorbeeld van hoe ze in Unreal Engine kunnen gebruikt worden.
ForLoop
De ForLoop is een lus die een vast aantal keer zal worden uitgevoerd.
Het aantal keer dat de lus wordt uitgevoerd wordt bepaald door het verschil tussen Input-parameters First Index en Last Index. De Output-parameter Index geeft de huidige index aan.
De nodes (instructies) komende na Loop Body worden herhaald.
Eens de herhaling gedaan wordt verder gegaan met de nodes (instructies) die volgen op Completed.
Onderstaand voorbeeld herhaalt een lus 10 keer en print de index uit. Na de 10 herhalingen verschijnt een melding dat de “De lus is beëindigd!”.
Let op, tijdens het uitvoeren van een lus wordt er niets anders uitgevoerd. Een te lange lus kan dus uw programma laten “hangen”, wat in een 3D omgeving zoals Unreal Engine erger is dan in een gewone administratieve omgeving. Onderstaande video demonstreert dit.
ForLoopWithBreak
De ForLoopWithBreak biedt de mogelijkheid de lus voortijdig te onderbreken. Deze onderbreking dient te komen van binnen de lus.
Onderstaande video bouwt verder op de voorgaande en voegt een ForLoopWithBreak toe.
WhileLoop
De WhileLoop wordt uitgevoerd zolang een Booleaanse vergelijking True is. Belangrijk is dat de waarde die getest wordt binnen de WhileLoop kan wijzigen naar een waarde die de WhileLoop beëindigt, zo niet komt in een oneindige lus terecht.
Onderstaand voorbeeld genereert een willekeurige waarde tussen -100 en 100 voor de Variabele Punt. Negatieve waarden worden echter niet aanvaard, vandaar dat er getest wordt op het al dan niet negatief zijn en dat er nieuwe waarden blijven gegenereerd worden zolang deze negatief blijven.
Merk op dat:
- de waarde van de variabele Punt binnen de lus eveneens verandert.
- dat de waarde die getest wordt SET Punt eveneens het eindpunt is binnen de lus.
Onderstaand voorbeeld doet ongeveer hetzelfde maar test nu op basis van onze functie TestPunt.
Merk op dat:
- de functie TestPunt een True levert als het geteste punt een correct punt is. Omdat de WhileLoop test zolang de Booleaanse vergelijking True is en er in dit geval moet herhaald worden zolang er geen correct punt gevonden is moet de NOT-operator gebruikt worden. Dus er wordt herhaald zolang het gegenereerde punt geen correct punt is.
- de geteste variabele TestResultaat van het datatype Boolean is.
- binnen de Loop Body de code die het punt genereert volledig herhaalt wordt.
- de waarde die getest wordt Testresultaat eveneens het eindpunt is binnen de lus.
Tevens staat er ook wat extra code bij om een passende melding te genereren en weer te geven via Print String.
DoN en DoOnce
DoN en DoOnce zijn eigen aan Unreal Engine, ik ga me dan ook beperken tot het tonen van een voorbeeld van hoe ze in Unreal Engine kunnen gebruikt worden.
Extra – Control Flow technieken eigen aan Unreal Engine
Gewoon even ter info en om “volledig” te zijn een paar typische Control Flows voor Unreal Engine.
Wie wilt kan nog even verder lezen en de bijhorende video’s bekijken.
? IsValid
? IsValid wordt in Unreal Engine vaak gebruikt om na te gaan of een object beschikbaar en bruikbaar is.
Naargelang het object Is Valid of Is Not Valid kan de Control Flow een andere weg volgen.
Gate
Het Open of Close zijn van de Gate bepaalt of de achterliggende code (na Exit) al dan niet wordt uitgevoerd.
Behandelde Basiscompetenties uit de module ICT Programmeren – Specifieke ontwikkelomgeving: eenvoudige functionaliteiten
- IC BC017 – kan ICT veilig en duurzaam gebruiken
- IC BC234 – kan de basisprincipes van programmeren in een specifieke ontwikkelomgeving toepassen
- IC BC236 – kan eenvoudige wijzigingen aan een programma aanbrengen
- IC BC241 – kan een programma in een specifieke ontwikkelomgeving maken
- IC BC250 – kan bij het programmeren in functie van een specifieke ontwikkelomgeving, een juiste logica volgen