Cheapest way to move object?

Hello everyone!

So I wrote this script for moving objects, but especially in VR this will result in a considerable impact on performance when moving about 4 objects at the same time:

IEnumerator MoveObject (GameObject OBJ, Vector3 pos0, Vector3 pos1, float stepSize, float waitTime) {
    Vector3 currentPos;
    for (float i = 0.0f; i < 1.0f; i += stepSize) {
        currentPos = Vector3.Lerp(pos0,pos1,i);
        OBJ.transform.position = currentPos;
        yield return new WaitForSeconds(waitTime);
    }
}

Do you know if there is a cheaper way to do this?

Many thanks for any ideas,
Greetings,
Shu

Well, you can cache the transform so that youā€™re not using the ā€˜transformā€™ property. Even though they claimed to have cached it for you now, Iā€™ve heard itā€™s still not performing very well (though havenā€™t measured it myself - I always cache it).

You can also put the ā€˜new WaitForSeconds(waitTime)ā€™ outside of the for loop in a variable to avoid allocating new memory for it on each iteration (and invoking the GC).

1 Like

Iā€™d guess itā€™s not the code itself thatā€™s hurting performance; Iā€™d guess youā€™re moving an object that has a Collider but no Rigidbody, which is a long-standing Unity issue. If itā€™s an object with a Collider, add a Rigidbody to it and check ā€œIs Kinematicā€ - the rest of the Rigidbody fields donā€™t matter - then it should move more smoothly.

2 Likes

Thanks for your help, @JasonBricco !
I was thinking about caching the transform before, but I thought it wouldnā€™t make a big difference.
However, I will try caching it! The YieldInstruction, too, of course!
Maybe this already solves the problem.

Just to make sure, this is how it would look like, right?:

IEnumerator MoveObject (GameObject OBJ, Vector3 pos0, Vector3 pos1, float stepSize, float waitTime) {
    Transform objTransform = OBJ.transform;
    Vector3 currentPos;
    YieldInstruction _waitTime = new WaitForSeconds(waitTime);
    for (float i = 0.0f; i < 1.0f; i += stepSize) {
        currentPos = Vector3.Lerp(pos0,pos1,i);
        objTransform.position = currentPos;
        yield return _waitTime;
    }
}

Thank you for the suggestion! Itā€™s good to know! If I remember correctly, though, I didnā€™t use a collider on the object because it was supposed to be some visual effect only. Anyway, Iā€™m not positive and when I run into some issues again, I will make sure to take another look at Collider / Rigidbody components as you suggested!

The reason Iā€™m thinking itā€™s something unrelated to the code is that while Jason is correct that fixing those things may give you a tiny bump in performance, that code should not be having any ā€œconsiderable impact on performanceā€ with only 4 objects. If you were moving 4000 objects then maybe, but for only 4 that shouldnā€™t even be a blip. You can use the Profiler window and turn on ā€œDeep Profilingā€ and run to see what is taking up the most time each frame; I doubt that itā€™s that code.

2 Likes

Oh, four objects. Didnā€™t catch that. Yeah, with only four it definitely shouldnā€™t be causing problems as is. Thatā€™s fairly strange. Iā€™d have to agree with using the profiler to see whatā€™s going on.

2 Likes

My guess, is that you are wrong about what you are thinkingā€¦ What is happening in your script is Every X seconds, you move an object from one point to another, and because this is not smooth, you feel that it is hitting performance. You honestly have to run this at the fixed Update level and have thousands of them doing this for it to affect performance.

The next part, is that you are stepping a Lerp, which is really not going to be a good idea anyways. You would want to use MoveTowards with the distance divided by the steps. Still, you are only approximating what you should be doing.

This of course, if each of these objects is ā€œnormalā€ or not super high poly count.

Super high polycounts would probably give monster performance issues.

This being said, you should transform this into a simple controller that happens each Update cycle, and try not to use coroutines.

Here is an alternate implementation of what you are trying to achieve.

// this code moves each block
using UnityEngine;
using System.Collections;

public class MovingObject : MonoBehaviour
{
    public Vector3 target;
    public float speed = 3;

    public GameObject callBackObject;

    void Update()
    {
        if (target == null) return;

        if ((target - transform.position).sqrMagnitude == 0 && callBackObject != null)
            callBackObject.BroadcastMessage("MoveFinished", gameObject, SendMessageOptions.DontRequireReceiver);
        var position = transform.position;
        position = Vector3.MoveTowards(position, target, speed * Time.deltaTime);
        transform.position = position;
            
    }

    // callback for moving this obj
    void Move()
    {
        target = transform.position + Random.insideUnitSphere * 20;
        this.target = target;
    }
}
// this is a master controller to add more blocks to the scene
using UnityEngine;
using System.Collections;

public class MovingMasterController : MonoBehaviour
{
    // Add obj to the controller
    void AddObject(GameObject obj)
    {
        if (obj == null)
            obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
        var controller = obj.GetComponent<MovingObject>();
        if (controller == null) controller = obj.AddComponent<MovingObject>();
        controller.callBackObject = gameObject;

        MoveFinished(obj);
    }

    // callback on controller obj
    void MoveFinished(GameObject obj)
    {
        obj.BroadcastMessage("Move", SendMessageOptions.DontRequireReceiver);
    }


    float nextObject = 0;
    float timePerObject = 2;
    void Update()
    {
        if (nextObject <= 0)
        {
            AddObject(null);
            nextObject = timePerObject;
        }
        nextObject -= Time.deltaTime;
    }
}
2 Likes

For reference, moving static colliders in UT5+ is no longer an issue - or at least not as much as it was with previous versions. http://blogs.unity3d.com/2014/07/08/high-performance-physics-in-unity-5/

I agree that moving only four objects shouldnā€™t be too much of an issue, however that also heavily depends upon what those objects are and how many child objects they contain. Moving terrain for example can be quite heavy due to the time it takes to reconfigure culling. Moving one object with many children will also come with addition overhead.

2 Likes

Yeah, as bigmisterb said, what your script actually does is cause the object to warp from one spot to the next every ā€œwaitTimeā€ secondsā€¦ I assumed thatā€™s what you wanted it to do. Maybe youā€™re seeing that and assuming itā€™s a bad framerate. If you want it to move smoothly:

IEnumerator MoveObject (GameObject OBJ, Vector3 pos0, Vector3 pos1, float duration) {
   float timer = 0f;
   while(timer <= duration) {
       OBJ.transform.position = Vector3.Lerp(pos0, pos1, timer/duration);
       timer += Time.deltaTime;
       yield return null;
   }
   OBJ.transform.position = pos1;
}
1 Like

Thank you for all of your replies and sample scripts!

This may be somewhat OT now as it doesnā€™t seem to be an issue after all, but how would I be caching a Transform?

Transform objTransform = OBJ.transform;

// or

Transform objTransform = OBJ.GetComponent<Transform>();

Thatā€™s not the issue actually. When I use stepSize and waitTime small enough, I will get a decently looking, smooth and accurate animation from one point in space to the other. The issue is that when reducing the waitTime to make it smooth, the impact on performance will be visible due to lag of the first-person controller.
Maybe in the next days I will take a deeper look into the profiler again and let you know what I find.

The objects were very basic spheres (Iā€™m not sure if Unityā€™s internal spheres, which would have included a Collision component, which I might not have removed), but the poly count was probably < 1000. Also, the objects didnā€™t have any children attached and a very basic (reflective semi-transparent) material.

EDIT:
Just to make sure, I usually pass values at about that size:
waitTime = 0.01f
stepSize = 0.02f

Maybe it is a problem solved by caching after all? After all, I am calling the for loop 100 times per second (ideally).

EDIT2:
Oh and Iā€™d really like to use a coroutine as I donā€™t want to play it all the time and checking a bool each frame seems unnecessary, right? Especially when I want to have quite an amount of objects moving and not necessarily all at the same time (resulting in a bool for each object). It also makes sure the object transitions all the way through to pos1 instead of stopping in the middle when it needs to be cancelled.

Transform objTransform = OBJ.transform; Will do the trick but youā€™ll need to pass it to the functionā€¦

IEnumerator MoveObject (Transform objTransform, Vector3 pos0, Vector3 pos1, float duration)
1 Like

Iā€™m confused. Why is that? This should work, right?:

IEnumerator MoveObject (GameObject OBJ, Vector3 pos0, Vector3 pos1, float stepSize, float waitTime) {
    Transform objTransform = OBJ.GetComponent<Transform>();
    Vector3 currentPos;
    YieldInstruction _waitTime = new WaitForSeconds(waitTime);
    for (float i = 0.0f; i < 1.0f; i += stepSize) {
        currentPos = Vector3.Lerp(pos0,pos1,i);
        objTransform.position = currentPos;
        yield return _waitTime;
    }
}

Of course, I will still end up with a .GetComponent once for the whole animation, but not each waitTime seconds.

Dont do it in a co-routine to start withā€¦

Caching transform is only going to provide a minimal performance improvement.

1 Like

Can you explain why I shouldnā€™t use a coroutine?
I just tested the coroutine using 48 standard Unity spheres, which is 160k verts. Resulted in about 110fps - I guess thatā€™s okay? Will have to test in VR scenario to be positive, though.
Also, I couldnā€™t see any change in performance when removing the sphere collider.

EDIT:
In the test scenario, I just tried to use the same script but without caching. This will result in a fps loss of about 5-10. Not sure if this is enough to make a difference in the VR scenario.

I dont think co-routines are designed for this sort of thing for oneā€¦ they generate garbage if you dont know what youre doing, which is never a good thing.

Those performance results are abysmal, but I dont know what hardware youre running.

I can get much better performance moving hundreds of gameobjects

1 Like

Sure, but as you say youā€™ll be caching the component whenever you run the animation.

1 Like

Thereā€™s no way that caching the transform outside of that loop is going to save 10 FPS unless something has gone horribly wrong with your CPU.

1 Like

Yes, thatā€™s true, but the thing is: I want to access other things from the GameObject, too. So it makes sense passing the GameObject? Iā€™m not sure if caching a component about once per second (after each animation) is better or additionally passing Transform.

I donā€™t know what to tell you. Thatā€™s just how it is. With caching both the YieldInstruction and the Transform, I get about 105-120fps (average 112,5fps). Without caching, I get about 95 - 110fps 102,5 (average 102,5fps).

So you use MoveTowards in Update()? I just try to avoid using Update as much as possible for things like that, because I want a clean way to animate things without creating a mess, yet being able to keep things variable. For example, if I want the resulting position to change, I will have a problem when doing this during Update as I donā€™t know at which state the object is. When running the coroutine, I can set a new pos1 exactly after it reaches pos0 again.

Here are some of my specs (not used for VR, but used for test scene):
Unity 5.2.2
Retina Macbook Pro 2015, 2,8 GHz i7, quad-core, 16 GB RAM, Intel Iris Pro 1536 MB
OSX 10.11.5

EDIT:
I just ran an empty scene and get about 90 - 140 fps (about 3/4 of the time around 90), so average about 102,5 fps, which is kind of weird. It actually runs faster with 48 spheres and the script that caches the YieldInstruction and the Transform and about just as fast as with the script that doesnā€™t cache.

You should really use the Profiler window and look at what is taking up time in your frames. Click in the ā€œCPU Usageā€ section and youā€™ll get a list of everything in order thatā€™s taking up time. If ā€œMoveObjectā€ isnā€™t near the top of that list, then itā€™s unlikely that itā€™s having much of an effect.

Just to make sure I wasnā€™t crazy, I copied and pasted the exact script from your original post and put it on 1000 spheres and set the wait time really low so that it basically ran every frame, and there was no discernible difference between whether the transform was cached. Caching the WaitForSeconds reduced garbage a bit but not enough that it impacted the framerate. I have a much less beefy computer than you and I got 200+ FPS with a thousand spheres. With only four spheres I got 2000+ FPS. If youā€™re only seeing 100 FPS with only four spheres, something else is going on.

1 Like

If you need a reference to GameObject and Transform, just pass them both to the functionā€¦ but this really is micro optimization so I shouldnā€™t worry too much. It certainly sounds like you have bigger issues reducing your performance. Perhaps something outside of UT eating your CPU?

Regarding co-routine garbage, hereā€™s one solution: http://forum.unity3d.com/threads/free-more-effective-coroutines.387679/

1 Like