Forum begins after the advertisement:


[General] Adding a Shop Passive Upgrades!

Home Forums Video Game Tutorial Series Creating a Rogue-like Shoot-em Up in Unity [General] Adding a Shop Passive Upgrades!

Viewing 11 posts - 1 through 11 (of 11 total)
  • Author
    Posts
  • #15565
    Cam
    Level 23
    Silver Supporter (Patron)
    Helpful?
    Up
    2
    ::

    Hey Everybody I said i would show my Shop scripts in the stream the other day. its not perfect but its working so here you go

    The first part of this was to build a Scriptable object i can make for each upgrade, this was an easy part. it was to show the upgrade name an icon and then to take the players stats for what we can upgrade then i added levels to the upgrade so when you buy it it can have differant costs and stats for each level

    UpgradeData

    using UnityEngine;
    
    [CreateAssetMenu(fileName = "UpgradeData", menuName = "Shop/UpgradeData")]
    public class UpgradeData : ScriptableObject
    {
        public string upgradeName;
        public Sprite icon;
    
        [System.Serializable]
        public class UpgradeLevel
        {
            public string description;
            public int cost;
            public PlayerStats.Stats statBoost;
        }
    
        public UpgradeLevel[] levels;
    }

    i made a new folder called Skill Shop to hold the script.

    this will let you make shop items with levels for each upgrade

    ShopManager next i made a shop manager script for handling showing and buying the upgrades

    using UnityEngine;
    using UnityEngine.UI;
    using TMPro;
    using System.Collections;
    using System.Collections.Generic;
    
    public class ShopManager : MonoBehaviour
    {
        public UpgradeData[] upgrades;
        public OneOffItemData[] oneOffItems; // Array of one-off items
        public Transform shopContent;
        public GameObject upgradeButtonPrefab; // Use the same prefab for both upgrades and one-off items
        public PlayerStats playerStats;
        public TMP_Text goldText;
        public PassiveUpgrades passiveUpgrades;
        public GameObject shopUI;
        public Button removeAllButton; // Reference to the remove all button
    
        [Header("Cannot Buy")]
        public Color cannotBuyFlashColor = Color.red; // Color to flash when unable to purchase
        public float cannotBuyShakeDuration = 0.5f; // Duration of the shake effect
        public float cannotBuyShakeMagnitude = 10f; // Magnitude of the shake effect
    
        [Header("Can Buy")]
        public Color canBuyFlashColor = Color.green; // Color to flash when purchase is successful
        public float canBuyFlashDuration = 0.5f; // Duration of the flash effect
    
        private int playerGold = 100; // Example starting gold amount
        private bool isShopOpen = false; // Track if the shop is open
    
        private void Start()
        {
            InitializeShop();
            UpdateGoldUI();
            ToggleShop(false); // Ensure the shop starts closed
            removeAllButton.onClick.AddListener(RemoveAllBoughtItems); // Add listener to the remove all button
        }
    
        private void Update()
        {
            if (Input.GetKeyDown(KeyCode.O))
            {
                ToggleShop(!isShopOpen);
            }
        }
    
        private void InitializeShop()
        {
            // Clear existing buttons first
            foreach (Transform child in shopContent)
            {
                Destroy(child.gameObject);
            }
    
            // Create buttons for upgrades
            foreach (UpgradeData upgrade in upgrades)
            {
                CreateUpgradeButton(upgrade);
            }
    
            // Create buttons for one-off items
            foreach (OneOffItemData item in oneOffItems)
            {
                CreateOneOffItemButton(item);
            }
        }
    
        private void CreateUpgradeButton(UpgradeData upgrade)
        {
            GameObject button = Instantiate(upgradeButtonPrefab, shopContent);
    
            button.transform.Find("Icon").GetComponent<Image>().sprite = upgrade.icon;
            button.transform.Find("Name").GetComponent<TMP_Text>().text = upgrade.upgradeName;
    
            int currentLevel = GetUpgradeLevel(upgrade.upgradeName);
            if (currentLevel < upgrade.levels.Length)
            {
                button.transform.Find("Cost").GetComponent<TMP_Text>().text = upgrade.levels[currentLevel].cost.ToString();
                button.transform.Find("Level").GetComponent<TMP_Text>().text = $"Level {currentLevel} -> {currentLevel + 1}";
                button.transform.Find("Description").GetComponent<TMP_Text>().text = upgrade.levels[currentLevel].description;
                Button purchaseButton = button.transform.Find("PurchaseButton").GetComponent<Button>();
                purchaseButton.onClick.AddListener(() => PurchaseUpgrade(upgrade, button));
            }
            else
            {
                button.transform.Find("Cost").GetComponent<TMP_Text>().text = "";
                button.transform.Find("Level").GetComponent<TMP_Text>().text = "Maxed Level";
                button.transform.Find("Description").GetComponent<TMP_Text>().text = "";
                button.transform.Find("PurchaseButton").GetComponent<Button>().interactable = false;
                Debug.Log($"{upgrade.upgradeName} is maxed out");
            }
        }
    
        private void CreateOneOffItemButton(OneOffItemData item)
        {
            GameObject button = Instantiate(upgradeButtonPrefab, shopContent);
    
            button.transform.Find("Icon").GetComponent<Image>().sprite = item.icon;
            button.transform.Find("Name").GetComponent<TMP_Text>().text = item.itemName;
            button.transform.Find("Cost").GetComponent<TMP_Text>().text = item.cost.ToString();
            button.transform.Find("Description").GetComponent<TMP_Text>().text = item.description;
            button.transform.Find("Level").GetComponent<TMP_Text>().text = ""; // No level for one-off items
            Button purchaseButton = button.transform.Find("PurchaseButton").GetComponent<Button>();
            purchaseButton.onClick.AddListener(() => PurchaseOneOffItem(item, button));
    
            if (passiveUpgrades.HasOneOffItem(item.itemName))
            {
                button.transform.Find("Icon").GetComponent<Image>().color = Color.grey;
                purchaseButton.interactable = false;
            }
        }
    
        private int GetUpgradeLevel(string upgradeName)
        {
            foreach (var upgrade in passiveUpgrades.purchasedUpgrades)
            {
                if (upgrade.upgradeName == upgradeName)
                {
                    return upgrade.level + 1; // Upgrade levels start from 0, so return the next level
                }
            }
            return 0; // If not found, assume it’s at level 0
        }
    
        private void PurchaseUpgrade(UpgradeData upgrade, GameObject button)
        {
            int currentLevel = GetUpgradeLevel(upgrade.upgradeName);
            Debug.Log($"Attempting to purchase {upgrade.upgradeName} at level {currentLevel}");
    
            if (currentLevel < upgrade.levels.Length && playerGold >= upgrade.levels[currentLevel].cost)
            {
                playerGold -= upgrade.levels[currentLevel].cost;
                Debug.Log($"Purchased {upgrade.upgradeName} level {currentLevel} for {upgrade.levels[currentLevel].cost} gold.");
                ApplyUpgrade(upgrade, currentLevel);
                StartCoroutine(FlashButton(button, canBuyFlashColor, canBuyFlashDuration)); // Flash green on successful purchase
                UpdateGoldUI();
                InitializeShop(); // Reinitialize the shop to refresh the buttons
            }
            else
            {
                StartCoroutine(FlashRedAndShake(button));
                Debug.Log("Not enough gold or upgrade maxed out!");
            }
        }
    
        private void PurchaseOneOffItem(OneOffItemData item, GameObject button)
        {
            Debug.Log($"Attempting to purchase one-off item {item.itemName}");
    
            if (playerGold >= item.cost)
            {
                playerGold -= item.cost;
                Debug.Log($"Purchased one-off item {item.itemName} for {item.cost} gold.");
                StartCoroutine(FlashButton(button, canBuyFlashColor, canBuyFlashDuration)); // Flash green on successful purchase
                passiveUpgrades.AddOneOffItem(item);
                UseOneOffItem(item);
                UpdateGoldUI();
                InitializeShop(); // Reinitialize the shop to refresh the buttons
            }
            else
            {
                StartCoroutine(FlashRedAndShake(button));
                Debug.Log("Not enough gold!");
            }
        }
    
        private void ApplyUpgrade(UpgradeData upgrade, int level)
        {
            Debug.Log($"Applying upgrade: {upgrade.upgradeName} at level {level}");
            passiveUpgrades.AddUpgrade(upgrade, level);
            passiveUpgrades.ApplyUpgrades(playerStats);
        }
    
        private void UseOneOffItem(OneOffItemData item)
        {
            Debug.Log($"Using one-off item {item.itemName}");
            // Implement the effect of the one-off item here
            // For example, if it's a Double XP item, you can double the XP gain for a certain period
    
            // Simulate the effect and removal of the one-off item
            StartCoroutine(RemoveOneOffItemAfterUse(item));
        }
    
        private IEnumerator RemoveOneOffItemAfterUse(OneOffItemData item)
        {
            yield return new WaitForSeconds(5f); // Simulate some delay before removing the item
            passiveUpgrades.RemoveOneOffItem(item);
            Debug.Log($"One-off item {item.itemName} used and removed from inventory.");
        }
    
        private void UpdateGoldUI()
        {
            goldText.text = $"Gold: {playerGold}";
            Debug.Log($"Updated gold: {playerGold}");
        }
    
        private void ToggleShop(bool isOpen)
        {
            isShopOpen = isOpen;
            shopUI.SetActive(isOpen);
            Debug.Log($"Shop is now {(isOpen ? "open" : "closed")}");
        }
    
        private void RemoveAllBoughtItems()
        {
            Debug.Log("Removing all bought items");
            // Clear the shop buttons first
            foreach (Transform child in shopContent)
            {
                Destroy(child.gameObject);
            }
            passiveUpgrades.purchasedUpgrades.Clear();
            passiveUpgrades.purchasedOneOffItems.Clear();
            PlayerPrefs.DeleteAll(); // This will reset all upgrades to level 0
            InitializeShop(); // Reinitialize the shop to reflect the changes
            passiveUpgrades.ApplyUpgrades(playerStats); // Reapply upgrades to ensure no stats are carried over
            Debug.Log("All bought items removed");
        }
    
        private IEnumerator FlashRedAndShake(GameObject button)
        {
            Image[] images = button.GetComponentsInChildren<Image>();
            Color[] originalColors = new Color[images.Length];
            for (int i = 0; i < images.Length; i++)
            {
                originalColors[i] = images[i].color;
                images[i].color = cannotBuyFlashColor;
            }
    
            Vector3 originalPosition = button.transform.localPosition;
    
            float elapsedTime = 0f;
    
            while (elapsedTime < cannotBuyShakeDuration)
            {
                float xOffset = Random.Range(-cannotBuyShakeMagnitude, cannotBuyShakeMagnitude);
                float yOffset = Random.Range(-cannotBuyShakeMagnitude, cannotBuyShakeMagnitude);
    
                button.transform.localPosition = new Vector3(originalPosition.x + xOffset, originalPosition.y + yOffset, originalPosition.z);
    
                elapsedTime += Time.deltaTime;
    
                yield return null;
            }
    
            for (int i = 0; i < images.Length; i++)
            {
                images[i].color = originalColors[i];
            }
    
            button.transform.localPosition = originalPosition;
        }
    
        private IEnumerator FlashButton(GameObject button, Color flashColor, float flashDuration)
        {
            Image[] images = button.GetComponentsInChildren<Image>();
            Color[] originalColors = new Color[images.Length];
            for (int i = 0; i < images.Length; i++)
            {
                originalColors[i] = images[i].color;
                images[i].color = flashColor;
            }
    
            yield return new WaitForSeconds(flashDuration);
    
            for (int i = 0; i < images.Length; i++)
            {
                images[i].color = originalColors[i];
            }
        }
    }

    this script i attached it to a empty game object and called in shop manager

    this script holds handling the ui for the shop and pushing “o” opens the shop ui.

    you put each upgrade scriptable Object in the upgrades tab build the shop ui in the canvas i can go in to mmore details in what ive done if you need me to but for now ill just show you layout in an image

    ive also been working on another type of item that only lasts one death but for now it just gets removed after a few seconds but its just a test for now

    OneOffItemData

    using UnityEngine;
    
    [CreateAssetMenu(fileName = "OneOffItemData", menuName = "Shop/OneOffItemData")]
    public class OneOffItemData : ScriptableObject
    {
        public string itemName;
        public PlayerStats.Stats statBoost;
        public int cost;
        public string description;
        public Sprite icon;
    }

    next was how and where to store the passive upgrades that have been brought, this is where it can be a bit more painfull youll also need to create a save and load manager there are heaps of referances you can find online to get this stated its need to save when you buy the updrades and then loads when the game starts, but ill heres the PassiveUpgrades script that store the skills you have brought, this will be attached to the player. you should try to add things to this in the editor the game will manage the pasives skills by its self.

    PassiveUpgrades

    using System.Collections.Generic;
    using UnityEngine;
    
    public class PassiveUpgrades : MonoBehaviour
    {
        private SaveLoadManager saveLoadManager;
    
        [System.Serializable]
        public class Upgrade
        {
            public string upgradeName;
            public int level;
            public PlayerStats.Stats statBoost;
        }
    
        [System.Serializable]
        public class OneOffItem
        {
            public string itemName;
            public PlayerStats.Stats statBoost;
        }
    
        public List<Upgrade> purchasedUpgrades = new List<Upgrade>();
        public List<OneOffItem> purchasedOneOffItems = new List<OneOffItem>();
    
        private void Start()
        {
            saveLoadManager = FindObjectOfType<SaveLoadManager>();
            LoadUpgrades();
        }
    
        public void AddUpgrade(UpgradeData upgradeData, int level)
        {
            Upgrade existingUpgrade = purchasedUpgrades.Find(upgrade => upgrade.upgradeName == upgradeData.upgradeName);
            if (existingUpgrade != null)
            {
                existingUpgrade.level = level;
                existingUpgrade.statBoost = upgradeData.levels[level].statBoost;
                Debug.Log($"Upgraded {upgradeData.upgradeName} to level {level}");
            }
            else
            {
                Upgrade newUpgrade = new Upgrade
                {
                    upgradeName = upgradeData.upgradeName,
                    level = level,
                    statBoost = upgradeData.levels[level].statBoost
                };
                purchasedUpgrades.Add(newUpgrade);
                Debug.Log($"Added new upgrade: {upgradeData.upgradeName} at level {level}");
            }
            SaveUpgrades();
        }
    
        public void AddOneOffItem(OneOffItemData itemData)
        {
            OneOffItem newItem = new OneOffItem
            {
                itemName = itemData.itemName,
                statBoost = itemData.statBoost
            };
            purchasedOneOffItems.Add(newItem);
            Debug.Log($"Added new one-off item: {itemData.itemName}");
            SaveUpgrades();
        }
    
        public bool HasOneOffItem(string itemName)
        {
            return purchasedOneOffItems.Exists(item => item.itemName == itemName);
        }
    
        public void ApplyUpgrades(PlayerStats playerStats)
        {
            foreach (Upgrade upgrade in purchasedUpgrades)
            {
                playerStats.ActualStats += upgrade.statBoost;
            }
            foreach (OneOffItem item in purchasedOneOffItems)
            {
                playerStats.ActualStats += item.statBoost;
            }
            playerStats.RecalculateStats();
        }
    
        public void RemoveOneOffItem(OneOffItemData itemData)
        {
            purchasedOneOffItems.RemoveAll(item => item.itemName == itemData.itemName);
            Debug.Log($"Removed one-off item: {itemData.itemName}");
            SaveUpgrades();
        }
    
        private void SaveUpgrades()
        {
            if (saveLoadManager != null)
            {
                saveLoadManager.SaveUpgrades(purchasedUpgrades, purchasedOneOffItems);
            }
        }
    
        private void LoadUpgrades()
        {
            if (saveLoadManager != null)
            {
                (purchasedUpgrades, purchasedOneOffItems) = saveLoadManager.LoadUpgrades();
            }
        }
    }

    here while my game is running you can see i have level 5 heath upgrade and level 2 might

    and now to apply the stats to the player ive eddited the Playerstat.cs

    public void RecalculateStats()
    {
        // Start with base stats
        actualStats = baseStats;
    
        // Apply equipped passive item boosts
        foreach (PlayerInventory.Slot s in inventory.passiveSlots)
        {
            Passive p = s.item as Passive;
            if (p != null)
            {
                actualStats += p.GetBoosts();
            }
        }
    
        // Apply upgrades from PassiveUpgrades
        PassiveUpgrades passiveUpgrades = GetComponent<PassiveUpgrades>();
        if (passiveUpgrades != null)
        {
            foreach (var upgrade in passiveUpgrades.purchasedUpgrades)
            {
                actualStats += upgrade.statBoost;
            }
    
            foreach (var oneOffItem in passiveUpgrades.purchasedOneOffItems)
            {
                actualStats += oneOffItem.statBoost;
            }
        }
    
        // Apply gem effects
        ApplyEquippedRuneEffects();
    
        // Update collector radius
        collector.SetRadius(actualStats.magnet);
    
        // Update health if it exceeds maxHealth
        if (CurrentHealth > actualStats.maxHealth)
        {
            CurrentHealth = actualStats.maxHealth;
        }   
    }

    now you can see when i play the game my stats have been boosted

    ive forgot to say above but in the ShopManager.cs there is a line “private int playerGold = 100; // Example starting gold amount” this sets the gold you are given at the start of every game you can change this to what evey you want ive have writen mine so each enemy has a gold drop and then the players luck also add a higher drop rate for gold and then the shop looks for the plays gold and that what it spends

    this was just the 1st basic setup for my shop hope you guys like it

    and 1 other person have upvoted this post.
    #15568
    Terence
    Level 30
    Keymaster
    Helpful?
    Up
    0
    ::

    Thank you Cam. You’re awesome.

    I’ve stickied your topic and gave you a new badge on your profile. We are rolling out a new XP system on the blog, and these badges will contribute to your XP points in future. You will be able to use these XP points to get perks, as well as unlock Patron-locked content permanently for your account.

    The XP system is meant to do 2 things:

    1. Encourage participation, so that we cultivate a more vibrant community here.
    2. Make the content more accessible for people from countries with smaller currencies. US$10 is a lot of money for certain countries in Asia, and a lot of our viewers there cannot afford the content.
    Content Contributor
    #15940
    정춤고 (ChoomGo)
    Level 13
    Former Patron
    Helpful?
    Up
    0
    ::

    That’s amazing. If it doesn’t bother you, can I add it to my project?

    #15945
    Cam
    Level 23
    Silver Supporter (Patron)
    Helpful?
    Up
    1
    ::

    Yes of course! I’m happy to help if you need also.

      1 anonymous person
    has upvoted this post.
    #15947
    정춤고 (ChoomGo)
    Level 13
    Former Patron
    Helpful?
    Up
    0
    ::

    Thank you, Mr.Cam

    #16226
    정춤고 (ChoomGo)
    Level 13
    Former Patron
    Helpful?
    Up
    0
    ::
    using UnityEngine;
    
    [CreateAssetMenu(fileName = "UpgradeData", menuName = "Shop/UpgradeData")]
    public class UpgradeData : ScriptableObject
    {
        public string upgradeName;
        public Sprite icon;
    
        [System.Serializable]
        public class UpgradeLevel
        {
            public string description;
            public int cost;
            //public PlayerStats.Stats statBoost;
            public CharacterData.Stats statboosts;
        }
    
        public UpgradeLevel[] levels; 
    }
    public CharacterData.Stats statboosts;

    Is it okay to replace it with this?

    #16235
    Terence
    Level 30
    Keymaster
    Helpful?
    Up
    0
    ::
    public CharacterData.Stats statboosts; Is it okay to replace it with this?
    If the change doesn’t create any errors in your code, this is totally fine.
    #16289
    Supremacy Music
    Level 3
    Participant
    Helpful?
    Up
    1
    ::

    Amazing!

    has upvoted this post.
    #16598
    SingleBigNameLOL
    Level 4
    Participant
    Helpful?
    Up
    0
    ::

    Amazing upgrade system, Cam. I have a few questions if you could help me. This shop opens in the main scene (Game), right? I was trying to add something similar to the character selection scene, but it always goes wrong because it can’t find the PlayerStats. Even when I use DontDestroyOnLoad, it doesn’t work. Do you know a way to make this work? Another question is if you could share your SaveAndLoad system? Thank you so much for all your hard work.

    #16633
    Cam
    Level 23
    Silver Supporter (Patron)
    Helpful?
    Up
    1
    ::

    Hey mate. I have my shop upgrade it only accessible in the character section, I have customised my a lot different from the tutorial, but I will have a look on my next day off, I’ve also added more to my shop system that I should update to this

    Yeah I’m happy to share my save and load system I don’t know if it’s done in the right ways but it seems to work for now

    has upvoted this post.
    #16646
    Terence
    Level 30
    Keymaster
Viewing 11 posts - 1 through 11 (of 11 total)
  • You must be logged in to reply to this topic.

Go to Login Page →


Advertisement below: