Adding One weapon at a time

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ActiveInventory : Singleton<ActiveInventory>
{
    private int activeSlotIndexNum = 0;
    private PlayerActions playerActions;

    protected override void Awake()
    {
        base.Awake();
        playerActions =  new PlayerActions();
    }

    private void Start()
    {
        playerActions.Inventory.Keyboard.performed += ctx => 
                ToggleActiveSlot((int)ctx.ReadValue<float>());
    }

    private void OnEnable()
    {
        playerActions.Enable();
    }

    public void EquipStartingWeapon()
    {
        //Give the player the sword in the inventory
        ToggleActiveHighlight(0);
    }

    private void ToggleActiveSlot(int numPressed)
    {
        //Need to match up number pressed to the actual inventory index {0-4} [1-5]that is why we -->(- 1)
        ToggleActiveHighlight(numPressed -1);
    }

    private void ToggleActiveHighlight(int indexNum)
    {
        activeSlotIndexNum = indexNum;
        //First set all agme objects for the inventory slots off
        foreach (Transform inventorySlot in this.transform)
        {
            inventorySlot.GetChild(0).gameObject.SetActive(false);
        }
        //Now set the one that we want to be active
        this.transform.GetChild(indexNum).GetChild(0).gameObject.SetActive(true);
        ChangeActiveWeapon();
    }

    private void ChangeActiveWeapon()
    {
        if(PlayerHealth.Instance.IsDead){return;}
       
        if(ActiveWeapon.Instance.CurrentActiveWeapon != null)
        {
            Destroy(ActiveWeapon.Instance.CurrentActiveWeapon.gameObject);
        }

        Transform childTransform = transform.GetChild(activeSlotIndexNum);
        InventorySlot inventorySlot = childTransform.GetComponentInChildren<InventorySlot>();
        WeaponInfo weaponInfo = inventorySlot.GetWeaponInfo();
        // if nothing is in an inventory slot
        if (weaponInfo == null)
        {
            //return null
            ActiveWeapon.Instance.WeaponNull();
            return;
        }
        GameObject weaponToSpawn = weaponInfo.weaponPrefab;
        //Now Instantiate the new weapon
        GameObject newWeapon = Instantiate(weaponToSpawn, ActiveWeapon.Instance.transform.position, Quaternion.identity);
        // we are cleaning out first the rotation of a prior wepon so any weapon does not spin on a certain point.
        ActiveWeapon.Instance.transform.rotation = Quaternion.Euler(0,0,0);
        //now make the weapon a child of our active weapon.
        newWeapon.transform.parent = ActiveWeapon.Instance.transform;
        ActiveWeapon.Instance.NewWeapon(newWeapon.GetComponent<MonoBehaviour>());
    }

    public  InventorySlot GetEmptyInventorySlot() // NEW for active weapon.
    {
         foreach (Transform child in transform)
            {
                //Debug.Log("inventorySlot");
                if (child.TryGetComponent(out InventorySlot inventorySlot)) return inventorySlot;
                Debug.Log(inventorySlot);
            }
            return null;
        }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;// NEW for active weapon.

public class InventorySlot : MonoBehaviour
{
  [SerializeField] private WeaponInfo weaponInfo;
  public WeaponInfo GetWeaponInfo()
  {
    return weaponInfo;
  }

  public void AssingWeaponInfor(WeaponInfo weaponInfo) // NEW for active weapon.
  {
    this.weaponInfo = weaponInfo;
    transform.GetChild(1).GetComponent<Image>().sprite = weaponInfo.sprite;
    Debug.Log(weaponInfo.weaponRange);
  }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;// NEW for active weapon.

[CreateAssetMenu(menuName = "New Weapon")]
public class WeaponInfo : ScriptableObject
{
    public GameObject weaponPrefab;
    public float weaponCooldown;
    public int weaponDamage;
    public float weaponRange;
    public Sprite sprite; // NEW for active weapon.
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PickUp : MonoBehaviour
{
        private enum PickUpType
        {
            GoldCoin,
            StaminaGlobe,
            HealthGlobe,
            Sword,
        }

        [SerializeField] private PickUpType  pickUpType;
        [SerializeField] AudioClip pickupClip;
        [SerializeField] private float pickUpDistance = 5f;
        [SerializeField] private float moveSpeed = 3f;
        [SerializeField] private float accelarationRate = .4f;
        [SerializeField] private AnimationCurve animCurve;
        [SerializeField] private float heightY = 1.5f;
        [SerializeField] private float popDuration = 1f;

        ActiveInventory activeInventory;  // NEW
        WeaponInfo weaponInfo;

        private Vector3 moveDir;
        private Rigidbody2D rb;

        private void Awake()
        {
            rb = GetComponent<Rigidbody2D>();
            activeInventory = GetComponent<ActiveInventory>();
        }

        private void Start()
        {
            StartCoroutine(AnimCurveSpawnRoutine());
        }

        private void Update()
        {
            Vector3 palyerPosition = PlayerController.Instance.transform.position;
            if(Vector3.Distance(transform.position, palyerPosition) < pickUpDistance)
            {
                moveDir = (palyerPosition - transform.position).normalized;
                moveSpeed += accelarationRate;
            }
            else
            {
                moveDir = Vector3.zero;
                moveSpeed = 0f;
            }
        }

        private void OnTriggerEnter2D(Collider2D other)
        {
            if(other.gameObject.GetComponent<PlayerController>())
            {
                DetectPickupType();
                Destroy(gameObject);
            }
        }

        private void FixedUpdate()
        {
            rb.velocity = moveDir * moveSpeed * Time.deltaTime;
        }

        private IEnumerator AnimCurveSpawnRoutine()
        {
            Vector2 startPoint = transform.position;
            float randomX = transform.position.x + Random.Range(-2f,2f);
            float randomY = transform.position.y + Random.Range(-1f,1f);
            Vector2 endPoint = new Vector2(randomX,randomY);
            float timePassed = 0f;
            while (timePassed < popDuration)
            {
                timePassed += Time.deltaTime;
                float linearT = timePassed / popDuration;
                float heightT = animCurve.Evaluate(linearT);
                float height = Mathf.Lerp(0f,heightY,heightT);
                transform.position = Vector2.Lerp(startPoint,endPoint,linearT) + new Vector2(0f,height);
                yield return null;
            }
        }

        private void DetectPickupType()
        {
            switch (pickUpType)
            {
               case PickUpType.GoldCoin:
               //Do GoldCoin Stuff
               AudioSource.PlayClipAtPoint(pickupClip,Camera.main.transform.position);
               EconomyManager.Instance.UpdateCurrentGold();
               //Debug.Log("Gold Coin");
               break;

               case PickUpType.HealthGlobe:
               //Do Health Stuff
               AudioSource.PlayClipAtPoint(pickupClip,Camera.main.transform.position);
               PlayerHealth.Instance.HealPlayer();
               //Debug.Log("Health Gloab");
               break;

               case PickUpType.StaminaGlobe:
               //Do Stamina Globe stuff
               AudioSource.PlayClipAtPoint(pickupClip,Camera.main.transform.position);
               Stamina.Instance.RefreshStamina();
               //Debug.Log("Stmina Golbe");
               break;

               case PickUpType.Sword: //NEW For inventory
                //ActiveInventory.Instance.GetEmptyInventorySlot();  //One or the Other?
                activeInventory.GetEmptyInventorySlot();
                if (activeInventory != null)
                {
                    InventorySlot.AssingWeaponInfor(weaponInfo.sprite);
                }
               break;

            default:
            break;
            }
        }
}

nothing new was put in the “Active weapon class” But here is the code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ActiveWeapon : Singleton<ActiveWeapon>
{
    public MonoBehaviour CurrentActiveWeapon {get; private set;}

    private PlayerActions playerActions;
    private bool attackButtonDown, isAttacking = false;
    private float timeBetweenAttacks;

    protected override void Awake()
    {
        //This is for the Singleton classes
        base.Awake();
        playerActions = new PlayerActions();
    }

        private void OnEnable()
    {
        playerActions.Enable();
    }

    // Start is called before the first frame update

    private void Start()
    {
        playerActions.Combat.Attack.started += _ => StartAttacking();
        playerActions.Combat.Attack.canceled += _ => StoptAttacking();
        //don't attack when starting game.
        AttackCoolDown();
    }

        private void StartAttacking()
    {
        attackButtonDown = true;
    }

    private void StoptAttacking()
    {
        attackButtonDown = false;
    }

    public void NewWeapon(MonoBehaviour newWeapon)
    {
        CurrentActiveWeapon = newWeapon;
        // This is just a safty thing to stop a Coroutine if one is in progress.
        AttackCoolDown();
        timeBetweenAttacks = (CurrentActiveWeapon as IWeapon).GetWeaponInfo().weaponCooldown;
    }

    public void WeaponNull()
    {
        CurrentActiveWeapon = null;
    }

    private void Update()
    {
          Attack();
    }

    private void Attack()
    {
        if(attackButtonDown && !isAttacking && CurrentActiveWeapon)
        {
        AttackCoolDown();
        (CurrentActiveWeapon as IWeapon).Attack();
        }
    }

    private void AttackCoolDown()
    {
        isAttacking = true;
        StopAllCoroutines();
        StartCoroutine(TimeBetweenAttacksRoutine());
    }

    private IEnumerator TimeBetweenAttacksRoutine()
    {
        yield return new WaitForSeconds(timeBetweenAttacks);
        isAttacking = false;
    }
}

The changes are in Pickup, and you were close.
The ActiveInventory isn’t on the same GameObject as the Pickup. We need to get the ActiveInventory on the Player:

    private void Awake() {
        rb = GetComponent<Rigidbody2D>();
        activeInventory = PlayerController.Instance.GetComponent<ActiveInventory>();
    }

Again, you’re on the right track, but we need to get the result of GetEmptyInventorySlot() and act on that:

case PickUpType.Sword:
    InventorySlot slot = activeInventory.GetEmptyInventorySlot();
    if(slot!=null)
    {
          slot.AssignWeaponInfo(weaponInfo);
    }
    break;

Please read the guide below for how to format code within your posts. It’ll make it much easier old fogies like me to read the code. :slight_smile:

Where should I have the PickUp Script on? I currently have it on the Objects themselves (Coin, Orb Heart and a Sword) because I get a Error It cant find the activeInventory.

               case PickUpType.Sword: //NEW For inventory
               Debug.Log(activeInventory);
               InventorySlot slot = activeInventory.GetEmptyInventorySlot();
                if (slot != null)
                {
                    Debug.Log("TEST2");
                    slot.AssingWeaponInfor(weaponInfo);
                    Debug.Log("TEST3");
                }
               break;
            default:

The pickup script goes on the item, just like the coin, orb, etc…

One more adjustment to the code in Awake(), I forgot that ActiveWeapon is on a child object of the player.

so the code above should go in the “ActiveWeapon” code?

No, it goes in the Pickup code. This allows the pickup to find the ActiveInventory.

in the Pickup class I have
private void Awake() { rb = GetComponent<Rigidbody2D>(); activeInventory = PlayerController.Instance.GetComponentInChildren<ActiveInventory>(); }
and I still get the error as before.

In My ActiveInventory script I have
`
public InventorySlot GetEmptyInventorySlot() // NEW for active weapon.

    {

        Debug.Log("TEST1");

        foreach (Transform child in transform)

        {

            //Debug.Log("inventorySlot");

            if (child.TryGetComponent(out InventorySlot inventorySlot)) return inventorySlot;

            Debug.Log(inventorySlot);

        }

        return null;

    }

`

The Log (“TEST1”) doesnt come up. so I think it never gets into this script for some reason.

Paste in the complete Pickup script, and your complete ActiveInventory script again.
Quick note on formatting, it’s three ``` on it’s own line, not one ` to format a code block.

Pick Up Script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PickUp : MonoBehaviour
{
        private enum PickUpType
        {
            GoldCoin,
            StaminaGlobe,
            HealthGlobe,
            Sword,

        }
        [SerializeField] private PickUpType  pickUpType;
        [SerializeField] AudioClip pickupClip;
        [SerializeField] private float pickUpDistance = 5f;
        [SerializeField] private float moveSpeed = 3f;
        [SerializeField] private float accelarationRate = .4f;
        [SerializeField] private AnimationCurve animCurve;
        [SerializeField] private float heightY = 1.5f;
        [SerializeField] private float popDuration = 1f;

        ActiveInventory activeInventory;  // NEW
        WeaponInfo weaponInfo;

        private Vector3 moveDir; 
        private Rigidbody2D rb;

        private void Awake() 
        {
            rb = GetComponent<Rigidbody2D>();
            activeInventory = PlayerController.Instance.GetComponentInChildren<ActiveInventory>();
        }

        private void Start() 
        {
            StartCoroutine(AnimCurveSpawnRoutine());
        }

        private void Update() 
        {
            Vector3 palyerPosition = PlayerController.Instance.transform.position;

            if(Vector3.Distance(transform.position, palyerPosition) < pickUpDistance)
            {
                moveDir = (palyerPosition - transform.position).normalized;
                moveSpeed += accelarationRate;
            }
            else
            {
                moveDir = Vector3.zero;
                moveSpeed = 0f;
            }
        }
        private void OnTriggerEnter2D(Collider2D other) 
        {
            if(other.gameObject.GetComponent<PlayerController>())
            {
                DetectPickupType();
                Destroy(gameObject);
            }
        }

        private void FixedUpdate() 
        {
            rb.velocity = moveDir * moveSpeed * Time.deltaTime;
        }


        private IEnumerator AnimCurveSpawnRoutine()
        {
            Vector2 startPoint = transform.position;
            float randomX = transform.position.x + Random.Range(-2f,2f);
            float randomY = transform.position.y + Random.Range(-1f,1f);
            Vector2 endPoint = new Vector2(randomX,randomY);
            float timePassed = 0f;

            while (timePassed < popDuration)
            {
                timePassed += Time.deltaTime;

                float linearT = timePassed / popDuration;
                float heightT = animCurve.Evaluate(linearT);
                float height = Mathf.Lerp(0f,heightY,heightT);
                transform.position = Vector2.Lerp(startPoint,endPoint,linearT) + new Vector2(0f,height);
                yield return null;
            }
        }


        private void DetectPickupType()
        {
            switch (pickUpType)
            {
                
               case PickUpType.GoldCoin:
               //Do GoldCoin Stuff
               AudioSource.PlayClipAtPoint(pickupClip,Camera.main.transform.position);
               EconomyManager.Instance.UpdateCurrentGold();
               //Debug.Log("Gold Coin");
               break;
               case PickUpType.HealthGlobe:
               //Do Health Stuff
               AudioSource.PlayClipAtPoint(pickupClip,Camera.main.transform.position);
               PlayerHealth.Instance.HealPlayer();
               //Debug.Log("Health Gloab");
               break;
               case PickUpType.StaminaGlobe:
               //Do Stamina Globe stuff
               AudioSource.PlayClipAtPoint(pickupClip,Camera.main.transform.position);
               Stamina.Instance.RefreshStamina();
               //Debug.Log("Stmina Golbe");
               break;
               case PickUpType.Sword: //NEW For inventory
               AudioSource.PlayClipAtPoint(pickupClip,Camera.main.transform.position);
               Debug.Log(activeInventory);
               InventorySlot slot = activeInventory.GetEmptyInventorySlot();
                if (slot != null)
                {
                    Debug.Log("TEST2");
                    slot.AssingWeaponInfor(weaponInfo);
                    Debug.Log("TEST3");
                }
               break;
            default:
            break;
            }
        }
}

ActiveInventory Script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ActiveInventory : Singleton<ActiveInventory>
{
    private int activeSlotIndexNum = 0;
    private PlayerActions playerActions;

    protected override void Awake()
    {
        base.Awake();
        playerActions =  new PlayerActions();
    }

    private void Start()
    {
        playerActions.Inventory.Keyboard.performed += ctx => ToggleActiveSlot((int)ctx.ReadValue<float>());
    }

    private void OnEnable()
    {
        playerActions.Enable();
    }

    public void EquipStartingWeapon()
    {
        //Give the player the sword in the inventory
        ToggleActiveHighlight(0);
    }

    private void ToggleActiveSlot(int numPressed)
    {
        //Need to match up number pressed to the actual inventory index {0-4} [1-5]that is why we -->(- 1)
        ToggleActiveHighlight(numPressed -1);
    }

    private void ToggleActiveHighlight(int indexNum)
    {
        activeSlotIndexNum = indexNum;
        //First set all agme objects for the inventory slots off
        foreach (Transform inventorySlot in this.transform)
        {
            inventorySlot.GetChild(0).gameObject.SetActive(false);
        }

        //Now set the one that we want to be active
        this.transform.GetChild(indexNum).GetChild(0).gameObject.SetActive(true);
        ChangeActiveWeapon();
    }

    private void ChangeActiveWeapon()
    {
        if(PlayerHealth.Instance.IsDead){return;}
        if(ActiveWeapon.Instance.CurrentActiveWeapon != null)
        {
            Destroy(ActiveWeapon.Instance.CurrentActiveWeapon.gameObject);
        }
        Transform childTransform = transform.GetChild(activeSlotIndexNum);
        InventorySlot inventorySlot = childTransform.GetComponentInChildren<InventorySlot>();
        WeaponInfo weaponInfo = inventorySlot.GetWeaponInfo();
        // if nothing is in an inventory slot
        if (weaponInfo == null)
        {
            //return null
            ActiveWeapon.Instance.WeaponNull();
            return;
        }
        GameObject weaponToSpawn = weaponInfo.weaponPrefab;
        //Now Instantiate the new weapon
        GameObject newWeapon = Instantiate(weaponToSpawn, ActiveWeapon.Instance.transform.position, Quaternion.identity);
        // we are cleaning out first the rotation of a prior wepon so any weapon does not spin on a certain point.
        ActiveWeapon.Instance.transform.rotation = Quaternion.Euler(0,0,0);
        //now make the weapon a child of our active weapon.
        newWeapon.transform.parent = ActiveWeapon.Instance.transform;
        ActiveWeapon.Instance.NewWeapon(newWeapon.GetComponent<MonoBehaviour>());    
    }

    public  InventorySlot GetEmptyInventorySlot() // NEW for active weapon.
        {
            Debug.Log("TEST1");
            foreach (Transform child in transform)
            {
                //Debug.Log("inventorySlot");
                if (child.TryGetComponent(out InventorySlot inventorySlot)) return inventorySlot;
                Debug.Log(inventorySlot);
            }
            return null;
        }
}

Thank you.

This actually looks right, but I forgot one of my own rules in avoiding Race Conditions.
It’s possible for the Pickup’s Awake() to be executed before ActiveInventory’s Awake.

I also forgot that we made ActiveInventory a Singleton, which actually makes this much easier to work with.
All of our changes at this point should be in Pickup.cs:

  • Remove the line in Awake to get the ActiveInventory. We don’t need it.
    Also remove this line
ActiveInventory activeInventory; //NEW

we don’t need it. We can get a reference to the ActiveInventory through ActiveInventory.Instance.

Lastly, we just need to change our case statement in DetectPickupType()

will become

               case PickUpType.Sword: //NEW For inventory
               AudioSource.PlayClipAtPoint(pickupClip,Camera.main.transform.position);
               Debug.Log(ActiveInventory.Instance);
               InventorySlot slot = ActiveInventory.Instance.GetEmptyInventorySlot();
                if (slot != null)
                {
                    Debug.Log("TEST2");
                    slot.AssingWeaponInfor(weaponInfo);
                    Debug.Log("TEST3");
                }
               break;

we are really close now. I get logs 1,2 and not 3 yet. it seems like it does not like line 19 in InventorySlot script

  public void AssingWeaponInfor(WeaponInfo weaponInfo) // NEW for active weapon.
  {
    this.weaponInfo = weaponInfo;
    transform.GetChild(1).GetComponent<Image>().sprite = weaponInfo.sprite;
    Debug.Log(weaponInfo.weaponRange);
  }

}

So test 3 in the Pickup script doesn’t hit.

It’s not easy to tell which one of these things is line 19. There are two lines where a null reference could occur, and one of them has multiple lines… So let’s add some debugs to find the culprit.
This is in InventorySlot.cs:

    public void AssignWeaponInfo(WeaponInfo weaponInfo)
    {
        if (weaponInfo == null)
        {
            Debug.Log($"WeaponInfo is null!");
            return;
        }
        this.weaponInfo = weaponInfo;
        if (transform.childCount < 2)
        {
            Debug.Log($"Not enough slots!");
            return;
        }
        Image image = transform.GetChild(1).GetComponent<Image>();
        if (image == null)
        {
            Debug.Log($"Image is null");
            return;
        }
        transform.GetChild(1).GetComponent<Image>().sprite = weaponInfo.sprite;
    }

Ah, that makes sense.
WeaponInfo needs to be a [SerializedField] in Pickup

Privacy & Terms