如何为一个简单的游戏布局一个解耦的类结构?

Tim*_*ley 5 c# design-patterns

现在我有六个班级:

  1. 侦听器 - 管理套接字连接
  2. World - 实体和任务的集合
  3. Ticker - 坐标更新世界
  4. MessageProcessor - 接收来自玩家的命令
  5. 智力 - 控制非玩家角色的行为
  6. 任务 - 跟踪和执行任务

但是它们就像意大利面条一样到处相互引用…… The World 是 MessageProcessor、Intelligence 和 Tasks 类修改的数据模型。Ticker 协调这三个类更新世界。MessageProcessor 使用 Listener 接收消息,其他类使用 Listener 推送更新。

我怎样才能改善这种情况?

she*_*fly 3

好吧,我不确定我是否全面了解您遇到的问题,但从您到目前为止所提出的情况来看,我有一些可能性。(我实际上可能是在建议一些已经完成的事情,因为我不确定我是否从单行描述中获得了足够的信息来完全理解。

该模型

我想说,从您所描述的内容来看,让我印象最深刻的是您将希望开始在类模型中实现常见功能;您将需要可用于派生高级对象的接口或基类。

这样您就可以一致地处理事情,而无需付出额外的努力。我认为“架构层”的想法可以作为思考它的第一切很有用(例如低级硬件、套接字处理等,然后是中间层的东西,例如游戏中发生的事情) ,以及游戏机制如何工作等背后的细节,以及诸如 PC 或 NPC 可以做什么、环境在做什么等高级内容,以及你永远不想“跳跃”层的想法)。然而,归根结底,重要的是为你的游戏找到正确的抽象,并以这样的方式组织一切,这样你就不会觉得你正在处理的代码正在做两种完全不同的事情东西的。

因此,首先,让我们接受这样一个事实:听起来(自然地)有很多东西与世界状态相互作用。对于这样的事情,将大量“东西”分解到一个类中可能是有利的,然后大多数情况下只有一个类来做这项工作。理想情况下,您可以在它自己的小组中实现事件通信/消息传递,这样就不需要用处理内容的细节来污染您的更高级别的对象。

例如,你想关注高层对象中高层正在做的事情:在人工智能中,可能“开始向某个位置移动”、“加快速度”、“停止移动”;并在环境子系统中执行“开始下雨”、“增加风速”、“调暗灯光”;在用户类“火武器”、“睡眠”、“施法”中。但我不希望我的任何高级课程知道诸如“向世界发送消息”、“重置口渴计时器”、“接收套接字数据”或“健康周期滴答”之类的事情。(这些只是说明,不是建议。;D)

活动

例如,我认为让一个对象负责向世界发送事件可能是有价值的,这样就不再让每个人都与每个人交谈。我可能只是创建一组东西来处理一般事件。因此,也许您可​​以使用EventMain和 anenumEvents来使每种类型的事件都有一个特殊的 ID。然后使用 Event 作为需要额外功能的特定事件的基类。(我心里既有 ID 又有派生模型,因此像 Dispatcher 这样的东西可能只需要知道有关事件的非常基本的事情,而不必知道派生类。例如,调度程序可以接收事件并将其发送出去,而无需了解派生事件的内部结构。这可能有用,也可能没有用,但有这些选项是件好事。)您还可以拥有一个EventDispatcher具有要发送到其他子系统的事件队列。

您将需要一些通用的东西来接收和发送事件。您可以执行EventSourcer独立EventSinker的类,这些类可以在生成或接收事件的任何类中设置。或者,您可以这样做IEventSourceIEventSink这样您就可以在多个类上实现一个公共接口,或者可能实现两个类的公共类EventSourceAndSink,并且它是基类模型的一部分,以便任何可能需要处理事件的东西都可以派生从中。

我可能会ProtocolEncoder上课ProtocolDecoder。您始终可以将它们组合成一个对象,但是将它们作为两段独立的代码可能很有价值,并且如果做得充分,则不会导致任何问题。您还可以有一个 ProtocolHelper类来找出两者之间的任何共同点。编码器的唯一工作是从网络接收消息并将其转换为游戏的事件,然后将其传递到EventDispatcher. 解码器类将从调度程序获取需要发送到网络的事件,并从中获取数据并将其发送出去。

如何到达目的地

由于您确实有工作代码,因此我建议您从任何看起来自然的地方开始执行它。这可能是让你陷入困境的事情,或者你注意到在不同地方非常相似的事情,你可以用一个类或其他类型的抽象来定期处理这些事情,然后取出旧的并放入新的。一旦你找到了一个可行的类模型的第一部分,这应该会给你基于你已有的想法,并且当你不断地重新考虑你的模型,修复有问题的东西,起泡沫,冲洗,重复。

事实上,这并不需要做很多工作,事实上,我在代码工作中最令人欣慰的时刻是当我能够进行一次整洁的重构,使以前可怕的混乱状况变得更好的时候,消除了很多难以理解的代码,并用更容易理解的代码替换它,用更少的代码行,这为我的下一个添加开辟了一条道路,使我的下一个添加成为一种乐趣,而不是另一个“zomg我不必我再次触摸该代码可以吗?” 片刻。

祝你好运,下面是我正在谈论的事情的名义指南;第一部分更详细,因为主要事件类是更重要的概念之一,然后我尝试仅对类及其交互方式进行名义概述。我确信我可以在这上面花更多的时间,但此时我只想说:如果您有疑问,请问我,我会尽我所能给您一个好的答案:)

代码中的想法

哦,还有一件值得注意的事情是,如果您有多个线程,我根本没有处理增加的复杂性;如果你愿意的话,事情从简单到复杂都可以管理,但那是另一个练习。:)

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

    // this is internal to the project namespace, say, TimsWorld_o_Hurt
    // I'm now resisting calling everything Xxxx_o_Hurt :)

    // examples o' hurt
using EventHandlingLibrary;

namespace EventHandlingLibrary
{
    // this will provide the base class for all the events, and can
    // also have static methods like factory methods, destination 
    // lookups etc. 

    // I have the enums set to protected with the intent being that
    // specific factory functions should be called by other classes.
    // You should change this if it turns out to be too cumbersome.
    public class EventOfHurt
    {
        #region Event Definitions
            protected enum EEventType
            {
                // System Events
                SystemInitializing,
                SubsystemInitComplete,
                FatalErrorNotification,
                SubsystemPingReponse,
                SubsystemPingRequest,

                // Network Events
                FrameRateError,
                ThroughputData,
                ServerTimeout,
                ServerPingRequest,
                ServerPingResponse,

                // User Events
                WeaponsFire,
                MovementNotification,
                FatigueUpdate

                // and so forth
            }

            protected enum ESubsystem
            {
                System,
                Dispatcher,
                TickerTimer,
                WorldEntity,
                WorldTaskManager,
                UserMessageProcessor,
                NetworkListener,
                NetworkTransmitter,
                ProtocolEncoder,
                ProtocolDecoder,
                PlayerCharacter,
                NonPlayerCharacter,
                EventSink,
                EventSource

                // and such
            }
        #endregion

        #region Event Information
            public Guid EventId { get; protected set; }
            public EEventType EventType { get; protected set; }
            public ESubsystem SourceSubsystem { get; protected set; }
            public ESubsystem DestSubsystem { get; protected set; }

            private List<Tuple<EventOfHurt, DateTime>> 
                myEventReferences;

            // the event(s) that triggered it, if any, and when rec'd
            public Tuple<EventOfHurt, DateTime>[] 
                EventReferences 
            { 
                get { return myEventReferences.ToArray(); } 
            }

            public DateTime Timestamp { get; private set; }
        #endregion

        // we'll be using factor methods to create events
        // so keep constructors private; no default constructor
        private EventOfHurt(
            EEventType evt,
            ESubsystem src, 
            ESubsystem dest = ESubsystem.Dispatcher
        )
        {
            EventType = evt;
            SourceSubsystem = src;
            DestSubsystem =  dest;

            EventId = Guid.NewGuid();
            Timestamp = DateTime.UtcNow;
        }

        // called to create a non-derived event for simple things; 
        // but keep other classes limited to calling specific factory
        // methods
        protected static EventOfHurt CreateGeneric(
            EEventType evt, ESubsystem src, 
            ESubsystem dest = ESubsystem.Dispatcher,
            Tuple<EventOfHurt, DateTime>[] reasons = null
        )
        {
            EventOfHurt RetVal;

            if (dest == null)
                dest = ESubsystem.Dispatcher;

            List<Tuple<EventOfHurt, DateTime>> ReasonList = 
                new List<Tuple<EventOfHurt,DateTime>>();

            if (reasons != null)
                ReasonList.AddRange(reasons);

            // the initializer after the constructor allows for a 
            // lot more flexibility than e.g., optional params
            RetVal = new EventOfHurt(evt, src) {
                myEventReferences = ReasonList
            };

            return RetVal;
        }

        // some of the specific methods can just return a generic
        // non-derived event
        public static EventOfHurt CreateTickerTimerEvent(
            EEventType evt, ESubsystem dest
        )
        {
            ESubsystem src = ESubsystem.TickerTimer;
            return CreateGeneric(evt, src, dest, null);
        }

        // some may return actual derived classes
        public static EventOfHurt CreatePlayerActionEvent(
            EEventType evt, ESubsystem dest,
            Tuple<EventOfHurt, DateTime>[] reasons
        )
        {
            PlayerEvent PE = new PlayerActionEvent(42);
            return PE;
        }
    }

    // could have some specific info relevant to player 
    // events in this class, world location, etc.
    public class PlayerEvent :
        EventOfHurt
    {
    };

    // and even further speciailzation here, weapon used
    // speed, etc. 
    public class PlayerActionEvent :
        PlayerEvent
    {
        public PlayerActionEvent(int ExtraInfo)
        {
        }
    };
}

namespace EntitiesOfHurt
{
    public class LatchedBool
    {
        private bool myValue = false;
        public bool Value
        {
            get { return myValue; }
            set {
                if (!myValue)
                    myValue = value;
            }
        }
    }

    public class EventOfHurtArgs :
        EventArgs
    {
        public EventOfHurtArgs(EventOfHurt evt)
        {
            myDispatchedEvent = evt;
        }

        private EventOfHurt myDispatchedEvent;
        public EventOfHurt DispatchedEvent
        {
            get { return myDispatchedEvent; }
        }
    }

    public class MultiDispatchEventArgs :
        EventOfHurtArgs
    {
        public MultiDispatchEventArgs(EventOfHurt evt) :
            base(evt)
        {
        }

        public LatchedBool isHandled; 
    }

    public interface IEventSink
    {
        // could do this via methods like this, or by attching to the
        // events in a source
        void MultiDispatchRecieve(object sender, MultiDispatchEventArgs e);
        void EventOfHurt(object sender, EventOfHurtArgs e);

        // to allow attaching an event source and notifying that
        // the events need to be hooked
        void AttachEventSource(IEventSource evtSource);
        void DetachEventSource(IEventSource evtSource);
    }

    // you could hook things up in your app so that most requests
    // go through the Dispatcher
    public interface IEventSource
    {
        // for IEventSinks to map
        event EventHandler<MultiDispatchEventArgs> onMultiDispatchEvent;
        event EventHandler<EventOfHurtArgs> onEventOfHurt;

        void FireEventOfHurt(EventOfHurt newEvent);
        void FireMultiDispatchEvent(EventOfHurt newEvent);

        // to allow attaching an event source and notifying that
        // the events need to be hooked
        void AttachEventSink(IEventSink evtSink);
        void DetachEventSink(IEventSink evtSink);
    }

    // to the extent that it works with your model, I think it likely
    // that you'll want to keep the event flow being mainly just
    // Dispatcher <---> Others and to minimize except where absolutely
    // necessary (e.g., performance) Others <---> Others.

    // DON'T FORGET THREAD SAFETY! :)
    public class Dispatcher : 
        IEventSource, IEventSink
    {
    }

    public class ProtocolDecoder :
        IEventSource
    {
    }

    public class ProtocolEncoder :
        IEventSink
    {
    }

    public class NetworkListener
    {
        // just have these as members, then you can have the
        // functionality of both on the listener, but the 
        // listener will not send or receive events, it will
        // focus on the sockets.

        private ProtocolEncoder myEncoder;
        private ProtocolDecoder myDecoder;
    }

    public class TheWorld :
        IEventSink, IEventSource
    {

    }

    public class Character
    {
    }

    public class NonPlayerCharacter :
        Character,
        IEventSource, IEventSink
    {
    }

    public class PlayerCharacter :
        Character,
        IEventSource, IEventSink
    {
    }
}
Run Code Online (Sandbox Code Playgroud)