I am in the middle of my project. And I have many scripts, one drawing something another editing , lots of buttons.
So I was using C# events to control actions. But the problem I continuously having is:
For example… I can move the tree with mouseDown , but with the same I can orbit the camera.
It is almost like I need to transition from from edit state to navigation state and back.
I still do not have any managers in my project I was just using scriptable objects to pass the information.
Do you have an advice how solve these problem? Maybe a good tutorial to watch?
Did you have a problem? I mean, I didn’t know if I understood what you want help with. Seems that you’re changing states when clicking and releasing objects that seem like a good approach to me. Are you having problems during the transitions?
In MS paint, the same click and drag action can be used for drawing, erasing, selecting, etc. Think about how MS paint, or any other image editing program for that matter, might be managing this.
This is exactly where I have a problem. I am sure there are already ways to approach it.
Should I have a separate script that will keep track of all inputs and adjust the state accordingly?
For example, I started painting. As soon as the brush tool is active, camera orbit/pan/zoom should be disabled.
Where this should happen?
This seems to me to be more an FSM scenario than an observer. Have you considered using states to enter and exit your actions? Something like, when clicking on Brush Tool this will change the state to Brush tool that OnEnter will trigger a base Disable Navigation approach (supposing that every tool disables the navigation, otherwise trigger it just for the brush) and OnExit will Enable Navigation. Have you considered something like this?
Thank you for your advice. I think I will have to look at State Machine Pattern.
No, I haven’t tried to implement it yet.
Thought to get some thought from someone with experience.
I am just using delegates everywhere and it became hard to manage.
I was refering to how brush, selection, erase, are all tools. Only one tool is active at any one time. If you understand that as a concept however, then that means you are asking about implementation.
As already stated, the state-machine “pattern” does this quite well. This can be complemented with enums
//Setting up an enum
public enum MyToolState { Camera, Select, Move }
//Defining an enum variable
MyToolState state = MyToolState.Camera;
//Filtering tool state by enum
//I am assuming it is occuring in Update at the moment
switch(state){
case MyToolState.Camera:
//do camera things
break;
case MyToolState.Select:
//do selection things
break;
case MyToolState.Move:
//move things
break;
}
This however, can become quite messy when implemented naively. Since you are already using delegates, I suggest having your inputs fire off delegates or actions, and the statemachine defines what the actions/delegates call.
//actions which are called from your inputs
private Action MouseDown;
private Action MouseUp;
private Action<Vector2> MouseMoved;
//your buttons define which state is being accessed, and sets up the Actions.
//You can use strings or ints instead if you are move comfortable with them.
public void ToolChange(MyToolState toolstate){
switch(state){
case MyToolState.Move:
MouseDown = Move_MouseDown;
MouseUp = Move_MouseUp;
MouseMove = Move_MouseMovement;
break;
case MyToolState.Camera:
MouseDown = Camera_MouseDown;
MouseUp = Camera_MouseUp;
MouseMove = Camera_MouseMovement;
break;
case MyToolState.Select:
MouseDown = Select_MouseDown;
MouseUp = Select_MouseUp;
MouseMove = Select_MouseMovement;
break;
}
}
//individual functions which define your tool behavior
private void Move_MouseMovement(Vector2 mouseDelta){
//what happens on mouse move for Move state
}
private void Move_MouseDown(){
//what happens on Move mouse down for Move state
}
private void Move_MouseUp(){
//what happens on Move mouse up for Move state
}
private void Select_MouseDown(){
//what happens on Select mouse down for Select state
}
//and all the other individual functions representing your tool functionality
This way, the state machine only needs to be considered when switching tools, and your functionality is segmented by functions. If you make your behavior functions public, they can be placed pretty much anywhere, as long as you reference the class they are placed in, in the state machine class.
Thank you Laperen . This looks nice. Yes the option with delegates should work. I need to experiment with it myself just to get a clear picture in my head.
So I will have something like this:
GameState class with
Camera with navigation scripts (zoom,pan,orbit)
Some gameObject with script for moving it
So now I have:
Idle state
Zoom, Pan, Obit states
Move state
When I click on a button to move it should raise the event to transition from current state to move state…
Right?
Hi @Laperen
So after a week of reading and watching different tutorials about State Pattern, I come up with this.
Input Manager - gets touch/input Vector 3 and passes it to GameManager using event.
UI Controller - invokes events by clicking on UI Buttons and passes them onto GameManage.
GameManager - is a Context of a State Machine. GameManager has reference to UI Controller, Input Manager and all states.
States - separate classes for each state
CreatePolygon - just a class to draw lines
And it works
But I would like to get your view on it. I also have a couple of questions.
Each state has Enter / Cancel / HandleInput function. To change the state, I call these functions from GameManage. Because of this, I need to pass the GameManager reference to the state.
currentState.EnterState(this);
I fill like there is should be a better way ? What if my GameManager will extend StateMachine class?
And the second question is… This system works only with a brush tool (for example), but what if I will have a pen, eraser and so on…?
Thank you in advance
This pattern helps a lot. I removed so many if statements!
GameManager Class
/*
* GameManager controls the state of the app
* it send messages to responsible scripts and changes states
*/
public class GameManager : MonoBehaviour
{
// Refrence to other scripts
[SerializeField] public InputManager inputManager;
[SerializeField] public CreatePolygon createPolygon;
[SerializeField] public UIController uiController;
// States
public State currentState;
public NavigationState navigationState = new NavigationState();
public SelectionState selectionState = new SelectionState();
public DrawState drawState = new DrawState();
public EditState editState = new EditState();
// Start is called before the first frame update
void Start()
{
// Set starting state of the state machine
currentState = navigationState;
currentState.EnterState(this);
// Subscribe to events
inputManager.OnPointerDownHandler += HandleInput;
uiController.OnAddStreetHandler += HandleStreetBuildingMode;
uiController.OnCancelActionHandler += CancelAction;
}
// Handles input from Input Manager. Provides touch position
private void HandleInput(Vector3 position)
{
currentState.HandleInput(this, position);
}
// Handles tasks on UI button press, from UI Controller.
private void HandleStreetBuildingMode()
{
currentState = drawState;
currentState.EnterState(this);
}
// Handle tasks on UI button press, from UI Controller.
private void CancelAction()
{
currentState.CancelState(this);
}
}
Concrete State Class
public class DrawState : State
{
public override void EnterState(GameManager gameManager)
{
Debug.Log("I am in edit state.");
}
public override void HandleInput(GameManager gameManager, Vector3 position)
{
gameManager.createPolygon.AddNode(position);
Debug.Log("I am adding nodes...");
}
public override void CancelState(GameManager gameManager)
{
gameManager.currentState = gameManager.navigationState;
gameManager.currentState.EnterState(gameManager);
}
}
For the time being, you will simply have to learn and explore. I find it difficult to write a concise reply, having written and erased many paragraphs already in trying to formulate a response.
Just know that whatever “pattern”, “paradigm”, “architecture”, “design” you see being promoted as a solution to any problem, is a mere guideline and not at all concrete. Trying to follow any of them to the letter is impossible at best, and pointless at worst. Do not feel obliged to write your code as if you are building the thing in real life, only segment things away into chunks(be it classes or functions) when it makes sense. This intuition of course, comes with practice, hence the need to learn and explore.
For me, the only “pattern” that works is a clear defined separation between data and functionality. In my previous reply, I identified the “data” as the current state, and all user inputs. The switch/case “state machine” and individual tools make up all the functionality.
Thank you @Laperen
It is good advice. As you said I just need to keep going and refactor/change my code when I have a problem.
I am a self-taught dev so intuition is something I still have to develop, but it is only practice, I agree.