0
Answered

RigidBody2D Values Stored Incorrectly on SetActive(false)

SubMatterMatt 3 years ago updated by Lazlo Bonin (Lead Developer) 2 years ago 4

I have a scene where I am spawning multiple Enemy prefabs by pulling them from an object pool and doing a SetActive(true) to enable them.  In each Enemy FixedUpdate they are performing a movement function that adds an Impulse force towards the direction of the player (using timeline.rigidbody2d).  When the player dies I am setting my global main clock to a slowed speed which seems to change the values of the Rigidbody, specifically the Linear/Angular drags.  Then, if the player presses a button I reset the level (set the global time back to 1f, call Timeline.Reset() for all enemies, and set all enemies to SetActive(false)).

The issue I am having is that it seems like the enemies are storing their drag values during the SetActive(false) call and not resetting them to the default values.  So, when they spawn again they are moving extremely fast now that the drags are lowered and the time is back to normal.  Is there something I'm missing that I need to call during a reset?

I'm wondering, maybe the rigidbody values are set on the next FixedUpdate() call which isn't happening with a time scale of 1f before I call SetActive(false)?

Chronos Version:
2.2
Unity Version:
2017.1.1f1
Pending Review

Hm, interesting issue. In which order exactly are you doing these 3 things? Setting the global time scale back to 1f should reset the drag values. Can you try just doing that and seeing if it works? 

I'll try to give a general overview of the order I'm calling everything.  I have one class controlling the time slow downs doing something like this:

// Update is called once per frame
    void Update () {
        if(_isSlow)
        {
            _isSlow+= Time.deltaTime;             if(_isSlow>= TimeToSlow)
            {
                _isSlow = false;
                _globalClock.localTimeScale = 1f;
            }
        }     }
In another class's Update() I have:
if (_playerActions.ResetButton.WasPressed)
{
    Reset();
}

Then Reset() looks like this:

public void Reset()
{                _isSlow = false;
        Timekeeper.instance.Clock("MainClock").localTimeScale = 1f
       List<GameObject> enemies = new List<GameObject>();
        enemies.AddRange(GameObject.FindGameObjectsWithTag(TagManager.Get(Tags.Enemy)));
        for(int i = 0; i < enemies.Count; i++)
        {
            enemies[i].GetComponent<Timeline>().Reset();
            enemies[i].SetActive(false);
        }
}

I only have the one Clock and have all timelines looking at it (including the enemies and player).  As you can see in the Reset(), the time change and the enemy are changes in order.  I'll keep trying it but let me know if you see any issues there.

After some time away I came back to this and put in a patch to solve it.  I'm not experienced with the Chronos codebase to know if this is the exact reason or where it happens, but I'll explain what I assumed and how I solved it, maybe you'll be able to find an issue in the Chronos base.

Issue:
Clocks and time manipulation code seems to do a number of different types of Lerp which usually need to be called every Update or FixedUpdate().  Based on the assumption that Chronos calls these per update, I assumed that disabling the entire gameobject was disabling the Update and thus stopping the lerp of rigid body values on the object.  My guess is Chronos doesn't do anything with lerp between when the object is disabled and then enabled again (as would happen in an object pool design pattern).

Solution:
First, this solution is specific to my project so it might not be the best solution for everyone.  Instead of calling GameObject.SetActive(false) now, I changed it to be a 'soft disable' by instead disabling my main object components (SpriteRenderer, BoxCollider2D, etc) and leaving the RigidBody and Timeline on the object active.  This allows the Update/Lerp to continue to run in the background and seems to have solved the issue for now (hopefully!).

Hopefully some of this makes sense from a high level and can help implement a fix in Chronos if you determine that scenario is truly a bug.  :)

Answered

Hi SubMatter! Sorry for the late reply on this, glad you found a workaround.

I'm not going to change the core of what happens when a Timeline is disabled in order to avoid breaking existing games using Chronos. 

However, if you're using an object pooling pattern, I would recommend using Timeline.Reset() and Timeline.ResetRecording() after your SetActive(true) call from the pool.

Together, these calls should take care of clearing any Chronos data, including occurrences, time scales, delta time smoothing and snapshots. Note that it does reset the time scale to 1, however.

Hopefully that helps!