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.
↧