Distance check on Clickable Pickup

I think it would be a lot more useful to show how to make a version of Clickable Pickup that takes distance to the item into consideration. It’s sort of absurd to have a pickup all the way across the viewable terrain that you can instantly pickup. This would be a great time to also introduce multiple actions with one click(keeping in mind the final intended dynamic cursor).

For example, if you click on an item that is 100 feet away you will automatically run to the maximum pickup distance and then pick it up. This is something I was also hoping that would have been showcased with Core Combat with ranged weapons. Click the attack icon, run to the max distance, then start firing bow.

1 Like

Fortunately, that’s what crazy TAs in the Community Forums are for. :slight_smile:

Here’s my take on the ClickablePickup…
First, you need a class much like Fighter that is an IAction

public class ItemCollector : MonoBehaviour, IAction
{
     Pickup target;
     
     void Update()
     {
          if(target==null) return;
          if(!GetIsInRange(target.transform)  //Edited, forgot !
          {
                GetComponent<Mover>().MoveTo(target.transform.position, 1);
           }
           else
           {
                target.PickupItem();
                target=null;
           }
     }

     public void CollectItem(Pickup pickup)
     {
          GetComponent<ActionScheduler>().StartAction(this);
          target=pickup;
     }

         private bool GetIsInRange(Transform targetTransform)
        {
            return Vector3.Distance(transform.position, targetTransform.position) < 3;
        }
}

Now for our ClickablePickup, we just need to make it an IHandleRaycast

public class ClickablePickup : Pickup, IHandleRaycast
{
    public bool HandleRaycast(PlayerController controller)
    {
         if(Input.GetMouseDown(0))
         {
              controller.GetComponent<ItemCollector>().CollectItem(this);
         }
         return true;
    }
    public CursorType GetCursorType()
    {
         return CursorType.Pickup;
     }
}

You’ll need to add Pickup to the list of CursorTypes in the enum, and you’ll have to populate it with a Pickup related cursor.

2 Likes

Awesome as always!

I like this approach. I will mention I had to change a couple of things, however.

 public class ItemCollector : MonoBehaviour, IAction
 {
      Pickup target;
      
      void Update()
      {
           if(target==null) return;
           if(!GetIsInRange(target.transform) // have to add a "!" in front to move when NOT in range
           {
                 GetComponent<Mover>().MoveTo(target.transform.position, 1);
            }
            else
            {
                 target.PickupItem();
                 target=null;
            }
      }
 
      public void CollectItem(Pickup pickup)
      {
           GetComponent<ActionScheduler>().StartAction(this);
           target=pickup;
      }

And then in the Clickable Pickup script:

 public class ClickablePickup : Pickup, IHandleRaycast
 {
     private void Awake()
    {
       target = GetComponent<Pickup>();
    }
 
     public bool HandleRaycast(PlayerController controller)
     {
          if(Input.GetMouseDown(0))
          {
               controller.GetComponent<ItemCollector>().CollectItem(target); // when I had "this", I was getting "item = null" in the Pickup script. Needed to specifically pass Pickup script in calling this method.
          }
          return true;
     }
     public CursorType GetCursorType()
     {
          return CursorType.Pickup;
      }
 }

This worked great but i get one problem. After i put the itemcollector script on the player i get an argumentexception: the object you want to instantiate is null. when i click the message it takes me to the player prefab in the scene.

That sounds odd… as nothing is being instantiated in the scripts… is your pickup set up with a proper InventoryItem?

sry it was not the itemcollector but an inventoryitem that didnt have a pickup prefab. didnt see this bug before.

That would do it.

I’ve Implemented a UI to pickup Items instead of clicking the mouse button

So with the ItemCollector on the user like this.

public class ItemCollector : MonoBehaviour, IAction
    {
        Pickup target;

        void Update()
        {
            if (target == null) return;
            if (!GetIsInRange(target.transform)) 
            {
                GetComponent<Mover>().MoveTo(target.transform.position, 1);
            }
            else
            {
                target.PickupItem();
                target = null;
            }
        }

        public void CollectItem(Pickup pickup)
        {
            GetComponent<ActionScheduler>().StartAction(this);
            target = pickup;
        }

        private bool GetIsInRange(Transform targetTransform)
        {
            return Vector3.Distance(transform.position, targetTransform.position) < 3;
        }

        public void Cancel()
        {
            target = null;
            GetComponent<Mover>().Cancel();
        }
    }

Then the ClickablePickup script

public class ClickablePickup : Pickup, IRaycastable
    {
        Pickup pickup;
        bool isInRange = false;
        PickupUI pickupUI;

        private void Awake()
        {
            pickup = GetComponent<Pickup>();
            pickupUI = FindObjectOfType<PickupUI>();
        }

        public CursorType GetCursorType()
        {
            return CursorType.Pickup;
        }

        public bool HandleRayCast(PlayerController callingController)
        {
            if (Input.GetKeyDown(KeyCode.E))
            {
                callingController.GetComponent<ItemCollector>().CollectItem(pickup);
            }
            return true;
        }
        private void OnTriggerEnter(Collider other)
        {
            if (other.CompareTag("Player"))
            {
                pickupUI.ShowUI();
            }
        }
        private void OnTriggerExit(Collider other)
        {
            if (other.CompareTag("Player"))
            {
                pickupUI.HideUI();
            }
        }
    }

Then I have a simple UI to show or hide a UI telling the user to click “[E] Pickup” like so:

public class PickupUI : MonoBehaviour
    {
        [SerializeField] private GameObject panel = null;
        [SerializeField] private TextMeshProUGUI text = null;

        private void Start()
        {
            HideUI();
        }

        public void ShowUI()
        {
            panel.SetActive(true);
            text.text = "[E] Pickup ";
        }

        public void HideUI()
        {
            panel.SetActive(false);
        }
    }

Privacy & Terms