3

I have been doing Unity tutorials for about two weeks now and I have made some very basic things. Sorry if this is a super simple question, but I have spent the last 5 hours working on this and decided to come here.

What I have done so far:

I have made a very basic card game, where cards have properties like Attack and Health. These values are stored in a ScriptableObject for each card.

[CreateAssetMenu(fileName = "New Card", menuName = "Card")]
public class Card : ScriptableObject
{
public int instanceNumber;
public bool isDefaultCard = false;

public string cardName;
public string description;
public string flavorText;

public Sprite cardArt;

public int manaCost;
public int health;
public int attack;

public int characterClass;


public void printCardData()
{
    Debug.Log(cardName + ": " + description);
}
}

Now I also have a card prefab where on the Unity UI I can drag the prefab onto the scene and then drag my ScriptableObject into the Card Display Script and it updates the card and displays everything fine!

I can't for the life of me figure out how to do this with code though. I have no idea how to access my ScriptableObjects from code.

I understand I could instantiate a prefab with something like this:

Instantiate(myPrefab, new Vector3(0, 0, 0), Quaternion.identity);

But how do I add my card data to it? I want the game to have rewards at the end of a battle that shows a random selection of cards each time and you pick one. So this means create 3 card objects that have a random ScriptableObject attached to them. But again, no matter what I try I can't seem to find anyway to access my ScriptableObjects from code.

Sorry again if this is super simple, but I am very lost. I need a way to create an object of any my ScriptableObjects cards at any time. The player will have many times to add more cards to their deck, but their deck will only have 20-30 cards at a time.

GorillaMan33
  • 35
  • 1
  • 1
  • 3
  • ScriptableObjects are, by design, assets. They are a type of data (same as you would write a PNG file image reader or a JSON parser or an AVI codec). If you want something "attached" to a gameobject it has to be a Component (i.e. derive from MonoBheaviour). – Draco18s no longer trusts SE Oct 02 '19 at 22:42
  • How do I access this data in code then? Only way I found was dragging onto an object with the editor - onto a component that can take it. – GorillaMan33 Oct 02 '19 at 23:33
  • ...yes, because its an asset. You can't attach png files directly to game objects, you have to drag it into a component that has a field for a Sprite. ScriptableObjects work the same way. – Draco18s no longer trusts SE Oct 03 '19 at 03:47
  • I understand it's an asset. But coming from the world of web coding, if I want to access an image (or literally any file) that's stored anywhere on my server, get it's base64 value, load it into memory, turn into a txt file, whatever, I can do that, even if it's not on the web public folder. I don't understand how I can access the ScriptableObject asset from code without having to drag and drop it on a script. Does unity just not give me full access to all assets unless I put them in the resources folder? It seems really odd. – GorillaMan33 Oct 05 '19 at 02:17
  • ...you reference it like you do any other asset: with a public field on a MonoBehaviour derived class. – Draco18s no longer trusts SE Oct 05 '19 at 02:43

3 Answers3

2

So you have a Monobehaviour CardDisplayScript, and a ScriptableObject Card. The other 2 comments prior to this one mention a Singleton, and a list of reward cards. While they both can solve the issue, I've had success using a slightly different design. My approach allows for enemies to share a "common reward pool", and pull from that common pool on death. For bosses or rare enemies, I like to create a "Boss reward pool" that is slightly different.

For this design 4 scripts are needed:

Card ScriptableObject

As you have in your question:

[CreateAssetMenu(fileName = "New Card", menuName = "Card")]
public class Card : ScriptableObject
{
public int instanceNumber;
public bool isDefaultCard = false;

public string cardName;
public string description;
public string flavorText;

public Sprite cardArt;

public int manaCost;
public int health;
public int attack;

public int characterClass;


public void printCardData()
{
    Debug.Log(cardName + ": " + description);
}
}

CardRewardPool ScriptableObject

[CreateAssetMenu(fileName = "DefaultRewardPool", menuName = "Card Reward Pool")]
public class CardRewardPool : ScriptableObject
{
    public List<Card> cards;

    public Card GetRandomCard()
    {
        if(cards.Count > 0)
            return cards[Random.Range(0,cards.Count)];
        else
            return null;
    }
}

If it's possible for a reward pool to be empty, you should handle this by checking that the list has cards in it first (as shown in the code above). If a reward pool should never be empty, then you can remove the "if" check and handle the errors for when the list is empty appropriately.

CardDisplayScript Monobehaviour

public class CardDisplayScript : Monobehaviour
{
    public Card card;
    //... other properties
    public void UpdateCard(Card _card)
    {
        card = _card;
        //... reload in game attributes using the new card
    }
}

LootableEntity Monobehaviour

This script can be attached to anything that can be expected to reward cards (An enemy, a reward chest, story rewards, etc.). In this case if the battle ends, then whatever enemy you just defeated should have a "LootableEntity" (or another name if you prefer) attached to it that directly references the reward pool that can generate the rewards.

public class LootableEntity : Monobehaviour
{
    public CardRewardPool cardRewardPool;
    
    public Card GetBattleReward()
    {
        return cardRewardPool.GetRandomCard();
    }
}

Final remarks

This design allows you to assign a unique reward pool to each enemy, or have some enemies share a reward pool. If you do not wish to segregate your cards into different reward pools, you can have a generic reward pool with this same design. Additionally, you no longer have to persist a list of ScriptableObjects on each entity, so you do save a bit of memory.

Community
  • 1
  • 1
Erik Overflow
  • 2,220
  • 1
  • 5
  • 16
0

There will be some guessing (thus the terminology here is most likely unlike what you actually have in code), but as an example.

You want to use a Card object within a sample script:

public class CardUser : MonoBehaviour
{
    public Card cardScriptableObject {get;set;}
}

Few ways are possible, the easiest ones are using singleton (global provider of cards), but what often works is just assign the reference always after instancing a prefab, here is a very rough example:

public class CardUserInstancer : MonoBehaviour
{

    public CardUser cardUserPrefab;
    public Card mainCard;

    void Instantiate()
    {
        CardUser thisUser = Instantiate(cardUserPrefab, Vector3.zero, Quaternion.identity);
        thisUser.card = mainCard;
    }
}

Alternatively, if you don't want to have the reference in the inspector, you can list available objects from the Resources folder and pick one

var cards= Resources.FindObjectsOfTypeAll(typeof(Card)) as Card[];
zambari
  • 4,797
  • 1
  • 12
  • 22
0

I recently ran into a similar situation. What I did was create a new scriptable object "list" data type. For example, you could create a CardList class that is a scriptable object that holds an array (or List) of Cards. (Have methods to AddCard, RemoveCard, GetCard(int index), and GetRandomCard.)

You can then create a new CardList, and in the inspector add all of the potential reward cards to the list. Add the CardList to the Card Display Script and now you can access any card in the list.

On a side note, I would create a CardList for the player's deck, unlocked cards, grave yard, etc. You could then easily do something like

unlockedCardList.AddCard(rewardCardsList.GetRandomCard());
Joel
  • 1
  • 2