How to change player's position after the scene transition/fade in RPG game?

My game layout is similar to Stardew Valley and Zelda style. My player enters buildings by interacting with a Collider2D placed at the door. My player is a prefab with DontDestroyOnLoad.
I would like to set my player in a specific transform position indoor once the scene of the building is loaded.
I have also made a black fading animation for the scene transition.
Right now, with this script, my player’s new position glitches and changes immediately and very briefly in the village scene before the fading animation and new scene has time to change. However, it does correctly place the player in the desired position once the new scene is fully loaded too.
So my problem is that I do not know where to place (transform.position = new Vector3(xPosition, yPosition, 0)) or what to add to my script that would make the player’s position changes only after the new scene is fully loaded.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneLoader : MonoBehaviour
{
    public Animator transition;
    public float transitionTime = 1f;

    public string levelName;
    public int Index;
    public float xPosition;
    public float yPosition;

    void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            StartCoroutine(LoadLevel());
            other.transform.position = new Vector3(xPosition, yPosition, 0);

        }
    }

    IEnumerator LoadLevel()
    {
        transition.SetTrigger("Start");
        yield return new WaitForSeconds(transitionTime);
        SceneManager.LoadScene(Index);

    }
}

If you want something to happen after the scene is loaded, then putting it after “ScenManager.LoadScene” is probably a good bet.

To do that, your LoadLevel function will need to know which Transform it is supposed to move. One way to accomplish that would be to add a parameter to the function that allows the caller to pass in the appropriate Transform.

I’m fairly new to unity and coding… Can you guide me to what parameter to use and how you would code it in the function? I have a prefab with parent gameobject called “Player” and a child called “GFX” which has a tag called “Player” which has the transform I would like to change with the xPosition and yPosition.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneLoader : MonoBehaviour
{
    public Animator transition;
    public float transitionTime = 1f;

    public string levelName;
    public int Index;
    public float xPosition;
    public float yPosition;

    void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            StartCoroutine(LoadLevel(other.transform));
        }
    }

    IEnumerator LoadLevel(Transform playerTransform)
    {
        transition.SetTrigger("Start");
        yield return new WaitForSeconds(transitionTime);
        SceneManager.LoadScene(Index);
        playerTransform.position = new Vector3(xPosition, yPosition, 0);
    }
}
1 Like

Unfortunately, the glitch is still there…

Well…

I haven’t tested that code, and I don’t have experience doing complicated stuff around scene transitions. I could imagine something going wrong with the code I posted.

But “it happens exactly the same as before” is not one of the ways it could plausibly go wrong. Regardless of anything happening with the scene transition, just the fact that the movement was moved to be after the WaitForSeconds should at least mean that it happens at a different time.

So if you are seeing no change, I think there’s almost certainly something wrong with your test. Which could be any of:

  • You didn’t make exactly the changes that I gave you (e.g. you failed to delete line 22 of your original code).

  • You made the changes correctly, but you are unknowingly running old code (because you forgot to save, or didn’t give Unity a chance to rebuild, or there was a problem with the build that you didn’t notice, or your project contains two similar files and you changed the wrong one, or…)

  • You made a mistake in observing the results, and thought that this was the same as before but it actually wasn’t.

  • The problem you’re looking at was actually never caused by the code you posted in the first place, but by some other code that you didn’t post.

  • Something weird that I haven’t thought of.

If you really can’t find any problems of that nature, then all I can suggest is standard debugging techniques–add Debug.Log() calls to verify what’s happening, or step through the code in a debugger, etc.

I assure you that I definitely input the script properly and reviewed each line, and I’m saving constantly. There is a slight change. The glitch time seems much shorter. However, it is still visible in a split second. Nevertheless, thank you for your suggestion and help.

Use timeline. I have a unity timeline that plays on awake in the new scene. It is responsible for fading in the new scene, and it has a time line signal that positions the player after the fade out, but before the fade in.

Keep in mind LoadScene does NOT load the scene the same frame.

If I need to do something in the next frame, I usually just make a DoNextFrame class that will store a delegate and then when the scene has been loaded next frame, it runs the delegate.

Here’s my CallAfterDelay class: pass 0 in for the delay to just have it happen next frame.

Use the Create() factory method and pass that your delegate.

using UnityEngine;
using System.Collections;

public class CallAfterDelay : MonoBehaviour
{
    float delay;
    System.Action action;

    public static CallAfterDelay Create( float delay, System.Action action)
    {
        CallAfterDelay cad = new GameObject("CallAfterDelay").AddComponent<CallAfterDelay>();
        cad.delay = delay;
        cad.action = action;
        return cad;
    }

    IEnumerator Start()
    {
        yield return new WaitForSeconds( delay);
        action();
        Destroy ( gameObject);
    }
}

Thank you for your help. I must admit I don’t quite understand what to do with this script.
How would my player’s new Vector3 position be incorporated into this script?
As Michael Scott would say “Explain this to me like I’m an 8-year-old”.

This stuff is really fiddly to get right but it CAN be done. Try this approach to see what is actually happening:

In your OnTrigger call, when the “go here” condition is satisfied, call this:

Debug.Break();

This will pause the editor. Now don’t un-pause it but rather single-step it and see what happens the next frame. Single-stepping lets you inspect the hierarchy to see what scenes and objects are loaded, and also see the Debug console so you can output more Debug.Log() lines there to understand what is going on.

If you are moving the player before the fading is done, that would cause a visual glitch, and this would let you know that’s what is happening.

You may have to reorganize sequencing so that when you reach the door, a fade is started with a second “follow on” delegate that the fade routine calls when it is done. This is a similar principal to how the CallAfterDelay function works above.

I will not attempt to describe delegates / callbacks / anonymous functions / actions… there are plenty of tutorials already out there, but the tl;dr is that it is a variable that points to a piece of code that you (the caller) want the function (the callee) to call for you sometime in the future.

Your method sounds promising but I have never used a timeline. In videos that I have watched about the timeline, it seems they use it for cutscenes and add in a Playable Director. Can you guide me on how should my timeline Playable looks like with a signal or how it should be done? My “Fade_Out” and “Fade_In” animations are respectively 1 second each with a “FadeCtrl” Controller. My player has a “PlayerCtrl” Controller for idle/walking movement.

So for my project setup I use one persistent scene that stays loaded at all times. It holds all my ‘managers’ and anything in the game that is present at all times. All other scenes are loaded or unloaded using the additive scene loading technique. Don’t destroy on load is kinda doing something similiar for you, in a manner of speaking.

Anyway, in this scene there are two timelines i keep around that operate on a ui image. One for fading out, and one for fading in. The Default Playables package on the asset store is free and has timeline track for controlling the fade of images. They both fade over exactly 2 seconds. I communicate with these timelines from other scenes by using a scriptable object game event that is based largely on this video tutorial of scriptable objects:

Watch it. It has some very useful tidbits. Anyway, say the player opens a door that acts as a transition to another scene. First I raise the fade out event, and then i raise another event called “Load Weapon Shop” or something. For this example, we’ll assume a trigger in some active scene raises the events when the player enters the trigger.

The fade out event will start the fade out timeline in the main scene. The Load Weapon Shop scene also has a listener in the main scene, that starts a behavior tree on an object inside the main scene. The behavior tree waits for 2 seconds(the time it takes to fade out), then it unloads everything but the main scene, after which it additively loads the weapon shop scene. When the loading finishes, the behavior tree raises the Fade In event.

The weapon shop scene itself has a timeline inside of it that is set to start in on awake. At the start of the timeline, I use a timeline signal to call a function on a custom script inside the weapon shop scene that disables player input for a few seconds, then fetches the game object tagged as Player, and places it at a spawn point inside the scene, after which it plays a little animation of the player entering the shop, before finally another timeline signal restores control to the player.

One key tidbit here would be to make sure that your scripts are done in a reusable and easily debuggable manner so that you can tell wtf is going on. As mentioned above, Debug.Break() is one way of doing this. Debug.Log() is another. I’m a big fan of this behavior tree asset:

Essentially it allows me to design code in small conditional tasks that I can chain together, and when executed I can watch the IDE and see which tasks are returning failure. If you haven’t bothered writing your own FSM or behavior tree, then its worth investigating.

Thank you for taking your time to explain it! It’s very insightful information. I’ll make sure to look at this approach and try it out.

I totally forgot about the single-step tracking! It was an important and useful reminder. As you said, I noticed my player is moving in my main scene exactly one frame before the fade even started and that’s the visual glitch. I would have to learn more about delegate indeed and try out your CallAfterDelay function. Good stuff. Thank you.

1 Like