How can I separate the structure of the decoupled class for a simple game?

I now have six classes:

  • Listener - manages socket connections
  • World - a set of objects and tasks
  • Ticker - coordinates the renewal of the world
  • MessageProcessor - receives commands from players
  • Intelligence - defines the behavior of non-player characters
  • Tasks - tracking and completing tasks

But they are like spaghetti with reference to each other everywhere ... The world is a data model that the classes MessageProcessor, Intelligence and Tasks modify. The ticker coordinates these three classes that renew the World. The listener is used by MessageProcessor to enter messages, and by other classes to update messages.

How can I improve this situation?

+4
source share
2 answers

Well, I'm not sure that I have a full idea of ​​what your problems are, but I have several possibilities from what you have put forward so far. (Perhaps, in fact, I suggest some things that have already been done, since I'm not sure that I have enough from single-layer descriptions to fully understand.

Model

I would say that from what you described, the main thing that strikes me is that you want to start implementing common functions in a class model; you will need interfaces or base classes that you can use to get high-level objects.

This way you can process things sequentially with little extra effort. I think the idea of ​​“architectural layers” can be useful as a first stretch of how to think about it (for example, low-level hardware, socket processing, etc.), and then mid-level material, for example, what happens in your game as well as detailed information on how game mechanics, etc., and high-level materials, such as what a PC or NPC can do, what the environment does, etc., as well as the idea that you never want to "jump" through the layers). However, when it comes to this, it’s important to just find the right abstractions for your game and keep everything organized so that you never feel that the bit of code you are working on does two completely different types of things.

So, firstly, let me accept the fact that it sounds (naturally) there are many things interacting with a world state. For something like this, it is probably beneficial to include many “things” in one class, and then basically only one class does the job. Ideally, you implement, say, messaging / messaging in your own small group, so there is no need to pollute higher-level objects using elementary processing data.

For example, you want to focus on what you are doing at a high level in objects of a higher level: in AI it is possible to “start moving to a place”, “establish my haste”, “stop moving”; and in the subsystem of the environment “start to rain”, “increase the speed of the wind”, “dim lights”; in the user class “firearms”, “sleep”, “spell spell”. But I would not want any of my high-level classes to even know about things like “send a message to the world” or “reset the thirst timer”, or “get socket data” or “checkmark for the health cycle”. (All these are just explanations, not suggestions .; D)

Developments

For example, I think that it would be useful to keep one object responsible for sending events to the world, so that you will no longer talk with everyone. Most likely, I'm just creating a set of things to handle events in general. Thus, it is possible for the EventMain and enumEvents that you use so that each type of event has a special identifier. And then use Event as the base class for certain events that need additional functions. (I mean both the identifier and the derivation model, so things like the dispatcher, which probably should know only the most basic things about the event, also don't need to know about the derived classes. The dispatcher can take part in the event and send it without need to know the internals of the derived event. This may or may not be useful, but it is good to have options.) You can also have an EventDispatcher which has an event queue for sending to other subsystems.

You need something in common to receive and send events. You can make standalone classes EventSourcer and EventSinker , which can be configured in any class that generates or receives events. Or you could instead make IEventSource and IEventSink so that you can implement a common interface for several classes, or perhaps a common EventSourceAndSink class that implements both, and which is part of your base class model, so all that may need to be handled events can simply flow from it.

I would probably do the ProtocolEncoder and ProtocolDecoder classes. You can always combine them into one object, but it can be valuable and should not cause any problems if it is performed properly so that they are two separate pieces of code. You can also have a ProtocolHelper class that separates something in common between them. The only task of coders is to receive messages from the network and include them in events for your game, which are then transmitted to EventDispatcher . The decoder class will receive events from the dispatcher, which should go online, and it will take data from them and send them.

How to get where you go

Since you have working code, I would recommend that you just start doing this when it seems natural. It can be things that bind you, or things that you notice are very similar in different places that you could do regular with a class or some other type of abstraction, then pull out the old and insert the new. After you figure out a workable first cut of the class model, this should give you ideas based on what you already have, and as you go, constantly review your model, fixing what is the problem, lather , rinse, repeat.

This should not be a lot of work, in fact, some of the most enjoyable moments that I worked on the code were that I was able to make a neat refactor that left a previously disgusting mess much better than the form, removing a lot of hard to understand code and replacing it being easier to understand in fewer lines of code, and this opened the way to my next addition, which is a pleasure instead of another “zomg i don”, Do I need to touch this code again? "moment.

We wish you good luck, here is a nominal guide to what I talked about; the first bit is more detailed, because the main class of events is one of the most important concepts, and then I try to just give a nominal overview of the classes and their interaction. I’m sure I can spend even more hours on this, but now I’ll just say: ask me if you have any questions and I will do everything in my power to give you a good answer :)

Code Ideas

Oh, one more note: I did not deal with the difficulties added if you have multiple threads; there are things from simple to complex to manage it all if you do it, but this is a different exercise. :)

 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 eg, 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 (eg, 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 { } } 
+3
source

I recently answered this question . The goal was to improve code testability, for which a common solution is to loosen the connection. The focus of this previous answer was on unbinding network code connected to the network from the world, and this is logical because network code is not a single test, and it is also a pain to scoff at it.

The above solution was to use an interface for incoming messages, so you separate the MessageProcessor (named Handler in another message) from the network code and similarly separate the UpdateNotifier from the world.

enter image description here

A dashed line is simply an indirect reference processed by an interface or delegate. There is currently no direct connection between the World Wide and the network component, which makes it verifiable. This is really just a Model View Adapter template app.

M-V-A

This is not like the design you described, except that you are missing multiple interfaces. With this model of interface-based UpdateNotifiers used for push updates, I essentially reuse the same architecture to handle NPCs, tasks, or anything else that is being processed elsewhere. You cherry pick the events you need for a specific area and apply a specific Notifier class for them, so you have several adapters on the same model.

enter image description here

And it really only looks more complicated than it is. A World object has no direct dependencies on anything else, and every other class has at most one direct dependency. You can also isolate the timer from the world, since it probably isn’t needed there, but perhaps one of the biggest hurdles is handling synchronization between different adapters.

+4
source

All Articles