Resource Gathering for Third person

OK just not to get me confused… This means that the coroutine will restart each time we return to the game? What’s the point of a global timer then? :sweat_smile:

The goal was that the timer starts and ends based on global time, so it doesn’t matter when in the game world are you signing in or out, it’s all globally calculated

No. The coroutine only starts if the resource was destroyed. You want the resource to respawn 8 hours after it was destroyed. How do you know 8 hours have passed? We store the time that the resource was destroyed. This is what the global timer gives us. When the game starts, it still knows when the resource was destroyed because we saved that time. You could just calculate how long it must still wait and just use that. current global time - stored time = time left to wait. So then you don’t need a while loop.

protected override IEnumerator CheckForRespawn()
{
    yield return new WaitForSeconds(_timeKeeper.GetGlobalTime() - destroyedTime);
    SpawnResource();
}

Yes, this is the goal. And what I’ve been giving you

Hey man, sorry I’ve been out for long today. I’ll give this a go at home and update you on how it goes

Sorry about the big delay today…

OK so… I’m not sure where to write the virtual method for that and where to override it, so for now I skipped it:

After a few minor tests, integrating this did help (as an error margin for the timer, as long as we don’t leave the game scene):

HOWEVER, the problem of the timer not working independent of scenes still exists, so if I mine a rock, leave the game and return, even before the timer is up, the rock respawns, and it clearly still doesn’t care about the timer… (which is not what I am seeking :sweat_smile:)

Any other solutions you can think of? If it helps, this is what my ‘ResourceRespawner.cs’ script stands at:

using System;
using System.Collections;
using GameDevTV.Saving;
using Newtonsoft.Json.Linq;
using UnityEngine;

namespace RPG.ResourceManager {

public class ResourceRespawner : MonoBehaviour, IJsonSaveable
{
    [SerializeField] ResourceGathering resourceToSpawn;
    [SerializeField] int hideTime;
    [SerializeField] float checkInterval = 30f; // 30 second check interval, for incredibly long resource respawn times

    private double destroyedTime;
    private ResourceGathering resourceObject;
    private TimeKeeper timeKeeper;

    private void Awake()
    {
        timeKeeper = TimeKeeper.GetTimeKeeper();
        SpawnResource();
    }

    public GameObject GetResource()
    {
        return resourceObject.gameObject;
    }

    public bool IsCollected()
    {
        return GetResource() == null;
    }

    private void SpawnResource()
    {
        resourceObject = Instantiate(resourceToSpawn, transform.position, Quaternion.identity);
        resourceObject.OnResourceDestroyed += OnResourceDestroyed;
    }

    private void DestroyResource()
    {
        var resourceToDestroy = GetResource();
        if (resourceToDestroy != null)
        {
            Destroy(resourceToDestroy);
            destroyedTime = timeKeeper.GetGlobalTime();
            StartCoroutine(CheckForRespawn());
        }
    }

    private IEnumerator CheckForRespawn()
    {
        var hideSeconds = new WaitForSeconds(checkInterval);
        while (true)
        {
            yield return hideSeconds;
            var currentTime = timeKeeper.GetGlobalTime();
            if (currentTime - destroyedTime >= hideTime)
            {
                SpawnResource();
                break;
            }
        }
    }

    private void OnResourceDestroyed(ResourceGathering _)
    {
        resourceObject.OnResourceDestroyed -= OnResourceDestroyed;
        DestroyResource();
    }

    public JToken CaptureAsJToken()
    {
        var data = new ResourceData(destroyedTime, IsCollected());
        return JToken.FromObject(data);
    }

    public void RestoreFromJToken(JToken state)
    {
        var data = state.ToObject<ResourceData>();
        destroyedTime = data.DestroyedTime;

        var shouldBeCollected = data.ShouldBeCollected;
        if (shouldBeCollected && !IsCollected())
        {
            // We don't use DestroyResource() here because we don't want to reset the destroyedTime
            resourceObject.OnResourceDestroyed -= OnResourceDestroyed;
            Destroy(resourceObject.gameObject);
            StartCoroutine(CheckForRespawn());
        }
        if (!shouldBeCollected && IsCollected())
        {
            SpawnResource();
        }
    }
}
[Serializable]
public struct ResourceData
{
    public double DestroyedTime;
    public bool ShouldBeCollected;

    public ResourceData(double destroyedTime, bool shouldBeCollected)
    {
        DestroyedTime = destroyedTime;
        ShouldBeCollected = shouldBeCollected;
    }
}

}

OK, So I tested it and it almost worked. There was one small issue with leaving the scene and coming back, but stopping the game and starting it again was working as expected. The problem was that there is a ‘load’ followed by a ‘save’ in the Portal that happens in the same frame so the spawner destroys the resource (when it shouldn’t be there yet) but then it saves before Unity actually destroyed the resource causing it to think it’s there again.

I fixed the issue and cleaned up the code a little. Here’s the updated version

using System;
using System.Collections;
using GameDevTV.Saving;
using Newtonsoft.Json.Linq;
using UnityEngine;

namespace RPG.ResourceManager
{
    public class ResourceRespawner : MonoBehaviour, IJsonSaveable
    {
        [SerializeField] ResourceGathering resourceToSpawn;
        [SerializeField] int hideTime;

        private double destroyedTime;
        private ResourceGathering resourceObject;
        private TimeKeeper timeKeeper;

        private void Awake()
        {
            timeKeeper = TimeKeeper.GetTimeKeeper();
            SpawnResource();
        }

        public ResourceGathering GetResource()
        {
            return resourceObject;
        }

        public bool IsCollected()
        {
            return GetResource() == null;
        }

        private void SpawnResource()
        {
            resourceObject = Instantiate(resourceToSpawn, transform.position, Quaternion.identity);
            resourceObject.transform.SetParent(transform);
            resourceObject.OnResourceDestroyed += OnResourceDestroyed;
        }

        private void DestroyResource(bool setTime)
        {
            if (resourceObject != null)
            {
                resourceObject.OnResourceDestroyed -= OnResourceDestroyed;
                Destroy(resourceObject.gameObject);
                resourceObject = null;
                if (setTime) destroyedTime = timeKeeper.GetGlobalTime();
                StartCoroutine(WaitAndRespawn());
            }
        }

        private IEnumerator WaitAndRespawn()
        {
            var elapsedTime = (float)(timeKeeper.GetGlobalTime() - destroyedTime);
            yield return new WaitForSeconds(hideTime - elapsedTime);
            SpawnResource();
        }

        private void OnResourceDestroyed(ResourceGathering _)
        {
            DestroyResource(true);
        }

        public JToken CaptureAsJToken()
        {
            var data = new ResourceData(destroyedTime, IsCollected());
            return JToken.FromObject(data);
        }

        public void RestoreFromJToken(JToken state)
        {
            var data = state.ToObject<ResourceData>();
            destroyedTime = data.DestroyedTime;

            var shouldBeCollected = data.ShouldBeCollected;
            if (shouldBeCollected && !IsCollected())
            {
                DestroyResource(false);
            }
            if (!shouldBeCollected && IsCollected())
            {
                SpawnResource();
            }
        }
    }
    [Serializable]
    public struct ResourceData
    {
        public double DestroyedTime;
        public bool ShouldBeCollected;

        public ResourceData(double destroyedTime, bool shouldBeCollected)
        {
            DestroyedTime = destroyedTime;
            ShouldBeCollected = shouldBeCollected;
        }
    }
}

This was my bad, sorry. I pasted code from my notepad++ that wasn’t quite the correct version to paste

OK I’m not sure if I was missing something or not, but…

This did not work for me, and neither did starting and stopping the game simulator work for me… I returned, and the rock was already respawned, which means it still didn’t probably care about the global timer

did you do any external fixes for this? :sweat_smile: (are we talking about the ‘Portal.Transition()’ script? If so, how did you fix that, and what long term issues can this bring up?)

(you have a copy of my project, please consider giving that a look :smiley:)

I made no external fixes. I gave you everything you need.

Everything works as expected, now. The resource is spawned in Awake() like we did for the pickup spawner and then destroyed again if it shouldn’t be there. When it’s destroyed, the timer starts and waits for the time that it should respawn and then it respawns it. When you go through a portal and return, the Awake() creates it again, and the save system will destroy it again if it shouldn’t be there yet

From the sound of it it looks like you may not have set up the ResourceSpawner correctly. Perhaps I should’ve added [RequiresComponent(typeof(SaveableEntity))] to the top of the script

OK that worked (to a great extent), but I noticed a new problem… and a bug (which is just natural evolution from what we did):

[PROBLEM]
if you exit the game and don’t finish mining the rock or cutting the tree and return, then to the game you didn’t even begin harvesting. I think we didn’t add that to the saving system.

So let’s assume we mined 5 out of the 10 resources available, and quit the game and returned, to the game you didn’t mine anything last time, so now you got 10 resources available (although you should only be having 5 left)

[BUG]
This one is hard to explain, but I’ll try my best:

Occasionally (THAT"S THE BIG PROBLEM, IT’S OCCASIONAL), the rock that respawns when we return to a load scene will not be detected by the targeter that Brian created for our third person transition, so the error we previously had that was solved by creating an event (check the chat above) in ‘ResourceGathering.cs’ and subscribing to it is suddenly back… I don’t know how or why, but it’s there again (it’s the exact same problem me and Brian were trying to solve prior to the Saving System)

here’s the first NRE (when I hit the mapped key):

MissingReferenceException: The object of type 'ResourceGathering' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
RPG.Core.RangeFinder`1[T].FindNearestTarget () (at Assets/Project Backup/Scripts/Core/RangeFinder.cs:56)
RPG.States.Player.PlayerFreeLookState.InputReader_HandleResourceGatheringEvent () (at Assets/Project Backup/Scripts/State Machines/Player/PlayerFreeLookState.cs:136)
RPG.InputReading.InputReader.OnInteractWithResource (UnityEngine.InputSystem.InputAction+CallbackContext context) (at Assets/Project Backup/Scripts/Input Controls/InputReader.cs:176)
UnityEngine.InputSystem.Utilities.DelegateHelpers.InvokeCallbacksSafe[TValue] (UnityEngine.InputSystem.Utilities.CallbackArray`1[System.Action`1[TValue]]& callbacks, TValue argument, System.String callbackName, System.Object context) (at Library/PackageCache/com.unity.inputsystem@1.7.0/InputSystem/Utilities/DelegateHelpers.cs:46)
UnityEngine.InputSystem.LowLevel.<>c__DisplayClass7_0:<set_onUpdate>b__0(NativeInputUpdateType, NativeInputEventBuffer*)
UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate(NativeInputUpdateType, IntPtr)

and the second one:

MissingReferenceException while executing 'performed' callbacks of 'Player/InteractWithResource[/Keyboard/u]'
UnityEngine.InputSystem.LowLevel.NativeInputRuntime/<>c__DisplayClass7_0:<set_onUpdate>b__0 (UnityEngineInternal.Input.NativeInputUpdateType,UnityEngineInternal.Input.NativeInputEventBuffer*)
UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate (UnityEngineInternal.Input.NativeInputUpdateType,intptr)

Yeah, if you don’t save it, it won’t get restored.

So, why do you think this is happening?

I gave it a go, but I’m sure that’s not the best way around it. Can you please have a look? It was all very simple modifications:

public JToken CaptureAsJToken()
    {
        var data = new ResourceData(destroyedTime, IsCollected(), resourceToSpawn.GetQuantityLeft());
        return JToken.FromObject(data);
    }

    public void RestoreFromJToken(JToken state)
    {
        var data = state.ToObject<ResourceData>();
        destroyedTime = data.DestroyedTime;

        var shouldBeCollected = data.ShouldBeCollected;
        var quantityLeft = data.QuantityLeft;

        if (shouldBeCollected && !IsCollected())
        {
            DestroyResource(false);
        }
        if (!shouldBeCollected && IsCollected())
        {
            SpawnResource();
            resourceToSpawn.quantityLeft = quantityLeft;
        }
    }
}

[Serializable]
public struct ResourceData
{
    public double DestroyedTime;
    public bool ShouldBeCollected;
    public int QuantityLeft;

    public ResourceData(double destroyedTime, bool shouldBeCollected, int quantityLeft)
    {
        DestroyedTime = destroyedTime;
        ShouldBeCollected = shouldBeCollected;
        QuantityLeft = quantityLeft;
    }
}

}

as far as I understood, it’s mainly happening if I respawn the player back into the game scene at the same moment a rock (or tree, or any other resource gathering system…) gets respawned…? Not too sure to be honest

It’s not really the spawner’s job to store the resource’s quantity. But I guess it will do

Yes, but I asked why you think it’s happening, not when

well… with my current attempt, it did not work for some reason :sweat_smile: (and if it’s not the job of the spawner, then I guess it’s best we clean it, for good coding purposes because one day I’ll return to read this behemoth of a program… :stuck_out_tongue_winking_eye: ). To give that a second go, I tried doing it in ‘ResourceGathering.cs’. Is this correct?:

        public JToken CaptureAsJToken()
        {
            return JToken.FromObject(GetQuantityLeft());
        }

        public void RestoreFromJToken(JToken state)
        {
            var data = state.ToObject<int>();
            quantityLeft = data;
        }

well… from recent experience, it’s because we did not subscribe the rock to the ‘OnResourceDestroyed’ event in ‘ResourceGathering.cs’, which removes it from the targeter, but we already did that in ‘ResourceRespawner.cs’, right? (I mean it’s the last line of ‘ResourceRespawner.SpawnResource()’, and the first line of ‘ResourceRespawner.DestroyResource()’)

Is it because we have two destroy functions, one being in ‘ResourceRespawner.cs’, and one in ‘ResourceGathering.cs’, and ‘ResourceGatherer.cs’ uses the one in ‘ResourceGathering.cs’?

Does it work?

Close, but no. This is not how events work. ResourceGathering is the sender. It does not subscribe to it. The spawner is a subscriber / listener / handler.
So, this problem is on me. I did not consider this. I need to check the spawner and see how we can fix this. But it’s 3am and I need to work in the morning

nope :sweat_smile:

no worries man, have a good night rest. Will be waiting to hear from you again tomorrow (if, miraculously, I manage to solve it, I’ll let you know :slight_smile:)

so as not to baffle the confused guy here (me), who is trying to get better (I don’t know how or when, but it usually just hits me like a flood one day, where everything makes sense… I’m waiting for that day), let’s just stick to calling it as a “subscriber” :stuck_out_tongue_winking_eye:

So, the problem I created was that the saving system will destroy the resource if it shouldn’t be there, but the targeter may already have a reference to it. When the spawner destroys the resource, it doesn’t fire an event so the targeter never knows that the resource was destroyed.
Here’s a fix. I have rearranged the code a little so that the spawner tells the resource to destroy itself. Now the event will fire as usual and the targeter will know what to do

First thing we have to do is update ResourceGathering.DestroyResource() to remove the parent from the resource when the resource is destroyed (it causes a bug that we may as well fix now). I wrote about this like 2 years ago or something (Edit: Found it. It’s here). The portal loads and then saves in the same frame. The load may destroy the resource, but then it’s still around when the save happens and then it’s all messed up. Here’s the update

public void DestroyResource()
{
    OnResourceDestroyed?.Invoke(this);
    transform.SetParent(null); // unset the parent
    Destroy(gameObject);
    isDestroyed = true;
}

And here’s the updated ResourceSpawner. It does not include the saving bits for the quantity. That’s an exercise for you.

using System;
using System.Collections;
using GameDevTV.Saving;
using Newtonsoft.Json.Linq;
using UnityEngine;

namespace RPG.ResourceManager
{

    public class ResourceRespawner : MonoBehaviour, IJsonSaveable
    {
        [SerializeField] ResourceGathering resourceToSpawn;
        [SerializeField] int hideTime;

        private double destroyedTime;
        private TimeKeeper timeKeeper;

        private void Awake()
        {
            timeKeeper = TimeKeeper.GetTimeKeeper();
            SpawnResource();
        }

        public ResourceGathering GetResource()
        {
            return GetComponentInChildren<ResourceGathering>();
        }

        public bool IsCollected()
        {
            return GetResource() == null;
        }

        private void SpawnResource()
        {
            var resourceObject = Instantiate(resourceToSpawn, transform.position, Quaternion.identity);
            resourceObject.transform.SetParent(transform);
            resourceObject.OnResourceDestroyed += OnResourceDestroyed;
        }

        private IEnumerator WaitAndRespawn()
        {
            var elapsedTime = timeKeeper.GetGlobalTime() - destroyedTime;
            yield return new WaitForSeconds(hideTime - elapsedTime);
            SpawnResource();
        }

        private void OnResourceDestroyed(ResourceGathering resourceNode)
        {
            resourceNode.OnResourceDestroyed -= OnResourceDestroyed;
            destroyedTime = timeKeeper.GetGlobalTime();
            StartCoroutine(WaitAndRespawn());
        }

        public JToken CaptureAsJToken()
        {
            var data = new ResourceData(destroyedTime, IsCollected());
            return JToken.FromObject(data);
        }

        public void RestoreFromJToken(JToken state)
        {
            var data = state.ToObject<ResourceData>();
            destroyedTime = data.DestroyedTime;

            var shouldBeCollected = data.ShouldBeCollected;
            if (shouldBeCollected && !IsCollected())
            {
                var resourceObject = GetResource();
                resourceObject.OnResourceDestroyed -= OnResourceDestroyed;
                resourceObject.DestroyResource();
                StartCoroutine(WaitAndRespawn());
            }
            if (!shouldBeCollected && IsCollected())
            {
                SpawnResource();
            }
        }
    }

    [Serializable]
    public struct ResourceData
    {
        public double DestroyedTime;
        public bool ShouldBeCollected;

        public ResourceData(double destroyedTime, bool shouldBeCollected)
        {
            DestroyedTime = destroyedTime;
            ShouldBeCollected = shouldBeCollected;
        }
    }
}

OK so… I gave this a second try, but for some reason it’s not coming along well. Here’s what I tried doing (all changes are in ‘ResourceRespawner.cs’):

  1. In the resource data struct, I integrated a new variable called ‘QuantityLeft’, as follows:
[Serializable]
public struct ResourceData
{
    public double DestroyedTime;
    public bool ShouldBeCollected;
    public int QuantityLeft;

    public ResourceData(double destroyedTime, bool shouldBeCollected, int quantityLeft)
    {
        DestroyedTime = destroyedTime;
        ShouldBeCollected = shouldBeCollected;
        QuantityLeft = quantityLeft;
    }
}

  1. When capturing the JToken, I integrated the ‘GetQuantityLeft()’ function I created, so this is what the new data line looks like:
        var data = new ResourceData(destroyedTime, IsCollected(), GetQuantityLeft());
  1. When restoring the JToken, here’s what I added:
// similar to getting the destroyedTime and 'shouldBeCollected':
        var quantityLeft = data.QuantityLeft;

// in the second if statement of the restore function:
            GetResource().quantityLeft = quantityLeft;

So the final saving system looks like this:

// new function:
    public int GetQuantityLeft() 
    {
        return GetResource().GetQuantityLeft();
    }

// saving stuff:
public JToken CaptureAsJToken()
    {
        var data = new ResourceData(destroyedTime, IsCollected(), GetQuantityLeft());
        return JToken.FromObject(data);
    }

    public void RestoreFromJToken(JToken state)
    {
        var data = state.ToObject<ResourceData>();
        destroyedTime = data.DestroyedTime;

        var shouldBeCollected = data.ShouldBeCollected;
        var quantityLeft = data.QuantityLeft;

        if (shouldBeCollected && !IsCollected())
        {
            var resourceObject = GetResource();
            resourceObject.OnResourceDestroyed -= OnResourceDestroyed;
            resourceObject.DestroyResource();
            StartCoroutine(WaitAndRespawn());
        }
        if (!shouldBeCollected && IsCollected())
        {
            SpawnResource();
            GetResource().quantityLeft = quantityLeft;
        }
    }
}

[Serializable]
public struct ResourceData
{
    public double DestroyedTime;
    public bool ShouldBeCollected;
    public int QuantityLeft;

    public ResourceData(double destroyedTime, bool shouldBeCollected, int quantityLeft)
    {
        DestroyedTime = destroyedTime;
        ShouldBeCollected = shouldBeCollected;
        QuantityLeft = quantityLeft;
    }
}

SOO… why is this still not working?

This actually highlights an issue I didn’t consider and we should address.

There are 4 states the spawner could be in, but only 2 of them are handled. The same is true for the pickup spawner, but it’s not an issue for the pickup spawner because the unhandled states are implicitly handled.

  • Handled
    • The resource is spawned but should not be spawned
    • The resource is not spawned but should be
  • Not handled
    • The resource is spawned and should be spawned
    • The resource is not spawned and should not be spawned

We have to handle the two unhandled cases.

  • The resource is spawned and should be spawned.
    We have to handle this case because a new save is loaded and the quantity should be updated to whatever is saved.
  • The resource is not spawned and should not be spawned.
    We have to stop the timer and recreate it with the saved start time

You may want to update RestoreFromJToken with something like this

public void RestoreFromJToken(JToken state)
{
    var data = state.ToObject<ResourceData>();
    destroyedTime = data.DestroyedTime;

    var shouldBeCollected = data.ShouldBeCollected;
    var quantityLeft = data.QuantityLeft;

    if (shouldBeCollected && !IsCollected())
    {
        // Should be destroyed but isn't - destroy it
        var resourceObject = GetResource();
        resourceObject.OnResourceDestroyed -= OnResourceDestroyed;
        resourceObject.DestroyResource();
        StartCoroutine(WaitAndRespawn());
    }
    else if (!shouldBeCollected && IsCollected())
    {
        // Shouldn't be destroyed but is - spawn it
        SpawnResource();
        GetResource().quantityLeft = quantityLeft;
    }
    else if (shouldBeCollected && IsCollected())
    {
        // Should be destroyed and is - reset timer
        StopAllCoroutines();
        StartCoroutine(WaitAndRespawn());
    }
    else
    {
        // Shouldn't be destroyed and isn't - reset quantity
        var resourceObject = GetResource();
        GetResource().quantityLeft = quantityLeft;
    }
}

I don’t like all the if’s here, but let’s get it working

OK so that worked perfectly between save files, where I tried loading another file and returning to the previous one to confuse the project, but it identified them perfectly. However, there is one major problem:

NullReferenceException: Object reference not set to an instance of an object
RPG.ResourceManager.ResourceRespawner.GetQuantityLeft () (at Assets/Project Backup/Scripts/ResourceManager/Old Scripts/ResourceRespawner.cs:36)
RPG.ResourceManager.ResourceRespawner.CaptureAsJToken () (at Assets/Project Backup/Scripts/ResourceManager/Old Scripts/ResourceRespawner.cs:62)
GameDevTV.Saving.JSONSaveableEntity.CaptureAsJToken () (at Assets/Project Backup/Scripts/Saving/JSON Saving System/JSONSaveableEntity.cs:29)
GameDevTV.Saving.JSONSavingSystem.CaptureAsToken (Newtonsoft.Json.Linq.JObject state) (at Assets/Project Backup/Scripts/Saving/JSON Saving System/JSONSavingSystem.cs:134)
GameDevTV.Saving.JSONSavingSystem.Save (System.String saveFile) (at Assets/Project Backup/Scripts/Saving/JSON Saving System/JSONSavingSystem.cs:44)
RPG.SceneManagement.SavingWrapper.Save () (at Assets/Project Backup/Scripts/SceneManagement/SavingWrapper.cs:190)
RPG.UI.PauseMenuUI.SaveAndQuit () (at Assets/Project Backup/Scripts/UI/PauseMenuUI.cs:55)
UnityEngine.Events.InvokableCall.Invoke () (at <ba783288ca164d3099898a8819fcec1c>:0)
UnityEngine.Events.UnityEvent.Invoke () (at <ba783288ca164d3099898a8819fcec1c>:0)
UnityEngine.UI.Button.Press () (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:70)
UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:114)
UnityEngine.EventSystems.ExecuteEvents.Execute (UnityEngine.EventSystems.IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:57)
UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:272)
UnityEngine.EventSystems.EventSystem:Update() (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:514)
  1. This NRE is a major issue, because it will show up when the resource source we just harvested is dead, and we will never be able to save and quit the game until that resource comes back to life. After a bit of investigation, I tracked it down to this function:
    public int GetQuantityLeft() 
    {
        return GetResource().GetQuantityLeft();
    }

and if you need to know what ‘GetResource()’ contains, here you go:

    public ResourceGathering GetResource()
    {
        return GetComponentInChildren<ResourceGathering>();
    }

and ‘GetQuantityLeft()’ from ‘ResourceGathering.cs’:

        public int GetQuantityLeft() {
            return quantityLeft;
        }

(P.S: I tried to change them into variables instead of called functions, but that messed things up…)

  1. Through minor testing (will still extensively test that), I noticed something new… the respawn timer does not seem to work when the game is paused. So if you press pause (not the Unity Engine pause, but the ‘Escape’ key pause that we implemented in-game), the respawn timer will pause as well (I’ll test it with shorter times for consistency). Not sure if that happens or not, just something I noticed on the fly… Can you keep me updated about how this works?

(For the second point, it’s true. For the rock (or any resource) to respawn, we must be out of the pause menu for the timer to count… Is it possible to make the timer count independent of the pause menu status?)

Yeah, if the resource is dead GetResource() will return null. You would have to check it. You can return 0 if it is null because it wouldn’t matter if the resource is dead (and that’s what the value of a dead resource would be anyway)

The game is paused, so it won’t work. The global timer doesn’t care about pauses, but the coroutine does. This means the actual spawn time will be hideTime plus the total pause duration. If the hideTime is 5 minutes and you pause for 30 seconds and then for another 1 minute and 30 seconds, the resource will only spawn after 7 minutes because the timer will wait for the correct duration, but also not run for a total of 2 minutes. You could go back to the 30 second (or 1 second for short respawns) wait loop. Then the timer won’t accumulate as much of the pause duration because after the unpause it will wait - at most - 30 seconds before checking if the resource should spawn

at first it made a mistake where it confused two save files’ quantityLeft values, but then it got things right through 3 different files, so all is good for that for now

Are we talking about this?

Privacy & Terms