Taking Full Advantage of IAction

So we’ve seen in the RPG course how we can use the IAction interface and an ActionScheduler to cancel combat by clicking anywhere in the terrain to cause a movement (and thought it’s not really noticeable, cancel movement to invoke a fighting action).

If we were only thinking of fighting and moving, this IAction interface might seem like a bit of overengineering, and you’d probably be right. There are other behaviors down the line in the Inventory, Dialogues and Quests, and the upcoming Shops and Abililities course that could stand to become IActions as well. This topic is going to cover that use case scenario.

We’ll start with a reminder about IAction… IAction is an interface. Interfaces are a handy way to reference different types of objects that carry a common piece of functionality. In our case, the interface is very simple:

public interface IAction
{
     void Cancel();
}

This interface simply guarantees that any class that implements IAction will have a Cancel method.
In our RPG project, we give you two such classes: Mover, and Fighter… The Cancel methods in each of these either stops movement or clears the target, depending on the class.
If you’re moving because you clicked on a point in the terrain, Mover tells the ActionScheduler that it is the current IAction, and any other IAction should be Cancelled. If you happened to be engaged in combat when this occurs, then Fighter will clear it’s target (thus disengaging from the enemy).

So let’s think about another use case for the IAction interface… In Dialogues and Quests, we’re introduced to the PlayerConversant… The PlayerConversant class opens a Dialogue (referenced by an AIConversant on the npc). As the course is written now, the PlayerConversant opens the dialogue automatically, even if the AIConversant is on the other side of the room. As long as it is on screen, you can access it. This is hardly realistic… you would be shouting across the terrain at the AI Speaker. Wouldn’t it be better if the character needed to be in range?

Enter the IAction interface, along with the same logic already found in our Fighter class…

We’ll start by creating an extra method in PlayerConversant() and modifying another:

AIConversant targetConversant = null;
Dialogue targetDialogue = null;
public void StartDialogueAction(AIConversant newConversant, Dialogue newDialogue)
{
     if(newConversant == currentConversant) return;
     Quit();  //clear any old conversant 
     GetComponent<ActionScheduler>().StartAction(this);
     targetConversant = newConversant;
     targetDialogue = newDialogue;
}


public void Cancel()
{
     Quit();
}

void Update()
{
      if(!targetConversant) return;
      if(Vector3.Distance(transform.position, targetConversant.transform.position)>3.0f)
      {
           GetComponent<Mover>().MoveTo(targetConversant.transform.position, 1.0f);
      } else
      {
            StartDialogue(targetConversant, targetDialogue);
            targetConversant=null;
       }
}

Of course, you’ll also need to add IAction to the PlayerConversant class declaration

public class PlayerConversant: MonoBehavior, IAction

Now in AIConversant, we need to make a very slight change to the HandleRaycast code. Instead of calling StartDialogue, we’ll call StartDialogueAction

if(Input.GetMouseButtonDown(0))
{
     callingController.GetComponent<PlayerConversant>().StartDialogueAction(this, dialogue);
}

So what will these changes do?

When we call StartDialogueAction, first a sanity check is made to ensure that we’re not already speaking to this speaker. This is important or clicking on the speaker again would cause us to restart the dialogue (this is actually important if you’re NOT implementing this IAction behavior as well).
We then call Quit, just to make absolutely certain any current conversation is closed. This normally wouldn’t be necessary, but in this case it is because if we were already talking to a conversant, the same IAction would be in effect, and cancel would never be called). Then we tell ActionScheduler to start a new action. This will cancel any other action that may be in effect.
Then a targetConversant and targetDialogue are set. This will tell Update that it needs to move the character to the destination conversant.

The update loop should look familiar, it’s very close to the Fighter’s update loop. It checks to see if the character is in range. If it’s out of range, then it moves the player to the target, if it’s in range, then it starts the dialogue. In this case, it also clears the targetConversant so that the Update loop won’t continue running checks. Once we’ve opened the dialogue, we don’t need to keep checking the distance. We’d only ever move out of range if we intentionally clicked elsewhere, and this would cancel the dialogue on it’s own.

This same logic can be applied to any other potential action. Another might be a Collector which is responsible for moving the character close enough to a pickup to collect the pickup. Much of the same logic would be in place… a target pickup, set in StartCollectionAction, which is picked up and added to inventory as soon as the player is close enough. Once Shops and Abilities is available, you might add this logic to the Shopper class so that the player moves close enough to the shop to interact.

Once all of these classes are outfitted with the IAction interface, the true power of the interface will be shown. A single click can begin or cancel any action if it is properly configured to be a part of the ActionScheduler. The possibilities are endless.

3 Likes

Thank you very much for this expansion to the IAction interface implementation.
I just wanted to add in the Update() method in the PlayerConversant.cs under “else” to add

else
{
   GetComponent<Mover>().Cancel(); //stop player movement once in range of conversant
   StartDialogue(targetConversant, targetDialogue);
   targetConversant = null;                   
}

Otherwise, the player will continue to move into the AI conversant indefinitely or until the player reaches the conversant’s transform location (resulting in them shoving the conversant out of the way rather ungracefully). A minor fix for anyone that was running into this problem.

1 Like

Good catch!

1 Like

Hello, after starting the StartDialogueAction() I have changed my mind and want to go away from Conversant. However, after clicking to some move destination, the Player continues to go to the Conversant. Do you have the same behaviour?

Edit: I have added a boolean “isDialogueActionActive” and slightly changed Update(), StartDialogueAction() and Cancel();

bool isDialogueActionActive = false;

void Update()
{
    if (!isDialogueActionActive) return;

    if (Vector3.Distance(transform.position, targetConversant.transform.position) > conversationDistance)
    {
        GetComponent<Mover>().MoveTo(targetConversant.transform.position, 1.0f);
    }
    else
    {
        GetComponent<Mover>().Cancel();
        StartDialogue(targetConversant, targetDialogue);
        targetConversant = null;
        isDialogueActionActive = false;
    }
}

public void StartDialogueAction(AIConversant newConversant, Dialogue newDialogue)
{
    if (newConversant == currentConversant) return;
    Quit();  //clear any old conversant 
    GetComponent<ActionScheduler>().StartAction(this);
    targetConversant = newConversant;
    targetDialogue = newDialogue;
    isDialogueActionActive = true;
}

void IAction.RH_Cancel()
        {
            Quit();
            isDialogueActionActive = false;
        }
1 Like

Privacy & Terms