创建健壮的物品系统


12

我的目标是创建一个模块化/尽可能通用的物料系统,该系统可以处理以下内容:

  • 可升级物品(+6武士刀)
  • Stat修饰符(+15敏捷)
  • 物品修饰符(%X几率造成Y伤害,几率冻结)
  • 充电物品(魔术师使用30次)
  • 套装物品(装备4件X激活Y功能)
  • 稀有度(常见,独特,传奇)
  • 可分解的(分解成一些手工材料)
  • 可制作(可以使用某些材料制作)
  • 消耗(5分钟%X攻击强度,恢复+15 hp)

*我能够解决以下设置中加粗的功能。

现在,我尝试添加许多选项以反映我的想法。我不打算添加所有必要的功能,但是我希望能够按照自己的意愿实施它们。这些也应该与库存系统和数据序列化兼容。

我计划完全不使用继承,而是使用实体组件/数据驱动的方法。最初,我想到的系统具有:

  • BaseStat:通用类,可随时随地保存统计信息(也可用于项和字符统计信息)
  • Item:包含数据的类,例如列表,名称,itemtype和与ui,actionName,description等相关的事物。
  • IWeapon:武器界面。每个武器都有实现IWeapon的自己的类。这将具有Attack(攻击)和对角色统计的引用。装备武器后,它的数据(物品类别的stat)将被注入角色stat(无论拥有什么BaseStat,它都会作为Stat奖励添加到角色类别中)因此,例如,我们想制作一把剑(想产生带有json的物品类),所以剑将对角色属性增加5点攻击。因此,我们有一个BaseStat为(“ Attack”,5)(我们也可以使用enum)。装备时,此属性将作为BonusStat(将是不同的类)添加到角色的“攻击”属性中。因此,名为Sword的类将实现IWeapon项目类别已创建。因此,我们可以向这把剑注入角色统计数据,并且在攻击时,它可以从角色统计数据中检索总的攻击统计数据,并在Attack方法中造成伤害。
  • BonusStat:是一种将统计信息添加为奖励而无需触及BaseStat的方法。
  • IConsumable:与IWeapon相同的逻辑。添加直接属性相当容易(+15 hp),但是我不确定要使用此设置添加临时武器(%x攻击5分钟)。
  • IUpgradeable:可以使用此设置来实现。我正在考虑将UpgradeLevel作为基本属性,在升级武器时会增加它。升级后,我们可以重新计算武器的BaseStat以匹配其升级级别。

在此之前,我可以看到该系统相当不错。但是对于其他功能,我认为我们还需要其他东西,因为例如,我无法在其中实现Craftable功能,因为我的BaseStat无法处理该功能,这就是我遇到的问题。我可以将所有成分添加为统计信息,但这没有任何意义。

为了使您更轻松地进行此操作,以下一些问题可能会对您有所帮助:

  • 我是否应该继续执行此设置以实现其他功能?没有继承就可能吗?
  • 您有什么办法可以想到,在没有继承的情况下实现所有这些功能?
  • 关于物品修饰符,如何实现?因为它的性质非常通用。
  • 有什么建议可以做些什么来简化构建这种架构的过程?
  • 有没有我可以挖掘的与此问题相关的来源?
  • 我确实尽力避免继承,但是您认为在保持相当可维护性的同时,可以轻松地通过继承解决/实现这些继承吗?

由于我的问题范围很广,因此可以随意回答一个问题,这样我就可以从不同的方面/人那里获得知识。


编辑


遵循@ jjimenezg93的回答,我用C#创建了一个非常基本的系统进行测试,它可以正常工作!看看是否可以添加任何内容:

public interface IItem
{
    List<IAttribute> Components { get; set; }

    void ReceiveMessage<T>(T message);
}

public interface IAttribute
{
    IItem source { get; set; }
    void ReceiveMessage<T>(T message);
}

到目前为止,IItem和IAttribute是基本接口。不需要(我可以想到)具有消息的基本接口/属性,因此我们将直接创建一个测试消息类。现在开始测试课程:


public class TestItem : IItem
{
    private List<IAttribute> _components = new List<IAttribute>();
    public List<IAttribute> Components
    {
        get
        {
            return _components;
        }

        set
        {
            _components = value;
        }
    }

    public void ReceiveMessage<T>(T message)
    {
        foreach (IAttribute attribute in Components)
        {
            attribute.ReceiveMessage(message);
        }
    }
}

public class TestAttribute : IAttribute
{
    string _infoRequiredFromMessage;

    public TestAttribute(IItem source)
    {
        _source = source;
    }

    private IItem _source;
    public IItem source
    {
        get
        {
            return _source;
        }

        set
        {
            _source = value;
        }
    }

    public void ReceiveMessage<T>(T message)
    {
        TestMessage convertedMessage = message as TestMessage;
        if (convertedMessage != null)
        {
            convertedMessage.Execute();
            _infoRequiredFromMessage = convertedMessage._particularInformationThatNeedsToBePassed;
            Debug.Log("Message passed : " + _infoRequiredFromMessage);

        }
    }
} 

public class TestMessage
{
    private string _messageString;
    private int _messageInt;
    public string _particularInformationThatNeedsToBePassed;
    public TestMessage(string messageString, int messageInt, string particularInformationThatNeedsToBePassed)
    {
        _messageString = messageString;
        _messageInt = messageInt;
        _particularInformationThatNeedsToBePassed = particularInformationThatNeedsToBePassed;
    }
    //messages should not have methods, so this is here for fun and testing.
    public void Execute()
    {
        Debug.Log("Desired Execution Method: \nThis is test message : " + _messageString + "\nThis is test int : " + _messageInt);
    }
} 

这些是所需的设置。现在我们可以使用系统了(以下是Unity)。

public class TestManager : MonoBehaviour
{

    // Use this for initialization
    void Start()
    {
        TestItem testItem = new TestItem();
        TestAttribute testAttribute = new TestAttribute(testItem);
        testItem.Components.Add(testAttribute);
        TestMessage testMessage = new TestMessage("my test message", 1, "VERYIMPORTANTINFO");
        testItem.ReceiveMessage(testMessage);
    }

}

将此TestManager脚本附加到场景中的组件上,您可以在调试中看到消息已成功传递。


为了说明问题:游戏中的每个项目都将实现IItem接口,并且每个属性(名称都不应使您感到困惑,这意味着项目功能/系统。如Upgradeable或Disenchantable)将实现IAttribute。然后,我们有了一种处理消息的方法(为什么需要消息,将在进一步的示例中进行说明)。因此,在上下文中,您可以将属性附加到项目,然后让其余的工作为您完成。这非常灵活,因为您可以轻松添加/删除属性。因此,一个伪示例将是可分解的。我们将在Disenchant方法中有一个名为Disenchantable(IAttribute)的类,它将要求:

  • 列出成分(当分解物品时,应该给玩家什么物品)注意:IItem应该扩展为具有ItemType,ItemID等。
  • int resultModifier(如果实现了某种增强分解功能的功能,则可以在此处传递一个int来增加分解时收到的成分)
  • int FailureChance(如果分解进程有失败的机会)

等等

这些信息将由名为DisenchantManager的类提供,它将接收该物品并根据物品(被分解时物品的成分)和玩家进程(resultModifier和failureChance)形成此消息。为了传递此消息,我们将创建DisenchantMessage类,该类将充当此消息的主体。因此DisenchantManager将填充DisenchantMessage并将其发送到该项目。Item将收到消息并将其传递给所有附加的属性。由于Disenchantable类的ReceiveMessage方法将查找DisenchantMessage,因此只有Disenchantable属性将接收此消息并对其进行操作。希望这能为我带来更多的收获:)。


1
您可能会在For Honor
DMGregory

@DMGregory嘿!感谢您的链接。尽管它看起来非常足智多谋,但不幸的是,我需要进行实际的演讲才能理解这一概念。而且,我尝试找出实际的话题,很遗憾,这是GDCVault仅限会员使用的内容(一年495 $太疯狂了!)。(如果您具有GDCVault成员资格,则可以在此处找到相关话题-,
Vandarthul

您的“ BaseStat”概念究竟如何排除可制造武器?
Attackfarm's

这并不是真正的排除,但并不完全适合我的想法。可以在制作配方中添加“ Wood”(木材2)和“ Iron”(铁)5作为BaseStat,这将产生剑。我认为在这种情况下,将BaseStat的名称更改为BaseAttribute会更好。但是,该系统仍无法满足其目的。考虑耗时5分钟-攻击力%50的消耗品。我如何将其作为BaseStat传递?“ Perc_AttackPower”,50,这需要解决为“如果是Perc,则将整数视为百分比”,并且缺少分钟信息。希望你明白我的意思。
Vandarthul

@Attackfarm再考虑一下,可以通过具有一个int列表而不是一个int来扩展此“ BaseStat”概念。因此,对于消耗性增益,我可以提供“ Attack”,50、5、1,并且IConsumable将查找3个整数,即1.-值,2--分钟,3-如果不是百分比。但是,当其他系统进入系统并被迫仅以int形式进行解释时,它会带来激励。
Vandarthul

Answers:


6

我认为您可以通过使用具有基本继承的实体组件系统和消息传递系统来实现可伸缩性和可维护性方面的目标。当然,请记住,此系统是我能想到的最模块化/可自定义/可扩展的系统,但是它的性能可能会比当前解决方案差。

我将进一步说明:

首先,创建一个接口IItem和一个interface IComponent。您要存储的任何项目都必须继承IItem,而要影响您的项目的任何组件都必须继承IComponent

IItem将有一个组件数组和一个处理方法IMessage。这种处理方法只是将任何收到的消息发送到所有存储的组件。然后,对给定消息感兴趣的组件将相应地起作用,其他组件将忽略它。

例如,一条消息属于类型损坏,它同时通知攻击者和被攻击者,因此您知道击中了多少,并可能根据该损坏向愤怒的酒吧收费。或者,如果敌人击中您并造成少于2HP的伤害,则其AI可以决定运行。这些是愚蠢的示例,但是使用与我提到的系统类似的系统,除了创建消息和适当的处理以添加大多数此类机制之外,您无需做任何其他事情。

在这里有一个带有消息传递的ECS的实现,但这用于实体而不是项目,并且使用C ++。无论如何,我认为它可以帮助,如果你看一看component.hentity.hmessages.h。有很多事情有待改进,但这在我简单的大学工作中对我有用。

希望能帮助到你。


嘿@ jjimenezg93,谢谢您的回答。因此,为了详细说明您所解释的内容:我们想要一把剑,它是:-Disenchantable [Component]-Stat Modifier [Component]-Upgradeable [Component]我有一个包含所有类似内容的动作列表-DISENCHANT-MODIFY_STAT-升级每当项目接收到此消息,遍历其所有组件并发送此消息时,每个组件都会知道如何处理给定消息。从理论上讲,这看起来很棒!我没有检查您的示例,但我会非常感谢!
Vandarthul

@Vandarthul是的,基本上就是这个想法。这样,物品将完全不了解其组件,因此完全没有耦合,同时,它具有您所需的所有功能,这些功能也可以在不同类型的物品之间共享。希望它能满足您的需求!
jjimenezg93 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.