moving an object left or right based on available positions

I am a bit new to Unity, I can move an object over a period of time in the following way

public GameObject objectectA;
public GameObject objectectB;

void Start()
{
    StartCoroutine(moveToPos(objectectA.transform, objectectB.transform.position, 1.0f));
}


bool isMoving = false;

IEnumerator moveToPos(Transform fromPosition, Vector3 toPosition, float duration)
{
    if (isMoving)
    {
        yield break;
    }
    isMoving = true;

    float counter = 0;
    Vector3 startPos = fromPosition.position;

    while (counter < duration)
    {
        counter += Time.deltaTime;
        fromPosition.position = Vector3.Lerp(startPos, toPosition, counter / duration);
        yield return null;
    }

    isMoving = false;
}

I am trying to do something that is more complex. I have three lists, filledHolesPos, emptyHolesPos and holesPos (combines both filled and empty, a total of six holes), all represent positions since the holes are arranged in a straight line horizontally. When a new object fills a hole, after 6 seconds it leaves the hole to fill the next hole (lerp’s to new hole in 2 seconds) to the right, if it’s empty. If the hole is occupied, it check’s if the position to it’s left is empty, if it’s empty then it changes direction to fill the left and continues moving left till it meets a filled hole, then changes back to right. I am not too sure how to go about this. Please help. :slight_smile:

Well, perhaps start with what data this script will need to do its job. On any particular frame, it will need to know:

  • Is it time to move yet? (Have a ‘float nextMoveTime’ property.)

  • Which way should I move? (Have an ‘int direction’ property, +1 for right and -1 for left.)

  • Which hole am I in now? (Have an ‘int holeNum’ property.)

  • Is the neighboring hole filled?

That last one is a little trickier, since it’s not knowledge local to each script. One way to tackle it would be to have a static property, which is data shared by all scripts. You say you have three lists, though I would probably have just one list of booleans, true if a hole is filled and false if it’s empty. Make this a static list so it’s accessible to all instances of the script:

public static bool[] holeFilled = new bool[6];

Now you can start putting it all together. Your update method would look something like this.

void Update() {
    // Do nothing until it's time to move.
    if (Time.time < nextMoveTime) return;

    // See if the next hole is empty.  If not, reverse direction and check again.
    if (holeNum + direction < 0 || holeNum + direction > 5 || holeFilled[holeNum + direction]) {
        // Can't go that way... try the other way.
        direction = -direction;
        if (holeNum + direction < 0 || holeNum + direction > 5 || holeFilled[holeNum + direction]) {
            // Doh!  Blocked that way too!  Give up and check again later.
            nextMoveTime = Time.time + 6;
            return;
        }
    }
   
    // Do the move.  Be sure to keep track of which holes are now filled!
    holeFilled[holeNum] = false;
    holeNum += direction;
    holeFilled[holeNum] = true;
    StartCoroutine(moveToPos(transform.position, HolePos(holeNum), 2.0f);
   
    // Check again after 8 seconds (2 seconds move, plus 8 seconds rest.)
    nextMoveTime = Time.time + 8;
}

Note that this assumes a HolePos function that returns the world position for any hole number.

2 Likes

You may find it helpful to create a gameObject for each hole, with a script.

Then you can have a list of Hole objects instead of managing separate data lists.

using UnityEngine;
public class Hole : MonoBehaviour
{
    public GameObject objectHeld;
    public bool IsEmpty { get { return objectHeld == null; } }
}

I also prefer to use Coroutines when dealing with waits. Here’s a fairly complete example for you to consider. I took bits from your code and the above solution, so it’s just about identical functionally, but quite a bit more verbose (which is my preference).

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

public class MovingObject : MonoBehaviour
{
    public Hole startingHole; // assigned in the inspector perhaps
    public float movementDuration = 2f;
    public float movementDelay = 6f;

    private List<Hole> holes;
    private Hole currentHole;
    private int movementDirection = 1; // default moving right

    private void Awake()
    {
        // using a bit of Linq to convert array to list
        holes = FindObjectsOfType<Hole>().ToList();

        // order them in the list left to right by X position (more Linq)
        holes.OrderBy(hole => hole.transform.position.x);

        currentHole = startingHole;
    }

    private void Start()
    {
        StartCoroutine(MovementLogic());
    }

    private void ReverseDirection()
    {
        movementDirection *= -1;
    }

    private Hole GetNextHole()
    {
        int nextIndex = holes.IndexOf(currentHole) + movementDirection;

        // if index is out of range
        if(nextIndex < 0 || nextIndex >= holes.Count)
        {
            return null;
        }
        else
        {
            return holes[nextIndex];
        }
    }

    private IEnumerator MovementLogic()
    {
        // keep going until deactivated or disabled
        while(this.isActiveAndEnabled)
        {
            if(currentHole != null)
            {
                // get the next hole
                Hole hole = GetNextHole();
                // if the next hole was found, and isn't empty
                if(hole != null && hole.IsEmpty)
                {
                    currentHole.objectHeld = null;
                    hole.objectHeld = gameObject;
                    currentHole = hole;
                    yield return MoveToHole(hole, movementDuration);
                    yield return new WaitForSeconds(movementDelay);
                }
                else
                {
                    // if a valid hole wasn't found, try the other direction next frame
                    ReverseDirection();
                }
            }
            yield return null;
        }
    }

    private IEnumerator MoveToHole(Hole hole, float duration)
    {
        float elapsedTime = 0;
        Vector3 startPos = transform.position;
        Vector3 endpos = hole.transform.position;

        while(elapsedTime < duration)
        {
            transform.position = Vector3.Lerp(startPos, endpos, elapsedTime / duration);
            elapsedTime += Time.deltaTime;
            yield return null;
        }
    }
}
1 Like

Thanks a lot. Because of the way in which I am have designed my game, position lists would work better for me and I’ve done some work in this direction. I used the code you provided and reworked it for positions instead of gameObjects. I am using three lists, a freePositions, filledPositions and holePositions (a combination of both list). I am not too sure what I’ve done wrong, but the objects are not moving. In my game there are also objects that don’t move that fill the holes, I randomly select which type should fill a hole. Please see the code below:

    private void ReverseDirection()
    {
        rightMovementDirection *= -1;
    }
  
    private float getNextPos(float currentPos)
    {
        int nextIndex = holePositions.IndexOf(currentPos) + rightMovementDirection;
      
        // if index is out of range
        if(nextIndex < 0 || nextIndex >= holePositions.Count)
        {
            return 0.0f;
                  }
        else
        {
            return holePositions[nextIndex];
        }
    }

private IEnumerator MovementLogic(GameObject movingObject)
    {
        // keep going until deactivated or disabled
        float currentPos = movingObject.transform.position.x;

        while(filledPositions.Contains(currentPos))
        {
            float newPos = getNextPos(currentPos);

                      if(freePositions.Contains(newPos))

                {
                    freePositions.Add (currentPos);
                    filledPositions.Remove (currentPos);

                    yield return moveObject(newPos, movementDuration);

                    freePositions.Remove (newPos);
                    filledPositions.Add (newPos);


                    //update positions
                    yield return new WaitForSeconds(movementDelay);


                }
                else
                {
                    // if a valid hole wasn't found, try the other direction next frame
                    ReverseDirection();
                }
            }
          
        yield return null;
        //}
    }

    private IEnumerator moveObject(float posX, float duration)
    {
        float elapsedTime = 0;
        Vector3 startPos = transform.position;
        Vector3 endpos = new Vector3 (posX, objectPositionY, camera.nearClipPlane);//hole.transform.position;
      
        while(elapsedTime < duration)
        {
            transform.position = Vector3.Lerp(startPos, endpos, elapsedTime / duration);
            elapsedTime += Time.deltaTime;
            yield return null;
        }
    }

Without understanding the full scope of what’s happening outside of this code, (and with limited time to respond today), I would suggest doing some input/output verification. Use Debug.Log and print values to the console so that you can verify what code is being run, and what values are correct or incorrect.

Is “moveObject” being reached? Is “startPos” and “endpos” values that you expect?

For some strange reason though “if(freePositions.Contains(newPos))” is satisfied, “moveObject” never gets run I guess, since the logs I put never print.

After carefully examining the code I realised that the issue was with “private IEnumerator moveObject(float posX, float duration)”. I forgot that the script wasn’t attached to the hole object. But I’ve sorted that now. Currently, I am having an issue with the destruction of the moving objects. Some move and others are stationary. When I try to destroy all the objects that move, only a few of them are destroyed. Please see the relevant code below:

private void ReverseDirection()
    {
        rightMovementDirection *= -1;
    }
    private float getNextPos(float currentPos)
    {
        int nextIndex = holePositions.IndexOf(currentPos) + rightMovementDirection;
    
        // if index is out of range
        if(nextIndex < 0 || nextIndex >= holePositions.Count)
        {
            return 0.0f;
                  }
        else
        {
            return holePositions[nextIndex];
        }
    }
private IEnumerator MovementLogic(GameObject movingObject)
    {
        // keep going until deactivated or disabled
        float currentPos = movingObject.transform.position.x;
        while(filledPositions.Contains(currentPos))
        {
            float newPos = getNextPos(currentPos);
                      if(freePositions.Contains(newPos))
                {
                    freePositions.Add (currentPos);
                    filledPositions.Remove (currentPos);
                    freePositions.Remove (newPos);
                    filledPositions.Add (newPos);
                    
                    yield return moveObject(movingObject, newPos, movementDuration);
                   
                    //update positions
                    yield return new WaitForSeconds(movementDelay);
                }
                else
                {
                    // if a valid hole wasn't found, try the other direction next frame
                    ReverseDirection();
                }
            }
        
        yield return null;
        //}
    }
    private IEnumerator moveObject(GameObject movingObject, float posX, float duration)
    {
        float elapsedTime = 0;
        Vector3 startPos = movingObject.transform.position;
        Vector3 endpos = new Vector3 (posX, objectPositionY, camera.nearClipPlane);//hole.transform.position;
    
        while(elapsedTime < duration)
        {
            movingObject.transform.position = Vector3.Lerp(startPos, endpos, elapsedTime / duration);
            elapsedTime += Time.deltaTime;
            yield return null;
        }
    }

The destruction of all the moving objects is done as below:

                    for (int j = 0; j < holeObjects.Count; j++) {
                        if (holeObjects [j].getHoleObjectsType () == holeObjectsSetup.holeObjectType.moving) {

                            movingHoleObjectsCount += 1;
                            holeObjects [j].destroyHoleObject ();
                            holeObjects.RemoveAt (j);
                        }


                        if (j == holeObjects.Count - 1) {
                            Debug.Log ("movingHoleObjectsCount is " + movingHoleObjectsCount);
                  }
}

From my log in the code above (movingHoleObjectsCount), not all the moving objects are destroyed. I suspect “private IEnumerator MovementLogic(GameObject movingObject)”, where I change the filledPositions and freePosition arrays accordingly, before calling moveObject:

  freePositions.Add (currentPos);
                    filledPositions.Remove (currentPos);
                    freePositions.Remove (newPos);
                    filledPositions.Add (newPos);
                   
                    yield return moveObject(movingObject, newPos, movementDuration);

Since there are a few moving objects at any given time, there is a possibility of my destroying moving objects just before they are about to move or during their movement, in such a case I don’t think they’ll be recognised at their given index. I tried populating the lists after the “moveObject” but that created the issue of two objects occupying the same position occasionally. How can I solve this problem of some moving objects not being recognised during destruction? Also, I am not too sure how to stop the “MovementLogic” coroutine when I destroy a moving object.

When a gameobject is disabled or destroyed, all coroutines running on that object are stopped.

I’m not sure how “getHoleObjectsType()” is working, or what determines the hole object’s “holeObjectType”.

One solution to know if the object is moving is to add a boolean “isMoving” which you set true at the start of the movement logic, and false at the end. If your “holeObjectType” is a state enum, then you can set it to “moving” there. Then test for that when destroying.

You should have your hole objects carrying all the relevant information. If you need to know which hole they are moving towards, make a “targetHoleIndex” variable, or a “lastHoleIndex” variable or something.

I keep getting the error:

MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it. Your script should either check if it is null or you should not destroy the object

pointing to the line:

        Vector3 startPos = movingObject.transform.position;

in

private IEnumerator moveObject(GameObject movingObject, float posX, float duration)

when I destroy the moving objects as shown in my earlier post.

Try this:

private IEnumerator moveObject(GameObject movingObject, float posX, float duration) {
    if(movingObject != null) {
        float elapsedTime = 0;
        Vector3 startPos = movingObject.transform.position;
        Vector3 endpos = new Vector3(posX, objectPositionY, camera.nearClipPlane);//hole.transform.position;

        while(movingObject != null && elapsedTime < duration) {
            movingObject.transform.position = Vector3.Lerp(startPos, endpos, elapsedTime / duration);
            elapsedTime += Time.deltaTime;
            yield return null;
        }
    }
}

Alternatively you could try doing “holeObjectScript.StopAllCoroutines()” before destroying.

1 Like

After several hours I realised that the way I was destroying the moving objects was prevented all of them from getting destroyed. After adding the nullity check as you recommended I am still getting the same " MissingReferenceException: The object of type ‘GameObject’ …" error but this time it’s pointing to the line

            movingObject.transform.position = Vector3.Lerp(startPos, endpos, elapsedTime / duration);

in the same “moveObject” method. I tried adding another nullity check surrounding the contents of the while loop but that made Unity non-responsive every time I destroyed a moving object while it was moving. Please see my new code for destroying moving objects below:

   for (int i = 0; i < holeObjects.Count; i++) {
Debug.Log ("countingholeobjectindex"+ i );

if (col.gameObject.Equals (holeObjects [i].getHoleObject ())) {
Debug.Log ("condition satisfied!" );

foreach( HoleObjectSetup holeItem in holeObjects )
{
if (holeItem.getHoleObjectType () == HoleObjectSetup.holeObjectType.moving) {
int holeItemIndex = holeObjects.IndexOf(holeItem);
holeItem.StopAllCoroutines();
holeItem.destroyHoleObject ();

//holeObjects.RemoveAll(item=>item==null);
//holeObjects.RemoveAt(holeItemIndex);

}
}

}

What is happening in the “destroyHoleObject” function? Is it not possible for you to use “Destroy(holeObject)”?

What exactly is inside your “holeItem” class? Why is “getHoleObject” necessary? Can you use “holeItem.gameObject”?

I have a feeling that it is your holeItem implementation is what is causing these issues for you. This is a pretty straightforward behavior you’re looking for, so it shouldn’t be giving you this much grief.

Also, you have to call StopAllCoroutines on the monobehavior that is running them. Is your “HoleItem” class the one starting the coroutines?

Basically, I have a “HoleObjectSetup” script that contains:

The code for DestroyHoleObject() which is in HoleObjectSetup is:

public void DestroyHoleObject () {
Destroy (currentHoleObject);
}

and

public void createHoleObjectType (GameObject holeObject, HoleObjectType holeObjectType, Vector3 holeObjectPosition, bool isCollider2DEnabled) {

currentHoleObject = (GameObject)Instantiate (holeObject, holeObjectPosition, Quaternion.identity);
setHoleObjectType (holeObjectType);

}

“holeItem” is of type “HoleObjectSetup”, “holeItem.getHoleObject()” represents the hole game object. I realised that I missed the change you recommended earlier to moveObject “while(movingObject !=null&& elapsedTime <duration)”. The error is gone :), but one more thing, when I check the “holeObjects.Count”, it still recognises the same number of objects as before the destruction. I guess it still has the null objects. How and where will be the most appropriate place to remove them from the holeObjects list?

So your HoleObjectSetup is a container for the actual hole object?

Is your “holeObjects” list supposed to represent the holes or the objects in them?

Destroying a hole object doesn’t destroy the container object, so “holeObjects” will still have a reference to the item.

I think you’re making this much harder for yourself than is necessary by referencing objects through container objects, but you could try doing this to destroy and remove:

for(HoleObjectSetup holeItem in holeObjects.ToList()) { // linq method "ToList" allows this list to be modified in the loop
    if(holeItem.getHoleObjectType() == HoleObjectSetup.holeObjectType.moving) {
        holeItem.StopAllCoroutines();
        holeItem.destroyHoleObject();
        holeObjects.Remove(holeItem);
    }
}

Is this the intended end result? “holeObjects” will be one entry smaller, and the hole object contained within it will be destroyed first.