Hi all. Alright so at the moment, I have 2 big bugs in my game that definitely need to be dealt with as soon as possible:
- The main bug is my ‘Resume’ button leads to a black (black for me) screen and never goes back to the normal alpha value (i.e: doesn’t show me the game). From the debugger and my mouse UI, the game works… just the screen stays black due to a Respawner issue. I’ll paste the stack trace below:
NullReferenceException: Object reference not set to an instance of an object
RPG.Respawnables.RespawnManager.Respawn () (at Assets/Project Backup/Scripts/Respawnables/RespawnManager.cs:103)
RPG.Respawnables.RespawnManager.Start () (at Assets/Project Backup/Scripts/Respawnables/RespawnManager.cs:47)
Here is the RespawnManager Script the Stack Trace is leading to:
using System.Collections.Generic;
using GameDevTV.Saving;
using RPG.Attributes;
using RPG.Control;
using UnityEngine;
using RPG.Combat;
using RPG.Respawnables; // Test: Delete if failed
namespace RPG.Respawnables
{
// Since SaveableEntity relies on the ISaveable information of each character to
// bundle it into our Save file, there are cases when our enemies' state is non-existant
// hence we need to create a brand new RespawnManager class, as shown below:
public class RespawnManager : SaveableEntity // in Saving below, we will have to replace the logic, hence we inherit from 'SaveableEntity.cs'
{
// This class will handle the following:
// spawning our enemy to the scene
// listen to death notifications (i.e: Enemy is dead, proceed to keeping him dead for the following steps)
// hide the body after 'hideTime'
// deleting the enemy after 'hideTime' is complete
// Respawn the enemy after 'respawnTime'
[SerializeField] AIController spawnableEnemy; // the enemy to spawn/respawn
[HideInInspector] private AIController lastSpawnableEnemy; // this AI Controller ensures its an enemy we are respawning
[SerializeField] private float hideTime = 60; // time before hiding our dead character
[SerializeField] private float respawnTime = 90; // time before respawning our hidden dead character, as an alive character
[SerializeField] PatrolPath patrolPath; // the path our character will follow, from his spawn point
[SerializeField] AggroGroup aggroGroup; // aggrevated group of guards, based on wrong dialogue player has said
[SerializeField] bool hasBeenRestored; // checks if the enemy has been restored before fading in from the main menu, or a load scene, or not
/* // TEST Variables: Delete if failed
[SerializeField] AggroGroup aggroGroup; // the group of enemies we want attacking our player, if things go bad
[SerializeField] DialogueAggro dialogueAggro; */
private AIController spawnedEnemy;
// --------------------------- NOTE: RestoreState() occurs BEFORE Start(), hence we need to ensure everything works accordingly --------------
private void Start()
{
// Check if the Enemy has been restored first or not, prior to Respawning him (ensuring RestoreState(), which occurs first, works properly)
if (!hasBeenRestored) Respawn(); // LINE 47: Stack trace leads to here
}
private void Respawn()
{
/* // TEST: Delete if failed
if (aggroGroup != null) {
dialogueAggro.SetAggroGroup();
aggroGroup.AddFighter(spawnedEnemy.GetComponent<Fighter>());
} */
if (spawnedEnemy)
{
// Dude is not dead no longer, so delete his previous 'onDeath' record after he's respawned
spawnedEnemy.GetComponent<Health>().onDie.RemoveListener(OnDeath);
}
foreach (Transform child in transform)
{
// Start the Respawn by deleting any existing gameObjects
Destroy(child.gameObject);
}
// Respawn the enemy, and parent the enemy to our respawnManagers' transform
spawnedEnemy = Instantiate(spawnableEnemy, transform);
// Get the spawned/respawned enemies' health, and listen for death notifications
spawnedEnemy.GetComponent<Health>().onDie.AddListener(OnDeath);
if (patrolPath != null)
{
Debug.Log($"Assigning Patrol Path {patrolPath} to {spawnedEnemy.name}");
spawnedEnemy.AssignPatrolPath(patrolPath);
}
else
{
Debug.Log($"No Patrol Path to assign");
}
// --------------------------- Extra Functionality: Setting up Aggro Group + Adding Fighters ---------------
if (spawnedEnemy.TryGetComponent(out DialogueAggro dialogueAggro) && aggroGroup != null) {
dialogueAggro.SetAggroGroup(aggroGroup);
}
aggroGroup.AddFighterToGroup(spawnedEnemy.GetComponent<Fighter>()); // LINE 103 (the Stack Trace leads here)
// ---------------------------------------------------------------------------------------------------------
}
void HideCharacter()
{
// Hide the dead character
foreach (Renderer renderer in spawnedEnemy.GetComponentsInChildren<Renderer>())
{
renderer.enabled = false;
}
}
void OnDeath()
{
// hide the character after 'hideTime', and then respawn him after 'respawnTime'
Invoke(nameof(HideCharacter), hideTime);
Invoke(nameof(Respawn), respawnTime);
/* // TEST: Delete if failed
if (aggroGroup != null) {
aggroGroup.RemoveFighter(spawnedEnemy.GetComponent<Fighter>());
} */
}
public void WaitingPeriod() {
// Get a little suspicious before patrolling again
spawnedEnemy.SuspicionBehaviour();
}
public override object CaptureState()
{
var state = new Dictionary<string, object>();
foreach (var saveable in spawnedEnemy.GetComponents<ISaveable>())
state[saveable.GetType().ToString()] = saveable.CaptureState();
return state;
}
public override void RestoreState(object state)
{
// Fixing the FadeIn issue, from both loading and starting the game (because 'RestoreState()' occurs before 'Start()'):
Respawn();
var stateDict = (Dictionary<string, object>)state;
foreach (var saveable in spawnedEnemy.GetComponents<ISaveable>())
{
var typeString = saveable.GetType().ToString();
if (stateDict.ContainsKey(typeString)) saveable.RestoreState(stateDict[typeString]);
}
if (spawnedEnemy.GetComponent<Health>().IsDead())
{
OnDeath();
}
}
private void OnValidate()
{
// This function checks if we changed the Spawnable Character
// (ensuring any character we attach to the spawnableEnemy is automatically added
// to the RespawnManager when editing the game,
// hence we dont accidentally spawn the wrong dude at the right spot)
if (spawnableEnemy != lastSpawnableEnemy)
{
lastSpawnableEnemy = spawnableEnemy;
foreach (Transform child in transform) {
Destroy(child.gameObject);
}
Instantiate(spawnableEnemy, transform);
}
}
}
}
// THE ARCHER AND ENEMY 2 ARE BOTH CHILDREN OF THE 'CHARACTER' PREFAB. DISCONNECT THEM FROM THAT, OTHERWISE THEY'LL KEEP INSTANTIATING
// FROM UNDER THE QUEST GIVER, WHO IS ALSO A PREFAB FROM THE CHARACTER PREFAB!!!