将 Unity 可编写脚本的对象附加到 GameObject

Attaching Unity Scriptable Object to GameObject

我已经做了大约两周的 Unity 教程,并且做了一些非常基础的东西。对不起,如果这是一个超级简单的问题,但我花了最后 5 个小时来解决这个问题并决定来这里。

到目前为止我做了什么:

我制作了一款非常基础的纸牌游戏,纸牌具有攻击力和生命值等属性。这些值存储在每张卡片的 ScriptableObject 中。

[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);
}
}

现在我在 Unity 上还有一个卡片预制件 UI 我可以将预制件拖到场景中,然后将我的 ScriptableObject 拖到卡片显示脚本中,它会更新卡片并显示一切正常!

虽然我一辈子都想不出如何用代码来做到这一点。我不知道如何从代码访问我的 ScriptableObjects。

我知道我可以用这样的东西实例化预制件:

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

但是我该如何添加我的卡数据呢?我希望游戏在战斗结束时有奖励,每次都会显示随机选择的卡片,你可以选择一张。所以这意味着创建 3 个卡片对象,它们附加了一个随机的 ScriptableObject。但同样,无论我尝试什么,我似乎都无法找到从代码访问我的 ScriptableObjects。

如果这太简单了,再次抱歉,但我很迷茫。我需要一种方法来随时创建我的任何 ScriptableObjects 卡的对象。玩家将有很多次将更多卡牌添加到他们的牌组中,但他们的牌组一次只能有 20-30 张牌。

会有一些猜测(因此这里的术语很可能与代码中实际使用的术语不同),但作为示例。

您想在示例脚本中使用 Card 对象:

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

可能的方法很少,最简单的方法是使用单例(卡片的全球供应商),但通常有效的是在实例化预制件后始终分配引用,这是一个非常粗略的例子:

public class CardUserInstancer : MonoBehaviour
{

    public CardUser cardUserPrefab;
    public Card mainCard;

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

或者,如果您不想在检查器中使用引用,您可以列出资源文件夹中的可用对象并选择一个

var cards= Resources.FindObjectsOfTypeAll(typeof(Card)) as Card[];

我最近 运行 遇到了类似的情况。我所做的是创建一个新的可编写脚本的对象 "list" 数据类型。例如,您可以创建一个 CardList class,它是一个可编写脚本的对象,其中包含一个卡片数组(或列表)。 (具有 AddCard、RemoveCard、GetCard(int index) 和 GetRandomCard 的方法。)

然后您可以创建一个新的 CardList,并在检查器中将所有可能的奖励卡添加到列表中。将 CardList 添加到卡片显示脚本中,现在您可以访问列表中的任何卡片。

附带说明一下,我会为玩家的套牌、解锁的牌、墓地等创建一个 CardList。然后您可以轻松地执行类似

的操作
unlockedCardList.AddCard(rewardCardsList.GetRandomCard());

所以你有一个 Monobehaviour CardDisplayScript 和一个 ScriptableObject Card。在此之前的其他 2 条评论提到了单例和奖励卡列表。虽然它们都可以解决问题,但我使用略有不同的设计取得了成功。我的方法允许敌人共享一个“共同的奖励池”,并在死亡时从那个共同的池中撤出。对于boss或者稀有敌人,我喜欢打造一个略有不同的“Boss奖励池”。

此设计需要 4 个脚本:

卡片 ScriptableObject

正如你的问题:

[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”检查并适当处理列表为空时的错误。

CardDisplayScript 单一行为

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 单一行为

这个脚本可以附加到任何可以奖励卡片的东西上(敌人、奖励宝箱、故事奖励等)。在这种情况下,如果战斗结束,那么你刚刚击败的任何敌人都应该附加一个“LootableEntity”(或者你喜欢的其他名称),它直接引用可以产生奖励的奖励池。

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

最后的评论

此设计允许您为每个敌人分配一个独特的奖励池,或者让一些敌人共享一个奖励池。如果您不想将您的卡片分成不同的奖励池,您可以拥有一个具有相同设计的通用奖励池。此外,您不再需要在每个实体上保留一个 ScriptableObjects 列表,因此您确实节省了一些内存。