【问题标题】:How to structure / design events and tasks in a quest system to make code manageable?如何在任务系统中构建/设计事件和任务以使代码易于管理?
【发布时间】:2016-10-23 05:25:04
【问题描述】:

我正在创建一个 Javascript 游戏,它提供任务供用户玩。每个任务都有一些任务,当满足某些要求时完成。以下是我最初打算使用的设计。

如您所见,每个TaskGameEvent(简化为Event)都有一个类型和一些数据(封装在一个字典中)。当一个事件被发送到QuestManager 时,它被传递给活动的Quest 对象,假设它们的类型相同,这些对象又将它传递给每个Task。然后针对事件中的相关data 测试任务中的每个requirement。我不做1:1检查的原因是因为一个事件可能有额外的数据。

例如data{item: someItem, locationFound: someLocation} != requirement{item: someItem}

这在最大限度地减少需要创建的类的数量方面非常有效。事件和任务是通用的,因此无需创建大量子类即可轻松创建和测试它们。

但是这种设计产生了一个问题。由于其普遍性,如果不首先找到相同类型的事件并检查发送给它的数据,就永远无法确定应该对任务提出什么要求。随着项目的扩展,这将成为管理的噩梦。

相反,我可以为 EventTask 创建涵盖各种类型的子类,如下所示:

Event -> ItemPickupEvent, MoveEvent, etc.
Task -> ItemPickupTask, ItemPickedUpAtLocationTask, MoveTask, etc.

但它们之间的数据相似,似乎非常不必要。

我最后的想法是创建数据对象来封装某些事件类型传递的数据,如下:

ItemPickupData、MoveData、TalkData 等

事件和任务可以被赋予适当的数据对象。这为正在使用的数据提供了更好的结构。如果一条数据不应该成为需求,可以简单地默认为空。

在可维护性和结构设计方面,这是一个适当的解决方案还是有更好的方法来解决这个问题?

谢谢。

【问题讨论】:

    标签: javascript oop design-patterns event-handling observer-pattern


    【解决方案1】:

    由于它的普遍性,如果不首先找到相同类型的事件并检查发送给它的数据,就永远无法确定应该对任务提出什么要求。

    据我了解,问题是几乎相同的数据在事件对象和任务对象中重复。

    例如,您有Event { 'type': 'ItemPickup', data: { 'name': 'MagicSword', 'kind': 'weapon' } }

    然后你想添加一个任务来拾取魔法剑并创建Task { 'type': 'ItemPickup', data: { 'name': 'MagicSword' } }。 所以这就是问题所在,在这里您需要查找您是如何配置事件并将相同的数据放入任务对象中的。

    我认为将这些数据移动到单独的对象中的解决方案应该很有效。 另一件看起来不正确的事情是你有type 字段,它暗示你需要有子类。 如果您创建 ItemPickupDataMoveData 等数据对象,这也将得到解决。

    另一种方法是让Event 本身成为这个数据对象,并有这样的东西(伪代码):

    class Event // base class for events with common data and methods
    
    // the subclasses can be almost empty
    class ItemPickupEvent extends Event  
    class MoveEvent extends Event
    
    class Task
        _requirements = [
            new ItemPickupEvent('MagicSword'),
            // and maybe it is even better to wrap events into "Requirment" objects, 
            // which can, for example, be marked as completed
            new Requirement(new MoveEvent('OldForest')),
            ...
        ]
    

    然后您可以通过这种方式管理需求:

    class Task
        isComplete(event)
            complete = false
            // go over requirements to see if this task is complete
            for requirement in this._requirments:
               complete = complete && requirement->isComplete(event)
            return complete
    
    class Requirement
        isComplete(event)
            # check event class and properties to understand if the requirement is satisfied
            if class(event) == class(this._event):
                if event.isNameMatches(this._event):
                   this->_complete = true
            return this->_complete
    

    也可以在没有类名检查的情况下完成。 这个想法是让基础Requirement 类对每个事件类都有特殊的方法,并默认拒绝它们。 然后子类可以处理特定的事件类型:

    class Requirement
        isComplete(event)
            // redirect the type detection to event object
            return event->isComplete(this)
    
        isCompleteItemPickup(event)
            return false
    
        isCompleteMove(event)
            return false
    
    class ItemPickupRequirement
        isCompleteItemPickup(event)
            // here we know that event is ItemPickupEvent
            if event.isNameMatches(this._event):
                   this->_complete = true
            return this->_complete
    
    class MoveRequirement
        isCompleteItemPickup(event)
            // here we know that event is MoveEvent
            if event.isNameMatches(this._event):
                   this->_complete = true
            return this->_complete
    

    Requirment 类的 isComplete 方法从 Task 类中使用,它将类型检测重定向到事件对象。 事件对象然后将自身传递回需求的适当方法:

    class Event
        isComplete(requirement) // should be implemented in subclasses
    
    class ItemPickupEvent
        isComplete(requirement)
            // here we call the specific requirement method
            // now we know the exact event object type (it is ItemPickupEvent)
            return requirement->isCompleteItemPickup(this)
    
    class MoveEvent
        isComplete(requirement)
            return requirement->isCompleteMove(this)
    

    结构可能看起来有点复杂,但根据具体情况,您可能需要Requirement 类及其子类(例如,可以有通用的ItemPickupRequirement 和一些特定的PowerItemPickupRequirement)。

    【讨论】:

    • 感谢您的回答。似乎最终不可避免地要为事件和任务恢复到子类。正如您所说,有时要求必须更加具体(即可能需要处理额外的属性)。目前,任务所需的所有数据都将在 EventData 对象中捕获,因此如果我只是检查非空属性,我可以进行一些灵活的测试。如果将来由于某种原因这会产生问题,我将不得不考虑一种更符合您自己想法的方法。
    • @sookie 再注意一点,我认为避免创建大量类是一个错误,看起来它可以让您避免复杂性和大量打字,但实际上并没有。您仍然需要为所有实体编写代码,只是使用小型数据结构代替小型子类,但管理小型子类比无类型和无方法数据结构更容易。
    猜你喜欢
    • 2016-01-25
    • 2015-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-25
    • 1970-01-01
    相关资源
    最近更新 更多