In just under 20 hours since installing Unity for the first time I’ve completed my first project, a basic Solitaire game. It’s really impressive the power of Unity. I figured it would take months to get to a point where anything worked. This is also my first time using a game engine so maybe all of them are this awesome
Now that I have a little bit of the Unity basics under my belt I’d like to go back and clean up the code and understand how Unity apps should be structured.
I understand OOP, design patterns, etc but as an example here’s one thing I’m having a hard time reorganizing in my code.
I have a Card prefab that also has my component/script attached. This script encapsulates all the card data and responsibilities, etc. However, the movement of the card should be determined by the logic of the GameManager.
Currently, in my Card object I listen to the OnMouseDown, OnMouseDrag, and OnMouseUp methods and each also has a related EventHandler that the GameManager subscribes to. So in my card when it’s clicked, it does the things it needs to then notifies subs of the event.
The GameManager then does it’s logic when a event is fired. It checks what the card is colliding with and decides if it’s a valid move then moves the card.
This works fine but is messy. I guess I’m just looking for basic design advice on how to handle input in this situation.
You don’t need to use callbacks and don’t need to subscribe to events unless you absolutely HAVE to. Instead, you can place a “root” object into the scene, attach GameManager to it, and have cards spawn as its children. When spawned, the cards would locate GameManager using FindComponentInParent function, and call its methods directly. (Grabbing references to anything is best done within OnEnable. See: Unity - Manual: Order of execution for event functions )
You can split messy UI logic away and put it into a separate component. For that, put the mess of UI handling into something like “GuiHandler”, create class like “IInteractable”, with methods like “onClicked()” and then have GUI handler grab all the components derived from IInteractable on component startup. In this scenariio you’ll attach GuiHandler (which will be the same) and something like “CardData” :)public IInteractable) to the same game object, and GuiHandler would fire methods of IInteractable-derived components that are attached to the same object. This will allow polymorphism.
If you’re using polymorphism on objects that are nt derived from GameEngine.Object, MonoBheavior, and ScriptableObject and store references to them in serializable locations, be aware of serialization system quirks. Unity - Manual: Script serialization
Try to read and get used of idea of “component design”. Traditional OOP approach almost expects you to have an 'Overlord" class that boss hordes of “minions” around and controls them around. This can heavily rely on polymorphism. In unity, however, you can approach the problem as designing a couple of small self-contained components that are largely independent but are placed on objectgs that exists in physics sandbox. I.e. rather than “overlord and minions”, you’re making a “drone swarm with no central controller”. This gets a bit of time getting used to, but once it clicks, you get a paradigm shift.
The logic you used is not errorneous, by the way, and would work for a card game. The card game is sorta bound to have a “table” or “rule enforcer”, and is less suitable for drone swarm approach. However, you are likely able to make the code clearer by splitting into more components.
Thanks so much for taking the time to response. This helps a lot as my issue is the paradigm shift and inexperience I’m sure as I learn more I’ll get the hang of it eventually.
Working through this let’s say I move the drag and drop functionality away from the Card and moved it into it’s own component. This allows me to use drag&drop on any gameObject now.
However, the GameManager needs to know about these events so it can check if the card moved to a valid spot or not. If it was dragged to an invalid place, it needs to revert back to it’s original position.
The logic for what a valid move is should be outside of the Card as this Card will be in different games with different rules.
Game manager does not need to know about actual drag and drop, though.
The scheme I described goes like this
You have two components - GuiHandler and CardData. They sit on same “Card” game object.
Gui stuff is handled by GuiHandler.
However, GuiHandler calls some sort of interface function from CardData which derives from some interface of your choice. This way CardData does not know anything about drag and drop internals.
In turn, CardData, can call any public method of a GameManager, without using events.
Additionally. You can get rid of direct call to GameManager.
Instead, you can introduce something like “CardSlot”, and use “CardSlot–>CanAcceptCard(CardData card)” or something similar. This will give you a single point of entry which decides whether a card can be accepted or not.
We can go further. Does CardData directly control card movement on the table? You can move this into another component. Here you can create “CardAnimator” which would be placed on the same GameObject as CardData, and would concern itself ONLY with card movement. And nothing else. And it could even control card movement in screen space.
In this scenario.
GuiHandler would deal with drag and drop mechanic.
Upon completion of drag, it would invoke interface method of CardData
CardData asks CardSlot whether proposed target is acceptable or not.
And depending on response, it would call CardAnimator (on the same component) to initiate animation/card movement.
And animation would continue automatically without further involvement of CardData.
Please note that in this scenario we no longer really have a game manager. The game goes on its own, through interaction of multiple components, and that’s pretty much “Drone Swarm” approach I described earlier.
However, a word of warning. Central top down appraoch with “GameManager” controlling everything is perfectly valid one, and in situation with tabletop games has an advantage because the board is stored somewhere as a singular state. Going overboard with decentralization may actually result in situaiton where discovering board state during debugging takes more time, because it is now spread across multiple components.
So you’ll probably want some sort of middle ground approach - where some of the mundane tasks are abstracted away into components (card animation and menial procedures of handling drag and drop are good candidates), but there’s still some sort of “GameManager” which controls game state.
However, my early explanation, I hope, should be sufficient to get a rough idea of alternative approaches possible in unity.