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.
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.
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;
}
}
}
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:
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:
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.
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
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
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?
â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.