0
votes

I want to create a follower AI like Sparx from Spyro. You can see the mechanics in this video

https://youtu.be/2DicrivJ2xc?t=5

Sparx looks for gems on the ground, flies to them and flies back to Spyro. When flying to them, the gems start to fly to the player too. So Sparx is collecting the gems.

I already created the functionality making the gem fly to the player. Let's say I call this method

StartMovingToPlayer();

Sparx will call this method from the gem in his script.

public class PlayerFollower : Ai
{
    private void OnTriggerEnter(Collider col) // something entered the trigger
    {
        Collectable collectable = col.GetComponent<Collectable>(); // try to get the collectable script

        if (collectable != null) // is it a collectable?
            collectable.StartMovingToPlayer(); // make it move to the player
    }
}

So my question is, how can I make the AI fly to the gem, call the method, fly back and have all the gems in the trigger stored in a queue because when there is more than one gem in the trigger, the AI has to queue it.


So here's an update, I tried to make the follower collecting the gems when having them stored in my list

public class PlayerFollower : Ai { private List collectablesToCollect = new List(); // have all the gems in range stored here private bool isCollecting = false; // is the follower currently collecting gems? private float movementSpeed = 10; // collecting speed

private void Update()
{
    if (CollectablesInRange()) // any gems in range?
    {
        if (!isCollecting) // collecting queued?
            MoveToCollectable(); // collect it
    }
    else
    {
        // follow the player
    }
}

private void OnTriggerEnter(Collider col)
{
    Collectable collectable = col.GetComponent<Collectable>(); // get the gem

    if (collectable != null) // is the object a gem?
    {
        if (!collectable.GetMovementState()) // got the gem already collected?
            collectablesToCollect.Add(collectable); // add it to the queue
    }
}

private void OnTriggerExit(Collider col)
{
    Collectable collectable = col.GetComponent<Collectable>();

    if (collectable != null)
        collectablesToCollect.Remove(collectable); // remove it from the queue
}

private void MoveToCollectable() // start collecting
{
    isCollecting = true; 

    if (CollectablesInRange())
    {
        Collectable collectable = collectablesToCollect.First(); just look for one gem
        Vector3 defaultPosition = transform.position;

        transform.position = Vector3.MoveTowards(transform.position, collectable.GetCollectablePosition(), movementSpeed * Time.deltaTime); // move to the gem

        collectable.StartMovingToPlayer(); // call the gems movement

        transform.position = Vector3.MoveTowards(transform.position, defaultPosition, movementSpeed * Time.deltaTime); // move back to the player

        collectablesToCollect.Remove(collectable); // remove it from the queue

        isCollecting = false;

        if (CollectablesInRange()) // collect again, when the list is not empty
            MoveToCollectable();
    }
}

private bool CollectablesInRange()
{
    return collectablesToCollect.Count > 0; // are there any gems in range?
}

}

1

1 Answers

1
votes

Here's what I would do:

You have your code on the Gem (Collectable) and your code on your Collecting-AI (PlayerFollower).

PlayerFollower needs a Trigger that represents its search-radius and it needs a collider that represents its physical position. Also put the AI on its own physical layer. Your collectables require the same setup, but I would advise you to set the Trigger on the same layer as the AI, while the collision should be on he same as the world (so you can touch it, but the AI passes through).

Whenever an object enters the Trigger of PlayerFollower that has a component of type Collectable, you store it in a List<Collectable>. Whenever an object exits the Trigger of PlayerFollower that has a component of type Collectable, you remove it from the list. That's how you track what can be collected and what not.

Now when an object enters the Trigger and gets added, you check if you have a current target to move to and if not, you set the added object as target.

When your AI enters the trigger of another object, this object gets its OnTriggerEnter fired and recognizes the AI. It calls a method on the AI that states that it is now collected and probably sets a boolean that can be read from other objects to true, stating that it is now collected and not part of the world anymore.

This method removes the object from the list and searches for the next target (in the list). When nothing can be found, it just goes back to the player and waits for the next object to collect.


Possible (not tested) implementation for the AI:

public class PlayerFollower : AI 
{
    List<Collectable> possibleTargets = new List<Collectable>();
    Collectable target;    

    void OnTriggerEnter(Collider other)
    {
        Collectable collectable = other.GetComponent<Collectable>(); 

        if (collectable != null && !collectable.Collected)
        {
            possibleTargets.Add(collectable);
            SetNextTarget();
        }
    }

    public void Collect(Collectable collectable)
    {
        possibleTargets.Remove(collectable);
        SetNextTarget();
    }

    void SetNextTarget() 
    {
        target = null;
        for(int i = 0; i < possibleTargets.Count; i++)
            if(!possibleTargets[i].Collected)
            {
                target = possibleTargets[i];
                return;
            }
    }
}

Possible (not tested) implementation for the Gem:

public class Collectable : MonoBehaviour
{
    private bool collected;
    public bool Collected { get { return collected; } }

    void OnTriggerEnter(Collider other)
    {
        PlayerFollower ai = other.GetComponent<PlayerFollower>(); 

        if (ai != null)
        {
            collected = true;
            ai.Collect(this);
        }
    }
}