The answer to “Why” in both these instances is because one of the core principles of OOP is encapsulation.
Yes, to avoid creating god-classes and monolithic code. For example, having an InventoryComponent would not only allow the player to have an inventory, but an inventory could be created for the enemies, who could then use the items within it with their own methods.
Setting all your variables and methods to Public just for the sake of accessing it by whoever you pass a reference to creates nightmare scenario’s in large scale projects.
There is a reason why the default access specifier in a C++ class is private. Nothing is public unless you set it to be. Rust goes even further and variables are constants by default.
In my examples, I use the UI as a single example for needing to get and/or set variables on the components that belong to the player but extend this.
But if you later want to use the UI and Inventory Component on something that is not your current player class? What do you do then? Make a new base class with the common components and inherit both of the classes from that? What if you need a third class, which has different common components?
I’m being attacked by 10 trolls, 3 goblins and 2 sorcerers, now I have to give different classes direct access to my players components as well as re-write methods in each class to access the information it needs.
Finding the right level of abstraction where you can find the most common denominator is where the magic happens. Your UI doing operations on something that has an Inventory, so it likely implements the InventoryComponent. Your enemies are attacking something that can be attacked.
Now, what if I need to change some of the code these classes are accessing? I then have to find all classes that have access to these components and refactor each one individually instead of calling the primary method on the class that actually owns the component these other classes are accessing.
I’d still argue that if you insist on a getter and a setter, instead of placing a function in the player class, you place that function in the Inventory Component class.
Sure, Interfaces and delegates can be used in some instances. When multiple unrelated classes need access to the same information, Interfaces makes sense because you don’t have to artificially force a class relationship. However, to send a message to the component to get the required information still requires access to the object it is trying to send a message too which means you are still passing the same pointer around to many objects allowing them all access to a component/class that doesn’t belong to them.
In such a situation the InventoryComponent would function as more or less a “code tab” for the character class, with a direct dependency. The UI would depend on the Player who then depends on having the InventoryComponent.
While it’s not strictly always bad - the CharacterMovementComponent is an example of a component that makes plenty of assumptions about its owner - I think such dependencies should be avoided if possible.
Here’s a real world type example that can be extended into gameplay: Assume you are at a merchant to buy some product. You as the Player have a component Wallet that tracks all your finances. When you buy the items, do you hand the merchant/cashier your wallet and say “Here, figure out how to pay and I’ll account for it later” or do you personally go into your wallet and decide how the merchant should be paid?
I’m not really sure how this pertains to the UI example.
How I’d do it, though: Player’s WalletComponent would have a “PurchaseItem” function, which would have ItemPrice as an input variable. The PurchaseItem function would likely be called by the MerchantUI. If all the necessary checks are valid, the money is substracted and item added to the inventory.
So, in this case, the merchant would “ask someone with a wallet” if they have enough money and then make a purchase. If whatever they are interacting with doesn’t have a wallet, nothing happens.
It may seem like I’m over thinking this, and I very well could be, but from what I understand about OOP is that giving direct access to objects/classes that don’t own the object/class it is trying to access is bad practice and breaks a core principle of OOP.
Even in Unreal Engine’s source code, there is no “GetGravityScale” or “SetGravitySCale” within the character class. GravityScale is a public variable in the CharacterMovementComponent. You have a getter for CharacterMovementComponent, but not for the CharacterMovementComponent’s variables.
Yet for the in-built damage system, there is a generic “Apply Damage” method.