The right mouse click will have to go through quite a few function calls before calling the avatar's CastSpell
method. The call graph would look something like the following screenshot:
A few things happen between right click and spell cast. They are as follows:
Avatar
object. When the Avatar
object detects a right-click, it will pass the click event to HUD
through AAvatar::MouseRightClicked()
.struct Widget
class to keep track of the items the player had picked up. struct Widget
only had three members:struct Widget { Icon icon; FVector2D pos, size; ///.. and some member functions };
We will need to add an extra property for struct Widget
class to remember the spell it casts.
The HUD
will determine if the click event was inside Widget
in AMyHUD::MouseRightClicked()
.
Widget
that casts a spell, the HUD
then calls the avatar back with the request to cast that spell, by calling AAvatar::CastSpell()
.We will implement the preceding call graph in reverse. We will start by writing the function that actually casts spells in the game, AAvatar::CastSpell()
, as shown in the following code:
void AAvatar::CastSpell( UClass* bpSpell ) { // instantiate the spell and attach to character ASpell *spell = GetWorld()->SpawnActor<ASpell>(bpSpell, FVector(0), FRotator(0) ); if( spell ) { spell->SetCaster( this ); } else { GEngine->AddOnScreenDebugMessage( 1, 5.f, FColor::Yellow, FString("can't cast ") + bpSpell->GetName() ); } }
You might find that actually calling a spell is remarkably simple. There are two basic steps to casting the spell:
SpawnActor
functionOnce the Spell
object is instantiated, its Tick()
function will run each frame when that spell is in the level. On each Tick()
, the Spell
object will automatically feel out monsters within the level and damage them. A lot happens with each line of code mentioned previously, so let's discuss each line separately.
To create the Spell
object from the blueprint, we need to call the SpawnActor()
function from the World
object. The SpawnActor()
function can take any blueprint and instantiate it within the level. Fortunately, the Avatar
object (and indeed any Actor
object) can get a handle to the World
object at any time by simply calling the GetWorld()
member function.
The line of code that brings the Spell
object into the level is as follows:
ASpell *spell = GetWorld()->SpawnActor<ASpell>( bpSpell, FVector(0), FRotator(0) );
There are a couple of things to note about the preceding line of code:
bpSpell
must be the blueprint of a Spell
object to create. The <ASpell>
object in angle brackets indicates that expectation.Spell
object starts out at the origin (0, 0, 0), and with no additional rotation applied to it. This is because we will attach the Spell
object to the Avatar
object, which will supply translation and direction components for the Spell
object.We always test if the call to SpawnActor<ASpell>()
succeeds by checking if( spell )
. If the blueprint passed to the CastSpell
object is not actually a blueprint based on the ASpell
class, then the SpawnActor()
function returns a NULL
pointer instead of a Spell
object. If that happens, we print an error message to the screen indicating that something went wrong during spell casting.
When instantiating, if the spell does succeed, we attach the spell to the Avatar
object by calling spell->SetCaster( this )
. Remember, in the context of programming within the Avatar
class, the this
method is a reference to the Avatar
object.
Now, how do we actually connect spell casting from UI inputs, to call AAvatar::CastSpell()
function in the first place? We need to do some HUD
programming again.
The spell cast commands will ultimately come from the HUD. We need to write a C++ function that will walk through all the HUD widgets and test to see if a click is on any one of them. If the click is on a widget
object, then that widget
object should respond by casting its spell, if it has one assigned.
We have to extend our Widget
object to have a variable to hold the blueprint of the spell to cast. Add a member to your struct Widget
object by using the following code:
struct Widget { Icon icon; // bpSpell is the blueprint of the spell this widget casts UClass *bpSpell; FVector2D pos, size; Widget(Icon iicon, UClass *iClassName) }
Now, recall that our PickupItem
had the blueprint of the spell it casts attached to it previously. However, when the PickupItem
class is picked up from the level by the player, then the PickupItem
class is destroyed.
// From APickupItem::Prox_Implementation(): avatar->Pickup( this ); // give this item to the avatar // delete the pickup item from the level once it is picked up Destroy();
So, we need to retain the information of what spell each PickupItem
casts. We can do that when that PickupItem
is first picked up.
Inside the AAvatar
class, add an extra map to remember the blueprint of the spell that an item casts, by item name:
// Put this in Avatar.h TMap<FString, UClass*> Spells;
Now in AAvatar::Pickup()
, remember the class of spell the PickupItem
class instantiates with the following line of code:
// the spell associated with the item Spells.Add(item->Name, item->Spell);
Now, in AAvatar::ToggleInventory()
, we can have the Widget
object that displays on the screen. Remember what spell it is supposed to cast by looking up the Spells
map.
Find the line where we create the widget, and just under it, add assignment of the bpSpell
objects that the Widget
casts:
// In AAvatar::ToggleInventory() Widget w( Icon( fs, tex ) ); w.bpSpell = Spells[it->Key];
Add the following function to AMyHUD
, which we will set to run whenever the right mouse button is clicked on the icon:
void AMyHUD::MouseRightClicked() { FVector2D mouse; APlayerController *PController = GetWorld()- >GetFirstPlayerController(); PController->GetMousePosition( mouse.X, mouse.Y ); for( int c = 0; c < widgets.Num(); c++ ) { if( widgets[c].hit( mouse ) ) { AAvatar *avatar = Cast<AAvatar>( UGameplayStatics::GetPlayerPawn(GetWorld(), 0) ); if( widgets[c].spellName ) avatar->CastSpell( widgets[c].spellName ); } } }
This is very similar to our left mouse click function. We simply check the click position against all the widgets. If any Widget
was hit by the right-click, and that Widget
has a Spell
object associated with it, then a spell will be cast by calling the avatar's CastSpell()
method.
To connect this HUD function to run, we need to attach an event handler to the mouse right-click. We can do so by going to Settings | Project Settings, and from the dialog that pops up, adding an Input option for Right Mouse Button, as shown in the following screenshot:
Declare a function in Avatar.h
/Avatar.cpp
called MouseRightClicked()
with the following code:
void AAvatar::MouseRightClicked() { if( inventoryShowing ) { APlayerController* PController = GetWorld()- >GetFirstPlayerController(); AMyHUD* hud = Cast<AMyHUD>( PController->GetHUD() ); hud->MouseRightClicked(); } }
Then, in AAvatar::SetupPlayerInputComponent()
, we should attach MouseClickedRMB
event to that MouseRightClicked()
function:
// In AAvatar::SetupPlayerInputComponent(): InputComponent->BindAction( "MouseClickedRMB", IE_Pressed, this, &AAvatar::MouseRightClicked );
We have finally hooked up spell casting. Try it out, the gameplay is pretty cool, as shown in the following screenshot:
3.133.157.142