OK so for the time being, here’s a quick rundown of exactly what was going on (because honestly, I’m a little confused as of what was happening)
(This assumes that you’ve done the banking tutorial, and the third person RPG Conversion course by Brian, which… have the pre-requisite of the entire RPG Series, and the Third Person Combat and Traversal Course. So, you got 7 pre-requisites for this to make sense)
- Put the old pickup solution aside, and implement a new state machine:
// stateMachine.SwitchState(new PlayerPickupState(stateMachine, stateMachine.PickupFinder.CurrentTarget));
// Our current Pickup State above is being replaced by an advanced 'PlayerInventoryCollectionState.cs' below.
// This is essentially an alternative for picking stuff up in the game world, using a one-way transfer, pickup
// inventory-based approach that focuses on giving the player extra freedom, and speed, on what exactly they want to
// pickup in the game world (it works by combining the 'IInventorySource.cs' Interface, specifically created for this
// system, with 'PickupFinder.cs'):
stateMachine.SwitchState(new PlayerInventoryCollectionState(stateMachine));
and of course, create a new state machine for that:
using GameDevTV.Inventories;
namespace RPG.States.Player {
public class PlayerInventoryCollectionState : PlayerBaseState
{
public PlayerInventoryCollectionState(PlayerStateMachine stateMachine) : base(stateMachine) {}
public static event System.Action<IInventorySource, System.Action> OnCollectionStateOpened;
private void OnCollectionFinished()
{
stateMachine.SwitchState(new PlayerFreeLookState(stateMachine));
}
public override void Enter()
{
OnCollectionStateOpened?.Invoke(stateMachine.PickupFinder, OnCollectionFinished);
}
public override void Tick(float deltaTime)
{
Move(deltaTime);
}
public override void Exit() {}
}
}
- Convert your Inventory to an IInventorySource Interface, and implement all the necessary functions (if you got any complains about missing stuff or what not, just throw it into your interface. The interface below is what worked for my case):
using System;
using System.Collections;
using UnityEngine;
using System.Collections.Generic;
namespace GameDevTV.Inventories
{
// This interface allows the player to classify between his inventory, and other inventories
// (mainly the Pickup Inventory, for our replacement to our classic pickup approach, and the bank inventory)
public interface IInventorySource
{
/// <summary>
/// Solution to allow this interface access to part of the MonoBehaviour
/// </summary>
/// <returns></returns>
GameObject GetGameObject();
/// <summary>
/// Broadcasts when the items in the slots are added/removed
/// </summary>
event Action inventoryUpdated;
/// <summary>
/// What's the display name of this Inventory?
/// </summary>
/// <returns></returns>
string GetDisplayName();
/// <summary>
/// Could this item fit anywhere in the inventory?
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool HasSpaceFor(InventoryItem item);
/// <summary>
/// Do you have space for IEnumerable Inventory Items? Specifically made for checking sword and shield positionings
/// </summary>
/// <param name="items"></param>
/// <returns></returns>
public bool HasSpaceFor(IEnumerable<InventoryItem> items);
/// <summary>
/// How many slots are in the inventory?
/// </summary>
/// <returns></returns>
int GetSize();
/// <summary>
/// Attempt to add the items to the first available slot
/// </summary>
/// <param name="item">The item to add</param>
/// <param name="number">The number to add</param>
/// <returns></returns>
bool AddToFirstEmptySlot(InventoryItem item, int number, bool refresh = true);
/// <summary>
/// Is there an instance of the item in the inventory?
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
bool HasItem(InventoryItem item);
/// <summary>
/// Return the item type in the given slot
/// </summary>
/// <param name="slot"></param>
/// <returns></returns>
InventoryItem GetItemInSlot(int slot);
/// <summary>
/// Get the number of items in the given slot
/// </summary>
/// <param name="slot"></param>
/// <returns></returns>
int GetNumberInSlot(int slot);
/// <summary>
/// Remove a number of items from the given slot. Will never remove more than there are
/// </summary>
/// <param name="slot"></param>
/// <param name="number"></param>
void RemoveFromSlot(int slot, int number, bool refresh = true);
/// <summary>
/// Will add an item to the given slot if possible. If there is already
/// a stack of this type, it will add to the existing stack. Otherwise,
/// it will be added to the first empty slot
/// </summary>
/// <param name="slot"></param>
/// <param name="item"></param>
/// <param name="number"></param>
/// <returns></returns>
public bool AddItemToSlot(int slot, InventoryItem item, int number, bool refresh = true);
/// <summary>
/// Transfer all items in the Inventory from one inventory to another
/// </summary>
/// <param name="otherInventory"></param>
public void TransferAllInventory(Inventory otherInventory); // This is only here so 'InventoryUI' would be quiet
/// <summary>
/// Remove InventoryItem item, in 'number' quantity, from the Inventory
/// </summary>
/// <param name="item"></param>
/// <param name="number"></param>
/// <param name="refresh"></param>
public void RemoveItem(InventoryItem item, int number, bool refresh = true);
}
}
- in ‘InventoryUI.cs’, change your ‘Inventory’ type to ‘IInventorySource’ as follows:
public IInventorySource SelectedInventory => selectedInventory;
// CACHE
IInventorySource selectedInventory;
- Fix all the Syntax errors that this conversion will create in ‘InventorySlotUI.cs’ (use the Interface functions above)
I’ll leave that up to the reader, but anything like ‘TryGetComponent’ and other stuff, try use the ‘GetGameObject()’ from the Interface first. Most of these bugs will be gone by that one. For example:
inventory.CompareTag("Player");
won’t work anymore, but this will work:
inventory.GetGameObject().CompareTag("Player")
- Create a ‘PickupWindow.cs’ script, and attach it to your ‘PickupInventory’, which you will setup in your hierarchy:
using System;
using GameDevTV.Inventories;
using GameDevTV.UI.Inventories;
using RPG.States.Player;
using RPG.UI;
using RPG.UI.Inventories;
using UnityEngine;
public class PickupWindow : WindowController
{
[SerializeField] private InventoryWindow inventoryWindow;
private System.Action callback;
protected override void Subscribe()
{
PlayerInventoryCollectionState.OnCollectionStateOpened += TogglePickupWindow;
gameObject.SetActive(false);
}
protected override void Unsubscribe()
{
PlayerInventoryCollectionState.OnCollectionStateOpened -= TogglePickupWindow;
}
private void TogglePickupWindow(IInventorySource source, Action finishedCallback)
{
callback = finishedCallback;
inventoryWindow.gameObject.SetActive(true);
GetComponentInChildren<InventoryUI>().Setup(source.GetGameObject());
gameObject.SetActive(true);
}
protected override void OnDisable()
{
base.OnDisable();
inventoryWindow.gameObject.SetActive(false);
callback?.Invoke();
}
}
- Implement your IInventorySource.cs script in your ‘PickupFinder.cs’ script:
using System;
using System.Collections.Generic;
using System.Linq;
using GameDevTV.Inventories;
using RPG.Core;
using UnityEngine;
namespace RPG.Inventories
{
public class PickupFinder : RangeFinder<PickupTarget>, IInventorySource
{
protected override void AddTarget(PickupTarget target)
{
base.AddTarget(target);
target.OnPickedUp += RemoveTarget;
Debug.Log($"Pickup Finder: Adding {target.name}");
}
protected override void RemoveTarget(PickupTarget target)
{
base.RemoveTarget(target);
target.OnPickedUp -= RemoveTarget;
Debug.Log($"Pickup Finder: Removing {target.name}");
}
public PickupTarget GetNearestPickup()
{
CurrentTarget = Targets.OrderBy(t => Vector3.Distance(transform.position, t.transform.position)).FirstOrDefault();
return CurrentTarget;
}
// ------------------------------------ IINVENTORYSOURCE INTERFACE IMPLEMENTATION ---------------------------------------------
// Most of the Functions down here are for 'IInventorySource.cs' to work. We don't need them all,
// but Interfaces demand everything to be used, so most will be empty
// (it's either this, or a performance-intense solution of programming a second inventory, which
// can be a bit of a messy solution)
// (This is all invoked from 'PlayerInventoryCollectionState.cs', the replacement for 'PlayerPickupState.cs')
public event Action inventoryUpdated;
public bool AddItemToSlot(int slot, InventoryItem item, int number, bool refresh = true)
{
return false; // No returns. Only takes
}
public bool AddToFirstEmptySlot(InventoryItem item, int number, bool refresh = true)
{
return false; // No returns. Only takes
}
public string GetDisplayName()
{
return "PICKUPS"; // Controls the Title of the 'Pickups' Window
}
public GameObject GetGameObject()
{
return gameObject;
}
public InventoryItem GetItemInSlot(int slot)
{
return Targets[slot].GetComponent<Pickup>().GetItem();
}
public int GetNumberInSlot(int slot)
{
return Targets[slot].GetComponent<Pickup>().GetNumber();
}
public int GetSize()
{
return Targets.Count; // How many total Pickups are around you...?
}
public bool HasItem(InventoryItem item)
{
foreach (PickupTarget pickupTarget in Targets)
{
if (pickupTarget.GetComponent<Pickup>().GetItem() == item) return true; // if you got this far, it means you found the item
}
return false; // can't find it nearby? Then it's not there
}
public bool HasSpaceFor(InventoryItem item)
{
return false; // No returns. Only takes
}
public bool HasSpaceFor(IEnumerable<InventoryItem> items)
{
return false; // No returns. Only takes
}
public void RemoveFromSlot(int slot, int number, bool refresh = true)
{
int remainder = GetNumberInSlot(slot) - number;
if (remainder > 0)
{
Targets[slot].GetComponent<Pickup>().SetNumber(remainder);
}
else
{
var item = Targets[slot];
Targets.Remove(item);
Destroy(item.gameObject);
}
if (refresh) {inventoryUpdated?.Invoke();}
}
public void RemoveItem(InventoryItem item, int number, bool refresh = true)
{
if (item == null) return; // No item, no pickup
foreach (PickupTarget pickupTarget in Targets)
{
var pickup = pickupTarget.GetComponent<Pickup>();
if (pickup.GetItem() == item) // you found an item to pickup
{
int currentNumber = pickup.GetNumber();
if (currentNumber > number) // consume the amount of item picked up by the player (below max limit)
{
pickup.SetNumber(currentNumber - number);
if (refresh) {inventoryUpdated?.Invoke();}
return;
}
else // max number of consumed items taken exceeded, so consume everything
{
number -= currentNumber;
Targets.Remove(pickupTarget);
Destroy(pickupTarget.gameObject);
if (refresh) {inventoryUpdated?.Invoke();}
if (number <= 0) return;
}
}
}
}
public void TransferAllInventory(Inventory otherInventory)
{
// For now, this is empty. It's just to get 'InventoryUI' to be quiet about whole inventory
// transfers, that it made it in the 'IInventorySource' Interface (but we don't really need it for now)
}
// ------------------------------------ END OF IINVENTORYSOURCE INTERFACE IMPLEMENTATION --------------------------------------
}
}
Again, this is what worked for me
- Test run, and enjoy
(Until Brian lets me know if we’re getting the slots to not vanish or not, this is what worked for me )