Is there a better method to programatically move offscreen objects?

Hey,

I’m creating a 2D vertical endless runner as my first game. I’ve wrote a script to place 5 wall prefabs at each side of the screen and 3 background prefabs. The camera follows the player vertically (with some dampening) and so when a prefab enters and then leaves the screen, it’s moved above the highest prefab:

public class SceneBuilder : MonoBehaviour
{

    public Transform lWall;
    public Transform rWall;
    public Transform bg;

    private IList<Entry> lWalls = new List<Entry>();
    private IList<Entry> rWalls = new List<Entry>();
    private IList<Entry> bgs = new List<Entry>();
    private float wallDistance = 3.75f;

    void Awake()
    {
        for (int y = -2; y <= 2; y++)
        {
            lWalls.Add(new Entry { Prefab = (Transform)Instantiate(lWall, new Vector3(-wallDistance, y * lWall.localScale.y, 0.5f), Quaternion.identity) });
            rWalls.Add(new Entry { Prefab = (Transform)Instantiate(rWall, new Vector3(wallDistance, y * rWall.localScale.y, 0.5f), Quaternion.identity) });
            if (y > -2 && y < 2)
            {
                bgs.Add(new Entry { Prefab = (Transform)Instantiate(bg, new Vector3(0, y * bg.localScale.y, 3f), Quaternion.identity) });
            }
        }
    }

    void Update()
    {
        new List<IList<Entry>>() { lWalls, rWalls, bgs }.ForEach(CheckVisibility);
    }

    private void CheckVisibility(IList<Entry> entries)
    {
        foreach (var entry in entries)
        {
            if (!entry.BeenVisible && entry.Prefab.renderer.isVisible) 
            {
                entry.BeenVisible = true; 
            }

            if (!entry.WentInvisible && entry.BeenVisible && !entry.Prefab.renderer.isVisible)
            {
                entry.WentInvisible = true;
            }
        }

        if (entries.Any(p => p.WentInvisible))
        {
            var entry = entries.Where(p => p.WentInvisible).OrderBy(p => p.Prefab.transform.position.y).First();
            entry.BeenVisible = false;
            entry.WentInvisible = false;
            MovePrefab(entry.Prefab, entries.OrderByDescending(p => p.Prefab.transform.position.y).First().Prefab.transform.position.y);
        }
    }

    private void MovePrefab(Transform prefab, float maxPos)
    {
        var height = prefab.transform.localScale.y;
        if (height == 3)
        {
            prefab.transform.position = new Vector3(prefab.transform.position.x, maxPos + 3, prefab.transform.position.z);
        }
        else
        {
            prefab.transform.position = new Vector3(prefab.transform.position.x, maxPos + 8, prefab.transform.position.z);
        }
    }

    private class Entry
    {
        public Transform Prefab { get; set; }
        public bool BeenVisible { get; set; }
        public bool WentInvisible { get; set; }
    }
}

I want to know if there’s a better way to achieve the same results that I’m not aware of. I’m experienced with C# but I’m learning Unity’s API as I go so I could have missed something that would make things easier/optimal.

A couple of alternative methods to consider.

  • Place a trigger just offscreen. When the GameObject hits the trigger then move them. (OnTriggerEnter)
  • Use transform.position to figure out when an object is offscreen and move

Creating new lists every frame is going to kill you from a GC perspective. In many cases its more efficient to repopulate an existing list.

Here’s an example of a script that might do the job. Use this by placing on each GameObject that is part of the background. You can set the public variables via the inspector or via code.

public float lowerLimit;
public float distanceToMoveUp;

void Update (){
    if(transform.position.y < lowerLimit){
        transform.position = new Vector3 (transform.position.x, transform.position.y + distanceToMoveUp, transform.position.z);
    }
}