Attaching right mouse click to cast spell

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:

Attaching right mouse click to cast spell

A few things happen between right click and spell cast. They are as follows:

  • As we saw before, all user mouse and keyboard interactions are routed through the Avatar object. When the Avatar object detects a right-click, it will pass the click event to HUD through AAvatar::MouseRightClicked().
  • Recall from Chapter 10, Inventory System and Pickup Items where we used a 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().

  • If the click was on the Widget that casts a spell, the HUD then calls the avatar back with the request to cast that spell, by calling AAvatar::CastSpell().

Writing the avatar's CastSpell function

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:

  • Instantiate the spell object using the world object's SpawnActor function
  • Attach it to the avatar

Once 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.

Instantiating the spell – GetWorld()->SpawnActor()

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.
  • The new 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.

if(spell)

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.

spell->SetCaster(this)

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.

Writing AMyHUD::MouseRightClicked()

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.

Activating right mouse button clicks

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:

Activating right mouse button clicks

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:

Activating right mouse button clicks
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.157.142