How to get my bot to move to a placed target?

Hi everyone. I’m working on a side project in which a bot uses A* to navigate through a bunch of obstacles. Its my first time working with Unity and I’m really only doing it to try to teach myself a bit about it. I’ve everything working right except for my target. Right now its attached to my mouse, so wherever I move my mouse to, the bot moves to. What I’d like to do is be able to place the target with the mouse, and have the bot move to the placed target. Here’s my script to place the target:

using UnityEngine;
using System.Collections;

public class Click2Place : MonoBehaviour {

    Ray ray;
    RaycastHit hit;
    public GameObject prefab;
    // Use this for initialization
    void Start () {
    }
    // Update is called once per frame
    void Update () {
       
        ray=Camera.main.ScreenPointToRay(Input.mousePosition);
       
        if(Physics.Raycast(ray,out hit))
        {
           
            if(Input.GetKey(KeyCode.Mouse0))
            {
                GameObject obj=Instantiate(prefab,new Vector3(hit.point.x,hit.point.y,hit.point.z), Quaternion.identity) as GameObject;
            }
        }
    }
}

I have a target prefab set to the prefab variable, and everything works, sort of. I can run it all in game mode, and I can click to place a new target. What doesn’t work is the bot. It continues to move towards my mouse, and ignores the newly placed target. What am I doing wrong?

You’re instantiating a new GameObject based on the target prefab and then doing absolutely nothing with it. The reference to it here dies when the conditional loop ends, because the scope of “obj” is local. If you want to do something with the new target object, it needs to be referenced in a way that persists longer than a microsecond, first off, for instance assigning it to a GameObject class variable.

There no reference at all to the bot object, any sort of manager for the bot object, or anything like that here. Is this script you posted attached to the Bot GameObject, or is it a completely different object? If the former, then you need to reference the other script using

gameObject.GetComponent<OtherScriptName>().ChangeTarget(obj);

or something like that. If it’s the latter (completely different objects) then you’ll need to make a public GameObject member for this script and then drag the Bot object into it in the inspector- or use GameObject.FindGameObjectWithTag(“bot tag name”) followed by a similar GetComponent to find the script in question again.

The “ChangeTarget()” is just an example of a function within that script which can change the “goal” of the Bot and have it move toward that instead of the cursor. However, more precise code is impossible because you haven’t included the Bot’s management script here. If you want more help, include the other script. :slight_smile:

Thanks for the help. The bot uses two scripts. I should warn you, they’re quite lengthy. I took a sample scene from the A* Pathfinding Project, stripped the stuff I didn’t need, then added in my own stuff.

The first is the seeker script:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Pathfinding;
using System.Diagnostics;

[AddComponentMenu ("Pathfinding/Seeker")]
/** Handles path calls for a single unit.
* \ingroup relevant
* This is a component which is meant to be attached to a single unit (AI, Robot, Player, whatever) to handle it's pathfinding calls.
* It also handles post-processing of paths using modifiers.
* \see \ref calling-pathfinding
*/
public class Seeker : MonoBehaviour {

    //====== SETTINGS ======
   
    /* Recalculate last queried path when a graph changes. \see AstarPath.OnGraphsUpdated */
    //public bool recalcOnGraphChange = true;
   
    public bool drawGizmos = true;
    public bool detailedGizmos = false;
   
    /** Saves nearest nodes for previous path to enable faster Get Nearest Node calls.
     * This variable basically does not a affect anything at the moment. So it is hidden in the inspector */
    [HideInInspector]
    public bool saveGetNearestHints = true;
   
    public StartEndModifier startEndModifier = new StartEndModifier ();
   
    [HideInInspector]
    public TagMask traversableTags = new TagMask (-1,-1);
   
    [HideInInspector]
    /** Penalties for each tag.
     * Tag 0 which is the default tag, will have added a penalty of tagPenalties[0].
     * These should only be positive values since the A* algorithm cannot handle negative penalties.
     * \note This array should always have a length of 32.
     * \see Pathfinding.Path.tagPenalties
     */
    public int[] tagPenalties = new int[32];
   
    //====== SETTINGS ======
   
    //public delegate Path PathReturn (Path p);
   
    /** Callback for when a path is completed. Movement scripts should register to this delegate.\n
     * A temporary callback can also be set when calling StartPath, but that delegate will only be called for that path */
    public OnPathDelegate pathCallback;
   
    /** Called before pathfinding is started */
    public OnPathDelegate preProcessPath;
   
    /** For anything which requires the original nodes (Node[]) (before modifiers) to work */
    public OnPathDelegate postProcessOriginalPath;
   
    /** Anything which only modifies the positions (Vector3[]) */
    public OnPathDelegate postProcessPath;
   
    //public GetNextTargetDelegate getNextTarget;
   
    //DEBUG
    //public Path lastCompletedPath;
    [System.NonSerialized]
    public List<Vector3> lastCompletedVectorPath;
    [System.NonSerialized]
    public List<GraphNode> lastCompletedNodePath;
   
    //END DEBUG
   
    /** The current path */
    [System.NonSerialized]
    protected Path path;

    /** Previous path. Used to draw gizmos */
    private Path prevPath;
   
    /** Returns #path */
    public Path GetCurrentPath () {
        return path;
    }
   
    private GraphNode startHint;
    private GraphNode endHint;
   
    private OnPathDelegate onPathDelegate;
    private OnPathDelegate onPartialPathDelegate;

    /** Temporary callback only called for the current path. This value is set by the StartPath functions */
    private OnPathDelegate tmpPathCallback;
   
    /** The path ID of the last path queried */
    protected uint lastPathID = 0;
   
#if PhotonImplementation
    public Seeker () {
        Awake ();
    }
#endif
   
    /** Initializes a few variables
     */
    public void Awake () {
        onPathDelegate = OnPathComplete;
        onPartialPathDelegate = OnPartialPathComplete;
       
        startEndModifier.Awake (this);
    }
   
    /** Cleans up some variables.
     * Releases any eventually claimed paths.
     * Calls OnDestroy on the #startEndModifier.
     *
     * \see ReleaseClaimedPath
     * \see startEndModifier
     */
    public void OnDestroy () {
        ReleaseClaimedPath ();
        startEndModifier.OnDestroy (this);
    }
   
    /** Releases an eventual claimed path.
     * The seeker keeps the latest path claimed so it can draw gizmos.
     * In some cases this might not be desireable and you want it released.
     * In that case, you can call this method to release it (not that path gizmos will then not be drawn).
     *
     * If you didn't understand anything from the description above, you probably don't need to use this method.
     */
    public void ReleaseClaimedPath () {
        if (prevPath != null) {
            prevPath.ReleaseSilent (this);
            prevPath = null;
        }
    }
   
    private List<IPathModifier> modifiers = new List<IPathModifier> ();
   
    public void RegisterModifier (IPathModifier mod) {
        if (modifiers == null) {
            modifiers = new List<IPathModifier> (1);
        }
       
        modifiers.Add (mod);
    }
   
    public void DeregisterModifier (IPathModifier mod) {
        if (modifiers == null) {
            return;
        }
        modifiers.Remove (mod);
    }
   
    public enum ModifierPass {
        PreProcess,
        PostProcessOriginal,
        PostProcess
    }
   
    /** Post Processes the path.
     * This will run any modifiers attached to this GameObject on the path.
     * This is identical to calling RunModifiers(ModifierPass.PostProcess, path)
     * \see RunModifiers
     * \since Added in 3.2
     */
    public void PostProcess (Path p) {
        RunModifiers (ModifierPass.PostProcess,p);
    }
   
    /** Runs modifiers on path \a p */
    public void RunModifiers (ModifierPass pass, Path p) {
       
        //Sort the modifiers based on priority (bubble sort (slow but since it's a small list, it works good))
        bool changed = true;
        while (changed) {
            changed = false;
            for (int i=0;i<modifiers.Count-1;i++) {
                if (modifiers[i].Priority < modifiers[i+1].Priority) {
                    IPathModifier tmp = modifiers[i];
                    modifiers[i] = modifiers[i+1];
                    modifiers[i+1] = tmp;
                    changed = true;
                }
            }
        }
       
        //Call eventual delegates
        switch (pass) {
            case ModifierPass.PreProcess:
                if (preProcessPath != null) preProcessPath (p);
                break;
            case ModifierPass.PostProcessOriginal:
                if (postProcessOriginalPath != null) postProcessOriginalPath (p);
                break;
            case ModifierPass.PostProcess:
                if (postProcessPath != null) postProcessPath (p);
                break;
        }
       
        //No modifiers, then exit here
        if (modifiers.Count    == 0) return;
       
        ModifierData prevOutput = ModifierData.All;
        IPathModifier prevMod = modifiers[0];
       
        //Loop through all modifiers and apply post processing
        for (int i=0;i<modifiers.Count;i++) {
            //Cast to MonoModifier, i.e modifiers attached as scripts to the game object
            MonoModifier mMod = modifiers[i] as MonoModifier;
           
            //Ignore modifiers which are not enabled
            if (mMod != null && !mMod.enabled) continue;
           
            switch (pass) {
            case ModifierPass.PreProcess:
                modifiers[i].PreProcess (p);
                break;
            case ModifierPass.PostProcessOriginal:
                modifiers[i].ApplyOriginal (p);
                break;
            case ModifierPass.PostProcess:
               
                //Convert the path if necessary to match the required input for the modifier
                ModifierData newInput = ModifierConverter.Convert (p,prevOutput,modifiers[i].input);
               
                if (newInput != ModifierData.None) {
                    modifiers[i].Apply (p,newInput);
                    prevOutput = modifiers[i].output;
                } else {
                   
                    UnityEngine.Debug.Log ("Error converting "+(i > 0 ? prevMod.GetType ().Name : "original")+"'s output to "+(modifiers[i].GetType ().Name)+"'s input.\nTry rearranging the modifier priorities on the Seeker.");
               
                    prevOutput = ModifierData.None;
                }
           
                prevMod = modifiers[i];
                break;
            }
           
            if (prevOutput == ModifierData.None) {
                break;
            }
           
        }
    }
   
    /** Is the current path done calculating.
     * Returns true if the current #path has been returned or if the #path is null.
     *
     * \note Do not confuse this with Pathfinding.Path.IsDone. They do mostly return the same value, but not always.
     *
     * \since Added in 3.0.8
     * \version Behaviour changed in 3.2
     * */
    public bool IsDone () {
        return path == null || path.GetState() >= PathState.Returned;
    }
   
    /** Called when a path has completed.
     * This should have been implemented as optional parameter values, but that didn't seem to work very well with delegates (the values weren't the default ones)
     * \see OnPathComplete(Path,bool,bool) */
    public void OnPathComplete (Path p) {
        OnPathComplete (p,true,true);
    }
   
    /** Called when a path has completed.
     * Will post process it and return it by calling #tmpPathCallback and #pathCallback */
    public void OnPathComplete (Path p, bool runModifiers, bool sendCallbacks) {
       
        AstarProfiler.StartProfile ("Seeker OnPathComplete");
       
       
        if (p != null && p != path && sendCallbacks) {
            return;
        }
       
       
        if (this == null || p == null || p != path)
            return;
       
        if (!path.error && runModifiers) {
            AstarProfiler.StartProfile ("Seeker Modifiers");
            //This will send the path for post processing to modifiers attached to this Seeker
            RunModifiers (ModifierPass.PostProcessOriginal, path);
           
            //This will send the path for post processing to modifiers attached to this Seeker
            RunModifiers (ModifierPass.PostProcess, path);
            AstarProfiler.EndProfile ();
        }
       
        if (sendCallbacks) {
           
            p.Claim (this);
           
            AstarProfiler.StartProfile ("Seeker Callbacks");
           
            lastCompletedNodePath = p.path;
            lastCompletedVectorPath = p.vectorPath;
           
            //This will send the path to the callback (if any) specified when calling StartPath
            if (tmpPathCallback != null) {
                tmpPathCallback (p);
            }
           
            //This will send the path to any script which has registered to the callback
            if (pathCallback != null) {
                pathCallback (p);
            }
           
            //Recycle the previous path
            if (prevPath != null) {
                prevPath.ReleaseSilent (this);
            }
           
            prevPath = p;

            //If not drawing gizmos, then storing prevPath is quite unecessary
            //So clear it and set prevPath to null
            if (!drawGizmos) ReleaseClaimedPath ();

            AstarProfiler.EndProfile();
        }
       
        AstarProfiler.EndProfile ();
    }
   
    /** Called for each path in a MultiTargetPath. Only post processes the path, does not return it.
      * \astarpro */
    public void OnPartialPathComplete (Path p) {
        OnPathComplete (p,true,false);
    }
   
    /** Called once for a MultiTargetPath. Only returns the path, does not post process.
      * \astarpro */
    public void OnMultiPathComplete (Path p) {
        OnPathComplete (p,false,true);
    }
   
    /*public void OnEnable () {
        //AstarPath.OnGraphsUpdated += CheckPathValidity;
    }
   
    public void OnDisable () {
        //AstarPath.OnGraphsUpdated -= CheckPathValidity;
    }*/
   
    /*public void CheckPathValidity (AstarPath active) {
       
        /*if (!recalcOnGraphChange) {
            return;
        }
       
       
       
        //Debug.Log ("Checking Path Validity");
        //Debug.Break ();
        if (lastCompletedPath != null && !lastCompletedPath.error) {
            //Debug.Log ("Checking Path Validity");
            StartPath (transform.position,lastCompletedPath.endPoint);
           
            /*if (!lastCompletedPath.path[0].IsWalkable (lastCompletedPath)) {
                StartPath (transform.position,lastCompletedPath.endPoint);
                return;
            }
               
            for (int i=0;i<lastCompletedPath.path.Length-1;i++) {
               
                if (!lastCompletedPath.path[i].ContainsConnection (lastCompletedPath.path[i+1],lastCompletedPath)) {
                    StartPath (transform.position,lastCompletedPath.endPoint);
                    return;
                }
                Debug.DrawLine (lastCompletedPath.path[i].position,lastCompletedPath.path[i+1].position,Color.cyan);
            }*
        }*
    }*/
   
    //The frame the last call was made from this Seeker
    //private int lastPathCall = -1000;
   
    /** Returns a new path instance. The path will be taken from the path pool if path recycling is turned on.\n
     * This path can be sent to #StartPath(Path,OnPathDelegate,int) with no change, but if no change is required #StartPath(Vector3,Vector3,OnPathDelegate) does just that.
     * \code Seeker seeker = GetComponent (typeof(Seeker)) as Seeker;
     * Path p = seeker.GetNewPath (transform.position, transform.position+transform.forward*100);
     * p.nnConstraint = NNConstraint.Default; \endcode */
    public ABPath GetNewPath (Vector3 start, Vector3 end) {
        //Construct a path with start and end points
        ABPath p = ABPath.Construct (start, end, null);
       
        return p;
    }
   
    /** Call this function to start calculating a path.
     * \param start        The start point of the path
     * \param end        The end point of the path
     */
    public Path StartPath (Vector3 start, Vector3 end) {
        return StartPath (start,end,null,-1);
    }
   
    /** Call this function to start calculating a path.
     * \param start        The start point of the path
     * \param end        The end point of the path
     * \param callback    The function to call when the path has been calculated
     *
     * \a callback will be called when the path has completed.
     * \a Callback will not be called if the path is canceled (e.g when a new path is requested before the previous one has completed) */
    public Path StartPath (Vector3 start, Vector3 end, OnPathDelegate callback) {
        return StartPath (start,end,callback,-1);
    }
   
    /** Call this function to start calculating a path.
     * \param start        The start point of the path
     * \param end        The end point of the path
     * \param callback    The function to call when the path has been calculated
     * \param graphMask    Mask used to specify which graphs should be searched for close nodes. See Pathfinding.NNConstraint.graphMask.
     *
     * \a callback will be called when the path has completed.
     * \a Callback will not be called if the path is canceled (e.g when a new path is requested before the previous one has completed) */
    public Path StartPath (Vector3 start, Vector3 end, OnPathDelegate callback, int graphMask) {
        Path p = GetNewPath (start,end);
        return StartPath (p, callback, graphMask);
    }
   
    /** Call this function to start calculating a path.
     * \param p            The path to start calculating
     * \param callback    The function to call when the path has been calculated
     * \param graphMask    Mask used to specify which graphs should be searched for close nodes. See Pathfinding.NNConstraint.graphMask. \astarproParam
     *
     * \a callback will be called when the path has completed.
     * \a Callback will not be called if the path is canceled (e.g when a new path is requested before the previous one has completed) */
    public Path StartPath (Path p, OnPathDelegate callback = null, int graphMask = -1) {
        p.enabledTags = traversableTags.tagsChange;
        p.tagPenalties = tagPenalties;
       
#if !AstarFree && FALSE
        //In case a multi target path has been specified, call special logic
        if (p.GetType () == typeof (MultiTargetPath)) {
            return StartMultiTargetPath (p as MultiTargetPath,callback);
        }
#endif
        //Cancel a previously requested path is it has not been processed yet and also make sure that it has not been recycled and used somewhere else
        if (path != null && path.GetState() <= PathState.Processing && lastPathID == path.pathID) {
            path.Error();
            path.LogError ("Canceled path because a new one was requested.\n"+
                "This happens when a new path is requested from the seeker when one was already being calculated.\n" +
                "For example if a unit got a new order, you might request a new path directly instead of waiting for the now" +
                " invalid path to be calculated. Which is probably what you want.\n" +
                "If you are getting this a lot, you might want to consider how you are scheduling path requests.");
            //No callback should be sent for the canceled path
        }
       
        path = p;
        path.callback += onPathDelegate;
        path.nnConstraint.graphMask = graphMask;
       
        tmpPathCallback = callback;
       
        //Set the Get Nearest Node hints if they have not already been set
        /*if (path.startHint == null)
            path.startHint = startHint;
           
        if (path.endHint == null)
            path.endHint = endHint;
        */
       
        //Save the path id so we can make sure that if we cancel a path (see above) it should not have been recycled yet.
        lastPathID = path.pathID;
       
        //Delay the path call by one frame if it was sent the same frame as the previous call
        /*if (lastPathCall == Time.frameCount) {
            StartCoroutine (DelayPathStart (path));
            return path;
        }*/
       
        //lastPathCall = Time.frameCount;
       
        //Pre process the path
        RunModifiers (ModifierPass.PreProcess, path);
       
        //Send the request to the pathfinder
        AstarPath.StartPath (path);
       
        return path;
    }
   
    /** Starts a Multi Target Path from one start point to multiple end points. A Multi Target Path will search for all the end points in one search and will return all paths if \a pathsForAll is true, or only the shortest one if \a pathsForAll is false.\n
     * \param start            The start point of the path
     * \param endPoints        The end points of the path
     * \param pathsForAll    Indicates whether or not a path to all end points should be searched for or only to the closest one
     * \param callback        The function to call when the path has been calculated
     * \param graphMask        Mask used to specify which graphs should be searched for close nodes. See Pathfinding.NNConstraint.graphMask. \astarproParam
     *
     * \a callback and #pathCallback will be called when the path has completed. \a Callback will not be called if the path is canceled (e.g when a new path is requested before the previous one has completed)
     * \astarpro
     * \see Pathfinding.MultiTargetPath
     * \see \ref MultiTargetPathExample.cs "Example of how to use multi-target-paths"
     */
    public MultiTargetPath StartMultiTargetPath (Vector3 start, Vector3[] endPoints, bool pathsForAll, OnPathDelegate callback = null, int graphMask = -1) {
        MultiTargetPath p = MultiTargetPath.Construct (start, endPoints, null, null);
        p.pathsForAll = pathsForAll;
        return StartMultiTargetPath (p, callback, graphMask);
    }
   
    /** Starts a Multi Target Path from multiple start points to a single target point. A Multi Target Path will search from all start points to the target point in one search and will return all paths if \a pathsForAll is true, or only the shortest one if \a pathsForAll is false.\n
     * \param startPoints    The start points of the path
     * \param end            The end point of the path
     * \param pathsForAll    Indicates whether or not a path from all start points should be searched for or only to the closest one
     * \param callback        The function to call when the path has been calculated
     * \param graphMask        Mask used to specify which graphs should be searched for close nodes. See Pathfinding.NNConstraint.graphMask. \astarproParam
     *
     * \a callback and #pathCallback will be called when the path has completed. \a Callback will not be called if the path is canceled (e.g when a new path is requested before the previous one has completed)
     * \astarpro
     * \see Pathfinding.MultiTargetPath
     * \see \ref MultiTargetPathExample.cs "Example of how to use multi-target-paths"
     */
    public MultiTargetPath StartMultiTargetPath (Vector3[] startPoints, Vector3 end, bool pathsForAll, OnPathDelegate callback = null, int graphMask = -1) {
        MultiTargetPath p = MultiTargetPath.Construct (startPoints, end, null, null);
        p.pathsForAll = pathsForAll;
        return StartMultiTargetPath (p, callback, graphMask);
    }
   
    /** Starts a Multi Target Path. Takes a MultiTargetPath and wires everything up for it to send callbacks to the seeker for post-processing.\n
     * \param p                The path to start calculating
     * \param callback        The function to call when the path has been calculated
     * \param graphMask    Mask used to specify which graphs should be searched for close nodes. See Pathfinding.NNConstraint.graphMask. \astarproParam
     *
     * \a callback and #pathCallback will be called when the path has completed. \a Callback will not be called if the path is canceled (e.g when a new path is requested before the previous one has completed)
     * \astarpro
     * \see Pathfinding.MultiTargetPath
     * \see \ref MultiTargetPathExample.cs "Example of how to use multi-target-paths"
     */
    public MultiTargetPath StartMultiTargetPath (MultiTargetPath p, OnPathDelegate callback = null, int graphMask = -1) {
           
        //Cancel a previously requested path is it has not been processed yet and also make sure that it has not been recycled and used somewhere else
        if (path != null && path.GetState () <= PathState.Processing && lastPathID == path.pathID) {
            path.ForceLogError ("Canceled path because a new one was requested");
            //No callback should be sent for the canceled path
        }
       
        OnPathDelegate[] callbacks = new OnPathDelegate[p.targetPoints.Length];
        for (int i=0;i<callbacks.Length;i++) {
            callbacks[i] = onPartialPathDelegate;
        }
       
        p.callbacks = callbacks;
        p.callback += OnMultiPathComplete;
        p.nnConstraint.graphMask = graphMask;
       
        path = p;
       
        tmpPathCallback = callback;
       
        //Save the path id so we can make sure that if we cancel a path (see above) it should not have been recycled yet.
        lastPathID = path.pathID;
       
        //Delay the path call by one frame if it was sent the same frame as the previous call
        /*if (lastPathCall == Time.frameCount) {
            StartCoroutine (DelayPathStart (path));
            return p;
        }
       
        lastPathCall = Time.frameCount;*/
       
        //Pre process the path
        RunModifiers (ModifierPass.PreProcess, path);
       
        //Send the request to the pathfinder
        AstarPath.StartPath (path);
       
        return p;
    }
   
    public IEnumerator DelayPathStart (Path p) {
        yield return null;
        //lastPathCall = Time.frameCount;
       
        RunModifiers (ModifierPass.PreProcess, p);
       
        AstarPath.StartPath (p);
    }
   
#if !PhotonImplementation
    public void OnDrawGizmos () {
        if (lastCompletedNodePath == null || !drawGizmos) {
            return;
        }
       
        if (detailedGizmos) {
            Gizmos.color = new Color (0.7F,0.5F,0.1F,0.5F);
           
            if (lastCompletedNodePath != null) {
                for (int i=0;i<lastCompletedNodePath.Count-1;i++) {
                    Gizmos.DrawLine ((Vector3)lastCompletedNodePath[i].position,(Vector3)lastCompletedNodePath[i+1].position);
                }
            }
        }
       
        Gizmos.color = new Color (0,1F,0,1F);
       
        if (lastCompletedVectorPath != null) {
            for (int i=0;i<lastCompletedVectorPath.Count-1;i++) {
                Gizmos.DrawLine (lastCompletedVectorPath[i],lastCompletedVectorPath[i+1]);
            }
        }
    }
#endif
   
}

The second is the Minebot AI script:

using UnityEngine;
using System.Collections;
using Pathfinding.RVO;

namespace Pathfinding {
    /** AI controller specifically made for the spider robot.
     * The spider robot (or mine-bot) which is got from the Unity Example Project
     * can have this script attached to be able to pathfind around with animations working properly.\n
     * This script should be attached to a parent GameObject however since the original bot has Z+ as up.
     * This component requires Z+ to be forward and Y+ to be up.\n
     *
     * It overrides the AIPath class, see that class's documentation for more information on most variables.\n
     * Animation is handled by this component. The Animation component refered to in #anim should have animations named "awake" and "forward".
     * The forward animation will have it's speed modified by the velocity and scaled by #animationSpeed to adjust it to look good.
     * The awake animation will only be sampled at the end frame and will not play.\n
     * When the end of path is reached, if the #endOfPathEffect is not null, it will be instantiated at the current position. However a check will be
     * done so that it won't spawn effects too close to the previous spawn-point.
     * \shadowimage{mine-bot.png}
     *
     * \note This script assumes Y is up and that character movement is mostly on the XZ plane.
     */
    [RequireComponent(typeof(Seeker))]
    public class MineBotAI : AIPath {
       
        /** Animation component.
         * Should hold animations "awake" and "forward"
         */
        public Animation anim;
       
        /** Minimum velocity for moving */
        public float sleepVelocity = 0.4F;
       
        /** Speed relative to velocity with which to play animations */
        public float animationSpeed = 0.2F;
       
        /** Effect which will be instantiated when end of path is reached.
         * \see OnTargetReached */
        public GameObject endOfPathEffect;
       
        public new void Start () {
           
            //Prioritize the walking animation
            anim["forward"].layer = 10;
           
            //Play all animations
            anim.Play ("awake");
            anim.Play ("forward");
           
            //Setup awake animations properties
            anim["awake"].wrapMode = WrapMode.Clamp;
            anim["awake"].speed = 0;
            anim["awake"].normalizedTime = 1F;
           
            //Call Start in base script (AIPath)
            base.Start ();
        }
       
        /** Point for the last spawn of #endOfPathEffect */
        protected Vector3 lastTarget;
       
        /**
         * Called when the end of path has been reached.
         * An effect (#endOfPathEffect) is spawned when this function is called
         * However, since paths are recalculated quite often, we only spawn the effect
         * when the current position is some distance away from the previous spawn-point
        */
        public override void OnTargetReached () {
           
            if (endOfPathEffect != null && Vector3.Distance (tr.position, lastTarget) > 1) {
                GameObject.Instantiate (endOfPathEffect,tr.position,tr.rotation);
                lastTarget = tr.position;
            }
        }
       
        public override Vector3 GetFeetPosition ()
        {
            return tr.position;
        }
       
        protected new void Update () {
           
            //Get velocity in world-space
            Vector3 velocity;
            if (canMove) {
           
                //Calculate desired velocity
                Vector3 dir = CalculateVelocity (GetFeetPosition());
               
                //Rotate towards targetDirection (filled in by CalculateVelocity)
                RotateTowards (targetDirection);
               
                dir.y = 0;
                if (dir.sqrMagnitude > sleepVelocity*sleepVelocity) {
                    //If the velocity is large enough, move
                } else {
                    //Otherwise, just stand still (this ensures gravity is applied)
                    dir = Vector3.zero;
                }

                if ( this.rvoController != null ) {
                    rvoController.Move ( dir );
                    velocity = rvoController.velocity;
                } else
                if (navController != null) {
    #if FALSE
                    navController.SimpleMove (GetFeetPosition(), dir);
    #endif
                    velocity = Vector3.zero;
                } else if (controller != null) {
                    controller.SimpleMove (dir);
                    velocity = controller.velocity;
                } else {
                    Debug.LogWarning ("No NavmeshController or CharacterController attached to GameObject");
                    velocity = Vector3.zero;
                }
            } else {
                velocity = Vector3.zero;
            }
           
           
            //Animation
           
            //Calculate the velocity relative to this transform's orientation
            Vector3 relVelocity = tr.InverseTransformDirection (velocity);
            relVelocity.y = 0;
           
            if (velocity.sqrMagnitude <= sleepVelocity*sleepVelocity) {
                //Fade out walking animation
                anim.Blend ("forward",0,0.2F);
            } else {
                //Fade in walking animation
                anim.Blend ("forward",1,0.2F);
               
                //Modify animation speed to match velocity
                AnimationState state = anim["forward"];
               
                float speed = relVelocity.z;
                state.speed = speed*animationSpeed;
            }
        }
    }
}
RotateTowards(targetDirection);

I can’t figure out what this does, from the code provided. Is it even valid, and is it supposed to just be “dir” instead? I honestly have trouble following a lot of this code because it seems to have a lot of bits and pieces that either aren’t doing anything or just aren’t doing anything useful.

Look through whatever documentation and stuff you have and see exactly how it is that you’re supposed to pass in a new position for a “path” goal to be determined in the seeker script. Then, do what I said about either referencing the bot using a public GameObject class member where you can drag the bot object into the slot in the inspector, or finding the bot using a tag that’s only used on the bot, or w/e. Use GetComponent() and pass in the position of the newly-created target object to whatever function is it where a new path is started. shrugs

1 Like