寻找比在 Scriptable Objects 中使用字符串在别处调用特定方法更好的解决方案

Posted

技术标签:

【中文标题】寻找比在 Scriptable Objects 中使用字符串在别处调用特定方法更好的解决方案【英文标题】:Looking for a better solution than using strings within Scriptable Objects to call specific methods elsewhere 【发布时间】:2021-01-06 11:24:32 【问题描述】:

我被要求在我的问题的先前版本中描述我的用例。因此,这篇文章非常详细,并详细介绍了我正在尝试做的事情。如果您遇到这个问题是希望它能帮助您解决自己的问题,这里有一个快速的 TL:DR,看看是否值得进一步研究:

TL:DR: 我在我的项目中使用可编写脚本的对象,并希望找到将脚本附加到这些对象的最佳方法。我使用字符串在其他地方调用方法的想法是可能的,但被认为昂贵且容易出错。如果您想了解为什么会这样认为,这里是link to my original question。

完整问题

我目前正在创建自己的纸牌游戏(单人游戏,所以我不需要担心多人游戏或服务器的问题)。我创建了一个可编写脚本的对象 (CardAsset) 来处理我所有填充的卡片都需要的一些字段。像这样的东西:

卡名(字符串) 成本(整数) 图片(图片) 描述文本(字符串)

现在我的原型的所有其他东西都完成了(比赛场地、套牌、我可以实例化从我的 CardAsset 填充的卡片等),我需要开始为我的卡片实际制作效果和对它们进行编程。

由于每张牌都不同,但有些效果是相同的(例如,“抽更多牌”和“对角色造成伤害”很常见),我想我可以通过更改我的脚本来节省大量时间对象 CardAsset 以包含一些基本脚本和变量:

卡名(字符串)

成本(整数)

图片(图片)

描述文本(字符串)

效果1(脚本)

Effect1Amount (int)

Effect2(脚本)

Effect2Amount (int)

Effect3(脚本)

Effect3Amount (int)

这样,我可以创建最多具有 3 种效果的 CardAssets(例如“对角色造成 X 伤害”或“抽 Y 牌”),然后在我的游戏中使用某些东西来调用这些脚本,并带有相关的玩家打出该卡时的变量(将任何不需要的插槽保留为空)。虽然任何真正独特的卡都可能需要它自己的脚本,但我认为这会大大缩短时间(而不是单独对每张卡进行编程)。

我的问题是我无法将脚本附加到 CardAsset,因此我认为合适的解决方法是在 CardAsset 的 Effect1/Effect2/Effect3 插槽中输入方法名称(DealDamage、DrawCards)(将它们更改为字符串),然后读取它们并在其他地方调用相关方法。

我了解此解决方案容易出错并且维护/更改可能会很痛苦,那么在这个特定项目中是否有更好的方法可以做到这一点?不幸的是,作为初学者,我目前缺乏元语言来轻松找到此问题的解决方案,因此任何指针都是最有益的。我很乐意自己做更多的研究,我只需要指出正确的方向。

【问题讨论】:

您可以阅读的内容可能会对您有所帮助。 1. 您可以使用装饰器或复合图案将多种效果组合成一个。这样您就不需要多种效果方法,也不会局限于三种效果。 2. 您可以阅读有关 C# 委托的信息。它们允许您将方法作为参数传递给另一个方法。我从 Unity/C# 开始学到的第一课 - 当字符串与显示文本无关时,忘记字符串的存在。 3. 或者您可以将效果设为可编写脚本的对象,并以您想要的任何组合将它们附加到 CardAssets。 不如使用接口并始终调用相同的方法呢?就像interface ICardEffect void RunEffect() 一样,您可以附加任意数量的效果并运行它们...... 【参考方案1】:

您可以为此使用抽象类(我建议只使用一两个函数)。

喜欢:

public abstract class Effect

    public virtual void runeffect(Card target)
    
    
    

    public virtual void runeffect()
    
    
    

然后,对于您想要的每个效果,您将编写一个使用抽象效果类(继承)的脚本,并将您想要的效果添加到主脚本中。 所以你的主要卡片脚本将是这样的:

public Effect[] cardeffects; //add effects you want here
//like --> public Effect cardeffects=new Drawcard(2);
public void playthecard()

    foreach(Effect e in cardeffects)
        e.runeffect;

还有一个效果例子:

public class DrawCard : Effect

    public override void runeffect()
    
    
    

【讨论】:

那么他会怎么做add them into your card in Unity.? ;) 你是对的。我在想别的东西我正在编辑它。谢谢 :)【参考方案2】:

我会建议像 abstract 这样的课程

public abstract class CardEffect : ScriptableObject

    // The GameManager reference might be useful later when you need e.g. StartCoroutine
    // or a transform reference where to spawn objects to etc
    // or simply to Invoke your different behaviours on 
    public abstract void RunEffect(GameManager behaviour);

然后您可以得出不同的效果及其行为,例如

[CreateAssetMenu]
public class DamageCardEffect : CardEffect

    [SerializeField] private float damageAmount = 1.5f;

    public override void RunEffect(GameManager gameManager)
    
        gameManager.SelectCard(DealDamageToSelectedCard);
    

    private void DealDamageToSelectedCard (Card card)
    
        Card.DealDamage(damageAmount);
    

或者以同样的方式

[CreateAssetMenu]
public class DrawCardsEffect : CardEffect

    [SerializeField] private int drawAmount = 3;

    public override void RunEffect (GameManager gameManager)
    
        gameManager.DrawCards(drawAmount);
    

等等……

那么在你的卡片里你就可以拥有

[CreateAssetMenu]
public class Card : ScriptableObject

    [SerializeField] private CardEffect [] effects;

    public void PlayCardEffects(GameManager gameManager)
    
        foreach(var effect in effects)
        
            effect.RunEffect(gameManager);
        
    


最后,在您的 GameManager 主控制器类中,您将实现相应的方法并引用不同的 Card 资产作为例如卡片组,例如

public class GameManager : MonoBehaviour

    // Reference assets via Inspector or edit it on runtime
    public List<Card> deck;

    public List<Card> hand;

    public void DrawCards(int amount)
    
        for(var i = 0; i < amount; i ++)
        
            if(deck.Count <= 0) return;

            var card = deck[0];
            deck.RemoveAt(0);

            hand.Add(card);
        
    

    public void SelectCard(Action<Card> onSelected)
    
        StartCoroutine(SelectCardRoutine (onSelected));
    

    private IEnumerator SelectCardRoutine(Action<Card> onSelected)
    
        // Somehow wait until User selects a Card

        // then Invoke the callback
        onSelected?.Invoke(theSelectedCard);
    

【讨论】:

以上是关于寻找比在 Scriptable Objects 中使用字符串在别处调用特定方法更好的解决方案的主要内容,如果未能解决你的问题,请参考以下文章

Unity零基础到进阶 | Unity中Scriptable Object介绍学习

Unity零基础到进阶 | Unity中Scriptable Object介绍学习

为啥在大型数据库上查找具有特定 ID 的对象的 Django 查询比在较小数据库上要慢?

Scriptable Object

如何在 PHP Scriptable Web Browser 中调用 javascript 函数

使用 Scriptable iOS 登录 PremiumSim