寻找比使用可脚本编写的对象中的字符串在其他地方调用特定方法更好的解决方案

问题描述

有人要求我在问题的先前版本中描述用例。因此,这篇文章非常详细,并详细介绍了我要做什么。如果您遇到了这个问题,希望可以帮助您解决自己的问题,请使用以下TL:DR快速入门,以了解是否值得进一步研究:

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

完整问题

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

我现在已经完成了原型的所有其他工作(在比赛场地,甲板上,我可以实例化从CardAsset填充的卡,等等),我需要开始为卡和对它们进行编程。

由于每张纸牌都不同,但是某些效果是相同的(例如“多画几张纸”和“对角色造成的伤害”非常普遍),所以我认为可以通过更改可编写脚本的方式节省很多时间对象CardAsset包含一些基本脚本和变量:

  • 卡名(字符串)

  • 成本(int)

  • 图片图片

  • 描述文字(字符串)

  • Effect1 (脚本)

  • Effect1Amount (int)

  • Effect2 (脚本)

  • Effect2Amount (int)

  • Effect3 (脚本)

  • Effect3Amount (int)

这样,我可以创建多达3种效果的CardAsset(例如“对角色造成X伤害”或“绘制Y卡”),然后在我的游戏中使用某些东西来调用具有相关联的脚本玩家玩该卡时的变量(将不需要的插槽留为null)。尽管任何真正独特的卡都可能需要它自己的脚本,但我认为这会大大节省时间(而不是单独编程每张卡)。

我的问题是我无法将脚本附加到CardAsset上,所以我发现合适的解决方法是让我在CardAsset的Effect1 / Effect2 / Effect3插槽中键入方法名称(Dealdamage,DrawCards)(将它们更改为字符串),然后让某些内容读取它们并在其他位置调用关联的方法

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

解决方法

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

赞:

public abstract class Effect
{
    public virtual void runeffect(Card target)
    {
    
    }

    public virtual void runeffect()
    {
    
    }
}

然后,针对所需的每种效果,将编写一个脚本,该脚本使用抽象的Effect类(继承)并将所需的效果添加到主脚本中。 因此,您的主卡脚本将是这样的:

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()
    {
    
    }
}
,

我建议像这样的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);
    }
}