dev-writeup-2024 / march

개발 1일 1글 스터디
2 stars 0 forks source link

[03-11] C# 상태머신 구현 (FSM) #26

Open Longseabear opened 3 months ago

Longseabear commented 3 months ago

FSM

최근 유니티를 다루면서 FSM의 중요도를 절실히 느꼈다. 간단히 FSM에 대한 정의를 설명하고 C#으로 구현하는 법을 정리한다.

FSM이란?

FSM은 유한한 수의 상태들을 가지며, 특정 시점에는 하나의 상태에만 머무를 수 있는 모델이다. 시스템은 이벤트나 조건에 따라 다른 상태로 전이(transition)할 수 있다. 각 상태는 시스템이나 객체가 취할 수 있는 특정한 형태(action)나 모드(mode)를 나타내는게 일반적. 상태 간 전이는 규칙에 따라 정의되며, 이러한 전이 규칙은 시스템의 동작을 결정짓는다.

FSM을 사용하면 복잡한 시스템을 이해하기 쉽고 관리하기 편한 단위로 쉽게 쪼갤 수 있다. 개발자는 어떤 상태에서 어떤 행동을 할지만 잘 정의하면 된다.

FSM 구성

1) 상태 시스템이 존재할 수 있는 모든 조건 또는 구성을 의미함

2) 전이 한 상태에서 다른 상태로 이동하기 위한 조건이나 이벤트. action이라고도 한다.

3) 이벤트 상태 전이를 유발하는 외부 또는 내부 조건

FSM Pattern

예시에서는 'State'라는 열거형(enum)으로 상태를 정의하고, 상태에 따라 다르게 동작하는 메소드를 구현한다. 상태 전환은 조건에 따라 이루어짐. C#을 사용해 FSM을 구현하는 예시 코드는 아래와 같다.

using System;

namespace SimpleFSM
{
    // 상태를 정의하는 열거형
    public enum State
    {
        Idle,
        Running,
        Finished
    }

    public class FiniteStateMachine
    {
        // 현재 상태를 저장하는 필드
        public State CurrentState { get; private set; }

        public FiniteStateMachine()
        {
            // 초기 상태 설정
            CurrentState = State.Idle;
        }

        // 상태를 업데이트하는 메서드
        public void Update()
        {
            switch (CurrentState)
            {
                case State.Idle:
                    // Idle 상태에서 할 작업
                    Console.WriteLine("Currently Idle. Going to start running.");
                    ChangeState(State.Running);
                    break;
                case State.Running:
                    // Running 상태에서 할 작업
                    Console.WriteLine("Currently Running. Finishing up.");
                    ChangeState(State.Finished);
                    break;
                case State.Finished:
                    // Finished 상태에서 할 작업
                    Console.WriteLine("Finished. Nothing more to do.");
                    break;
            }
        }

        // 상태 전환 메서드
        private void ChangeState(State newState)
        {
            CurrentState = newState;
        }
    }
}

위의 코드는 아주 간단한 FSM을 구현할 수 있다. 그러나, 유니티 등에서 FSM을 사용할 때는 주로 애니메이션 구현등에 사용하기 때문에 상태가 전이 될 때의 행동을 정의할 수 있으면 더 좋다.

Enum이 아닌 다형성(상속)을 활용하면 상태가 전이될 때 행동을 정의하기 쉬워진다.

객체지향을 이용한 신호등 FSM 예제

public interface ITrafficLightState
{
    void Handle(TrafficLight trafficLight);
}

신호등 각 상태를 interface로 정의한다.

public class RedState : ITrafficLightState
{
    public void Handle(TrafficLight trafficLight)
    {
        Console.WriteLine("Red light - wait");
        trafficLight.SetState(new GreenState());
    }
}

public class GreenState : ITrafficLightState
{
    public void Handle(TrafficLight trafficLight)
    {
        Console.WriteLine("Green light - go");
        trafficLight.SetState(new YellowState());
    }
}

public class YellowState : ITrafficLightState
{
    public void Handle(TrafficLight trafficLight)
    {
        Console.WriteLine("Yellow light - slow down");
        trafficLight.SetState(new RedState());
    }
}

각 상태를 클래스로 구현하고 무엇을 해야할지를 정의한다.

public class TrafficLight
{
    private ITrafficLightState _currentState;

    public TrafficLight(ITrafficLightState initialState)
    {
        SetState(initialState);
    }

    public void SetState(ITrafficLightState newState)
    {
        _currentState = newState;
    }

    public void Change()
    {
        _currentState.Handle(this);
    }
}

이제 FSM 메인 모델은 단순히 state를 새로운 state로 바꿔주는 controller 역할만 수행하게 된다.

위와 같이 뼈대를 만들고, 이제 각 상태 전이를 더 구체적으로 수정한다.

public interface ITrafficLightState
{
    void Enter(TrafficLight trafficLight);
    void Execute(TrafficLight trafficLight);
    void Exit(TrafficLight trafficLight);
}

public class RedState : ITrafficLightState
{
    public void Enter(TrafficLight trafficLight)
    {
        Console.WriteLine("Entering Red State: Stop.");
    }

    public void Execute(TrafficLight trafficLight)
    {
        Console.WriteLine("Red State Active: Waiting...");
        trafficLight.SetState(new GreenState());
    }

    public void Exit(TrafficLight trafficLight)
    {
        Console.WriteLine("Exiting Red State.");
    }
}

public class GreenState : ITrafficLightState
{
    public void Enter(TrafficLight trafficLight)
    {
        Console.WriteLine("Entering Green State: Prepare to go.");
    }

    public void Execute(TrafficLight trafficLight)
    {
        Console.WriteLine("Green State Active: Go.");
        trafficLight.SetState(new YellowState());
    }

    public void Exit(TrafficLight trafficLight)
    {
        Console.WriteLine("Exiting Green State.");
    }
}

public class YellowState : ITrafficLightState
{
    public void Enter(TrafficLight trafficLight)
    {
        Console.WriteLine("Entering Yellow State: Caution.");
    }

    public void Execute(TrafficLight trafficLight)
    {
        Console.WriteLine("Yellow State Active: Prepare to stop.");
        trafficLight.SetState(new RedState());
    }

    public void Exit(TrafficLight trafficLight)
    {
        Console.WriteLine("Exiting Yellow State.");
    }
}

이제 FSM은 A상태에서 B상태로 전이 될 때 각각 A상태의 Exit와 B상태의 Enter를 호출해주면 된다.

public class TrafficLight
{
    private ITrafficLightState _currentState;

    public TrafficLight(ITrafficLightState initialState)
    {
        SetState(initialState);
        _currentState.Enter(this);
    }

    public void SetState(ITrafficLightState newState)
    {
        _currentState?.Exit(this);
        _currentState = newState;
        _currentState.Enter(this);
    }

    public void Change()
    {
        _currentState.Execute(this);
    }
}
snaag commented 3 months ago

FSM: 저도 FSM 인데영

Longseabear commented 3 months ago

zzzzzzzzzzzzzzzzzzzzzzzz