Forum begins after the advertisement:
Creating a Metroidvania – Part 7: (RESPAWN PROBLEM)
Home › Forums › Video Game Tutorial Series › Creating a Metroidvania in Unity › Creating a Metroidvania – Part 7: (RESPAWN PROBLEM)
- This topic has 3 replies, 3 voices, and was last updated 2 weeks, 3 days ago by
Terence.
-
AuthorPosts
-
March 13, 2025 at 2:11 am #17522::
Problem Description:
- Respawn Button Stops Working After Scene Transition: On the current scene, the respawn button correctly calls the OnRespawnButtonClicked() method, which finds the Bonfire object and calls its RespawnPlayer() method.
After transitioning to another scene, the button stops responding to clicks. Clicking the button does not trigger any actions, and no messages about the method being called appear in the console. What Doesn’t Work:
- Respawn Button: After transitioning to another scene, the button does not respond to clicks.
Possible reasons:
The reference to the OnRespawnButtonClicked() method is lost after loading a new scene.
The Bonfire object is not found on the new scene, so FindObjectOfType<Bonfire>() returns null. Here is my code:
SceneTransiotion:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; public class SceneTransition : MonoBehaviour { // Start is called before the first frame update [SerializeField] private string transitionTo; [SerializeField] private Transform startPoint; [SerializeField] private Vector2 exitDirection; [SerializeField] private float exitTime; private void Start() { if (GameManager.Instance.transitionedFromScene == transitionTo) { PlayerController.Instance.transform.position = startPoint.position; StartCoroutine(PlayerController.Instance.WalkIntoNewScene(exitDirection, exitTime)); } StartCoroutine(UIManager.Instance.sceneFader.Fade(SceneFader.FadeDirection.Out)); } private void OnTriggerEnter2D(Collider2D _other) { if (_other.CompareTag("Player")) { GameManager.Instance.transitionedFromScene = SceneManager.GetActiveScene().name; PlayerController.Instance.pState.cutscene = true; StartCoroutine(UIManager.Instance.sceneFader.FadeAndLoadScene(SceneFader.FadeDirection.In, transitionTo)); } } }
PlayerController:
using System.Collections; using System.Collections.Generic; using System.Net.Sockets; using UnityEngine; using UnityEngine.UIElements; using UnityEngine.UI; using Unity.VisualScripting; 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 int jumpBufferCounter = 0; //stores the jump button input [SerializeField] private int 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 private float timeBetweenAttack, timeSinceAttck; [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; public float healTimer; [SerializeField] float timeToHeal; [Space(5)] [Header("Mana Settings")] [SerializeField] UnityEngine.UI.Image manaStorage; [SerializeField] float mana; [SerializeField] float manaDrainSpeed; [SerializeField] float manaGain; bool halfMana; [Space(5)] [Header("Spell Settings")] //spell stats [SerializeField] float manaSpellCost = 0.3f; [SerializeField] float timeBetweenCast = 0.5f; [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 timeSinceCast; float castOrHealTimer; [Space(5)] [HideInInspector] public PlayerStateList pState; private Animator anim; public Rigidbody2D rb; private SpriteRenderer sr; [Header("Camera Settings")] public float playerFallSpeedThreshold = -10; //Input Variables private float xAxis, yAxis; private bool attack = false; bool openMap; private bool canFlash = true; 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; Mana = mana; manaStorage.fillAmount = Mana; Health = maxHealth; } 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.alive) return; if (pState.cutscene) return; GetInputs(); ToggleMap(); UpdateJumpVariables(); UpdateCameraYDampForPlayerFall(); if (pState.dashing) return; RestoreTimeScale(); FlashWhileInvincible(); Move(); Heal(); CastSpell(); if (pState.healing) return; Flip(); Jump(); StartDash(); Attack(); Recoil(); if (Input.GetKeyDown(KeyCode.L)) { StartCoroutine(Death()); } } 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.cutscene) return; if (pState.dashing || pState.healing) return; Recoil(); } void GetInputs() { xAxis = Input.GetAxisRaw("Horizontal"); yAxis = Input.GetAxisRaw("Vertical"); attack = Input.GetButtonDown("Attack"); openMap = Input.GetButton("Map"); } void ToggleMap() { if (openMap) { UIManager.Instance.mapHandler.SetActive(true); } else { UIManager.Instance.mapHandler.SetActive(false); } } void Flip() { if (xAxis < 0) { transform.localScale = new Vector2(-3, transform.localScale.y); pState.lookingRight = false; } else if (xAxis > 0) { transform.localScale = new Vector2(3, transform.localScale.y); pState.lookingRight = true; } } private void Move() { rb.velocity = new Vector2(walkSpeed * xAxis, rb.velocity.y); anim.SetBool("Walking", rb.velocity.x != 0 && Grounded()); } void UpdateCameraYDampForPlayerFall() { if(rb.velocity.y < playerFallSpeedThreshold && !CameraManager.Instance.isLerpingYDamping && !CameraManager.Instance.hasLerpingYDamping) { StartCoroutine(CameraManager.Instance.LerpYDamping(true)); } if(rb.velocity.y >= 0 && !CameraManager.Instance.isLerpingYDamping && CameraManager.Instance.hasLerpingYDamping) { CameraManager.Instance.hasLerpingYDamping = false; StartCoroutine(CameraManager.Instance.LerpYDamping(false)); } } 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); if (!Grounded()) Instantiate(dashEffect, transform); yield return new WaitForSecondsRealtime(dashTime); rb.gravityScale = gravity; pState.dashing = false; yield return new WaitForSecondsRealtime(dashCooldown); canDash = true; } public IEnumerator WalkIntoNewScene(Vector2 _exitDir, float _delay) { //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 WaitForSecondsRealtime(_delay); print("cutscene played"); pState.cutscene = false; } void Attack() { timeSinceAttck += Time.deltaTime; if (attack && timeSinceAttck >= timeBetweenAttack) { timeSinceAttck = 0; anim.SetTrigger("Attacking"); if (yAxis == 0 || yAxis < 0 && Grounded()) { int _recoilLeftOrRight = pState.lookingRight ? 1 : -1; Hit(SideAttackTransform, SideAttackArea, ref pState.recoilingX,Vector2.right * _recoilLeftOrRight, recoilXSpeed); Instantiate(slashEffect, SideAttackTransform); } else if (yAxis > 0) { Hit(UpAttackTransform, UpAttackArea, ref pState.recoilingY, Vector2.up, recoilYSpeed); SlashEffectAtAngle(slashEffect, 80, UpAttackTransform); } else if (yAxis < 0 && !Grounded()) { Hit(DownAttackTransform, DownAttackArea, ref pState.recoilingY, Vector2.down, recoilYSpeed); SlashEffectAtAngle(slashEffect, -90, DownAttackTransform); } } } void Hit(Transform _attackTransform, Vector2 _attackArea, ref bool _recoilBool,Vector2 _recoilDir, float _recoilStrength) { Collider2D[] objectsToHit = Physics2D.OverlapBoxAll(_attackTransform.position, _attackArea, 0, attackableLayer); if (objectsToHit.Length > 0) { _recoilBool = true; } for (int i = 0; i < objectsToHit.Length; i++) { if (objectsToHit[i].GetComponent<Enemy>() != null) { objectsToHit[i].GetComponent<Enemy>().EnemyHit(damage, _recoilDir, _recoilStrength); 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 / 3, transform.localScale.y / 3); } 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) { if (pState.alive) { Health -= Mathf.RoundToInt(_damage); if (Health <= 0) { Health = 0; StartCoroutine(Death()); } else { 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 WaitForSecondsRealtime(1f); pState.invincible = false; } IEnumerator Flash() { sr.enabled = !sr.enabled; canFlash = false; yield return new WaitForSecondsRealtime(0.1f); canFlash = true; } void FlashWhileInvincible() { if (pState.invincible) { if (Time.timeScale > 0.2 && canFlash) { StartCoroutine(Flash()); } } else { sr.enabled = true; } } void RestoreTimeScale() { if (restoreTime) { if (Time.timeScale < 1) { Time.timeScale += Time.deltaTime * 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) { restoreTime = true; yield return new WaitForSecondsRealtime(_delay); } IEnumerator Death() { pState.alive = false; Time.timeScale = 1f; GameObject _bloodSpurtParticles = Instantiate(bloodSpurt, transform.position, Quaternion.identity); Destroy(_bloodSpurtParticles, 1.5f); anim.SetTrigger("Death"); yield return new WaitForSeconds(0.9f); StartCoroutine(UIManager.Instance.ActivateDeathScreen()); yield return new WaitForSeconds(0.9f); Instantiate(GameManager.Instance.shade, transform.position, Quaternion.identity); } public void Respawned() { if (!pState.alive) { pState.alive = true; halfMana = true; UIManager.Instance.SwitchMana(UIManager.ManaState.HalfMana); Mana = 0; Health = maxHealth; anim.Play("Idle"); } } public void RestoreMana() { halfMana = false; UIManager.Instance.SwitchMana(UIManager.ManaState.FullMana); } 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("Healing") && 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) { if (!halfMana) { mana = Mathf.Clamp(value, 0, 1); } else { mana = Mathf.Clamp(value, 0, 0.5f); } manaStorage.fillAmount = Mana; } } } void CastSpell() { if (Input.GetButtonDown("CastSpell") && 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 WaitForSecondsRealtime(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 // down cast else if (yAxis < 0 && !Grounded()) { downSpellFireball.SetActive(true); // Додаємо віддачу вгору після заклинання rb.velocity = new Vector2(rb.velocity.x, recoilYSpeed); pState.recoilingY = true; } Mana -= manaSpellCost; yield return new WaitForSecondsRealtime(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--; } } }
UIManager:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class UIManager : MonoBehaviour { public SceneFader sceneFader; public static UIManager Instance; [SerializeField] GameObject deathScreen; public GameObject mapHandler; [SerializeField] GameObject halfMana, fullMana; public enum ManaState { FullMana, HalfMana } public ManaState manaState; private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); } else { Instance = this; } DontDestroyOnLoad(gameObject); sceneFader = GetComponentInChildren<SceneFader>(); } public void SwitchMana(ManaState _manaState) { switch (_manaState) { case ManaState.FullMana: halfMana.SetActive(false); fullMana.SetActive(true); break; case ManaState.HalfMana: fullMana.SetActive(false); halfMana.SetActive(true); break; } manaState = _manaState; } public IEnumerator ActivateDeathScreen() { yield return new WaitForSeconds(0.8f); StartCoroutine(sceneFader.Fade(SceneFader.FadeDirection.In)); yield return new WaitForSeconds(0.8f); deathScreen.SetActive(true); } public IEnumerator DeactivateDeathScreen() { yield return new WaitForSeconds(0.5f); deathScreen.SetActive(false); StartCoroutine(sceneFader.Fade(SceneFader.FadeDirection.Out)); } }
Bonfire:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; public class Bonfire : MonoBehaviour { public bool interacted; // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } private void OnTriggerStay2D(Collider2D _collision) { if(_collision.CompareTag("Player") && Input.GetButtonDown("Interact")) { interacted = true; SaveData.Instance.bonfireSceneName = SceneManager.GetActiveScene().name; SaveData.Instance.bonfirePos = new Vector2(gameObject.transform.position.x, gameObject.transform.position.y); SaveData.Instance.SaveBonfire(); } } private void OnTriggerExit2D(Collider2D _collision) { if (_collision.CompareTag("Player")) { interacted = false; } } }
SaveData:
using System.Collections; using System.Collections.Generic; using UnityEngine; using System.IO; using UnityEngine.SceneManagement; [System.Serializable] public struct SaveData { public static SaveData Instance; //map Stuff public HashSet<string> sceneNames; public string bonfireSceneName; public Vector2 bonfirePos; public void Initialize() { if(!File.Exists(Application.persistentDataPath + "/save.bonfire.data")) { BinaryWriter writer = new BinaryWriter(File.Create(Application.persistentDataPath + "/save.bonfire.data")); } if (sceneNames == null) { sceneNames = new HashSet<string>(); } } public void SaveBonfire() { using (BinaryWriter write = new BinaryWriter(File.OpenWrite(Application.persistentDataPath + "/save.bonfire.data"))) { write.Write(bonfireSceneName); write.Write(bonfirePos.x); write.Write(bonfirePos.y); } } public void LoadBonfire() { if(File.Exists(Application.persistentDataPath + "/save.bonfire.data")) { using (BinaryReader reader = new BinaryReader(File.OpenRead(Application.persistentDataPath + "/save.bonfire.data"))) { bonfireSceneName = reader.ReadString(); bonfirePos.x = reader.ReadSingle(); bonfirePos.y = reader.ReadSingle(); } } } }
GameManager:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; public class GameManager : MonoBehaviour { public string transitionedFromScene; public Vector2 platformingRespawnPoint; public Vector2 respawnPoint; [SerializeField] Bonfire bonfire; public GameObject shade; public static GameManager Instance { get; private set; } private void Awake() { SaveData.Instance.Initialize(); if (Instance != null && Instance != this) { Destroy(gameObject); } else { Instance = this; } SaveScene(); DontDestroyOnLoad(gameObject); bonfire = FindObjectOfType<Bonfire>(); } public void SaveScene() { string currentSceneName = SceneManager.GetActiveScene().name; SaveData.Instance.sceneNames.Add(currentSceneName); } public void RespawnPlayer() { SaveData.Instance.LoadBonfire(); if(SaveData.Instance.bonfireSceneName != null) { SceneManager.LoadScene(SaveData.Instance.bonfireSceneName); } if(SaveData.Instance.bonfirePos != null) { respawnPoint = SaveData.Instance.bonfirePos; } else { respawnPoint = platformingRespawnPoint; } PlayerController.Instance.transform.position = respawnPoint; StartCoroutine(UIManager.Instance.DeactivateDeathScreen()); PlayerController.Instance.Respawned(); } }
March 13, 2025 at 2:21 am #17523::It looks like you’re encountering an issue where the respawn button stops working after transitioning to a new scene. This can happen for a few reasons, particularly related to how Unity handles scene transitions and object references.
To troubleshoot and fix the issue, here are a few steps and considerations:
1. Ensure the UI Button is Set Up Correctly
Make sure that the button in your UI is correctly set up to call the
OnRespawnButtonClicked()
method. You can check this in the Unity Editor:- Select the button in your scene.
- In the Inspector, ensure that the
OnClick()
event of the button is correctly linked to the method that is supposed to be called.
2. Verify Object Persistence
You are using
DontDestroyOnLoad()
for yourPlayerController
andUIManager
. Ensure that your button is also set up in a way that it persists across scene loads or is correctly reinitialized in each new scene. If the button is part of a UI that gets destroyed on scene load, you will need to recreate it in the new scene.3. Check for Null References
In the
RespawnPlayer()
method in yourGameManager
, you callFindObjectOfType()
to locate theBonfire
object. If this is done before the scene is fully loaded, it might return null. Ensure that theBonfire
object is present in the new scene when you try to access it.4. Debugging
Add debug logs to check if the
OnRespawnButtonClicked()
method is being called:public void OnRespawnButtonClicked() { Debug.Log("Respawn button clicked"); // Call the respawn method GameManager.Instance.RespawnPlayer(); }
If you don’t see this log when clicking the button in the new scene, it indicates that the button isn’t set up correctly or isn’t being referenced properly.
5. Ensure Scene Initialization
When transitioning to a new scene, make sure that any initialization that the new scene needs is properly set up. If you have any code that needs to run after the scene has loaded, consider using
Start()
orAwake()
methods appropriately, and ensure that any necessary references are established.6. Check Event System
Ensure that your scene contains an Event System (Unity UI requires this to handle UI interactions). If the Event System is missing, UI elements will not respond to clicks.
7. Scene Transition Logic
Review your scene transition logic:
- Make sure the
SceneTransition
script properly handles the loading of the new scene and that all relevant objects are still accessible post-transition.
Example Code for Button Setup
Here’s a basic example of how you might set up the button in the new scene:
public class UIManager : MonoBehaviour { public Button respawnButton; // Assign this in the Inspector private void Start() { respawnButton.onClick.AddListener(OnRespawnButtonClicked); } private void OnRespawnButtonClicked() { Debug.Log("Respawn button clicked"); GameManager.Instance.RespawnPlayer(); } }
Conclusion
Check all the above aspects to ensure everything is wired correctly. Debugging with logs will help you identify where the issue lies. If the button is set up correctly and the methods are being called as expected, the problem may be related to scene loading or object references being lost. If you continue to have issues, consider providing more details about how the UI elements are set up in your scene.
March 13, 2025 at 5:54 am #17524::@Bachok, I say the most likely cause of this is what the AI Assistant mentioned in (1) — double check that the respawn button in your death screen has the function assigned in your other scenes as well.
This is because there seems to be no messages and no errors when you click on the button, which means no action is assigned to it in your other scenes.
March 15, 2025 at 5:34 pm #17547::@Terence, I don’t think the problem is with the button because it carries over with me when switching scenes, and it doesn’t even exist in other scenes by default. I noticed that after switching scenes, the bonfire simply isn’t in my game manager—it doesn’t get saved. In the window where it should be for respawning, it’s completely missing, not even available to place manually. I have no idea what to do about this.
March 15, 2025 at 5:36 pm #17548::It sounds like you’re experiencing an issue where the bonfire object is not being saved or retained when switching scenes in your game. Here are some steps you can take to troubleshoot and potentially resolve this issue:
-
Check the Game Manager: Ensure that your Game Manager is set up correctly to persist data between scenes. If you’re using a singleton pattern or
DontDestroyOnLoad
, make sure that the Game Manager is not being destroyed when changing scenes. -
Inspect the Bonfire Prefab: Make sure that the bonfire is properly set up as a prefab in your project. Go to the prefab and ensure that all the necessary components and scripts are attached.
-
Scene Setup: Verify that the bonfire GameObject is included in the scene where you expect it to be. If the bonfire should be instantiated based on a condition or event (like player progress), ensure that this logic is correctly implemented.
-
Saving and Loading Logic: If you have logic for saving the state of the game (like player position, items, etc.), check that the bonfire’s state is included in that logic. Make sure that the bonfire’s information is being serialized and deserialized properly when switching scenes.
-
Debugging: Insert debug logs in your Game Manager where the bonfire is supposed to be instantiated or managed. This will help you see if the logic is being executed as expected. For example:
Debug.Log("Bonfire is being loaded: " + (bonfire != null));
-
Scene Reload: If you’re switching scenes and expect the bonfire to appear based on a previous state, consider reloading the scene or re-instantiating the bonfire in the new scene based on saved data.
-
Event System: If you’re using events or delegates to manage the spawning of the bonfire, make sure that these events are being triggered correctly when the scene loads.
-
Editor Setup: In the Unity editor, check the hierarchy when you switch to the scene where the bonfire should appear. Sometimes, objects may be disabled or not visible in the hierarchy.
-
Script Execution Order: If your Game Manager and bonfire scripts are dependent on certain initialization orders, ensure that their execution order is set correctly in the Unity project settings.
By following these steps, you should be able to identify where the issue lies and address it accordingly. If you still face difficulties, consider sharing relevant snippets of your code or additional details about your scene management for more specific assistance.
March 16, 2025 at 5:21 am #17549::@Bachok try moving the bonfire code from
Awake()
toOnEnable()
onGameManager
:private void Awake() { SaveData.Instance.Initialize(); if (Instance != null && Instance != this) { Destroy(gameObject); } else { Instance = this; } SaveScene(); DontDestroyOnLoad(gameObject);
bonfire = FindObjectOfType<Bonfire>();} void OnEnable() { bonfire = FindObjectOfType<Bonfire>(); }If that doesn’t work, we’ll need to analyse your
RespawnPlayer()
function to see whether it is working:public void RespawnPlayer() { print("Respawn player called"); SaveData.Instance.LoadBonfire(); if(SaveData.Instance.bonfireSceneName != null) { SceneManager.LoadScene(SaveData.Instance.bonfireSceneName); } if(SaveData.Instance.bonfirePos != null) { respawnPoint = SaveData.Instance.bonfirePos; } else { respawnPoint = platformingRespawnPoint; } print("Before Respawned()"); PlayerController.Instance.transform.position = respawnPoint; StartCoroutine(UIManager.Instance.DeactivateDeathScreen()); PlayerController.Instance.Respawned(); print("After Respawned() called"); }
That can give us some insight into where the code is being stopped, and by extension what we need to fix.
-
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: