- Home /
What is the best way to implement user input and movement when using a State Machine pattern?
Hello. I am trying to implement a state machine for my player. So far I've implemented two states, 'Move' and 'Idle', but I am planning on expanding it later. The movement system is as follows: when the left MB is pressed, the player's transform is moved towards the mouse position, or something like this:
Vector3 destination;
float speed = 1.0f;
RayCastHit2D hit;
void UpdateMousePosition()
{
if (Input.GetMouseButton(0))
{
destination = Camera.main.ScreenToWorldPoint(Input.mousePosition);
destination.z = 0;
hit = Physics2D.Raycast(transform.position, (destination - transform.position).normalized);
}
}
Then whoever handles the movement logic would call:
UpdateMousePosition();
transform.position = Vector3.MoveTowards(transform.position, new Vector3(destination.x, destination.y, 0), speed * Time.deltaTime);
My state machine consists of 5 classes: BaseState; MoveState and IdleState that derive from BaseState, a Factory that generates states, and a StateMachine that handles all the state logic and transitions in its Start() and Update() methods.
Also, there are 3 main methods (EnterState(), ExitState(), InsideState()) where InsideState() is responsible for what happens while the respective state is active. The other two should be self-explanatory. Only the StateMachine class is Monobehaviour.
There are several approaches that I can proceed with from here on, but I am not sure which one to use in terms of good code practices and SOLID.
A) I can create a separate script that handles the movement logic with static methods that I can directly call from the MoveState's InsideState() method. For example:
public class PlayerMovement
{
public static Vector3 mousePos;
public static void MovePlayer()
{ //movement logic
}
}
public class MoveState : BaseState // not MonoBehaviour
{
public override void InsideState()
{
PlayerMovement.MovePlayer();
}
}
My issue with this is that I am not sure where I should track the cursor's position since it would require an Update method. I could do that from within the StateMachine class:
public class StateMachine : MonoBehaviour
{
PlayerBaseState currentState;
void Update()
{
currentState.InsideState();
if (Input.GetMouseButton(0))
{
PlayerMovement.mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
}
}
}
but then that would ruin the Single Responsibility principle - should the State Machine be responsible for tracking the mouse at all? Shouldn't it be responsible solely for the states themselves? I watched several tutorials on State Machines and they didn't seem to advise against it.
A.1) I could also do that inside the PlayerMovement script by making it MonoBehaviour but I'm hesitant since I don't want to make my MoveState obsolete. I want to be able to fully utilize the State Machine pattern here.
B) I could omit the PlayerMovement script and add all of the movement logic to the MoveState script while having the StateMachine track if the mouse button is pressed and the cursor's position respectively. This way, MoveState's InsideState() method would only move the player towards the cursor's position, so, like this:
public class MoveState
{
public StateMachine context;
public override void InsideState()
{
MovePlayer(context.MousePos);
}
public static void MovePlayer(Vector3 dest)
{ //movement logic
}
}
public class StateMachine : MonoBehaviour
{
PlayerBaseState currentState;
public Vector3 MousePos { get; }
void Update()
{
currentState.InsideState();
if (Input.GetMouseButton(0))
{
mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
}
}
}
C) I could make an even higher abstraction and add a general Player script to my player component that tracks the mouse cursor, or even put it in a GameManager class?
I understand that I could do without a State Machine but my goal is to learn how to use this pattern correctly and to also apply it in future projects, while also making my current project open to extension, so I would appreciate any feedback to my question.
Answer by danielachy · 4 days ago
Update
The question is still valid.
I decided to put the mouse's logic in a separate script. This way we can get the clicked mouse's position from anywhere using a static method. This will also be useful for when I create different mouse actions other than movement (click on enemy, click on chest, etc). I still have the dilemma though. I made a script entirely for assigning the target position where the player needs to go to the cursor position. Everything from the question's movement logic is there except for Vector3.MoveTowards(...). My dilemma is exactly this - does my state machine use the MoveTowards() method for when the state is set to 'Move' or should I instead change a global boolean via the MoveState's method, that enables the Movement script to move the player?
Your answer
Follow this Question
Related Questions
Player moves to the middle of the room when the level starts (2d point and click) 2 Answers
How to keep the last good position when interacting with triggers? 1 Answer
I need to change the animation when my speed increases in a 2D game 0 Answers
Damaging Enemies 1 Answer
rotate 2d object toward movement 1 Answer