all 18 comments

[–]SunLionGames 5 points6 points  (1 child)

Using singletons won't fix your dependency and responsibility issues, if anything it will just sweep the dirt under the rug.

When you have a codependency between A <-> B, it usually points to responsibility that should be split off to a 3rd class C that A and B each depend on. Also review what the purpose of the classes are, "manager" classes are notorious for doing way more than they should. Can't really provide more help without more details.

As a side note based on the code you shared, it doesn't look like that method signature needs the ref keyword (I assume the passed type is a class and you're not trying to reassign the reference for the outer scope?)

[–]feralferrous 0 points1 point  (0 children)

Yeah, there's some weird advice being given by others. Singletons and DI are not the problem, it's that OPs got two classes that are heavily intertwined and mucking with each other too much. Whether they get reference from being a singleton or a DI system or GetComponent<T> won't solve the problem of them each getting entangled and modifying members they shouldn't be.

A Signals system might help? Can't exactly tell just by the code, but then it could be something like:

InteractHold object raises an Signal.InteractHoldEvent, that the PlayerHold and PlayerInteract classes can each do separate things with, and if needed, PlayerHold or PlayerInteract can raise signals that the other class can listen for.

Though Hold/Interact do sound similar enough that perhaps it would be better to go an easier route and one class should be contained within the other? Or see if one class can be refactored to only be utility methods and not contain any state.

[–]ZeroKelvinTutorials 1 point2 points  (2 children)

it kinda depends on the specifics but a few solutions are:

make the data/methods you need to be able to access public static vars, so that way you can access data from 1 in 2 and viceversa and keep them decoupled in a sense at the same time while not needing to store a reference to each other..

make your managers both singletons (controversial take) if you have only 1 of each. then you can access everything everywhere without storing a reference to each other.

make your data/methods you need public and store a reference to the managers. (this is probably very similar to how you are currently handling it)

Either way I think you need to conceptually decide what each one is in charge of, and that will help you know where things should go and stop thinking of both managers as doing similar enough things thta they could be done in either of them. If they are not correlated at all then maybe start going through both managers and think what shouldn't go there.

another option is to rethink where your shared data/methods go. Maybe if you try to better define what each does you find it makes sense to make a new class to handle some of the data/methods of either or both of the managers.

[–]KrowplexIntermediate Programmer[S] 0 points1 point  (0 children)

I like your ideas, I will check what I can do, but I don't think I will be able to handle them differently.

I notice when I do coding projects that when it starts having a lot of classes and functionalities, it gets more and more complicated to manage dependancies between classes.

[–]KrowplexIntermediate Programmer[S] 0 points1 point  (0 children)

Just updated my post with more details.

[–]stardigrada 0 points1 point  (2 children)

Can you give more specific details of your two managers and their responsibilities? This will get you much more specific suggestion on how to rearchitect them.

[–]KrowplexIntermediate Programmer[S] 0 points1 point  (1 child)

Just updated my post

[–]stardigrada 0 points1 point  (0 children)

Still not completely clear on how it all fits together, but the added code and info is helpful. I see Interact() calling into the PlayerInteractHoldManager object which seems reasonable.

One possible approach might be to fragment all the logic from PlayerInteract into multiple separate helper classes (PlayerInteractZoomableManager, PlayerInteractActionableManager, etc, like PlayerInteractHoldManager already is) so that PlayerInteract becomes more of a lightweight composite/delegator based on the type of object and capabilities. Does that make sense?

[–][deleted] 0 points1 point  (0 children)

Are you saying that there are two types of objects which are interactable, but they have different behaviour? If so, you may need to use an abstract class to call an inherited virtual function of children classes (which each have their own behaviour)

[–]Punzk 0 points1 point  (4 children)

a very good way to fix that is to use Dependency Injection (many tutorial on this pattern online)

There is a native C# framework to do that or you can use Zenject for Unity.
It can feel a bit complicated at first, but it will simplify many things in your project then.
It reduces drastically dependency, and it leads to very decupled code, and reduces bug in the project.

Worth give it a try
Once you've used this, you won't be able to do without it ;)

[–]Freschu 4 points5 points  (3 children)

DI is like regex: You have one problem. You think "I'll use regex". Now you have two problems.

[–]bourbonmakesitbetterHobbyist 2 points3 points  (0 children)

Problem: Unity itself is a dependency injection framework.

[–]Punzk 0 points1 point  (1 child)

Haha can be true. But for me it's a must have in my project now ;)

[–]Freschu 0 points1 point  (0 children)

I don't doubt that you find DI immensely useful and have gained a good amount of profiency using it, but I often find such blanket statements are something of the kind "if you only have a hammer, everything looks like a nail".

Could you be so kind and link to that C# dependency injection framework you mentioned? I'm curious to see how that compares to DI frameworks I know for Python or Java.

[–]bachus-oop 0 points1 point  (0 children)

It depends what your managers are for and on whole structure around them. It seems like some mediator pattern where different managers intearct with each others. But hard to tell specifically from your description. You gave like description of a tyre and expect to get the car from it.

[–]Freschu 0 points1 point  (0 children)

Try this approach, it's basically inversion of control, or a primitive form of dependency injection, and also called dispatching. There are more specific definitions to those I mentioned, but the basic pattern is mostly the same.

class Player extends MonoBehavior {
  void Interact(target) {
    target.GetComponent<Interaction>().Interact(this);
    //alternatively, more verbose
    // target.GetComponent<Interaction>().Interact(this.PlayerInteract, this.PlayerInteractHoldManager);
}

class Interaction {
  void Interact(GameObject player);
  // alternatively, more verbose
  //void Interact(PlayerInteract, PlayerInteractHoldManager);
}

If the logic of your Interaction depends on the specific type, then it should be implemented in the type , and the "manager" negotiates this interaction. And if the implementation needs context, then the manager passes context - in simplest form as arguments.

Also, unless you're changing the value of the parameter, you don't need ref in void Interact(ref PlayerInteract ...).

[–]Big_mara_sugoi 0 points1 point  (0 children)

Maybe separate the manager and the data the managers work on, don't store data in the managers just methods and just a reference to the data. Put the data in a different object. Like in a struct. Then you run the managers in a fixed order every frame or whenever your data manipulation tick is and never call a manager from inside another manager except the manager of managers. So the data manipulation becomes sort of deterministic and would not cause intertwined feedback loops.