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
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