Forum begins after the advertisement:
[Part 5] Scene Transition Errors
Home › Forums › Video Game Tutorial Series › Creating a Metroidvania in Unity › [Part 5] Scene Transition Errors
- This topic has 59 replies, 4 voices, and was last updated 23 hours, 39 minutes ago by
Ser Apustaja.
-
AuthorPosts
-
February 2, 2026 at 12:15 pm #19247::
It sounds like the player sliding issue may be related to the way the movement is being handled in your
PlayerControllerscript. Here are a few things to check:-
Friction Settings: Ensure that the physics material on your player’s
Rigidbody2Dis set correctly. If it has low friction, it may cause the player to slide. You can create a new Physics Material 2D with higher friction and assign it to the player’s collider. -
Movement Code: Review the movement code in your
Move()function. Make sure that the velocity is being set correctly and that there are no unintended forces acting on the player. For example:rb.velocity = new Vector2(walkSpeed * xAxis, rb.velocity.y); -
Ground Check: Ensure that the
Grounded()function is working correctly. If the player is not recognized as grounded, it may not be able to stop sliding properly. You can add debug logs to check if the player is grounded when expected. -
Drag Settings: Check the drag settings on the
Rigidbody2D. Adding some linear drag can help reduce sliding when the player stops moving. -
Animation Transitions: If you have animations set up, ensure that the transitions between walking and idle states are configured correctly in the Animator. If the animation is not playing as expected, it might affect the player’s movement perception.
If you could provide more details about how the sliding manifests (e.g., does it happen when the player stops moving, or does it slide while moving?), that would help narrow down the issue further.
February 6, 2026 at 10:03 am #19249::the player is not walking during the cutscene an instead continues to use the idle animation
February 6, 2026 at 10:07 am #19250::here are the updated player controller and scene transition scripts respectively
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UIElements; using UnityEngine.UI; public class PlayerController : MonoBehaviour { [Header("Horizontal Movement Settings:")] [SerializeField] private float walkSpeed = 1; //sets the players movement speed on the ground [Space(5)] [Header("Vertical Movement Settings")] [SerializeField] private float jumpForce = 45f; //sets how hight the player can jump private float jumpBufferCounter = 0; //stores the jump button input [SerializeField] private float jumpBufferFrames; //sets the max amount of frames the jump buffer input is stored private float coyoteTimeCounter = 0; //stores the Grounded() bool [SerializeField] private float coyoteTime; //sets the max amount of frames the Grounded() bool is stored private int airJumpCounter = 0; //keeps track of how many times the player has jumped in the air [SerializeField] private int maxAirJumps; //the max no. of air jumps private float gravity; //stores the gravity scale at start [Space(5)] [Header("Ground Check Settings:")] [SerializeField] private Transform groundCheckPoint; //point at which ground check happens [SerializeField] private float groundCheckY = 0.2f; //how far down from ground chekc point is Grounded() checked [SerializeField] private float groundCheckX = 0.5f; //how far horizontally from ground chekc point to the edge of the player is [SerializeField] private LayerMask whatIsGround; //sets the ground layer [Space(5)] [Header("Dash Settings")] [SerializeField] private float dashSpeed; //speed of the dash [SerializeField] private float dashTime; //amount of time spent dashing [SerializeField] private float dashCooldown; //amount of time between dashes [SerializeField] GameObject dashEffect; private bool canDash = true, dashed; [Space(5)] [Header("Attack Settings:")] [SerializeField] private Transform SideAttackTransform; //the middle of the side attack area [SerializeField] private Vector2 SideAttackArea; //how large the area of side attack is [SerializeField] private Transform UpAttackTransform; //the middle of the up attack area [SerializeField] private Vector2 UpAttackArea; //how large the area of side attack is [SerializeField] private Transform DownAttackTransform; //the middle of the down attack area [SerializeField] private Vector2 DownAttackArea; //how large the area of down attack is [SerializeField] private LayerMask attackableLayer; //the layer the player can attack and recoil off of [SerializeField] private float timeBetweenAttack; private float timeSinceAttack; [SerializeField] private float damage; //the damage the player does to an enemy [SerializeField] private GameObject slashEffect; //the effect of the slashs bool restoreTime; float restoreTimeSpeed; [Space(5)] [Header("Recoil Settings:")] [SerializeField] private int recoilXSteps = 5; //how many FixedUpdates() the player recoils horizontally for [SerializeField] private int recoilYSteps = 5; //how many FixedUpdates() the player recoils vertically for [SerializeField] private float recoilXSpeed = 100; //the speed of horizontal recoil [SerializeField] private float recoilYSpeed = 100; //the speed of vertical recoil private int stepsXRecoiled, stepsYRecoiled; //the no. of steps recoiled horizontally and verticall [Space(5)] [Header("Health Settings")] public int health; public int maxHealth; [SerializeField] GameObject bloodSpurt; [SerializeField] float hitFlashSpeed; public delegate void OnHealthChangedDelegate(); [HideInInspector] public OnHealthChangedDelegate onHealthChangedCallback; float healTimer; [SerializeField] float timeToHeal; [Space(5)] [Header("Mana Settings")] [SerializeField] UnityEngine.UI.Image manaStorage; [SerializeField] float mana; [SerializeField] float manaDrainSpeed; [SerializeField] float manaGain; [Space(5)] [Header("Spell Settings")] //spell stats [SerializeField] float manaSpellCost = 0.3f; [SerializeField] float timeBetweenCast = 0.5f; float timeSinceCast; [SerializeField] float spellDamage; //upspellexplosion and downspellfireball [SerializeField] float downSpellForce; // desolate dive only //spell cast objects [SerializeField] GameObject sideSpellFireball; [SerializeField] GameObject upSpellExplosion; [SerializeField] GameObject downSpellFireball; float castOrHealTimer; [Space(5)] [HideInInspector] public PlayerStateList pState; private Animator anim; private Rigidbody2D rb; private SpriteRenderer sr; //Input Variables private float xAxis, yAxis; private bool attack = false; //creates a singleton of the PlayerController public static PlayerController Instance; private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); } else { Instance = this; } DontDestroyOnLoad(gameObject); } // Start is called before the first frame update void Start() { pState = GetComponent<PlayerStateList>(); rb = GetComponent<Rigidbody2D>(); sr = GetComponent<SpriteRenderer>(); anim = GetComponent<Animator>(); gravity = rb.gravityScale; Health = maxHealth; Mana = mana; manaStorage.fillAmount = Mana; } private void OnDrawGizmos() { Gizmos.color = Color.red; Gizmos.DrawWireCube(SideAttackTransform.position, SideAttackArea); Gizmos.DrawWireCube(UpAttackTransform.position, UpAttackArea); Gizmos.DrawWireCube(DownAttackTransform.position, DownAttackArea); } // Update is called once per frame void Update() { if (pState.cutscene) return; GetInputs(); UpdateJumpVariables(); if (pState.dashing) return; RestoreTimeScale(); FlashWhileInvincible(); Move(); Heal(); CastSpell(); if (pState.healing) return; Flip(); Jump(); StartDash(); Attack(); } private void OnTriggerEnter2D(Collider2D _other) //for up and down cast spell { if(_other.GetComponent<Enemy>() != null && pState.casting) { _other.GetComponent<Enemy>().EnemyHit(spellDamage, (_other.transform.position - transform.position).normalized, -recoilYSpeed); } } private void FixedUpdate() { if (pState.dashing || pState.healing || pState.cutscene) return; Recoil(); } void GetInputs() { xAxis = Input.GetAxisRaw("Horizontal"); yAxis = Input.GetAxisRaw("Vertical"); attack = Input.GetButtonDown("Attack"); if (Input.GetButton("Cast/Heal")) { castOrHealTimer += Time.deltaTime; } else { castOrHealTimer = 0; } } void Flip() { if (xAxis < 0) { transform.localScale = new Vector2(-Mathf.Abs(transform.localScale.x), transform.localScale.y); pState.lookingRight = false; } else if (xAxis > 0) { transform.localScale = new Vector2(Mathf.Abs(transform.localScale.x), transform.localScale.y); pState.lookingRight = true; } } private void Move() { if (pState.healing) rb.velocity = new Vector2(0, 0); rb.velocity = new Vector2(walkSpeed * xAxis, rb.velocity.y); anim.SetBool("Walking", rb.velocity.x != 0 && Grounded()); } void StartDash() { if (Input.GetButtonDown("Dash") && canDash && !dashed) { StartCoroutine(Dash()); dashed = true; } if (Grounded()) { dashed = false; } } IEnumerator Dash() { canDash = false; pState.dashing = true; anim.SetTrigger("Dashing"); rb.gravityScale = 0; int _dir = pState.lookingRight ? 1 : -1; rb.velocity = new Vector2(_dir * dashSpeed, 0); if (Grounded()) Instantiate(dashEffect, transform); yield return new WaitForSeconds(dashTime); rb.gravityScale = gravity; pState.dashing = false; yield return new WaitForSeconds(dashCooldown); canDash = true; } public IEnumerator WalkIntoNewScene(Vector2 _exitDir, float _delay) { pState.invincible = true; pState.cutscene = true; //If exit direction is upwards if(_exitDir.y > 0) { rb.velocity = jumpForce * _exitDir; } //If exit direction requires horizontal movement if(_exitDir.x != 0) { xAxis = _exitDir.x > 0 ? 1 : -1; Move(); } Flip(); yield return new WaitForSeconds(_delay); pState.invincible = false; pState.cutscene = false; } void Attack() { timeSinceAttack += Time.deltaTime; if (attack && timeSinceAttack >= timeBetweenAttack) { timeSinceAttack = 0; anim.SetTrigger("Attacking"); if (yAxis == 0 || yAxis < 0 && Grounded()) { Hit(SideAttackTransform, SideAttackArea, ref pState.recoilingX, recoilXSpeed); Instantiate(slashEffect, SideAttackTransform); } else if (yAxis > 0) { Hit(UpAttackTransform, UpAttackArea, ref pState.recoilingY, recoilYSpeed); SlashEffectAtAngle(slashEffect, 80, UpAttackTransform); } else if (yAxis < 0 && !Grounded()) { Hit(DownAttackTransform, DownAttackArea, ref pState.recoilingY, recoilYSpeed); SlashEffectAtAngle(slashEffect, -90, DownAttackTransform); } } } void Hit(Transform _attackTransform, Vector2 _attackArea, ref bool _recoilDir, float _recoilStrength) { Collider2D[] objectsToHit = Physics2D.OverlapBoxAll(_attackTransform.position, _attackArea, 0, attackableLayer); List<Enemy> hitEnemies = new List<Enemy>(); if(objectsToHit.Length > 0) { _recoilDir = true; } for(int i = 0; i < objectsToHit.Length; i++) { Enemy e = objectsToHit[i].GetComponent<Enemy>(); if(e && !hitEnemies.Contains(e)) { e.EnemyHit(damage, (transform.position - objectsToHit[i].transform.position).normalized, _recoilStrength); hitEnemies.Add(e); if (objectsToHit[i].CompareTag("Enemy")) { Mana += manaGain; } } } } void SlashEffectAtAngle(GameObject _slashEffect, int _effectAngle, Transform _attackTransform) { _slashEffect = Instantiate(_slashEffect, _attackTransform); _slashEffect.transform.eulerAngles = new Vector3(0, 0, _effectAngle); _slashEffect.transform.localScale = new Vector2(transform.localScale.x, transform.localScale.y); } void Recoil() { if (pState.recoilingX) { if (pState.lookingRight) { rb.velocity = new Vector2(-recoilXSpeed, 0); } else { rb.velocity = new Vector2(recoilXSpeed, 0); } } if (pState.recoilingY) { rb.gravityScale = 0; if (yAxis < 0) { rb.velocity = new Vector2(rb.velocity.x, recoilYSpeed); } else { rb.velocity = new Vector2(rb.velocity.x, -recoilYSpeed); } airJumpCounter = 0; } else { rb.gravityScale = gravity; } //stop recoil if (pState.recoilingX && stepsXRecoiled < recoilXSteps) { stepsXRecoiled++; } else { StopRecoilX(); } if (pState.recoilingY && stepsYRecoiled < recoilYSteps) { stepsYRecoiled++; } else { StopRecoilY(); } if (Grounded()) { StopRecoilY(); } } void StopRecoilX() { stepsXRecoiled = 0; pState.recoilingX = false; } void StopRecoilY() { stepsYRecoiled = 0; pState.recoilingY = false; } public void TakeDamage(float _damage) { Health -= Mathf.RoundToInt(_damage); StartCoroutine(StopTakingDamage()); } IEnumerator StopTakingDamage() { pState.invincible = true; GameObject _bloodSpurtParticles = Instantiate(bloodSpurt, transform.position, Quaternion.identity); Destroy(_bloodSpurtParticles, 1.5f); anim.SetTrigger("TakeDamage"); yield return new WaitForSeconds(1f); pState.invincible = false; } void FlashWhileInvincible() { if (pState.cutscene) return; sr.material.color = pState.invincible ? Color.Lerp(Color.white, Color.black, Mathf.PingPong(Time.time * hitFlashSpeed, 1.0f)) : Color.white; } void RestoreTimeScale() { if (restoreTime) { if (Time.timeScale < 1) { Time.timeScale += Time.unscaledDeltaTime * restoreTimeSpeed; } else { Time.timeScale = 1; restoreTime = false; } } } public void HitStopTime(float _newTimeScale, int _restoreSpeed, float _delay) { restoreTimeSpeed = _restoreSpeed; if (_delay > 0) { StopCoroutine(StartTimeAgain(_delay)); StartCoroutine(StartTimeAgain(_delay)); } else { restoreTime = true; } Time.timeScale = _newTimeScale; } IEnumerator StartTimeAgain(float _delay) { yield return new WaitForSecondsRealtime(_delay); restoreTime = true; } public int Health { get { return health; } set { if (health != value) { health = Mathf.Clamp(value, 0, maxHealth); if (onHealthChangedCallback != null) { onHealthChangedCallback.Invoke(); } } } } void Heal() { if (Input.GetButton("Cast/Heal") && castOrHealTimer > 0.05f && Health < maxHealth && Mana > 0 && Grounded() && !pState.dashing) { pState.healing = true; anim.SetBool("Healing", true); //healing healTimer += Time.deltaTime; if (healTimer >= timeToHeal) { Health++; healTimer = 0; } //drain mana Mana -= Time.deltaTime * manaDrainSpeed; } else { pState.healing = false; anim.SetBool("Healing", false); healTimer = 0; } } float Mana { get { return mana; } set { //if mana stats change if (mana != value) { mana = Mathf.Clamp(value, 0, 1); manaStorage.fillAmount = Mana; } } } void CastSpell() { if (Input.GetButtonUp("Cast/Heal") && castOrHealTimer <= 0.05f && timeSinceCast >= timeBetweenCast && Mana >= manaSpellCost) { pState.casting = true; timeSinceCast = 0; StartCoroutine(CastCoroutine()); } else { timeSinceCast += Time.deltaTime; } if(Grounded()) { //disable downspell if on the ground downSpellFireball.SetActive(false); } //if down spell is active, force player down until grounded if(downSpellFireball.activeInHierarchy) { rb.velocity += downSpellForce * Vector2.down; } } IEnumerator CastCoroutine() { anim.SetBool("Casting", true); yield return new WaitForSeconds(0.15f); //side cast if (yAxis == 0 || (yAxis < 0 && Grounded())) { GameObject _fireBall = Instantiate(sideSpellFireball, SideAttackTransform.position, Quaternion.identity); //flip fireball if(pState.lookingRight) { _fireBall.transform.eulerAngles = Vector3.zero; // if facing right, fireball continues as per normal } else { _fireBall.transform.eulerAngles = new Vector2(_fireBall.transform.eulerAngles.x, 180); //if not facing right, rotate the fireball 180 deg } pState.recoilingX = true; } //up cast else if( yAxis > 0) { Instantiate(upSpellExplosion, transform); rb.velocity = Vector2.zero; } //down cast else if(yAxis < 0 && !Grounded()) { downSpellFireball.SetActive(true); } Mana -= manaSpellCost; yield return new WaitForSeconds(0.35f); anim.SetBool("Casting", false); pState.casting = false; } public bool Grounded() { if (Physics2D.Raycast(groundCheckPoint.position, Vector2.down, groundCheckY, whatIsGround) || Physics2D.Raycast(groundCheckPoint.position + new Vector3(groundCheckX, 0, 0), Vector2.down, groundCheckY, whatIsGround) || Physics2D.Raycast(groundCheckPoint.position + new Vector3(-groundCheckX, 0, 0), Vector2.down, groundCheckY, whatIsGround)) { return true; } else { return false; } } void Jump() { if (jumpBufferCounter > 0 && coyoteTimeCounter > 0 && !pState.jumping) { rb.velocity = new Vector3(rb.velocity.x, jumpForce); pState.jumping = true; } if (!Grounded() && airJumpCounter < maxAirJumps && Input.GetButtonDown("Jump")) { pState.jumping = true; airJumpCounter++; rb.velocity = new Vector3(rb.velocity.x, jumpForce); } if (Input.GetButtonUp("Jump") && rb.velocity.y > 3) { pState.jumping = false; rb.velocity = new Vector2(rb.velocity.x, 0); } anim.SetBool("Jumping", !Grounded()); } void UpdateJumpVariables() { if (Grounded()) { pState.jumping = false; coyoteTimeCounter = coyoteTime; airJumpCounter = 0; } else { coyoteTimeCounter -= Time.deltaTime; } if (Input.GetButtonDown("Jump")) { jumpBufferCounter = jumpBufferFrames; } else { jumpBufferCounter = jumpBufferCounter - Time.deltaTime * 10; } } }using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; public class SceneTransition : MonoBehaviour { [SerializeField] private string transitionTo; //Represents the scene to transition to [SerializeField] private Transform startPoint; //Defines the player's entry point in the scene [SerializeField] private Vector2 exitDirection; //Specifies the direction for the player's exit [SerializeField] private float exitTime; //Determines the time it takes for the player to exit the scene transition // Start is called before the first frame update private void Start() { if(GameManager.Instance.transitionedFromScene == transitionTo) { PlayerController.Instance.transform.position = startPoint.position; StartCoroutine(PlayerController.Instance.WalkIntoNewScene(exitDirection, exitTime)); } } private void OnTriggerEnter2D(Collider2D _other) { if (_other.CompareTag("Player")) { GameManager.Instance.transitionedFromScene = SceneManager.GetActiveScene().name; PlayerController.Instance.pState.cutscene = true; PlayerController.Instance.pState.invincible = true; SceneManager.LoadScene(transitionTo); } } }February 6, 2026 at 10:35 am #19251::To address the issue where the player is not walking during the cutscene and instead continues to use the idle animation, you should ensure that the
WalkIntoNewScenecoroutine in thePlayerControllerscript is correctly updating the player’s movement and animation state.Here are a few things to check and adjust:
-
Ensure Movement Logic is Executed: In the
WalkIntoNewScenecoroutine, you are setting thexAxisvariable based on the exit direction. Make sure that theMove()method is being called correctly to update the player’s velocity and animation state. -
Animation Trigger: The animation for walking is controlled by the line:
anim.SetBool("Walking", rb.velocity.x != 0 && Grounded());Ensure that
rb.velocity.xis being set correctly in theWalkIntoNewScenecoroutine when you are moving the player. You might want to explicitly set the velocity in the coroutine:rb.velocity = new Vector2(walkSpeed * _exitDir.x, rb.velocity.y); -
Check Exit Direction: Ensure that the
exitDirectionvector is set correctly in theSceneTransitionscript. If it’s not set to a value that would cause horizontal movement, the player will remain idle. -
Cutscene State Management: Make sure that the
cutscenestate is being managed correctly. You should setpState.cutscenetotruebefore starting the cutscene and set it back tofalseafter the cutscene is complete. -
Debugging: Add debug logs in the
WalkIntoNewScenecoroutine to check the values ofxAxis,rb.velocity, and the animation state to ensure they are being set as expected.
If you’ve checked these points and the issue persists, could you clarify if the player is moving at all during the cutscene, or is it completely stationary? This will help narrow down the problem further.
February 8, 2026 at 9:39 pm #19252February 8, 2026 at 10:10 pm #19253::It sounds like you’re experiencing issues with player state persistence during scene transitions, specifically with the mana and health storage not being retained when moving between scenes. Here are a few suggestions on how to address this problem:
Steps to Retain Player State Across Scenes:
-
Use
DontDestroyOnLoadfor Player Object: Ensure that your player GameObject is marked withDontDestroyOnLoad. It seems you already have this set up in yourPlayerControllerscript:DontDestroyOnLoad(gameObject);This will keep the player object alive when loading new scenes.
-
Persistent Game Manager: If you have a
GameManagerclass, it can be a great place to store global game state, such as player health and mana. Make sure that this manager is also marked withDontDestroyOnLoad. -
Store Player Stats in GameManager: When transitioning scenes, you can store the player’s health and mana in the
GameManagerbefore loading the new scene:GameManager.Instance.playerHealth = PlayerController.Instance.Health; GameManager.Instance.playerMana = PlayerController.Instance.Mana; -
Restore Player Stats After Scene Load: After loading the new scene, you can restore the player’s health and mana using the data from the
GameManager:PlayerController.Instance.Health = GameManager.Instance.playerHealth; PlayerController.Instance.Mana = GameManager.Instance.playerMana; -
Check Initialization Order: Ensure that your
GameManagerandPlayerControllerare initialized correctly before any scene transitions or player state updates happen. -
Debugging: Add debug logs to check the values of health and mana before the scene transition and after it has completed. This can help confirm whether the data is being lost or not set properly.
Debug.Log("Health before transition: " + PlayerController.Instance.Health); Debug.Log("Mana before transition: " + PlayerController.Instance.Mana);
Example Integration
Here’s a brief example of how you could set this up in your
SceneTransitionscript:private void OnTriggerEnter2D(Collider2D _other) { if (_other.CompareTag("Player")) { // Store player state before transitioning GameManager.Instance.playerHealth = PlayerController.Instance.Health; GameManager.Instance.playerMana = PlayerController.Instance.Mana; GameManager.Instance.transitionedFromScene = SceneManager.GetActiveScene().name; PlayerController.Instance.pState.cutscene = true; PlayerController.Instance.pState.invincible = true; SceneManager.LoadScene(transitionTo); } } private void Start() { if (GameManager.Instance.transitionedFromScene == transitionTo) { PlayerController.Instance.transform.position = startPoint.position; // Restore player state after transition PlayerController.Instance.Health = GameManager.Instance.playerHealth; PlayerController.Instance.Mana = GameManager.Instance.playerMana; StartCoroutine(PlayerController.Instance.WalkIntoNewScene(exitDirection, exitTime)); } }Make sure to have the
GameManagerclass properly set up to hold the player’s health and mana. If these steps don’t resolve your issue, please provide more details about how yourGameManageris structured or any other relevant code snippets, and I’ll be happy to assist further!February 17, 2026 at 10:50 pm #19256::I managed to fix it but it only works if I don’t use the move function and the player will sometimes be flipped if they exited the previous scene in that direction.
February 18, 2026 at 2:00 am #19257::It sounds like you’re encountering issues with the player’s movement and orientation during scene transitions. Here are some suggestions to address these problems:
-
Control Movement During Cutscene: If the player should not move during the cutscene, ensure that the movement logic is disabled when the cutscene is active. You can do this by adding a check in your
Move()function:private void Move() { if (pState.cutscene) return; // Prevent movement during cutscene rb.velocity = new Vector2(walkSpeed * xAxis, rb.velocity.y); anim.SetBool("Walking", rb.velocity.x != 0 && Grounded()); } -
Handle Player Orientation: To prevent the player from being flipped incorrectly when exiting a scene, you can store the exit direction and set the player’s scale accordingly before the transition. For example, in your
WalkIntoNewScenecoroutine, you can set the player’s scale based on the exit direction:if (_exitDir.x != 0) { xAxis = _exitDir.x > 0 ? 1 : -1; Flip(); // Call Flip to set the correct orientation Move(); // You might want to call Move here if needed } -
Reset Orientation on Scene Load: When the new scene loads, ensure that the player’s orientation is reset based on the direction they are entering from. You can do this in the
Start()method of yourSceneTransitionscript:if (GameManager.Instance.transitionedFromScene == transitionTo) { PlayerController.Instance.transform.position = startPoint.position; // Reset orientation based on exit direction PlayerController.Instance.Flip(); // Ensure the player is facing the correct direction StartCoroutine(PlayerController.Instance.WalkIntoNewScene(exitDirection, exitTime)); } -
Debugging Orientation Issues: If the player is still flipping unexpectedly, add debug logs to track the player’s scale before and after the transition. This can help identify where the issue might be occurring.
If you continue to experience issues, could you clarify how you’re determining the exit direction and how you’re implementing the
Flip()function? This information could help pinpoint the problem further.February 19, 2026 at 2:35 pm #19258::I managed to fix it but it only works if I don’t use the move function and the player will sometimes be flipped if they exited the previous scene in that direction.
If you would like me to have a look at it, you can share your project repository here.
March 2, 2026 at 2:04 am #19265::sorry for the late response here is the updated project repository https://github.com/KingPloof393/Metroidvania-Prototype
March 2, 2026 at 3:30 am #19266::No problem at all! It’s great to hear that you’ve made progress with your project.
Regarding the issues you’re facing with the player flipping or not moving correctly during the scene transition, here are some suggestions to help you troubleshoot and refine your implementation:
Addressing Player Movement and Flipping Issues:
-
Movement Logic During Cutscenes: Ensure that your
Movefunction is not being called when the player is in the cutscene state. You might need to adjust yourWalkIntoNewScenecoroutine to control the player’s movement explicitly during the transition. For example, instead of callingMove(), you can directly set the player’s velocity to handle walking in the cutscene. -
Flipping Logic: When transitioning between scenes, the player’s facing direction (flipping) can be set based on the exit direction. You can include logic in your
WalkIntoNewScenecoroutine to set the player’s scale based on the exit direction:if (_exitDir.x > 0) { transform.localScale = new Vector2(1, 1); // Facing right } else if (_exitDir.x 0) { transform.localScale = new Vector2(1, 1); // Facing right } else if (_exitDir.x < 0) { transform.localScale = new Vector2(-1, 1); // Facing left } // Move the player in the exit direction rb.velocity = new Vector2(_exitDir.x * walkSpeed, rb.velocity.y); yield return new WaitForSeconds(_delay); pState.invincible = false; pState.cutscene = false; // Reset velocity to zero after the cutscene rb.velocity = Vector2.zero; }
By implementing these changes, you should be able to maintain control over the player's movement and facing direction during scene transitions. If you continue to run into issues, feel free to share more specific details or code snippets, and I'll be happy to help you further!
March 2, 2026 at 10:22 pm #19267::sorry for the late response here is the updated project repository https://github.com/KingPloof393/Metroidvania-Prototype
Your
Walk_Into_New_Scene()is set up differently from how the tutorial does it. The trigger for it should not be firing at the same time as the scene change.Nonetheless, a few changes to the function will fix your issue:
public IEnumerator Walk_Into_New_Scene(Vector2 Exit_Direction, float Delay) {transform.localScale = new Vector3( transform.localScale.x * -Mathf.Sign(Exit_Direction.x), transform.localScale.y, transform.localScale.z ); if (Player_State == null) { yield break; } Player_State.Invincible = true;
if(Exit_Direction.y > 0) { Rigidbody.velocity = Jump_Force * Exit_Direction; } if(Exit_Direction.x != 0) { XAxis = Exit_Direction.x > 0 ? 1 : -1; Move(); } Flip(); yield return new WaitForSeconds(Delay); Player_State.Invincible = false; Player_State.Cutscene = false; }The facing issue is caused by
Walk_Into_New_Scene()failing to fire properly — normally, it handles the facing of the character, but in your case, the player actually gets destroyed by the game during the scene switch, soPlayer_Statebecomes null when your function fires.Hence, I’ve added a script to mandatorily set the facing of the character beforehand, and add a snippet of code to prevent the rest of the function from firing if the player is already destroyed.
March 8, 2026 at 11:20 pm #19271::The player now slides into the scene instead of walking. Is it possible there is something wrong with the move function in my script?
March 8, 2026 at 11:30 pm #19272::Yes, it sounds like there may be an issue with how the
Movefunction is being utilized during the scene transition. If the player is sliding instead of walking, it could be due to the way the velocity is being set in theWalkIntoNewScenecoroutine or how theMovefunction interacts with the player’s Rigidbody.Suggestions to Fix Sliding Issue:
-
Check Velocity Setting: In your
WalkIntoNewScenecoroutine, ensure that you are setting the player’s velocity correctly. If you are setting the velocity directly in the coroutine, make sure you are not also calling theMovefunction, as this could lead to conflicting velocity settings. -
Use Fixed Speed: When moving the player during the cutscene, you might want to set a fixed speed for the transition instead of using the
walkSpeedvariable. This can help control the sliding effect:rb.velocity = new Vector2(_exitDir.x * fixedCutsceneSpeed, rb.velocity.y); -
Disable Movement During Cutscene: Ensure that the
Movefunction is not being called while the player is in the cutscene state. You can add a check in theMovefunction:if (pState.cutscene) return; -
Reset Velocity After Transition: After the cutscene is complete, reset the player’s velocity to zero to prevent any lingering sliding:
rb.velocity = Vector2.zero; -
Adjust Drag on Rigidbody: If the player is still sliding, consider adjusting the drag settings on the Rigidbody2D component. Increasing the drag can help reduce sliding when the player is not actively moving.
Clarifying Question:
Could you provide more details about how you are currently calling the
Movefunction during the scene transition, or any specific values you are using forwalkSpeedand the Rigidbody settings? This will help in diagnosing the issue further. -
-
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: