C# 如何为一个简单的游戏设计一个解耦的类结构?

C# 如何为一个简单的游戏设计一个解耦的类结构?,c#,design-patterns,C#,Design Patterns,现在我有六门课: 侦听器-管理套接字连接 世界-实体和任务的集合 Ticker-坐标更新世界 MessageProcessor-接收来自玩家的命令 智力-控制非玩家角色的行为 任务-跟踪和执行任务 但他们就像意大利面一样,到处都是彼此的参照。。。世界是MessageProcessor、Intelligence和Tasks类修改的数据模型。股票代码协调这三个类更新世界。侦听器由MessageProcessor用于输入消息,由其他类用于推送更新 我怎样才能改善这种情况 不久前我做了一个演讲。这个主题

现在我有六门课:

  • 侦听器-管理套接字连接
  • 世界-实体和任务的集合
  • Ticker-坐标更新世界
  • MessageProcessor-接收来自玩家的命令
  • 智力-控制非玩家角色的行为
  • 任务-跟踪和执行任务
  • 但他们就像意大利面一样,到处都是彼此的参照。。。世界是MessageProcessor、Intelligence和Tasks类修改的数据模型。股票代码协调这三个类更新世界。侦听器由MessageProcessor用于输入消息,由其他类用于推送更新

    我怎样才能改善这种情况

    不久前我做了一个演讲。这个主题是关于提高代码的可测试性,一般的解决方案是放松耦合。前面的答案主要集中在将网络相关代码与世界及其逻辑分离上,因为网络代码不可单元测试,也很难模仿

    这里给出的解决方案是为传入消息使用一个接口,这样您就可以将MessageProcessor(在另一篇文章中命名为Handler)与网络代码解耦,并且类似地,将UpdateNotifier与世界解耦

    虚线只是由接口或委托处理的间接引用。现在,世界和网络组件之间没有直接关系,这使得它可以测试。这实际上只是该模式的一个应用程序

    这似乎与您描述的设计没有什么不同,只是您可能缺少一些接口。有了这种用于推送更新的基于接口的UpdateNotifier模式,我基本上重用了相同的体系结构来处理NPC、任务或其他在别处处理的任何东西。您可以为特定区域选择所需的事件通知程序,并为它们实现一个具体的通知程序类,以便在同一个模型上有多个适配器


    这看起来确实比实际情况更复杂。世界对象对任何其他对象都没有直接依赖关系,而其他每个类最多有一个直接依赖关系。您还可以将计时器与外界隔离开来,因为那里可能不需要计时器,但最大的障碍之一可能是处理不同适配器之间的同步。

    嗯,我不确定我是否完全了解您遇到的问题,但从您迄今为止所阐述的内容来看,我有一些可能性。(我可能实际上是在建议一些已经做过的事情,因为我不确定从一行文字的描述中是否有足够的内容可以完全理解

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

    通过这种方式,你可以在不需要额外努力的情况下始终如一地处理事情。我认为“架构层”的概念可以作为思考它的第一个切入点,(例如,低层次的硬件、套接字处理等,然后是中间层次的东西,比如游戏中发生了什么,以及游戏机制如何工作的细节等,还有高层次的东西,比如PC或NPC可以做什么,环境在做什么,等等,还有你永远不想“跳转”层的想法).然而,归根结底,重要的是为你的游戏找到正确的抽象概念,并以这样一种方式组织一切,你永远不会觉得你正在编写的代码是在做两种完全不同的事情

    因此,首先,让我们考虑一下这样一个事实:它听起来像(自然地)有很多东西与世界状态交互。对于这样的事情,将很多“东西”分解到一个类中,然后主要由一个类来完成这项工作可能是有利的。理想情况下,你可以在它自己的小组中实现,比如说,事件通信/消息传递,这样就没有必要污染你你的更高层次的对象具有处理事物的本质

    e、 例如,您希望关注在更高级别对象的高级别上正在做的事情:在AI中,可能是“开始向某个位置移动”、“设置我的速度”、“停止移动”;在环境子系统中,则是“开始下雨”、“增加风速”、“昏暗灯光”;在用户类中是“火力武器”、“睡眠”、“施放法术”但我不想让我的任何高级课程知道“向世界发送消息”、“重置口渴计时器”、“接收套接字数据”或“健康周期滴答声”之类的事情。(这些都只是说明,而不是建议。)

    事件 例如,我认为让一个对象负责向世界发送事件可能很有价值,这样你就不再让每个人都与每个人交谈。我可能只是创建一组东西来处理一般的事件。因此,可以使用
    EventMain
    enumEvents
    ,以便每种类型的事件都有一个然后使用事件作为需要额外功能的特定事件的基类。(我同时考虑了ID和派生模型,因此,像Dispatcher这样可能只需要了解事件的基本情况的东西不必也知道派生类。例如,Dispatcher可以接收事件并发送它,而不必知道派生事件的内部情况。Th结果可能有用,也可能不有用,但最好有这些选项。)您还可以有一个
    EventDispatcher
    ,它有一个要发送到其他子系统的事件队列

    您将需要一些用于接收和发送事件的常用功能。您可以执行
    EventSourcer
    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
        {
        }
    }