How to plug one gameobject into another in gameplay

Hey there! I’m very new to Unity and coding and I’m working on my first little game project.

My game revolves around placing character cards into locations and then the location and that character card deal damage to one another at a set rate (independent of one another) until one of them runs out of health.

I have two questions:

  1. Currently the location code has a serialized field to input the insertedCard scriptable object. I can do that manually myself through the unity interface just fine but how do I insert a gameobject into the field during gameplay through drag and drop?

  2. How do I properly reference the stats of the insertedCard within my location script? Snippet of my code below for example:

    private void GetCardStats()
    {
        if (insertedCard != null)
        {
           float cardInterval = insertedCard.GetComponent<CardStats>().cardInterval;
            float cardDamage = insertedCard.GetComponent<CardStats>().cardDamage;
            float cardHealth = insertedCard.GetComponent<CardStats>()._cardHealth;
        }
    }

    private void StartTimer()
    {
        GetCardStats();

        InvokeRepeating("TakeDamage", 1f, cardInterval);
        InvokeRepeating("DealDamage", 1f, locationTimerInterval);
        StartCoroutine(EndCombat());
    }

Thanks for any help you can offer! I’m sure these are extremely basic questions.

This is a pretty broad question as it covers multiple systems, of which themselves can be complicated in isolation.

Really the first question here is, do you have a working drag and drop system as of yet? If not, I would look for some tutorials in that domain, as they’ll likely answer your other questions once you understand the underlying principles.

Drag and drop system aside, you would have zones that, when something is dragged into it, check what type of object it is (or in the case of game objects, if it has specific component), and then do something with said object.

I’ve taken some time and worked out my drag and drop system. I have a Location panel with the ‘battle’ script attached to it which recognizes two scriptable objects one for the location and one for the card plugged into the location.

The card has its prefab image and such and then has the card scriptable object attached to that.

Then the location panel has a child panel that acts as a slot to plug the card into. That’s where my confusion is, how do I get the location panel to recognize the card plugged into it (and when a card is not plugged into it.)

Here’s my Drag and Drop script (attached to the card):

{
    public static GameObject itemBeingDragged;
    Vector3 startPosition;
    Transform startParent;

    public void OnBeginDrag(PointerEventData eventData)
    {
        itemBeingDragged = gameObject;
        startPosition = transform.position;
        startParent = transform.parent;
        GetComponent<CanvasGroup>().blocksRaycasts = false;
    }

    public void OnDrag(PointerEventData eventData)
    {
        transform.position = Input.mousePosition;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        itemBeingDragged = null;
        GetComponent<CanvasGroup>().blocksRaycasts = true;
        if (transform.parent == startParent)
        {
            transform.position = startPosition;
        }
    }
}   

And here’s the Slot script (attached to the panel child of the Location panel).

public class Slot : MonoBehaviour, IDropHandler
{
    public GameObject card
    {
        get
        {
            if (transform.childCount > 0)
            {
                return transform.GetChild(0).gameObject;
            }
            return null;
        }
    }

    public void OnDrop (PointerEventData eventData)
    {
        if (!card)
        {
            DragHandler.itemBeingDragged.transform.SetParent(transform);
        }
    }
}

I guess a few comments.

Firstly, you can get the game object from the PointerEventData without needing your own static property. You can see how this is done in the docs: Interface IDropHandler | Unity UI | 3.0.0-exp.4

Secondly, it’s better to not use the transform heirarchy to manage your data structure. Your slot should hold onto a reference to the card dropped onto it, and not look it up via a child transform every time. On top of that, you should reference it via a component, rather than a game object.

When you want to get information about a game object, you can use GetComponent, or preferably TryGetComponent these days. So when your slot component has something dropped onto it, it can check for a particular component and go from there.

You could write your slot to either be generic and standalone for other components to hook into, or you can design it to be inherited from (or both?).

So lets imagine you have a MonoBehaviour called Card. A standalone slot might look like:

using System;
using UnityEngine;

// sealed, so no inheritance!
public sealed class Slot : MonoBehaviour, IDropHandler
{
	private Card _card;
	
	public event Action<Card> OnSlotCardChanged;
	
	// try get pattern to get the current card
	public bool TryGetCurrentCard(out Card card)
	{
		card = _card;
		return card != null;
	}

    public void OnDrop (PointerEventData eventData)
    {
		var droppedGameObject = eventData.pointerDrag;
		if (droppedGameObject == null) // do nothing if null
		{
			return;
		}
		
		bool isCard = droppedGameObject.TryGetComponent(out Card card) == true;
		if (isCard == true)
		{
			_card = card;
			_card.transform.SetParent(this.transform);
			OnSlotCardChanged?.Invoke(_card);
		}
    }
}

Then you make other components that subscribe to the OnSlotCardChanged delegate.

One designed for inheritance might look like:

// lets make this abstract so only concrete derived types can be used
public abstract class Slot : MonoBehaviour, IDropHandler
{
    public void OnDrop (PointerEventData eventData)
    {
		var droppedGameObject = eventData.pointerDrag;
		if (droppedGameObject == null) // do nothing if null
		{
			return;
		}
		
		bool isCard = droppedGameObject.TryGetComponent(out Card card) == true;
		if (isCard == true)
		{
			OnCardDropped(card);
    }
	
	protected abstract void OnCardDropped(Card card);
}

// usage
public sealed class CharacterSlot : Slot
{
	private CharacterCard _characterCard;
	
	protected override void OnCardDropped(Card card)
	{
		if (card is CharacterCard characterCard)
		{
			_characterCard = characterCard;
			// etc etc
		}
	}
}

You probably want to add in some way to accept or deny a card being dropped into a slot as well.

Additionally, while I’m telling you to use GetComponent here, in your drag handler you have unnecessary uses of GetComponent. If these game objects always have a canvas group on them, then you should get the reference once in Awake, or reference it via a serialized field. No need to GetComponent for it every time.