Class hierarchy design and class comumication question.

Hi there.
I’ve read some best practices and watched videos about different patterns, but they did not give me the answer I would want to hear.
Say, I have:
BaseCharacter class which holds character movement logics and states
HumanCharacter and AnimalCharacter derived from the base class,
EnemyCharacter and PlayerCharacter derived from HumanCharacter
And a couple of interfaces:
ICharacter, IHuman : ICharacter and IAnimal : ICharacter.
Also, I have an inventory class, weapon holder class (for managing the weapon in character’s hands), an empty melee combat class (I’m going to build some melee system but I don’t know if I need to make refactoring in my current system, so I’m kinda stuck for now).
The question is: how should I manage all of this?
For now my HumanCharacter class has some gun specific properties (isAiming, isShooting, transforms that are parents for weapon equipped…), inventory and a simple parkour system. All this made by references to WeaponManager, Inventory and Parkour classes in HumanCharacter.cs. These are public properties so in example my ShootScript (instantiating bullets, raycasting, recoil) could determine if the character could and want to shoot, which is depends on his parkour state (in ShootScript:

if (Character.Parkour.State == ParkourStates.None)

and his moving state from base character class

    && Character.MoveState != MoveStates.Run)
        Shoot ();

The same time I get ParkourState in HumanCharacter to change it’s inAction state so BaseCharacter could set it’s movement settings accordingly.

The same time Parkour have its own reference to the IHuman interface so it could change Character’s state, example:

Human.Parkour.State = ParkourStates.Climb;

Also, ShootScripthas its own reference to the character to get his current equipped weapon info, move weapon holder transform by recoil force, etc.

So, is it ok to have such a cross references? Though in first case it’s a class references and in second they are interfaces, but still I don’t know, am I doing it more or less in proper way or maybe I should think in more OOP/COP ways, MVC patterns, etc.

P.S.: Also, I would be glad to hear about using events. I’ve tried it and I fear my code could become too much non-organizable with such amount of events and their listeners here and there in every class. Should I stop to be afraid of it?
PPS: If I decide to use events, I then most likely will need access to certain properties of the character, so I would send thisin event args giving a ‘whole’ character object to the listener. I’ve read that it’s not good and I should minimize any data I send to other classes. Are these opinions rightfull? Why can’t I send just thisand let subscriber to determine which property of the character it wants to use?

Kudos on you for thinking about your design. So many new students of Unity (rather particularly) think in terms of scripts (which isn’t really a programming concept, they’re text files).


While there are many purposes for classes, and you have the basic idea, a few guiding ideas may be useful. The first ‘modern’ computers (around the 50’s forward) were programmed in a one dimension, single list of instructions. When the first languages were devised this lead to functions (C# calls them methods, but these older versions were without classes). Functions are a somewhat natural result of a CPU’s ability to execute a call instruction (calling a function), which most of the earliest CPU’s didn’t actually have. Functions allowed for a second dimension of organization, greatly expanding the ambition of software targets while keeping code organized. The Unix and Linux operating systems are built in that way.


Objects (classes in C# and several similar languages, especially where instantiated), offer a 3rd dimension to organization of code. If you consider the one dimensional list, naturally oriented from a “top” expanding downward, functions would represent columns (the second dimension), while classes would wrap that 2d layout into pages. With the slightest clever effort, pages can become chapters, then books, the collection of books on shelves, entire libraries, etc.


There are hints for early students as to when to create a class. This is often when code is repeated. Sometimes that merely hints to a function, but where there is data associated with the concept it is a class.


As you have done so far, another means of designing classes is to encapsulate concepts. This is about all the motivation one has before code is written, but, as you’ve inquired, one must take care not to be trigger happy about the design. There is a significant point of intersection between what a class does and the concept it represents, but you may well find that mutates as you write the code. This has occurred to you when you inquire, for example, about events. There’s no reason to be concerned about events, but there’s always concern about organization of code to be readable, maintainable and efficient.


There can, and likely will, be quite a volume of objects. That should be reflective of that complexity you’re trying to organize. An example comes from my point about repeated code hinting to an object or a method (function). A repeated bit of code is most likely best put, at least, into a function so there’s one place to edit, expand and debug that code. There is naturally some associated parameters to that function, but when there are several variables (the function signature becomes too complicated), and especially where those variables represent grouped collection, it is likely an object. If one simply decided to start first making classes for all bits of repeated code, then the classes may not actually be organizing something significant, they may be just complicating the matter.


The opposite view of that tendency is the “uber object”. In this case there’s a class with a long, winding list of member variables. There are valid occasions when that is just the way a concept works, but frequently what we see in student or intermediate level code is a tendency to just dump concepts into an existing class, as if to resist creating classes because it’s more work. Unfortunately the truth is that when such a mess of a class actually represents several different concepts at once, there’s no organization to help future work, and while the short term convenience of merely expanding a class into some uber giant returns diminished returns. It seems clear that’s not your problem, but it is common.


You’ve sensed there’s a balance to this, which tells me your in a phase of moving upward in skill, and studying carefully. I regard that as the most important aspect of your post.


Objects (classes) function like components in a machine. If you consider a modern car, you realize there are components that fit together with clearly defined interfaces. The alternator bolts on, a belt is routed over its pulley, the wires are attached to the battery. The internal operation of the alternator is of no concern to the rest of the engine. Factually, when most mechanics are faced with a malfunctioning alternator, there is little reason to disassemble it to repair it, it is simply replaced (this was not quite the case in early cars).


When thinking about what a class should be, think about how it would function if it were part of a machine. If cars were built the way software was written in early computers of the 50’s, there could be a bug in the radio that would blow a tire. That seems absurd for a real car, because it is obvious the radio has nothing to do with the tires, but where there are no separations between components that becomes a real possibility. One (of many) primary purpose of the class is to be the outer case of a component, the shell that separates it from the rest of the machine being built out of logic and language.


Such components are black boxes. They may have a few connections for wires, a button or dial to adjust (maybe several), some readouts to monitor - that is, an interface to the user (the programmer, which may well be yourself). The interior is private, which is one reason it operates like a real machine. The less one can fiddle with the interior of a component, the less that can go wrong (assuming the component works well).


Early and intermediate students tend to confuse themselves over the fact they are the only programmer involved. The classes are theirs, it’s their code, so they tend to ignore the notion of private, internal parts of a class. They’ll liberally write classes that read and write to each other’s internals. This is because they’re not thinking in terms of that interface between components of a machine. It takes a while to practice the notion, and then see why it offers leverage. This is the nature of your inquiring about exchanging “this” among classes. It isn’t the exchange of “this” that is at issue. The usage if that reference is what is at issue. When a family of classes must operate with each other, they must ‘know’ each other (often by exchanging ‘this’), but what matters is how they use that connection to communicate with each other. In particular, one must be clear about what a class does, what it encapsulates in the shell of the class, and what it should not be doing for other classes.


Unity uses a version of the entity component system, a patter of design you’ll recognize from the use of GetComponent. Here, Unity leverages the simple notion of a Transform into as complex a composite object as is required. Unity students naturally adopt to this without necessarily using that system in their own classes. It is not always necessary to attach classes the way Unity does. Sometimes it is quite simply that a class has member objects, the way Transform owns a Vector3 and a Quaternion.


It is best not to overthink the obstacles before you work on the code. You can end up assuming a great deal you simply can’t predict until you write the code, gain experience and subsequently might have greater vision in years to come. Instead, begin with the core relationships you’ve defined, consider everything you write an experimental proposition (an idea that one maintains throughout a career), and be prepared to refactor and redesign as you notice what emerges.


A pair of classes like Vector3 and Quaternion have a rather obvious relationship, and the implied Transform rather obviously emerges from their pairing. It is rare that object designs are so clear before you write. You may well have (somewhat) working code you notice is getting out of control with complexity, then after review realize you have two or three classes mashed together in some uberclass that needs to be broken apart. That may be little more than moving methods from one place to another, with a few adjustments in code that reflects their interfaces. Embrace this practice or you’ll forever assume you should overthink design before you can write code.


As you can see, there’s a lot to the subject, you’ve asked so many questions, each of which lead to a book chapter, that I probably should have suggested a few C# books, but I don’t have a good list, I’m not fond of C# (I’m a C++ developer primarily, but I work in several languages as must we all). I’ve been a programmer/developer for over 30 years, so any language based on the C/C++/Java concepts is a quick adoption for me. You’re following a path most of us recognize, and you’ll need to continue study, experiment, analyze and remember what worked best, must like any engineering or scientific endeavor.