FSM(Finite State Machine, 유한 상태 기계)

게임 오브젝트의 상태를 체계적으로 관리하고 전환하기 위한 설계 패턴

플레이어나 적 AI처럼 상태에 따라 행동이 달라지는 시스템에 자주 사용

 

사용하는 이유

코드의 가독성과 유지보수성 향상
→ 상태별 로직을 분리해서 각 클래스나 메서드에 구현 가능.

 

복잡한 조건 분기를 명확하게 구조화
→ if-else나 switch 남발 대신 상태 전이 중심 설계.

 

확장성과 재사용성 좋음
→ 새로운 상태 추가 및 전이 조건 설정이 쉬움.

 

IState (상태 인터페이스)

// 상태 인터페이스
public interface IState
{
    void Enter();
    void Execute();
    void Exit();
}

 

역할

모든 상태들이 따라야 할 공통 규칙을 정의

각 상태는 이 인터페이스를 구현(implements)하여, 상태 진입, 유지, 종료 시의 동작을 정의함

 

IdleState, WalkState 등 (상태 클래스들)

// Idle 상태
public class IdleState : IState
{
    public void Enter()
    {
        Debug.Log("Idle: Enter");
    }

    public void Execute()
    {
        Debug.Log("Idle: Execute");
    }

    public void Exit()
    {
        Debug.Log("Idle: Exit");
    }
}

// Walk 상태
public class WalkState : IState
{
    public void Enter()
    {
        Debug.Log("Walk: Enter");
    }

    public void Execute()
    {
        Debug.Log("Walk: Execute");
    }

    public void Exit()
    {
        Debug.Log("Walk: Exit");
    }
}

 

역할

실제 상태별 동작을 정의하는 클래스

Enter()에선 애니메이션 시작이나 초기화,
Execute()에선 반복 동작(예: 걷기),
Exit()에선 상태 전환 전 정리 작업 등을 수행

 

StateMachine

public class StateMachine
{
    private IState currentState;

    public void ChangeState(IState newState)
    {
        currentState?.Exit();
        currentState = newState;
        currentState.Enter();
    }

    public void Update() // 생명주기 함수 Update 아님 주의(Execute로 이름 대체 가능)
    {
        currentState.Execute();
    }
}

역할

현재 상태를 기억하고,

새로운 상태로 전이(change)하며,

현재 상태의 로직을 실행(update)하는 중앙 관리자입니다.

즉, 상태 전환과 실행을 통제하는 컨트롤 타워 역할

 

왜 필요할까?

상태 객체만으로는 전환(변경)을 스스로 못함 → 누군가가 상태를 [관리]해줘야 함

StateMachine이 없으면, 상태끼리 서로 직접 호출해야 해서 코드가 복잡하고 꼬이게 됨

상태 전환을 일관되고 안전하게 처리할 수 있음

 

Player (FSM을 사용하는 MonoBehaviour)

public class Character : MonoBehaviour
{
    private StateMachine stateMachine;

    private void Start()
    {
        stateMachine = new StateMachine();
        stateMachine.ChangeState(new IdleState());
    }

    private void Update()
    {
        stateMachine.Update();
        if (Input.GetKeyDown(KeyCode.W))
        {
            stateMachine.ChangeState(new WalkState());
        }
        else if (Input.GetKeyDown(KeyCode.I))
        {
            stateMachine.ChangeState(new IdleState());
        }
    }
}

 


HFSM이란?

HFSM (Hierarchical Finite State Machine) 은 기존 FSM보다 한 단계 더 진화된 형태

FSM (Finite State Machine)  HFSM (Hierarchical FSM)
모든 상태가 동등한 계층 상태가 상위/하위 계층 구조
상태 전환만 있음 하위 상태, 공통 동작, 상속 구조 가능
예: Idle, Walk, Jump 예: Ground → Idle, Walk (하위 상태)

 

캐릭터 상태 예시

[GroundState]           ← 상위 상태 (공통 로직: 중력, 점프 감지 등)
   ├── [IdleState]         ← 멈춰 있을 때
   └── [WalkState]         ← 이동 중일 때

[AirState]              ← 공중 상태 (점프 중, 낙하 중)
   ├── [JumpState]
   └── [FallState]

IState (상태 인터페이스)

public interface IState
{
    void Enter();
    void Exit();
    void HandleInput();
    void LogicUpdate();
    void PhysicsUpdate();
}

IdleState (GroundState 상속)

public class PlayerIdleState : PlayerGroundState
{
    public PlayerIdleState(PlayerStateMachine playerStateMachine) : base(playerStateMachine)
    {
    }

    public override void Enter()
    {
        stateMachine.MovementSpeedModifier = 0f;
        base.Enter();
        StartAnimation(stateMachine.Player.AnimationData.IdleParameterHash);
    }

    public override void Exit()
    {
        base.Exit();
        StopAnimation(stateMachine.Player.AnimationData.IdleParameterHash);
    }

    public override void Execute()
    {
        base.Execute();
    }
}

PlayerStateMachine

public class PlayerStateMachine : StateMachine
{
    public Player Player { get; }
    public Vector2 MovementInput { get; set; }
    public float MovementSpeed { get; private set; }
    public float RotationDamping { get; private set; }
    public float MovementSpeedModifier { get; set; } = 1f;
    
    public float JumpForce { get; set; }

    public Transform MainCameraTransform { get; set; }
    
    public PlayerIdleState IdleState { get; private set; }

    public PlayerStateMachine(Player player)
    {
        this.Player = player;
        IdleState = new PlayerIdleState(this);
        ...중략
    }
}

 

PlayerStateMachine은 플레이어 전용 FSM으로

입력값, 속도, 점프력 등 캐릭터 제어에 필요한 데이터를 저장

IdleState 정보를 가지고 있음으로서 StateMachine에 상태(ChageState)에 접근할 수 있음

Player

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour // 입력 뿐만 아니라 여러가지 컴포넌트 관리해주는 클래스
{
    ...중략

    private PlayerStateMachine stateMachine;
    void Awake()
    {
       ...중략
        stateMachine = new PlayerStateMachine(this);
        stateMachine.ChangeState(stateMachine.IdleState);
    }

... 중략

    private void Update()
    {
        stateMachine.HandleInput();
        stateMachine.Execute();
    }

    private void FixedUpdate()
    {
        stateMachine.PhysicsExecute();
    }
}

'Unity > 디자인 패턴' 카테고리의 다른 글

전략 패턴  (0) 2025.05.04
싱글톤 패턴  (0) 2025.05.02
오브젝트 풀링 패턴  (0) 2025.03.08

전략 패턴이란?

정의: 알고리즘(행동, 동작)을 각각의 클래스로 정의하고, 이들을 교체할 수 있도록 만드는 패턴.

목표: 행동을 컨텍스트 객체와 분리해, 실행 중에도 전략을 쉽게 변경할 수 있도록 함.

 

구성 요소 클래스 예시 역할 설명
Strategy IAttackStrategy 행동(공격)의 인터페이스 정의. 구체 전략을 위한 공통 틀 제공
ConcreteStrategy MeleeAttack, RangedAttack, MagicAttack 실제로 공격을 구현하는 클래스들
Context Enemy 전략을 사용하는 객체. 공격 전략을 저장하고 실행함
Client GameManager 전략을 설정해주는 역할 (전략 선택 및 컨텍스트에 주입)

 

IAttackStrategy – 전략 인터페이스

public interface IAttackStrategy
{
    void Attack();
}

역할

모든 공격 전략이 따라야 할 공통 메서드 정의

이 인터페이스를 기반으로 여러 공격 방식이 교체 가능해짐

 

MeleeAttack, RangedAttack, MagicAttack – 구체 전략 클래스들

public class MeleeAttack : IAttackStrategy
{
    public void Attack()
    {
        Debug.Log("근접 공격: 칼을 휘두릅니다.");
    }
}

public class RangedAttack : IAttackStrategy
{
    public void Attack()
    {
        Debug.Log("원거리 공격: 활을 쏩니다.");
    }
}

public class MagicAttack : IAttackStrategy
{
    public void Attack()
    {
        Debug.Log("마법 공격: 파이어볼을 발사합니다.");
    }
}

역할

각 클래스는 Attack() 메서드를 자기 방식대로 구현

외부에서 어떤 클래스가 사용되든, Attack()만 호출하면 내부 구현이 실행됨

새로운 전략(예: 폭탄 공격)을 추가하려면 클래스 하나만 더 만들면 됨 — OCP(Open/Closed Principle) 충족

 

Enemy – 컨텍스트 클래스 (전략을 사용하는 주체)

public class Enemy : MonoBehaviour
{
    private IAttackStrategy attackStrategy;

    public void SetAttackStrategy(IAttackStrategy strategy)
    {
        attackStrategy = strategy;
    }

    public void PerformAttack()
    {
        attackStrategy?.Attack();
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            PerformAttack();
        }
    }
}

역할

외부에서 전달받은 전략(SetAttackStrategy)을 저장

PerformAttack()을 호출하면 현재 설정된 전략을 실행

클라이언트(예: GameManager)가 전략만 바꾸면 동작이 달라짐 → 컨텍스트는 바뀌지 않음

 

GameManager – 클라이언트 (전략을 정하는 역할)

public class GameManager : MonoBehaviour
{
    public Enemy enemy;

    void Start()
    {
        enemy.SetAttackStrategy(new MagicAttack()); // 원하는 전략 설정
    }
}

역할

전략을 선택하고 컨텍스트(Enemy)에 주입

전략 패턴의 장점은 여기서 발휘됨: 조건에 따라 전략을 바꾸기만 하면 됨

'Unity > 디자인 패턴' 카테고리의 다른 글

상태 패턴(FSM, HFSM)  (1) 2025.05.04
싱글톤 패턴  (0) 2025.05.02
오브젝트 풀링 패턴  (0) 2025.03.08

싱글톤 패턴이란?

싱글톤 패턴은 클래스의 인스턴스를 하나만 생성하고,

그 인스턴스를 어디서든 접근할 수 있도록 전역적인 접근점을 제공하는 패턴입니다.

게임에서 GameManager, AudioManager, UIManager처럼 한 개만 존재해야 하는 객체를 만들 때 주로 사용됩니다.

 

구현

유일성 보장: 추가 인스턴스 생성을 막아야 하므로 Awake()에서 중복을 제한합니다.

전역 접근: public static 속성을 사용해 언제 어디서든 접근할 수 있게 합니다.

 

기본 형태

public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }

    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
        }
        else
        {
            Destroy(gameObject); // 중복 방지
        }
    }

    public void GameStart()
    {
        Debug.Log("Game Started!");
    }
}

 

사용 예시

public class GameManagerUser : MonoBehaviour
{
    void Start()
    {
        GameManager.Instance.GameStart();
    }
}

 

확장형 싱글톤 패턴

싱글톤을 여러 매니저 클래스에 사용할 경우, 매번 같은 코드를 반복해야 하는 단점이 있습니다.
이를 해결하기 위해 제너릭(Generic) 을 활용한 확장형 싱글톤 패턴을 사용할 수 있습니다.

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T instance;

    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                instance = FindObjectOfType<T>();
                if (instance == null)
                {
                    GameObject obj = new GameObject();
                    instance = obj.AddComponent<T>();
                }
            }
            return instance;
        }
    }
    private void Awake()
    {
        if (instance == null)
        {
            instance = this as T;
            DontDestroyOnLoad(gameObject);
        }
        else if (instance != this)
        {
            Destroy(gameObject);
        }
    }
}
public class GameManager : Singleton<GameManager>
{
    public void GameStart()
    {
        Debug.Log("Game Started!");
    }
}

 

Singleton<T> 클래스를 한 번 만들어두면,

다른 매니저 클래스들은 상속만으로 싱글톤 기능을 바로 사용할 수 있어

코드 중복을 줄이고 유지보수가 쉬워집니다.

 

 

 

'Unity > 디자인 패턴' 카테고리의 다른 글

상태 패턴(FSM, HFSM)  (1) 2025.05.04
전략 패턴  (0) 2025.05.04
오브젝트 풀링 패턴  (0) 2025.03.08

Unity Gaming Services(UGS) 

Unity는 예전부터 다양한 게임 개발 지원 서비스를 제공해왔습니다.
그러나 각 서비스가 제각각 분리되어 제공되면서, 개발자 입장에서는 일관된 사용 경험을 얻기 어려운 문제가 있었습니다.
이러한 문제를 해결하기 위해, Unity는 기존 서비스를 하나로 통합하고 기능을 대폭 강화한

Unity Gaming Services(UGS) 를 선보였습니다.

 

인증(Authentication)

Unity Gaming Services(UGS)는 다른 서비스를 활용하기 위한 기본 인증 시스템을 제공합니다.

비용 : 무제한 무료

제공 인증 종류

- 익명(Anonymous)

- 일반/소셜(구글플레이/애플게임센터/유니티 통합 등) 로그인

- 에디터 상에서는 소셜 로그인이 어려우므로 일반 로그인 구현 추천

 

[공통] PackageManager에서 Authentication, Cloud Save 등 필요한 기능 설치해야 작동


인증 주요 메서드

InitializeUnityServices(); 
UnityServices.InitializeAsync(); 
// 유니티 게이밍 서비스 초기화

[계정]
AuthenticationService.Instance.SignInWithUsernamePasswordAsync(string username, string password);
비밀번호 규칙: 특수문자, 대문자, 12자 이상
[구글]
PlayGamesPlatform.Instance.GetServerAuthCode();
AuthenticationService.SignInWithGooglePlayGamesAsync(authCode)

 

https://docs.unity.com/ugs/manual/authentication/manual/platform-signin-username-password

 

Username & Password

Username & Password is an identity provider which is provided natively by the Unity Authentication service and the SDK. It provides support for the following scenarios: The username used by this provider is completely separate from the player name. Passwor

docs.unity.com

 

using System;
using System.Threading.Tasks; // Task 사용을 위한 네임스페이스
using TMPro; 
using Unity.Services.Authentication; // 인증 관련 네임스페이스
using Unity.Services.Core; // 유니티 서비스 네임스페이스
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class AuthenticationManager : MonoBehaviour
{
    private static AuthenticationManager _instance;
    [SerializeField] private TMP_InputField idField;
    [SerializeField] private TMP_InputField passwordField;

    public static AuthenticationManager Instance
    {
        ... 중략
    }
    private void Awake()
    {
        ... 중략

        Init();
    }

    private async void Init()
    {
        await InitializeUnityServices();
    }

    private async Task InitializeUnityServices()
    {
        try
        {
            await UnityServices.InitializeAsync();
            Debug.Log("Unity Services initialized");
        }
        catch(Exception e)
        {
            Debug.LogException(e);
        }
    }

    private async void Login()
    {
        string id = idField.text.Trim();
        string password = passwordField.text.Trim();

        if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(password))
        {
            Debug.Log("아이디와 비밀번호를 입력하세요");
            return;
        }

        try
        {
            await InitializeUnityServices();
            await AuthenticationService.Instance.SignInWithUsernamePasswordAsync(id, password);
            SceneManager.LoadScene("StartScene");
        }
        catch (AuthenticationException ex)
        {
            Debug.LogException(ex);
        }
        catch (RequestFailedException ex)
        {
            Debug.LogException(ex);
        }
    }

    private async void SignUp()
    {
        string id = idField.text.Trim();
        string password = passwordField.text.Trim();

        if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(password))
        {
            Debug.Log("아이디와 비밀번호를 입력하세요");
            return;
        }
        try
        {
            await InitializeUnityServices();
            await AuthenticationService.Instance.SignInAnonymouslyAsync();
            await AuthenticationService.Instance.AddUsernamePasswordAsync(id, password);
        }
        catch (AuthenticationException ex) // 회원가입 관련
        {
            Debug.LogException(ex);
        }
        catch (RequestFailedException ex) // 서버 요청 관련
        {
            Debug.LogException(ex);
        }
    }
}

 

에디터 상에서 버튼 하나로 직접 SetID를 실행할 수 있게 ContextMenu를 활용

 

아이디를 만들기 전 기본적으로 로그인이 되어 있어야 하므로

 

익명 로그인 후 계정 등록 가능함

 

입력받은 id, password가 null 또는 비어있는지(empty)를 확인

 

 

 

만약 Authentication에 정보가 등록되어 있다면

 

Services에서 Unity Project ID를 Unlink 후 프로젝트를 다시 생성해야한다.

 

 

정상 등록되었다면 이렇게 뜬다.

PlayerManagement에 정상적으로 아이디 생성이 완료되었다면 아이디가 뜬다.

Unity UI는 사용자 인터페이스를 만들 수 있는 Canvas 기반 시스템
플레이어가 게임과 상호작용할 수 있도록 버튼, 텍스트, 이미지, 슬라이더 등을 배치하는 데 사용

핵심 구조: Canvas System

1.  Canvas

  • 모든 UI 요소의 부모
  • UI는 이 Canvas 안에 자식으로 위치해야 화면에 보여짐
  • 3가지 Render Mode:
    • Screen Space - Overlay (카메라 무시하고 UI를 화면에 고정)
    • Screen Space - Camera (카메라 기준으로 UI 렌더링)
    • World Space (UI를 3D 오브젝트처럼 씀)

1-1.  Canvas Scaler

UI Scale Mode 캔버스에서 UI 요소가 스케일되는 방법을 결정합니다.
Constant Pixel Size UI 요소가 화면 크기에 관계없이 동일한 픽셀 크기로 유지됩니다.
Scale With Screen Size 화면이 커질수록 UI 요소도 커집니다.
Constant Physical Size 화면 크기와 해상도에 관계없이 UI 요소가 동일한 물리적인 크기로 유지됩니다.

 

https://docs.unity3d.com/kr/2022.3/Manual/script-CanvasScaler.html

 

캔버스 스케일러 - Unity 매뉴얼

캔버스 스케일러(Canvas Scaler) 컴포넌트는 캔버스 내 UI 요소의 전체적인 스케일과 픽셀 밀도를 제어하는 데 사용됩니다. 스케일은 글꼴 크기와 이미지 경계 등 캔버스 아래의 모든 요소에 영향을

docs.unity3d.com

 

2.  RectTransform

  • UI 요소의 좌표, 크기, 정렬(anchor) 등을 설정
  • Transform 대신 RectTransform을 씀

3.  EventSystem

  • UI 입력 이벤트(클릭, 드래그 등)를 감지하는 시스템
  • 대부분 자동으로 생성되며, UI가 작동하려면 필수 요소

4.  앵커 (Anchor)

  • UI 요소(RectTransform) 기준에서의 부착 지점
  • 부모 객체(Canvas나 Panel 등) 안에서 어디에 기준을 잡을지를 정하는 것
  • 앵커를 정하면, 화면 크기가 변해도 UI 위치와 크기가 자동으로 조정됨

📌 예시: 버튼의 앵커를 "오른쪽 아래"에 설정하면, 해상도가 바뀌어도 버튼은 항상 오른쪽 아래에 붙어있다.

  • 앵커는 (0,0)~(1,1) 사이 값으로 나타냄
    • (0,0) → 부모 왼쪽 아래
    • (1,1) → 부모 오른쪽 위

5. 피벗 (Pivot)

  • 오브젝트 자체의 중심점
  • 이 중심을 기준으로 회전하거나 스케일 조정이 일어남
  • 피벗은 주로 회전축, 크기 조정 시 중심을 바꿀 때 중요

📌 예시: 피벗을 (0,0)으로 설정하면, 왼쪽 아래 모서리를 기준으로 회전하거나 크기가 변함. 기본은 (0.5, 0.5) — 즉 중앙.

 

정리

항목 설명 주로 쓰는 상황
앵커 (Anchor) 부모 공간 안에서 고정할 위치 UI 위치 유지, 반응형 화면
피벗 (Pivot) 오브젝트 자체의 중심점 회전, 스케일 조정 중심

'Unity' 카테고리의 다른 글

월드좌표, 로컬좌표  (0) 2025.04.24
리소스 데이터 관리(제네릭 T, TryGetValue, TryAdd)  (0) 2025.04.08
SceneManager.GetActiveScene, AddListener  (0) 2025.03.21
플레이어까지 거리(벡터) 구하기  (0) 2025.03.19
[Resource.Load]  (0) 2024.11.13

+ Recent posts