C# 如何为一个简单的游戏设计一个解耦的类结构?
现在我有六门课:C# 如何为一个简单的游戏设计一个解耦的类结构?,c#,design-patterns,C#,Design Patterns,现在我有六门课: 侦听器-管理套接字连接 世界-实体和任务的集合 Ticker-坐标更新世界 MessageProcessor-接收来自玩家的命令 智力-控制非玩家角色的行为 任务-跟踪和执行任务 但他们就像意大利面一样,到处都是彼此的参照。。。世界是MessageProcessor、Intelligence和Tasks类修改的数据模型。股票代码协调这三个类更新世界。侦听器由MessageProcessor用于输入消息,由其他类用于推送更新 我怎样才能改善这种情况 不久前我做了一个演讲。这个主题
这看起来确实比实际情况更复杂。世界对象对任何其他对象都没有直接依赖关系,而其他每个类最多有一个直接依赖关系。您还可以将计时器与外界隔离开来,因为那里可能不需要计时器,但最大的障碍之一可能是处理不同适配器之间的同步。嗯,我不确定我是否完全了解您遇到的问题,但从您迄今为止所阐述的内容来看,我有一些可能性。(我可能实际上是在建议一些已经做过的事情,因为我不确定从一行文字的描述中是否有足够的内容可以完全理解 模型 我想说的是,从您所描述的内容来看,让我印象最深刻的是,您需要开始在类模型中实现公共功能;您需要可以用来派生高级对象的接口或基类 通过这种方式,你可以在不需要额外努力的情况下始终如一地处理事情。我认为“架构层”的概念可以作为思考它的第一个切入点,(例如,低层次的硬件、套接字处理等,然后是中间层次的东西,比如游戏中发生了什么,以及游戏机制如何工作的细节等,还有高层次的东西,比如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
{
}
}