Resource Gathering for Third person

I think I’ll go with this one, especially that I already have a TimeKeeper, but fitting this all into a global timer, saving and restoring it is probably just another problem waiting to be solved (I’m confused why you didn’t address this in your earlier comment tbh)

not exactly 8 hours, preferably more like ‘hideTime’

OK I’m a little confused… what is ‘storedRespawnTime’ that you placed in ‘Update()’? At first I thought it was a rename of ‘hideTime’, but now I’m confused…:

        void Update()
        {
            // Check if the current tree is destroyed and respawn it
            if (resourceObject == null && !isRespawning)
            {
                // StartCoroutine(RespawnAfterDelay());
                if (timeKeeper.GetGlobalTime() - storedRespawnTime > hideTime) SpawnSource();
            }
        }

the compiler doesn’t recognize it

It was an example, just like ‘10am on Monday, 5 Feb 2024’. Of course it will be hideTime

No, everything is already there and I did mention all of this.

Here’s a breakdown;

  • When the resource is destroyed, you get _timeKeeper.GetGlobalTime(). This is the time when the resource died and this is what you will store in the save file
  • Now you start the coroutine I mentioned (let’s go with the coroutine since it’s less confusing to stop than InvokeRepeating)
  • When the resource respawns the coroutine will stop, and you can ignore the global time in the save file. You should have a flag to specify whether or not the resource is dead. This is in the PickupSpawner as shouldBeCollected

So, building off the PickupSpawner it may look like this (all happening on the fly, so no testing was done)

public class ResourceSpawner : MonoBehaviour, IJsonSaveable
{
    [SerializeField] GameObject resourceToSpawn;
    [SerializeField] int hideTime;
    
    private double destroyedTime;
    private GameObject resourceObject;
    private TimeKeeper timeKeeper;
    
    private void Awake()
    {
        timeKeeper = TimeKeeper.Get();
        SpawnResource();
    }
    
    public GameObject GetResource()
    {
        return resourceObject;
    }
    
    public bool IsCollected()
    {
        return GetResource() == null;
    }
    
    private void SpawnResource()
    {
        resourceObject = Instantiate(resourceToSpawn, transform);
    }
    
    private void DestroyResource()
    {
        var resourceToDestroy = GetResource();
        if (resourceToDestroy != null)
        {
            Destroy(resourceToDestroy);
            destroyedTime = timeKeeper.GetGlobalTime();
            StartCoroutine(CheckForRespawn());
        }
    }
    
    private IEnumerator CheckForRespawn()
    {
        var thirtySeconds = new WaitForSeconds(30);
        while (true)
        {
            yield return thirtySeconds;
            var currentTime = timeKeeper.GetGlobalTime();
            if (currentTime - destroyedTime >= hideTime)
            {
                SpawnResource();
                break;
            }
        }
    }
    
    public JToken CaptureAsJToken()
    {
        var data = new ResourceData(destroyedTime, IsColledted());
        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
            Destroy(resourceObject);
            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;
    }
}

Again, I haven’t tested this. The DestroyResource() here is not used, but that’s because the code for destroying resources when it dies, etc. has not been added. This is just to get a resource to save and keep track of when it should spawn, and spawn when it should based on what the PickupSpawner is doing

I will, forever, be confused about how in the world do the both of you come up with incredibly complex code in a matter of minutes… Some of this stuff will probably take me months, if not years, to figure out

Anyway, I’ll give this a go in a bit, about an hour or so… I have to be elsewhere very soon

Just to be clear though, this is meant as a replacement for my ‘ResourceRespawner.cs’ script?

Yes, but also no. You have ResourceGathering stuff I don’t know anything about that you may still need. This here is just going to spawn an object at specific times. You will need to add the other stuff like deciding when the resource is depleted, etc. And later add the stump for destroyed trees

It was an example. It would be the time from the TimeKeeper when the resource was destroyed and saved in the save file, but because you don’t have any of that I just made it up.

ahh… the new script you wrote does not even get my resource object to show up. So basically, I can’t see the rock or the trees, none of that…

Not really that important, the Resource Respawner and the ResourceGathering.cs scripts are quite independent. Apart from just setting up a few public variables on respawn, there’s not much really else in common between them. I know that because, for the first time in my life, I am actually the original writer of that script :stuck_out_tongue_winking_eye:

If it helps though, this is my ‘ResourceGathering.cs’ script:

using UnityEngine;
using GameDevTV.Inventories;
using RPG.Control;
using RPG.Skills;
using UnityEngine.AI;
using RPG.Core;

// Steps to get the experience:
// 1. Declare a 'SkillExperience' variable
// 2. get the component of the variable in 'Awake()'
// 3. Split the type of XP you get, based on the tag... for trees, it's "Tree", and for Rocks, it's "ResourceRocks"
// 4. When gathering, call 'skillExperience.GainExperience()', so you get the experience you are supposed to get when gathering resources
// 5. Play and test multiple times...

namespace RPG.ResourceManager
{
    public class ResourceGathering : MonoBehaviour, ITarget//, IRaycastable
    {
        [SerializeField] ResourceDataStructure resourceToAccumulate;    // the loot we are getting from the tree, rock, or whatever we're harvesting
        [SerializeField] int maxResourcesPerHit;    // how many of that loot is being acquired, per mouse click
        public int quantityLeft;   // how many resources are in that source, before it's destroyed and re-instantiated

        Inventory playerInventory;  // the target inventory for our resources to go to (the player's inventory in this case)
        
        internal bool isDestroyed = false;    // flag, to kill the source if it's out of resources
        // bool isMovingTowardsResourceObject = false;   // (OBSOLETE IN THIRD PERSON) flag, to ensure that we are close to the source of the resources, before being able to gather resources from it

        // skill experience link, to get the skill, and fine-tune it according to the trained skill
        SkillExperience skillExperience;
        [SerializeField] Skill associatedSkill;
        [SerializeField] int experienceValue;
        [SerializeField] CursorType cursorType;
        [SerializeField] ResourceGatherer resourceGatherer;

        [Tooltip("Only change this value in the prefab if you want to see the effects in-game (between pauses only!)")]
        [SerializeField] public int minSkillRequired;

        [SerializeField] public int maxSkillRequired;

        SkillStore skillStore;

        private NavMeshAgent navMeshAgent;
        private int levelToUnlock;

        void Awake()
        {
            playerInventory = Inventory.GetPlayerInventory();
            if (playerInventory == null) Debug.Log("Player Inventory Not Found");

            // In very simple terms, when you got the playerInventory, you got a hold of the only in-game
            // component that actually has an 'Inventory.cs' script on him, the player. 
            // As the player has a hold of the 'SkillExperience' script as well, you can catch it
            // through his inventory as well, since you got a hold of him through his inventory, 
            // so that's what I did below:
            skillExperience = playerInventory.GetComponent<SkillExperience>();

            // Just like how we accumulated 'skillExperience' above, we accumulate
            // 'skillStore' as well with the exact same method (getting the object (Player)
            // that the only inventory in the game is attached to):
            skillStore = playerInventory.GetComponent<SkillStore>();
            // isMovingTowardsResourceObject = false;
            resourceGatherer = playerInventory.GetComponent<ResourceGatherer>();
        }

        public int GetMaxSkillRequired() {
            return maxSkillRequired;
        }

        public int GetMinSkillRequired() {
            return minSkillRequired;
        }

        public int GetMaxResourcesPerHit() {
            return maxResourcesPerHit;
        }

        public int GetQuantityLeft() {
            return quantityLeft;
        }

        public InventoryItem ResourceToAccumulate() {
            return resourceToAccumulate;
        }

        // Public event to fix the major issue of the system not being able to detect deleted resource sources
        // (Third Person Transition, for 'ResourceFinder.cs' to subscribe to)
        public event System.Action<ResourceGathering> OnResourceDestroyed;

        public void DestroyResource() {
            OnResourceDestroyed?.Invoke(this);
            Destroy(gameObject);
            isDestroyed = true;
        }

        public Skill GetAssociatedSkill() {
            return associatedSkill;
        }

        public int GetExperienceValue() {
            return experienceValue;
        }

        public void AccumulateXP()
        {
            skillExperience.GainExperience(associatedSkill, experienceValue);
        }

        public int LevelToUnlock()
        {
            return levelToUnlock = minSkillRequired;
        }

        bool ITarget.IsValid()
        {
            if (!Inventory.GetPlayerInventory().HasSpaceFor(resourceToAccumulate)) return false;
            if (skillStore.GetSkillLevel(associatedSkill) < LevelToUnlock()) return false; // place an 'or' ("||")statement later to count for holding a weapon associated to the skill (hatchet for trees, pickaxe for mining, etc), by checking the 'equipLocation.Weapon'
            // inject code here that checks if EquipLocation.Weapon has the required axe (level-based check) or not
            resourceGatherer.Gather(this);
            return true;
        }

        /* public CursorType GetCursorType()
        {
            if (isDestroyed == true) return CursorType.None;
            return cursorType;
        }

        public bool HandleRaycast(PlayerController callingController) {
                if (Input.GetMouseButtonDown(0)) {
                // if you don't have enough space, don't start resource gathering
                if(!Inventory.GetPlayerInventory().HasSpaceFor(resourceToAccumulate)) return false;
                // if you're not high enough of a level to touch a resource, or don't have the right weapon, don't start
                if (skillStore.GetSkillLevel(associatedSkill) < LevelToUnlock()) return false;  // place an 'or' ("||")statement later to count for holding a weapon associated to the skill (hatchet for trees, pickaxe for mining, etc), by checking the 'equipLocation.Weapon'
                // if all is well so far, start gathering the resources
                resourceGatherer.Gather(this);
                }
                return true;
        } */

    }

}

and the ‘ResourceGatherer.cs’ script:

using GameDevTV.Inventories;
using RPG.Core;
using RPG.Movement;
using RPG.Skills;
using UnityEngine;

namespace RPG.ResourceManager {

public class ResourceGatherer : MonoBehaviour, IAction
{

    [SerializeField] int acceptanceRadius;
    [SerializeField] float timeBetweenGathers;

    private ResourceGathering target;
    private float timeSinceLastGather = Mathf.Infinity;

    private SkillStore skillStore;
    private Inventory playerInventory;
    private SkillExperience skillExperience;

    private void Awake() {
        skillStore = GetComponent<SkillStore>();
        playerInventory = GetComponent<Inventory>();
        skillExperience = GetComponent<SkillExperience>();
    }

    private void Update() {

        timeSinceLastGather += Time.deltaTime;

        if (target == null) return;

        /* if (!GetIsInRange(target.transform)) {
            GetComponent<Mover>().MoveTo(target.transform.position, 1.0f);
        }
        else {
            GetComponent<Mover>().Cancel();
            GatherBehaviour();
        } */

    }

    /* private void TriggerGatheringAnimation() {

        if (GetAssociatedSkill() == Skill.Woodcutting) {
        
        playerInventory.GetComponent<Animator>().ResetTrigger("stopCuttingWood");
        playerInventory.GetComponent<Animator>().SetTrigger("cutWood");

        }

        else if (GetAssociatedSkill() == Skill.Mining) {

            playerInventory.GetComponent<Animator>().ResetTrigger("stopMiningRocks");
            playerInventory.GetComponent<Animator>().SetTrigger("mineRocks");

        }

        // you get the drill for the rest...

    } */

    public string GetGatheringAnimation() 
    {
        switch(GetAssociatedSkill()) 
        {
            case Skill.Woodcutting: return "cutWood";
            case Skill.Mining: return "mineRocks";
            // add more skills here, if necessary (fishing coming up soon...)
            default: return "Pickup";
        }
    }

    public void Gather(ResourceGathering target) {
        GetComponent<ActionSchedular>().StartAction(this);
        this.target = target;
        }

        private bool GetIsInRange(Transform targetTransform) {
        return Vector3.Distance(transform.position, targetTransform.position) < acceptanceRadius;
    }

    public void GatherBehaviour() {

        // Using the 'LookAt()' we invented below, instead of 'transform.LookAt()',
        // to avoid the character from sloping his y-axis values, the 'Michael Jackson' effect:
        // LookAt(target.transform.position);
        /* if (timeSinceLastGather < timeBetweenGathers) return;
        TriggerGatheringAnimation();
        Gather();
        timeSinceLastGather = 0f; */

        if (timeSinceLastGather > timeBetweenGathers) {
            GetGatheringAnimation();
            Gather();
            timeSinceLastGather = 0;
        }

    }

    /* void LookAt(Vector3 position) {
        position.y = transform.position.y;
        transform.LookAt(position);
    } */

    /* // Test
    private ResourceFinder resourceFinder;

    private void Start() 
    {
        resourceFinder = GetComponentInChildren<ResourceFinder>();
    }
    // End of Test */

    public void Gather() {

            // Steps for the 'Gather()' function:
            // 1. If your source is not out of resources yet, then start off by playing the 'gathering' animation
            // 2. get the players' Skill level for the skill (Woodcutting, Mining, Farming, etc)
            // 3. Get the probability of the player getting 'maxResourcesPerHit', each time he strikes the resource (the higher the level, the closer the player gets to 'maxResourcePerHit')
            // 4. If the player gets a resource, determine (out of a max value of 'maxResourcesPerHit') how many resources he can get, based on his resource-gathering level
            // 5. ensure that the player does NOT get more than the resources left in the source (so he can't get 6 logs if the tree only has 2 more left for example)
            // 6. Give the player the resource, deduct it from the source, and then give him XP for it
            // 7. If the source is out of resources, kill it and stop the 'gathering' animation

            if (target.quantityLeft > 0) {
            // TriggerGatheringAnimation();
            GetGatheringAnimation();
            var playerSkillLevel = skillStore.GetSkillLevel(target.GetAssociatedSkill());
            var playerDropChance = Mathf.Clamp01((playerSkillLevel - target.minSkillRequired)/(float)(target.maxSkillRequired - target.minSkillRequired));

            // if (Random.value <= playerDropChance) {
            int amountToGive = DetermineAmountToGive(playerSkillLevel, target.GetMinSkillRequired(), target.GetMaxSkillRequired(), target.GetMaxResourcesPerHit());
            amountToGive = Mathf.Min(amountToGive, target.GetQuantityLeft());
            playerInventory.AddToFirstEmptySlot(target.ResourceToAccumulate(), amountToGive);
            target.quantityLeft -= amountToGive;
            AccumulateXP();
            // }
        }

        if (target.quantityLeft <= 0) {
            
            target.DestroyResource();
        }

    }

    private int DetermineAmountToGive(int playerSkillLevel, int minLevel, int maxLevel, int maxResourcesPerHit) {

        // float randomValue = Random.value * (playerSkillLevel - minLevel);
        float randomValue = Mathf.CeilToInt(Random.value * (playerSkillLevel - minLevel));
        float fractionPerLevel = maxResourcesPerHit / (maxLevel - minLevel);
        float amountToGive = 1f;

        // for (int i = 0; i < (maxLevel - minLevel); i++) {

            // changing 'randomValue -= i' to 'randomValue -= 1' below 
            // ensures that the "CHANCES" of us getting more than a log at a time at higher
            // levels are properly calculated:
        //    randomValue -= 1;   

        //    amountToGive += fractionPerLevel;
                amountToGive += randomValue * fractionPerLevel;
        // if (randomValue <= 0f) break;
        // }
            // return Mathf.FloorToInt(amountToGive);
            return Mathf.FloorToInt(Mathf.Min(amountToGive, maxResourcesPerHit));
        }

    private void AccumulateXP() {
        skillExperience.GainExperience(GetAssociatedSkill(), GetExperienceValue());
    }

    public Skill GetAssociatedSkill() {
        return target.GetAssociatedSkill();
    }

    public int GetExperienceValue() {
        return target.GetExperienceValue();
    }

    /* private void StopGatheringAnimation() {
        
        if (GetAssociatedSkill() == Skill.Woodcutting) {
        
        GetComponent<Animator>().ResetTrigger("cutWood");
        GetComponent<Animator>().SetTrigger("stopCuttingWood");
        
        }

        else if (GetAssociatedSkill() == Skill.Mining) {

        GetComponent<Animator>().ResetTrigger("mineRocks");
        GetComponent<Animator>().SetTrigger("stopMiningRocks");

        }

        // you get the drill for the rest...

    } */

    public void Cancel()
    {
        // GetGatheringAnimation();
        target = null;
        GetComponent<Mover>().Cancel();
    }

}

}

and to keep your life simple, this is my original ‘ResourceRespawner.cs’ script (where I want to make the changes):

using System.Collections;
using UnityEngine;

namespace RPG.ResourceManager
{
    public class ResourceRespawner : MonoBehaviour//, IJsonSaveable
    {
        [SerializeField] GameObject resourceToRespawn;
        [SerializeField] int hideTime;
        [SerializeField] int originalQuantity = 10;

        private GameObject resourceObject;
        private bool isRespawning = false;

        void Start()
        {
            // Spawn the initial tree
            SpawnSource();
        }

        void Update()
        {
            // Check if the current tree is destroyed and respawn it
            if (resourceObject == null && !isRespawning)
            {
                StartCoroutine(RespawnAfterDelay());
            }
        }

        IEnumerator RespawnAfterDelay()
        {
            isRespawning = true;
            yield return new WaitForSeconds(hideTime);
            SpawnSource();
            isRespawning = false;
        }

        private void SpawnSource()
        {

            // Instantiate a new tree ('Quaternion.identity' = no Rotation assigned - Success):
            resourceObject = Instantiate(resourceToRespawn, transform.position, Quaternion.identity);

            // Make the current tree a child of this GameObject (the Tree Respawner - Success):
            resourceObject.transform.parent = transform;

            // Assign a tag to the spawned resource source - NOT NECESSARY THOUGH (Success):
            // resourceObject.gameObject.tag = "ResourceTree";

            // Access the ResourceGathering script on the instantiated object and set its properties (Success):
            ResourceGathering resourceGathering = resourceObject.GetComponent<ResourceGathering>();

            // If you have a new source for a specific resource, reset the values for another round of resource gathering:
            if (resourceGathering != null)
            {
                // Reset the quantity left for resource gathering
                resourceGathering.quantityLeft = originalQuantity;  // Reset the quantity for the next resource instance
                resourceGathering.isDestroyed = false; // Reset the IsDestroyed flag
            }
        }
    }
}

and then the ResourceDataStructure.cs script is an empty class that inherits from InventoryItem.cs, only so we can create resources to gather with

Brian, if you’re reading this, and you got confused somewhere, all we’re trying to do here is to save the timer for the resource respawners in a global timeKeeper format, using this script:


using System;
using UnityEngine;

namespace RPG {

    public class TimeKeeper : MonoBehaviour {

        private DateTime globalEpoch = new DateTime(2023,1,1);

        public static TimeKeeper GetTimeKeeper() {

            return FindObjectOfType<TimeKeeper>();

        }

        public double GetGlobalTime() {

            return (DateTime.Now - globalEpoch).TotalSeconds;

        }

    }

}

It’s something we created when making our Crafting System (Bixarrio helped me create it). The idea is to use this class to get the global computer time, and for the respawn times, we want to rely on that instead of the in-game engine timer, so long-time spawnables are not reliant on the gameplay timer, but it relies on the computer timer, which enables long-time respawns… that’s it

Without details, I don’t know what this means.

It looks like ResourceGathering is the resource. You should probably bind the ResourceSpawner to the ResourceGathering.OnResourceDestroyed event when you spawn it because it needs to know when the resource is destroyed so that it can store the global time.

Here’s a slightly updated version of the spawner

public class ResourceSpawner : MonoBehaviour, IJsonSaveable
{
    [SerializeField] ResourceGathering resourceToSpawn;
    [SerializeField] int hideTime;
    
    private double destroyedTime;
    private ResourceGathering resourceObject;
    private TimeKeeper timeKeeper;
    
    private void Awake()
    {
        timeKeeper = TimeKeeper.Get();
        SpawnResource();
    }
    
    public GameObject GetResource()
    {
        return resourceObject.gameObject;
    }
    
    public bool IsCollected()
    {
        return GetResource() == null;
    }
    
    private void SpawnResource()
    {
        resourceObject = Instantiate(resourceToSpawn, transform);
        resourceObject.OnResourceDestroyed += OnResourceDestroyed;
    }
    
    private void DestroyResource()
    {
        var resourceToDestroy = GetResource();
        if (resourceToDestroy != null)
        {
            Destroy(resourceToDestroy);
            destroyedTime = timeKeeper.GetGlobalTime();
            StartCoroutine(CheckForRespawn());
        }
    }
    
    private IEnumerator CheckForRespawn()
    {
        var thirtySeconds = new WaitForSeconds(30);
        while (true)
        {
            yield return thirtySeconds;
            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, IsColledted());
        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;
    }
}

there’s no syntax errors that I have seen as of yet. It just doesn’t get the resource to spawn at the start of the game, it’s more of a logic error I think…?

I couldn’t think of a better name for it, but yes… that’s true

soo… in ResourceGathering.cs’ Awake or Start, just place ‘resourceGathering.OnResourceDestroyed += SpawnSource;’, and then delete it in ‘ResourceGatherer.cs’ when it’s destroyed?

No. See my edit

Not much logic to have an error:
On awake, spawn resource.

Another problem is that you now have 2 scripts destroying the same resource: The ResourceSpawner and the ResourceGathering. The ‘spawner’ is going to destroy it first, so make sure there is no error when the ‘gathering’ is trying to destroy an already destroyed object

Speaking of that, I’d rather we not try do any sort of destroying in ‘ResourceRespawner.cs’… deleting that destroyer from Gathering will cause a ton of issues with the rangeFinder, it’ll be a nightmare to work with…

still doesn’t get spawned…

Good luck with that. The spawner needs to destroy the resource if the save system says it shouldn’t be there

OK so apparently the rock was showing up, but at the wrong position, so I tuned the instantiation line a bit, and now it looks like this:

        resourceObject = Instantiate(resourceToSpawn, transform.position, Quaternion.identity);

HOWEVER, the hide time is… double I think? (I used a stopwatch, and indeed it was double the given ‘hideTime’). I’m guessing having that second destroy in the resourceGatherer.cs doubled it (apparently deleting that one means I can’t even delete the rock to respawn again, so not the case here…)

(not to sound terrible, but saving and quitting the game and returning respawns the rock, although it’s not supposed to be there as of yet)

The way I had it would spawn it at (0,0,0) relative to the parent, which is the same as transform.position if it’s not parented. So, I don’t see how this is different.

No, not sure how a second destroy would double it. You’re just killing the object twice. Unless, for some unknown reason, you were adding times together. What did you set the hideTime to?

the only place ‘hideTime’ is being used, is in the IEnumerable. This is what it looks like (I changed the thirtySecond to something we can modify later). Other than that, hideTime was set to 10 seconds in the inspector, but takes 20 seconds to respawn a destroyed object:

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

lol trust me I have no clue either, just… one works, one didn’t for some reason

Above all else, saving and restoring didn’t work for god knows what reason… You mine a rock, leave the game and return in less than the 20 seconds assigned to hideTime (10 seconds for ‘hideTime’ so far, but 20 because it’s being destroyed twice I guess…) for example, and suddenly it’s there, when the entire leaving and returning operation took about 8 seconds (you can see how bad this can get over the long run)

Edit: Tried eliminating the double hideTime issue by linking the new destroy to be part of the old one through code, and that was a programming mess… ended up deleting it (on a serious note, this can get computationally expensive if not dealt with)

So now you’re just doing what you did before that wasn’t working. You’re just doing this again:

If the respawn time is 10 seconds and the load happens 1 second before it should respawn, you’re going to be 10 seconds late because the coroutine is going to wait another 10 seconds before it checks. I also mentioned that you will not have a respawn in exactly hideTime with the 30 second delay but you will be within 30 seconds. Now, using hideTime instead of 30 seconds you will not respawn within hideTime seconds, but be within hidetime seconds.

It’s fine if you want to configure the delay, in fact I encourage it. If the respawn time is less than 30 seconds, you need to delay for less than 30 seconds - probably 1. I used 30 seconds with respawn times of hours in mind. You have to configure it to be within an acceptable margin. If the hideTime is 10 seconds, an acceptable margin may be 1 second. If the hideTime is 8 hours, you can look at 30 seconds again. Or maybe even more.

OK now I’m actually really confused… I’ll try go through this again as soon as I’m home, because I really am confused about what we’re even doing right now

I’m really sorry for the trouble man, I’m just very confused right now

But if we’re doing a 30 second margin of respawn time delays of hours, wouldn’t it be a good idea to also have a 1 second delay for short respawn times, and deal with classifying them apart using boolean?

We’re just waiting to check when to respawn the resource.

You made it wait the full respawn time. If that was 8 hours, and you started the game with 10 seconds to go, the coroutine would wait another 8 hours before it checks, only to find that it should have respawned the tree 7 hours, 59 minutes and 50 seconds ago. That’s why we had 30 seconds. Then it would have waited 30 seconds, and the tree would’ve respawned after 8 hours and 20 seconds (because it still had 10 seconds to go). That’s the acceptable margin I am talking about. No one’s going to notice that the tree spawned 20 seconds late if it takes 8 hours for a tree to respawn. But if it should respawn within 10 seconds, then 30 seconds is a huge margin and you’d probably want something like 1 second delay.

Sure, but why not just specify it? Add a field for checkInterval and use that

[SerializeField] float checkInterval = 30f;

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

If it’s a short respawn, make the checkInterval shorter

SOO… We’re basically checking every ‘checkInterval’ seconds whether an item is spawned or not, in ‘CheckForRespawn()’? OK that kinda makes sense, so for short interval times, basically stuff that’s at low levels, we can make that margin small, but for higher end levels, that margin needs to increase? Got it

Still though, with the current saving system, you do realize that if we quit the game and return, it instantly respawns, right? (Which is what I’ve been trying to fight for a bit) :stuck_out_tongue:

at this point in time, I honestly just thought that doing so would be a headache, considering that we literally just re-wrote the entire ‘ResourceRespawner.cs’ script, and I’m still trying to get used to it

P.S: I’m still an hour off home, will probs be back in an hour or 2, no rush :slight_smile:

No, we’re checking if it should be spawned. If the coroutine is running it’s because it’s not spawned.

As it should. Just like the pickup, the resource is spawned immediately. Then, when the RestoreFromJToken runs, it will find that resource was destroyed and should no longer be there, so it destroys it again and starts the coroutine.

Privacy & Terms