Ну, я не уверен, что у меня есть полное представление о том, какие у вас проблемы, но у меня есть несколько возможностей из того, что вы изложили до сих пор.(Возможно, я на самом деле предлагаю кое-что, что уже сделано, поскольку я не уверен, что мне достаточно из однострочных описаний, чтобы полностью понять.
Модель
Я бы сказал, из чегоВы описали, главное, что меня поражает, это то, что вы захотите начать реализацию общей функциональности в модели классов, вам понадобятся либо интерфейсы, либо базовые классы, которые вы можете использовать для получения объектов высокого уровня.
Таким образом, вы можете обрабатывать вещи последовательно, без особых дополнительных усилий. Я думаю, что идея «архитектурных слоев» может быть полезна в качестве первого взгляда на то, как об этом думать (например, низкоуровневое оборудование, обработка сокетов и т. Д.)., затем средние слои, такие как то, что происходит в вашей игре, подробности о том, как работает игровая механика и т. д., и вещи высокого уровня, такие как то, что могут делать ПК или NPC, что делает окружающая среда и т. д.а также мысль о том, что вы никогда не хотите «прыгать» слои). Однако, когда дело доходит до этого важная вещьэто просто найти правильные абстракции для вашей игры и сохранить все организованным таким образом, чтобы вы никогда не чувствовали, что часть кода, над которым вы работаете, выполняет две совершенно разные разновидности вещей.
Итак, во-первых, давайте возьмем тот факт, что (естественно) есть много вещей, взаимодействующих с состоянием мира.Для чего-то вроде этого, вероятно, выгодно выделять много «материала» в один класс, и тогда в основном только один класс выполняет эту работу.В идеале вы, например, реализуете передачу сообщений / сообщений о событиях в своей собственной небольшой группе, так что вам не нужно загрязнять ваши объекты более высокого уровня тщательностью обработки вещей.
Например, вы хотите сосредоточиться на том, что происходит на высоком уровне в объектах более высокого уровня: в ИИ возможно «начать движение к определенному месту», «установить мою скорость», «остановить движение»;а в подсистеме среды «начать дождь», «увеличить скорость ветра», «приглушить свет»;в пользовательском классе «огнестрельное оружие», «сон», «заклинание».Но я бы не хотел, чтобы кто-либо из моих высокоуровневых классов даже знал о таких вещах, как «отправить сообщение миру», «сбросить таймер жажды», «получить данные сокета» или «цикл работоспособности».поставить галочку".(Это всего лишь разъяснения, а не предложения.; D)
События
Например, я думаю, что было бы полезно иметь один объект, отвечающий за отправку событий в Мир таким образом.вам больше не нужно, чтобы все говорили со всеми.Скорее всего, я бы просто создал набор вещей для обработки событий в целом.Итак, возможно EventMain
и enumEvents
, которые вы используете, чтобы у каждого типа событий был специальный идентификатор.А затем используйте Event в качестве базового класса для определенных событий, которые требуют дополнительной функциональности.(Я имею в виду как идентификатор, так и модель деривации, так что такие вещи, как Dispatcher, которые, вероятно, должны знать только самые базовые сведения о событии, не должны также знать о производных классах. Например,Диспетчер может принять событие и отправить его, даже не зная внутренности производного события. Это может или не может оказаться полезным, но хорошо иметь варианты.) Вы также можете иметь EventDispatcher
у которого есть очередь событий для отправки в другие подсистемы.
Вам понадобится что-то общее для получения и отправки событий.Вы можете создать EventSourcer
и EventSinker
автономные классы, которые можно настроить в любом классе, который генерирует или получает события.Или вы могли бы вместо этого сделать IEventSource
и IEventSink
, чтобы вы могли реализовать общий интерфейс для нескольких классов, или, возможно, общий класс 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
{
}
}