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.