OK so I got the Ingredients amount UI to change according to the value that it sees, and honestly speaking, it was a bit of an ingenious attempt as well, but I still haven’t gotten the entire UI to refresh based on that.
Anyway, here’s what I did:
-
Delete everything I wrote here and here
-
Start from fresh. Create a new script that only contains an event (so I don’t end up in a circular dependency). Let’s call it ‘FarmingQuantityEvent.cs’, for simplicity’s sake, that only contains an event, and a way to trigger it from outside the script:
using System;
public static class FarmingQuantityEvent
{
public static event Action<int> OnQuantityChanged;
public static void RaiseOnQuantityChanged(int quantity)
{
// Quantity invoked in a function because other scripts can't invoke an event in a different class otherwise
OnQuantityChanged?.Invoke(quantity);
}
}
- in ‘CraftingQuantity.cs’, you invoke that event:
public void OnQuantityChanged()
{
// Connected to the Buttons in the hierarchy
FarmingQuantityEvent.RaiseOnQuantityChanged(currentQuantity);
}
the ‘OnQuantityChanged’ is called in the inspector, on the plus and minus buttons’ OnClick functions
- in ‘CraftingSystem.cs’, you do the subscription and unsubscription to that event, as follows:
void OnEnable()
{
FarmingQuantityEvent.OnQuantityChanged += RefreshUIWithQuantity;
}
void OnDisable()
{
FarmingQuantityEvent.OnQuantityChanged -= RefreshUIWithQuantity;
}
along with the function that actually contains what happens (and here’s where the real magic happens):
private void RefreshUIWithQuantity(int quantity)
{
PopulateIngredientsList(recipe.GetIngredients(), quantity);
}
I introduced a second parameter in ‘PopulateIngredientsList’ that acts as a multiplier to make this work (I’ll get to refreshing everything for this little change soon)
- in ‘PopulateIngredientsList’, I made a few small changes, of just making 'ingredientUI accept a value:
private void PopulateIngredientsList(CraftingItem[] ingredients, int quantity = 1 /* <-- TEST */)
{
// Remove all children from the ingredient list container
CleanupIngredientsList();
// Go through each ingredient, create it's representation and add it to the list
foreach (var ingredient in ingredients)
{
var ingredientUI = Instantiate(ingredientPrefab, ingredientsListContainer);
ingredientUI.Setup(ingredient, quantity /* <-- Quantity is a test */);
}
}
- in ‘IngredientsUI.Setup()’, I reflected on those changes:
// Set up the ingredient
public void Setup(CraftingItem ingredient, int quantity /* <-- TEST */)
{
// Keep a reference to this ingredient
this.ingredient = ingredient;
// Set the ingredient icon
ingredientIcon.sprite = ingredient.Item.GetIcon();
// Set the ingredient name text
ingredientName.text = ingredient.Item.GetDisplayName();
// Set the ingredient amount text
ingredientAmount.text = (ingredient.Amount * quantity /* <-- quantity IS A TEST */).ToString();
// Stop any coroutines
StopAllCoroutines();
// If the player has the ingredient, we are done
if (PlayerHasIngredient(ingredient))
{
return;
}
// If the player does not have the ingredient, nor does he have enough money
// (handled in 'CraftingSystem.PopulateIngredientsList()'),
// start a coroutine to flash the ingredient amount
StartCoroutine(FlashAmountRoutine());
}
The next step will be to get the entire Crafting UI System to reflect on that, which means making stuff blink if you don’t have enough resources, disabling the crafting/farming button, etc.
I personally have no intentions of making my minimum quantity go below 1, so I won’t deal with the zero cases
- Change Step 4 to this (I figured this is much easier a little late):
private void RefreshUIWithQuantity(int quantity)
{
RefreshDetails(quantity);
}
Now, it’ll refresh everything, but this also means ‘RefreshDetails()’ will take a parameter reference, and some functions in there will do the same too. Long story short, here’s the new ‘RefreshDetails()’ function:
private void RefreshDetails(int quantity = 1 /* <-- TEST by Bahaa */)
{
// Remove all children from the ingredient list container
CleanupIngredientsList();
// Hide the image
recipeIcon.gameObject.SetActive(false);
// Hide the name
recipeName.gameObject.SetActive(false);
// Hide the description
recipeDescription.gameObject.SetActive(false);
// Initially, disable crafting
InitialiseCrafting();
// If we have no recipe, we're done
if (recipe == null)
{
return;
}
// Get the resulting item for this recipe
var resultingItem = recipe.GetResult();
// Set the resulting item icon
recipeIcon.sprite = resultingItem.Item.GetIcon();
// Set the resulting item name (and amount)
recipeName.text = resultingItem.GetRecipeName();
// Set the resulting item description
recipeDescription.text = resultingItem.Item.GetDescription();
// Populate the ingredients list
PopulateIngredientsList(recipe.GetIngredients(), quantity /* <-- TEST by BAHAA */);
// Show the recipe icon
recipeIcon.gameObject.SetActive(true);
// Show the recipe name (and amount)
recipeName.gameObject.SetActive(true);
// Show the recipe description
recipeDescription.gameObject.SetActive(true);
// Set the level requirement text
// recipeLevelRequirement.text = $"Requires Crafting Level {recipe.GetRequiredLevel()}";
recipeLevelRequirement.text = recipeLevelRequirementText(recipe);
// If the required level is greater than the player's level, show the required level text
recipeLevelRequirement.gameObject.SetActive(!PlayerMeetsLevelCriteria(recipe));
// Show the craft button
craftButton.gameObject.SetActive(true);
// Make the craft button interactable _if_ the player can craft this recipe
craftButton.interactable = CanCraftRecipe(recipe, quantity);
}
and then ‘CanCraftRecipe()’ will change too:
private bool CanCraftRecipe(Recipe recipe, int quantity = 1 /* <-- TEST by Bahaa */)
{
// Ask the crafting table if we can craft this recipe
return currentCraftingTable.CanCraftRecipe(recipe, quantity);
}
and the same goes for ‘CanCraftRecipe()’ in ‘CraftingTable.cs’ (except that this one is from the interface ICraftingTable, which doesn’t support optional parameters, so it will ALWAYS expect a ‘quantity’ parameter:
// Checks if the player has all the
// required ingredients of a recipe in their inventory
bool ICraftingTable.CanCraftRecipe(Recipe recipe, int quantity/* <-- (TEST) FOR FARMING, OR ANYTHING THAT HAS A QUANTITY MEASURE */)
{
// If we receive no recipe, return false
if (recipe == null)
{
return false;
}
// Get the player inventory
var playerInventory = Inventory.GetPlayerInventory();
// If we found no inventory, return false
if (playerInventory == null)
{
return false;
}
// Assuming the Players' Inventory and 'SkillStore' are on the same 'Player' gameObject (which they are, by the way), then get his 'SkillStore'
var skillStore = playerInventory.GetComponent<SkillStore>();
// If he doesn't have a skillStore (basically for NPCs... idk why this is even here), return false (i.e: Can't craft)
if (skillStore == null) return false;
// If the players' Skill level is below the required level, return false:
// if (skillStore.GetSkillLevel(Skill.Crafting) < recipe.GetRequiredLevel()) return false;
// 1. For Crafting:
if (recipe.GetRequiredSkill() == Skill.Crafting && skillStore.GetSkillLevel(Skill.Crafting) < recipe.GetRequiredLevel())
{
Debug.Log("Cannot Craft this Recipe");
return false;
}
// 2. For Smithing:
else if (recipe.GetRequiredSkill() == Skill.Smithing && skillStore.GetSkillLevel(Skill.Smithing) < recipe.GetRequiredLevel())
{
Debug.Log("Cannot Smith this Recipe");
return false;
}
// 3. For Cooking:
else if (recipe.GetRequiredSkill() == Skill.Cooking && skillStore.GetSkillLevel(Skill.Cooking) < recipe.GetRequiredLevel())
{
Debug.Log("Cannot Cook this Recipe");
return false;
}
// 4. For Farming:
else if (recipe.GetRequiredSkill() == Skill.Farming && skillStore.GetSkillLevel(Skill.Farming) < recipe.GetRequiredLevel())
{
Debug.Log($"Cannot Farm this Recipe");
return false;
}
// Check if the player has enough money to pay for the recipe (if he's broke, he can't craft the recipe)
var purse = playerInventory.GetComponent<Purse>();
// If you don't have enough money, you can't craft the item
if (purse.GetBalance() < recipe.GetRecipeCost()) return false;
// Go through each ingredient and check the player's inventory for that ingredient
foreach (var craftingItem in recipe.GetIngredients())
{
var hasIngredient = playerInventory.HasItem(craftingItem.Item, out int amountInInventory);
// If the player does not have the ingredient, return false
if (!hasIngredient)
{
return false;
}
// If the player does not have enough of an ingredient, return false
if (amountInInventory < craftingItem.Amount * quantity /* <-- TEST by Bahaa */)
{
return false;
}
}
// If we got to here, the player has all the ingredients required.
// Check if we have matching output
if (CraftedOutput == null || CraftedOutput.Item == null)
{
// We have no output, return true
return true;
}
// Compare the output to the recipe's output
var recipeOutput = recipe.GetResult();
if (!object.ReferenceEquals(CraftedOutput.Item, recipeOutput.Item))
{
return false;
}
// Check if we can stack the items
if (!CraftedOutput.Item.IsStackable())
{
return false;
}
return true;
}
And… my laptop just became insanely slow (it happens from time to time) and it needs a break, so I can’t perform any tests because of that just yet (I know it works though. I tested it on it’s insanely slow condition and all was fine. I just don’t know what the consequences on the rest of the crafting system are just yet)
Once this machine is recovered, I want to check for the cost as well and do other operations before this crazy concept is over (and the consumed quantity, becuase right now it’s a complete mess)
and… somewhere down the line, I broke the crafting functionality by accident… (pressing the button right now does nothing)
Edit: and as it turns out, the ‘Craft’ button no longer works because ‘CraftRecipe’, the function in ‘CraftingSystem.cs’ attached to the button, went missing after I added a parameter to the function. Now I need a way around this (and my laptop is still insanely slow)
Here’s the new problem with the hierarchy assignment of the Crafting Button, after giving ‘CraftRecipe.cs’ an integer parameter:
So I’ll have to assign it through in-code listeners I suppose
Edit number idk: I ended up taking a different approach. Create a new script (I called mine ‘CurrentQuantityHolder.cs’:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CurrentQuantityHolder : MonoBehaviour
{
private int currentQuantity;
public int GetCurrentQuantity()
{
return currentQuantity;
}
public void SetCurrentQuantity(int currentQuantity)
{
this.currentQuantity = currentQuantity;
}
}
assign the value from ‘CraftingQuantity.cs’, which both the new and ‘CraftingSystem.cs’ rely on, so no circular dependencies (CraftingSystem → CurrentQuantityHolder → CraftingQuantity), and get the value in ‘CraftingSystem.CraftRecipe()’ itself. Problem solved (but still prototyping)
And of course, don’t forget to assign this function to your button’s ‘OnClick()’ function in the hierarchy, otherwise you won’t get any updates:
public void SetCurrentQuantityHolder()
{
currentQuantityHolder.SetCurrentQuantity(currentQuantity);
}
(This is in ‘CraftingQuantity.cs’)
- Last, but not least, make sure to classify your ‘CraftingRecipe.cs’ between stuff that has ‘UseQuantityUI’ (I implemented this in the ‘ICraftingTable’ interface), and stuff that doesn’t, as follows:
// in 'CraftingSystem.cs'
// Bound to the craft button
public void CraftRecipe()
{
// Tell the crafting table to start the crafting process
if (currentCraftingTable.UseQuantityUI)
{
currentCraftingTable.CraftRecipe(recipe, currentQuantityHolder.GetCurrentQuantity()); // for farming, or stuff with quantity UI
}
else
{
currentCraftingTable.CraftRecipe(recipe, 1); // for non-farming, or stuff that doesn't have quantity UI
}
}
Now we can safely do the same multiplication for the pricing, and make sure to get the amount of XP based on how many plants have grown. The more, the higher the XP
Quite the ride with this system tbh… This is, by far, one of the most complicated systems I have personally ever dealt with on my own (this part at least, of getting the values to act right), and I am extremely proud of my own growth
Now I have a brand new challenge… how do I multiply the gained skill, which is in an individual ‘CraftingNotification.cs’ script (on the Crafting Table itself), with the ‘quantity’ value, which can be gained from ‘CurrentQuantityHolder.cs’, which is exactly where ‘CraftingSystem.cs’ is at…?!
In simple language, how can I get a hold of something on the same gameObject as the ‘CraftingSystem.cs’, from something on the same gameObject as the ‘CraftingTable.cs’ script?
Edit: I figured it out. Here’s what I did, in ‘CraftingNotification.cs’:
private CurrentQuantityHolder currentQuantityHolder;
void OnEnable()
{
currentQuantityHolder = FindObjectOfType<CurrentQuantityHolder>(); // Find the 'currentQuantityHolder' when you spawn the farming patch (hence 'OnEnable()'), so we can use its value to get the correct xp
}
private IEnumerator CraftingCompleteEstimation(Recipe recipe, float delay)
{
yield return new WaitForSecondsRealtime(delay);
// FARMING ONLY, as farming is the only skill that will have 'recipe.GetStages' not be equal to zero
// (Other Crafting-UI Based skills will be covered in 'CraftingSystem.cs')
if (recipe.GetStages.Length != 0)
{
GameObject.FindWithTag("Player").GetComponent<SkillExperience>().GainExperience(recipe.GetRequiredSkill(), recipe.GetXPReward() * currentQuantityHolder.GetCurrentQuantity());
}
MalbersAnimations.InventorySystem.NotificationManager.Instance.OpenNotification($"Crafting Complete:\n{recipe.name}");
}
and I also added this in ‘CraftingSystem.cs’, in ‘OnEnable()’, otherwise things can go for single quantity units quite wrong:
currentQuantityHolder.SetCurrentQuantity(1); // without this line, and 'CraftingQuantity' 's minimum quantity being 1, this thing will be a mess (because opening up value is a bit of a mess)
Tomorrow I’ll work on multiplying the price as well (if there is any… just a nice little addon), both the UI and the actual value consumption, and then find a way to lock the quantity UI if it’s one unit, and also lock the quantity UI if it’s in the crafting progress