Inhoud
- Wat vooraf ging
- Inleiding
- Communicatie
- Damage
- Directe Blueprint communicatie
- Blueprint Interfaces Message Calls
- Event Dispatchers
- Project Twin Stick Shooter – Damaging
- Masterclass – Blueprint Communications
Wat vooraf ging
- U bent vertrouwd met object georiënteerd programmeren.
- U bent vertrouwd met het Gameplay Framework.
- Een HUD aanmaken.
Inleiding
Blueprint communicatie is het overbrengen van gegevens (data, waarden van variabelen,…) van een blueprint naar een andere blueprint.
Situering van deze handleiding binnen Unreal Engine
Communicatie
Communicatie tussen Blueprints kan in twee richtingen verlopen:
- Object 1 zegt het andere object 2 dat het iets moet doen.
- Object 1 vraagt het andere object 2 naar een status, de waarde van een eigenschap/variabele, het resultaat van een functie, …
Wat u vooraf moet weten
- Blueprint Communicatie omvat steeds een verzendende Blueprint (Sender), die de communicatie opstart, en één of meer ontvangende Blueprints (Receivers). Ik spreek vanaf nu van Sender en Receiver.
- Unreal Engine kent geen broadcasting, een verzendende Blueprint verzend naar iedereen. Dit kan dus niet!
- Blueprint Communicatie vereist altijd een referentie naar (toegang tot) de andere Blueprint, hetzij aan de kant van de Sender of aan de kant van de Receiver.
- Alle communicatie is, technisch gezien, steeds eenrichting. Oké, er kan over en weer gegaan worden, in een soort vraag en antwoord, maar ook dan gaat het om een aantal opeenvolgende communicaties die ieder op zich telkens in één richting gaan.
Welke Blueprint Communicatie methoden zijn er?
Er zijn 3 methoden voor Blueprint Communicatie:
- Directe communicatie (via Referencing en Casting)
- Blueprint Interface Message Calls
- Event Dispatchers
Wanneer gebruik je welke methode?
Om het simpel te houden kunt u zich een paar vragen stellen, naargelang de antwoorden kiest u dan de gepaste methode.
Verstuurt de Sender informatie, data naar de Receiver(s)? Indien dit het geval is:
Luistert de Receiver(s) actief naar de Sender om te weten wanneer deze “iets doet”? Indien dit het geval is:
Kan de Sender een referentie naar de Receiver(s) opvragen? Indien dit het geval is;
Kan enkel de Receiver(s) een referentie naar de Sender(s) opvragen? Indien dit het geval is;
Oké, misschien is dit nu nog niet meteen duidelijk maar hopelijk wordt het dat als we dieper ingegaan zijn op deze methoden. Het kan echter geen kwaad dit momenteel al in het achterhoofd te hebben.
We gaan nu de methoden in detail bekijken.
Damage
Voor we Blueprint Communicatie bespreken zetten we eerst een Damage-systeem op dat we gaan gebruiken.
We gaan gebruik maken van het ingebouwde Damage-systeem om “damage” aan de gezondheid van ons Character toe te brengen. Nadien gaan we de mogelijkheid voorzien om de gezondheid van het karakter te herstellen.
We maken eerst een Damage-locatie.
Om dit aan te maken:
- Voeg een Cube toe aan het level. U vindt de Cube in het Modes-panel onder Basic.
- Geef de Cube een passende naam (bv. DamageCube).
- Plaats de Cube op de gewenste locatie. Weet dat om een object op de grond te plaatsen u het eerst boven de grond plaatst en dan met de End-toets hem positioneert op de grond.
- Ik heb de Cube op de Z-as geschaald naar 0,2 om hem minder hoog te maken.
- Voeg vervolgens een Text Render toe. U vindt de Text Render in het Modes-panel, u zoekt naar Text.
- Positioneer de Text Render voor de Cube.
- Roteer de Text Render over de Z-as met -180°.
- Geef de Text Render de gewenste tekst “Damage” en een passende naam (bv. DamageText).
- Geef de Text Render een opvallende rode Text Render Color.
- Voeg nu een Pain Causing Volume toe, u vindt dit in het Modes-panel onder Volumes. Een Pain Causing Volume bevat de functionaliteit om Damage aan te brengen.
Een Paint Causing Volume is enkel zichtbaar in de Editor en niet als u het programma draait, vandaar die vlakke Cube die we gaan gebruiken als indicator voor het Pain Causing Volume.
- Plaats het Pain Causing Volume over de DamageCube.
- Ik heb het geschaald naar 0,75.
- Wijzig de eigenschap Damage per sec van het Pain Causing Volume naar 10.
Zoals reeds aangehaald, Unreal Engine komt met een Damage-systeem maar niet met een vorm van Health-systeem. Dit gaan we zelf opzetten door een variabele Health (of een andere naam, de naam doet er niet toe) van het type Float toe te voegen aan ons ThirdPersonCharacter.
- Klik in de World Outliner op Edit ThirdPersonCharacter om in de Blueprint van de ThirdPersonCharacter te komen.
- Klik op + Variable om een variabele Health toe te voegen.
- Wijzig het type in Float.
- Klik op Compile.
- Geef de variabele Health een Default Value van 100.
- Ga naar de Event Graph van de ThirdPersonCharacter Blueprint. Scroll naar een lege ruimte.
Hit en Damage events
- Event Hit wordt getriggerd iedere keer er een Collision is tussen twee actors waarvan één van beiden Simulation Generates Hit Events op true heeft staan.
- Event Any Damage wordt getriggerd wanneer Damage is aangebracht.
- Event Point Damage wordt getriggerd wanneer gerichte Damage is aangebracht door een projectiel of een wapen bv. een kogel.
- Event Radial Damage wordt getriggerd wanneer radiala Damage is aangebracht door bv. een explosie.
De Pain Causing Volume zal bij overlapping een Damage-event genereren. Deze komen in drie vormen AnyDamage, RadialDamage en PointDamage. De verschillen liggen hem in de vorm van Damage dat is aangebracht en bijgevolg worden andere opties aangeboden. PointDamage biedt bv. de mogelijkheid om aan te geven welke “Bone” van het lichaam geraakt is.
We houden het eenvoudig en kiezen voor de AnyDamage-event. Deze AnyDamage bevat de toegebrachte Damage-pin (we hebben die op 10 ingesteld) en deze Damage gaan we aftrekken van de Health-variabele. Onderstaande programmeercode zou geen geheimen meer mogen bevatten.
- Compile, Save en Play.
U ziet dat de Health iedere seconde met 10 afneemt als u binnen de Pain Causing Volume (die onzichtbaar is vandaar die vlakke Cube) gaat staan.
Maar hebben we nu reeds communicatie tussen Blueprints? Nee, we hebben immers enkel en alleen binnen de ThirdPersonCharacter Blueprint gewerkt en communicatie vraagt minstens 2 Blueprints! We gaan het voorbeeld moeten uitbreiden met minstens 1 andere Blueprint voor we kunnen communiceren tussen Blueprints.
Onderstaande video berekent de toegebrachte Damage na een val.
Directe Blueprint communicatie
Directe Blueprint Communicatie is de eerste methode die we gaan bespreken en die we eigenlijk al meermaals gebruikt hebben in vorige handleidingen. Iedere keer we tot nu werkten met meerdere Blueprints en we informatie hebben opgevraagd uit een andere Blueprint hebben we Directe Blueprint Communicatie toegepast.
Wat hebt u nodig voor directe communicatie?
- U weet welk object de communicatie legt, u kent de Sender.
- U weet hoe u een referentie kunt leggen naar de Receiver(s).
- U weet welke data, waarden van variabelen/eigenschappen of functionaliteiten van de Receiver(s) u kunt opvragen of wijzigen.
Concreet, “alles” is eigenlijk gekend van beiden objecten van de communicatie. Als u teveel vragen moet stellen (bv. Als het een deur is doe dit, als het een venster is doe dat,…), als u dus te weinig direct weet en teveel moet testen om te weten dan is directe communicatie wellicht niet de geschikte methode.
Hoe verloopt de communicatie?
- De Sender vraagt en krijgt toegang tot de Receiver, hij legt dus een referentie naar de Receiver. Dit kan gebeuren via een toekenning (Assign) bij bv. de BeginPlay-event of bij Overlap-events of via Gets (Get Player Controller, Get All Actors of Class,…).
- De Sender zal eventueel een Casting naar het gewenste type uitvoeren om zeker te zijn dat het communiceert met het gewenste object (bv. via Cast To ThirdPersonCharacter,…).
Vergelijk het met telefoneren. Onder punt 1 typt u het gewenste nummer en maakt u de verbinding. Onder punt 2 vraagt u nog eens “Ben ik bij…” en slechts indien de persoon aan de andere kant van de lijn dit positief beantwoordt, u hebt dus de juiste persoon aan de lijn, pas dan gaat u verder naar punt 3. Hebt u niet de juiste persoon aan de lijn, dan legt u waarschijnlijk gewoon de hoorn in en doet u niets. - Als de casting gelukt is, en we zeker zijn dat we met het juiste object aan de slag zijn, kan de Sender:
- Data, waarden van eigenschappen/variabelen van de Receiver opvragen (Get).
- Data, waarden van eigenschappen/variabelen van de Receiver wijzigen (Set).
- Functies, functionaliteiten van de Receiver gebruiken.
Casting
Als u zeker bent dat de referentie van het juiste type is dan is een casting niet nodig!
Een eventuele casting verloopt als volgt:
Let op, in bovenstaande voorbeeld is de Blauwe lijn de aan te bevelen Control Flow omdat we dan verder werken als het type waarnaar gecast is (in dit voorbeeld als een object van het type Pawn). Bij If Cast succeeds weten we dat My Actor ook een Pawn is maar gebruiken we het verder als Actor (en niet direct als Pawn). We moeten we nog steeds deze Exec-pin gebruiken om de Control Flow te behouden (zie verderop voor een praktisch voorbeeld).
Indien de Cast Failed dan kunt u opnieuw een casting uitvoeren en een andere casting uitproberen.
U weet wel (zing allen maar mee):
Is het een vliegtuig? Is het een vogel? Nee, dat is het niet. Het is Mega Mindy die je aan de hemel ziet!
Weet dat als u teveel van deze castings moet uittesten, Directe Blueprint Communicatie wellicht niet de beste methode is.
Casting en Object Georiënteerd Programmeren
Laat ons nog even stilstaan bij Object Georiënteerd Programmeren en meer bepaald de overerving en de gevolgen voor Casting.
Veronderstel onderstaande structuur:
- Object
- Actor
- Pawn
- Character
- Wendy
- Michael
- Peter
- John
- Character
- Pawn
- Actor
Extra punt voor zij die de karakters herkennen!
In dit voorbeeld is Wendy een Object (ze zal dit niet graag horen, dus vertel dit niet verder) maar ook een Actor, Pawn, Character en ook nog eens Wendy zelf.
Een Casting naar het “type” Wendy heeft dus toegang tot alle eigenschappen/variabelen, functionaliteiten,… van het type Object, Actor, Pawn, Character en Wendy maar niet tot de typische eigenschappen/variabelen, functionaliteiten,… van Michael, Peter of John.
Het is dus belangrijk om naar het juiste niveau te casten. Een casting naar een te hoog niveau, bv. Pawn geeft u toegang tot eigenschappen/variabelen, functionaliteiten,… van de types Object, Actor en Pawn maar geeft u geen toegang tot eigenschappen/variabelen, functionaliteiten,… van de lagere niveau’s Character, Wendy, Michael, Peter of John.
Het is dus belangrijk dat als u eigenschappen/variabelen, functionaliteiten,… toevoegt u dit op het meest geschikte niveau doet want dit beïnvloedt de casting.
Als leidraad kunt u onderstaand gebruiken:
- Gemeenschappelijke eigenschappen/variabelen, functionaliteiten behoren in een hogere (Parent) klasse te staan. Bv. Wendy, Michael, Peter en John kunnen allen “vliegen”, dus komt de functionaliteit Vliegen in de klasse Character te staan.
- Doe geen dubbel werk. Als u merkt dat u dubbel werk verricht dan moet dus wellicht in een hogere (Parent) klasse komen.
Voorbeeld
We gaan nu verder werken aan ons bovenstaand Damage-voorbeeld en een HealingVolume aanmaken, dat, net zoals het reeds bestaande Pain Causing Volume, een Box zal bevatten die bij overlapping (Collision) onze Health zal herstellen.
We gaan hiervoor een nieuwe Blueprint aanmaken.
- Ga in de Content Browser naar de folder waar u de Blueprint wil plaatsen (bv. Content – ThirdPersonBP – Blueprints).
- Klik rechts in deze folder en kies voor Blueprint Class.
- Kies als Parent Class de klasse Actor (een object dat we in het level kunnen plaatsen).
- Geef het een passende naam (bv. HealingVolume).
- Dubbelklik om te openen.
Die witte bol die u ziet is de DefaultSceneRoot en bepaalt de locatie van de actor in het level, deze witte bol is niet zichtbaar in het level. We moeten deze dus uiteraard behouden.
We moeten nu een Box Collision toevoegen aan de Blueprint.
- Klik op Add Component.
- Zoek naar Box en u vindt Box Collision.
- Klik het aan en geef het eventueel een passende naam (bv. Volume).
De Box heeft een Shape die 32 groot is in de X, Y en Z-as.
- Wijzig de Shape – Box Extent naar 200 voor X, Y en Z. Dit maakt de Box even groot als het Pain Causing Volume.
De componenten zijn toegevoegd, nu moeten we de functionaliteit toevoegen.
- Ga naar de Event Graph van de Blueprint.
We houden het simpel, bij overlapping moet de variabele Health van het ThirdPersonCharacter terug geplaatst worden op 100.
De ActorBeginOverlap-event staat reeds klaar en kunnen we gebruiken voor de overlapping.
We moeten dus een variabele wijzigen die zich bevindt in een andere Blueprint, namelijk de ThirdPersonCharacter Blueprint. Hier zal dus Blueprint Communicatie nodig zijn!
Wat waren de 3 stappen weer?
- Een referentie naar het andere object, de andere Blueprint.
- Eventueel een casting naar het juiste type.
- Data opvragen via Get of wijzigen via Set of de resultaten van functionaliteiten opvragen.
Referentie
Wel de referentie, die hebben we reeds via de Other Actor-pin van de Event ActorBeginOverlap.
Eventueel Casting
Is casting nodig?
- Wel, trek een verbindingslijn vanuit de Other Actor-pin.
- Zoek naar de eigenschap/variabele Health die u wilt wijzigen.
U vindt niets!
Dus is casting nodig!
Naar wat gaat u casting?
Well naar die andere Blueprint waarin de variabele Health zich bevindt, we weten dat deze zich bevindt in de ThirdPersonCharacter Blueprint
- Trek een verbindingslijn vanuit de Other Actor-pin.
- Zoek naar Cast to ThirdPersonCharacter.
Data opvragen
Wat is nu het verschil tussen de bovenste Exec-pin en de As Third Person Character-pin?
- Trek een verbindingslijn vanuit de bovenste Exec-pin, zoek naar Health en u ziet dat u, via Third Person Character, toegang hebt tot Set Health. U vertrekt dus nog steeds vanuit de Actor.
- Trek nu een verbindingslijn vanuit de As Third Person Character-pin, zoek naar Health en u ziet dat u direct toegang hebt tot de variabele zelf en dit voor zowel een Get als een Set.
De tweede optie is dus de betere.
We wensen de Health op 100 te zetten dus:
- Trek een verbindingslijn vanuit de As Third Person Character-pin, zoek naar Health en kies voor Set. Geef als waarde voor Health 100 in. Let op, u moet nog steeds de verbinding maken met de Exec-pin om de Control Flow te behouden. Enkel als test om te debuggen voeg ik er nog een Print String aan toe (in een groen kleurtje).
- Compile en Save en ga terug naar het level, de ThirdPersonExampleMap.
Zoals hierboven voegen we opnieuw een Cube toe (bv. met de naam HealingCube) en een nieuwe Text Render met een passende tekst (bv. Healing) in een groen kleurtje.
- U doet dit het gemakkelijkste door de DamageCube en DamageText te selecteren en vervolgens met de Alt-toets ingedrukt te verslepen naar de gewenste plaats. Vervolgens wijzig je de namen en de gewenste eigenschapen.
- We plaatsen dan ons HealingVolume over de HealingCube en HealingText en schalen het zodat het er mooi over staat. Weet dat die witte bol niet te zien is in het eigenlijke programma
- Compile, Save en Play om dit uit te testen.
Zo, dit was een voorbeeld van directe Blueprint communicatie tussen 2 Blueprint.
Zoals reeds gesteld, directe communicatie is heel praktisch als het aantal castings beperkt is. Er moeten dus andere, betere technieken zijn om een groot aantal potentiële castings op te vangen. Dit kan via Blueprint Interfaces Message Calls.
Blueprint Interfaces Message Calls
Blueprint Interfaces worden gebruikt wanneer verschillende object op een andere manier reageren bij eenzelfde functionaliteit.
Stel het bekende spel The Settlers, stel dat om 8 uur in de ochtend de heraut de boodschap afroept “Aan het werk”! Deze boodschap “Aan het werk” kan voor iedere settler anders zijn, sommige settlers gaan brood bakken, anderen gaan vissen, anderen bouwen een huis,… en sommigen doen misschien gewoon niets. Dit kan uitgewerkt worden via Blueprint Interfaces Message Calls.
- De heraut die de boodschap afroept, de Sender die de boodschappen afroept (messages calls), trekt zich verder niets aan of en hoe de boodschap wordt uitgevoerd. De heraut/Sender verspreidt gewoon de boodschap “Aan het werk”, meer niet.
- Hoe en of de boodschap “aan het werk” uitgevoerd wordt, wordt bepaald door de settlers, de Receivers, de objecten en hun bijhorende Blueprints, zelf.
- De Interface is de lijst met mogelijke boodschappen, message calls, die de heraut/Sender kan afroepen en die begrepen en uitgevoerd kunnen worden door de settlers/Receivers.
- De heraut/Sender kan dus enkel boodschappen afroepen die op de lijst/Interface staan die door de settlers/Receivers kunnen begrepen en al dan niet uitgevoerd worden.
Neem ook ons voorbeeld van “The Force”, de Box, Rots en Vijand reageren allen op een andere manier wanneer “The Force” op hun wordt losgelaten. We hebben dus 3 objecten die allen anders reageren op eenzelfde functionaliteit. We zouden dit kunnen, en gaan dit ook, herwerken door gebruik te maken van Blueprint Interfaces Messages Calls.
Wat hebt u nodig voor Blueprint Interfaces Message Calls?
- Een Interface die enkel de functionaliteiten bevat, de naam en geen code, die kunnen gebruikt worden via Message Calls. Met andere woorden, een lijst met alle mogelijk Message Calls die kunnen verzonden en ontvangen worden. Belangrijk, de interface zelf bevat geen code.
- De Sender die op een gepast moment, bij de uitvoering van een specifieke event bijvoorbeeld, een specifieke boodschap verspreidt, een message call, die voorzien is in de Interface. De Sender hoeft hiertoe de Interface NIET te implementeren.
- De Receivers die de Interface implementeert en de Blueprint Visual Scripting, de programmeercode in een Blueprint, bevat om op zijn specifieke manier te reageren op de message call. Het is belangrijk dat de Receiver de Interface implementeert om weet te hebben van de mogelijke Messages Calls. Gebeurt deze implementatie niet dan is dit op zich geen probleem, het programma zal niet crashen, maar dan zal deze Receiver, dit object, niet kunnen reageren op de Messages Calls die de Interface bevat. Een object dat een Interface implementeert “luistert” constant of er boodschappen geroepen worden die bepaald zijn in de interface. Ontvangen het object zo een boodschap, dan doet het zijn ding (voert het de programmeercode uit die bij deze boodschap hoort).
Hoe verloopt de communicatie?
- Maak eerst een Blueprint Interface die alle Message Calls (functies) bevat. De Blueprint Interface bevat enkel de functienamen (en eventuele in- en voerparameter) maar geen code.
- Iedere andere Blueprint heeft toegang tot iedere Interface en kan optreden als Sender door Messages Calls.
- Indien een object een Message Call moet kunnen ontvangen (het object functioneert als Reveiver) moet de specifieke Interface Blueprint geïmplementeerd worden. Het object zelf bepaalt, via programmeercode, hoe het reageert op de Message call.
De implementatie is hier cruciaal.
- Indien u enkel Interfaces maakt maar deze nooit implementeert zullen de boodschappen, message calls, nooit ontvangen en uitgevoerd worden. Even terug naar onze Settlers, indien de settlers geen weet hebben van het bestaan van de boodschap “Aan het werk” zullen ze ook niet reageren wanneer de heraut “Aan het werk” roept. De herauten mogen constant roepen “Aan het werk”, niets of niemand zal reageren maar de wereld draait verder.
- Objecten die NIET moeten reageren op boodschappen, messages calls, hoeven geen interface te implementeren. Terug naar onze Settlers, objecten als huizen, bloemen, bomen,… moeten niet “Aan het werk” en hoeven dus de Interface die de boodschap “aan het werk” bevat niet te implementeren.
Voorbeeld
We gaan onderstaande Blueprint herwerken, gebruikmakend van Blueprint Interfaces Messages Calls.
Interface – de Interface aanmaken
Eerst gaan we de Interface aanmaken. Dit is een nieuwe Blueprint van het type Interface.
- Ga naar de map waar u uw eigen Blueprints plaatst.
- Klik met de rechtermuisknop in de Content Browser.
- Kies Blueprints – Blueprint Interface.
- Geef het een passende naam, hoewel het niet verplicht is worden Interface Blueprints vaak voorzien van de prefix i (bv. iForce).
- Dubbelklik om het te openen.
- Er staat een functie klaar die u een naam kunt geven (bv. UseTheForce).
Eventueel kunt u nieuwe functies toevoegen via Add New – Function.
Inputs– en Outputsparameters kunnen worden toegevoegd.
Merk op dat het niet mogelijk is om nodes toe te voegen aan de functie UseTheForce. Dat is ook niet nodig, een interface voorziet gewoon de namen van de functies die kunnen geïmplementeerd worden, meer niet, het bevat dus geen code.
Dit is alles wat we nodige hebben, de naam van een functie die we later gepast gaan implementeren.
- Compile en Save.
- Sluit het tabblad.
Receiver – implementatie
Wat gaan we implementeren?
Als u het voorbeeld bekijkt dan merkt u dat er 3 Actors, wanneer ze getraceerd worden, iets uitvoerden:
- PhysicsBox – verplaatste zich omhoog.
- DestructibleRots – verpulverder.
- Vijand – omver geworpen.
De gebruikte code ziet u hieronder:
Deze code gaan we nu implementeren in de Blueprints van de specifieke Actors bij het ontvangen van de Message UseTheForce.
PhysicsBox
- Open de Blueprint van PhysicsBox.
- Ga naar de Event Graph.
- Zoek naar UseTheForce.
U vindt deze wel, maar enkel de Message, de aanroep, maar niet de eigenlijke event en het is de event die we nodig hebben.
Het eerste wat we moeten doen is de eigenlijke Interface beschikbaar maken (let op, dit hoefde niet bij de Sender).
- Klik in de knoppenbalk op Class Settings.
- In het Details-panel onder Interfaces – Implemented Interfaces – Add selecteert u onze interface iForce. Merk op dat u meerdere interfaces kunt toevoegen.
- Compile.
- Zoek naar UseTheForce.
Merk op dat u nu ook de Event krijgt.
- Klik Event Use The Force aan.
- Herwerk, of kopieer en plak, de reeds ontwikkelde code achter
DestructibleRots
- Herhaal de stappen voor het implementeren van de Interface zoals hierboven beschreven voor de DestructibleRots Blueprint.
De code staat hieronder.
Vijand
- Herhaal de stappen voor het implementeren van de Interface zoals hierboven beschreven voor de Vijand Blueprint.
De code staat hieronder.
- Compile, Save en Start.
De Interface, de Messages calls en de Implementatie zijn gedaan en het zou moeten werken.
Onderstaande code doorloopt alle overlappende Actors, gaat na of ze een specifieke interface (hier Interact_Interface) geïmplementeerd hebben. Indien dit het geval is wordt een Interface-functie (hier Interact) uitgevoerd.
Sender – de aanroep (Message call)
Stel, we wensen de functie UseTheForce aanroepen vanuit de ThirdPersonCharacter Blueprint bij het drukken op bv. de G-toets.
- Open de ThirdPersonCharacter Blueprint (bv. via de World Outliner – Edit ThirdPersonCharacter).
- Voeg een G-event toe.
- Trek een verbindingslijn vanuit Pressed.
- Zoek naar UseTheForce (de aangemaakte functie in de interface). U merkt dat u deze meteen vindt als Message.
- Klik op Use The Force (Message).
Merk het envelopje op in de rechterbovenhoek van de node, verwijzend naar een Message Call.
- Compile.
Merk op dat u een foutmelding krijgt.
De reden is dat we geen Target voorzien hebben.
Even een stapje opzij, moest ik alle Settlers willen aanroepen dan zou ik onderstaande code schrijven.
Een Get All Actors From Class, als Actor Class zou ik dan de gewenste klasse (bv. Settlers) toekennen en via een ForEachLoop deze één voor één de Message doorsturen (let op, als u onderstaande code toepast gaat u ook een foutmelding krijgen omdat er geen Actor Class is gespecifieerd).
Dit willen we echter niet, we wensen de boodschap te versturen naar de getraceerde objecten. Die code hebben we reeds uitgewerkt (normaal onder de F-toets). We herwerken die hier nu onder de G-toets. De Target wordt nu Out Hit Hit Actor.
- Compile en Save.
Onderstaande video maakt gebruik van een Interface om de juiste deur met de juiste sleutel te openen.
Event Dispatcher
Een Event Dispatcher is een derde, en laatste methode om communicatie tussen Blueprints te realiseren.
In grote lijnen werkt het als volgt:
- De Sender definieert de Event Dispatcher en Calls, op het gewenste moment, deze Event Dispatcher. Meer doet de Sender niet, definiëren en Callen, de Sender bevat geen verdere programmeercode. Stel, de Sender is de ThirdPersonCharacter Blueprint en bevat een variabele Health. Iedere keer de Health wordt gewijzigd Calls de Sender de Event Dispatcher UpdateHealth. Meer niet.
- De Receivers verBinden zich met de Event Dispatcher, via een referentie naar de betreffende Blueprint waar de Event Dispatcher is gedefinieerd, en luisteren. Als de Sender deze Event Dispatcher Calls schieten de verbonden Receivers in actie en “doen ze hun ding”. Het zijn dus de Receivers die de programmeercode bevatten die moet worden uitgevoerd als de Event Dispatcher geCalled is. Bijvoorbeeld, de HUD kan zich verBinden met de Event Dispatcher UpdateHealth en iedere keer deze EventDispatcher UpdateHealth geCalled wordt doet de HUD zijn “ding”, het aanpassen van de Progressbar die de Health weergeeft.
- Het kan gebeuren dat de Receiver niet langer meer wilt luisteren naar Calls van de Event Dispatcher, dat er niet langer een verbinding moet zijn, dan kan de Receiver zich Unbinden.
- De Sender kan meerdere Calls bevatten en er kunnen meerdere Receivers zijn met elk hun eigen programmeercode.
Wat hebt u nodig voor Event Dispatching?
- Een Sender die de Event Dispatcher definieert en Calls.
- Een Receiver die via een referentie naar de Sender de Event Dispatcher Bindt en de programmeercode bevat die moet uitgevoerd worden als de Sender de Event Dispatcher Calls.
Hoe verloopt de communicatie?
- De Sender definieert de Event Dispatcher (in de My Blueprint-panel) en zijn eventuele inputs in het Details-panel.
- De Sender Calls, waar nodig, de Event Dispatcher.
- De Receiver legt een referentie naar de Sender.
- Met de referentie gelegd (ver)Bind de Receiver zich met de Sender en luistert naar mogelijke Calls.
- De Binding leidt tot een Custom Event dat de programmeercode bevat die uitgevoerd wordt bij een Call.
- Indien de Receiver niet meer wenst te luisteren kan hij zich Unbind(en). Er is ook de optie om meteen alle verbindingen te verbreken met Unbind All.
Event Dispatcher Opties
Eens een Event Dispatcher aangemaakt kunt u hem in de Blueprint slepen. Dit geeft u volgende opties (hier voor een Event Dispatcher UpdateHealth die we meteen gaan aanmaken):
- Call – De Sender roept, “broadcast”, dat de Event plaats vindt naar alle eventuele Receivers.
- Bind – De Receiver verbindt zich met de Event Dispatcher en start met luisteren naar mogelijke Calls. Een Bind heeft een pin naar de Custom Event die de code bevat die moet worden uitgevoerd.
- UnBind – De Receiver stopt met luisteren naar de Event Dispatcher Calls.
- Unbind all – Alle Receivers stoppen met luisteren naar de Event Dispatcher Calls.
- Event – maakt een Custom Event met dezelfde “signature” (inputs) als de Event Dispatcher. Deze Event bevat de uit te voeren programmeercode.
- Assign – is een Bind met een direct gekoppelde Event.
Voorbeeld
We vertrekken van het voorbeeld van de HUD-handleiding.
In dit voorbeeld werden een aantal variabelen aan de ThirdPersonCharacter Blueprint toegevoegd, waaronder de variabele Gezondheid. De waarde van de variabele Gezondheid werd via de HUD weergegeven in een ProgressBar.
We hebben toen reeds de opmerking gemaakt dat de Bindings in de HUD iedere Tick wordt geüpdatet en dat dit toch wel teveel van het goede is. Veel efficiënter zou zijn dat de HUD enkel dient geüpdatet indien de waarde van de variabele Gezondheid wijzigt. We hebben in die handleiding reeds een eenvoudige oplossing aangereikt om te updaten zonder Binding, maar we bespreken nu de meest efficiënte oplossing.
De meest efficiënte oplossing kunnen we bekomen via een Event Dispatcher. Merk op, ik heb het hier over de Binding van een eigenschap aan een variabele, dit is iets anders dan de Bind-optie hierboven besproken.
- Open het project, of maak het eventueel aan, uit de HUD-handleiding. Ik vertrek vanuit de versie met de Binding.
- Ga naar de ThirdPersonCharacter Blueprint.
We hebben reeds onderstaande ThirdPersonCharacter Blueprint.
Definieer de Event Dispatcher
Voeg een Event Dispatcher toe met de naam bv. UpdateHealth.
De Event Dispatcher UpdateHealth heeft de waarde van de variabele Gezondheid (een Integer) als Input nodig, u kunt dit instellen in het Details-panel.
Iedere keer als de waarde van de variabele Gezondheid wijzigt gaan we de Event Dispatcher UpdateHealth aanroepen (Call) zodat andere Blueprints, zoals de HUD, “horen” dat de Gezondheid gewijzigd is en de nodige acties kunnen ondernemen.
Call de Event Dispatcher
Als we onze huidige Blueprint nog eens bekijken zien we dat we tweemaal de Gezondheid Setten. Eenmaal bij Event BeginPlay en bij het drukken op de Z-toets.
We hebben dus tweemaal de variabele Gezondheid geüpdatet.
Hier kunnen (moeten) we dus tweemaal de Event Dispatcher UpdateHealth aanroepen (Call).
- Verwijder de Print String en de conversie in de Event BeginPlay.
- Sleep de Event Dispatcher UpdateHealth in de Blueprint.
- Selecteer Call.
- Verbindt de Call met SET Gezondheid.
- Herhaal dit voor de Z-event.
- Compile en Save.
U hebt nu onderstaande Blueprint.
Verbinden met de Receivers
We gaan nu de nodige Receivers Blueprints verbinden, in ons voorbeeld is dit slechts een Widget Blueprint (die gebruikt wordt om de HUD te bouwen), maar weet dat er meerdere Receivers kunnen zijn!
- Ga naar de gebruikte Widget Blueprint.
Normaal heeft deze een:
- TitelNaam verbonden met de variabele Naam van het ThirdPersonCharacter.
- Gezondheidbar verbonden met een berekening die het percentage gezondheid weergeeft.
Zoals gezegd worden Bindings iedere Tick uitgevoerd, dus ook de Binding met TitelNaam. Niet dat dit zo erg is maar het is ook niet erg efficiënt. We kunnen deze ook optimaliseren via een Event Dispatcher, maar voor het gemak gaan we de TitelNaam gewoon wissen en ons focussen op de GezondheidBar.
- Verwijder TitelNaam uit de Designer (selecteren en Delete-toets).
De GezondheidBar is eveneens verbonden, ditmaal via een berekening.
Laten we gewoon, als herinnering, een kijkje nemen naar deze berekening, u vindt ze Graph.
- Keer terug naar de Designer.
- Selecteer de GezondheidBar.
- Verwijder de Binding in het Details-panel onder Progress – Percent.
- Compile en Save.
- Start het programma (Play-knop).
U zal enkel een lege GezondheidBar zien, we hebben immers de Bindings, die niet efficiënt werken wegens een update iedere Tick, verwijderd.
We gaan nu opnieuw verbinden maar ditmaal met de Event Dispatcher.
- Keer terug naar de gebruikte Widget Blueprint.
- Ga naar Graph.
- Open Event Construct.
Ook hier staat al wat programmeercode klaar van de vorige handleiding, namelijk het opvragen de referentie naar ons ThirdPersonCharacter, dit komt goed uit want we hadden deze toch nodig.
- Sleep nu een verbindingslijn vanuit Mijn Karakter.
- Zoek naar UpdateHealth, u krijgt de verschillende opties.
Assign doet twee zaken in één, de Binding met de Event Dispatcher en een bijhorende Custom Event. Dus
- Klik op Assign.
U krijgt zowel de Bind als een Custom Event met de juiste “signatuur”.
Deze Custom Event bevat nu de code die moet uitgevoerd worden.
De Gezondheid wordt gedeeld door MaxGezondheid, die we halen uit onze referentie MijnKarakter. Dit levert het percentage op dat wordt toegekend aan de eigenschap Percent, via Set Percent, van onze ProgressBar GezondheidBar.
- Compile, Save en Play.
Het werkt,… bijna.
Bij het opstarten staat de GezondheidBar niet op 100, maar op 0!
Nochtans, hadden we de Event Dispatcher UpdateHealth niet aangeroepen bij de Event BeginPlay?
Yep, hoe komt het dat dit niet werkt?
Wel, bovenstaande Event BeginPlay roept inderdaad de UpdateHealth aan maar wat hier niet gebeurt is de Widget toekennen aan de Viewport, dit gebeurt in de Event BeginPlay van de gebruikte HUD.
Open de gebruikte HUD Blueprint en ga naar de Event Graph, u vindt er wellicht onderstaande code.
Het was leuk geweest hadden deze twee Blueprints met elk hun eigen Event BeginPlay perfect naadloos samengewerkt, maar dat is niet het geval. Los van het feit dat het niet werkt is het ook geen goed idee om code te spreiden over meerdere Event BeginPlay‘s.
We moeten de code dus behouden in één van beiden en verwijderen in de andere (u hoeft niet noodzakelijk de code zelf te verwijderen maar de verbindingslijn met Event BeginPlay). In welke u het behoudt en waar u het verwijdert maakt in deze niet veel verschil. Ik geef u beide oplossingen.
HUD Blueprint
- Na de Add to Viewport moet er toegang verkregen worden tot ThirdPersonCharacter via Get Player Character – Cast to ThirdPersonCharacter. Dit is een voorbeeld van Directe communicatie.
- Er wordt een referentie gelegd naar ThirdPersonCharacter in de variabele MijnKarakter.
- MaxGezondheid wordt opgehaald uit MijnKarakter en toegekend aan de variabele Gezondheid (SET Gezondheid).
- De waarde van de variabele Gezondheid wordt vervolgens als input meegegeven aan de Event Dispatcher Call UpdateHealth.
De code wordt vrij complex doordat meerdere waarden moeten opgevraagd worden uit een andere Blueprint via Directe communicatie.
ThirdPersonCharacter Blueprint
De Widget wordt aangemaakt via Get Player Controller – Create Widget en toegevoegd aan het scherm via Add to Viewport. De rest van de code stond er al.
Project Twin Stick Shooter – Damaging
Bekijk onderstaande video’s op Twin Stick Shooter with Blueprint (unrealengine.com).
- Damaging the Enemy
- Damaging the Hero
Masterclass – Blueprint Communications
De bron van inspiratie voor deze handleiding was onderstaande Masterclass.
Opmerking, rond 1:20 wordt de opmerking gemaakt dat, om toegang te krijgen tot de Interfaces op Level Blueprint niveau u een sublevel moet bouwen, omdat de Persistant Level Blueprint (de standaard Level Blueprint) geen Interfaces kan implementeren.
Dit is in versie 4.20 (en misschien ook al in eerdere versies) niet meer nodig omdat Level Blueprint nu wel de implementatie van Interfaces ondersteunt.