A high-performance, type-safe finite state machine system for Unity that eliminates GetComponent calls, provides per-instance state management, and includes advanced features like transition guards, state priorities, and event notifications.
- Features
- Installation
- Quick Start
- Core Concepts
- API Reference
- Advanced Features
- Best Practices
- Examples
- Troubleshooting
- Performance
- License
? Type-Safe Generic Implementation
- No casting required
- Compile-time type checking
- Full IntelliSense support
? Zero GetComponent Calls
- All components cached on initialization
- No reflection during runtime
- Maximum performance
? Per-Instance State Management
- Each GameObject has its own state instances
- No shared state between objects
- Thread-safe for multiple AI agents
? Complete Lifecycle Support
void Init(TClient client); // Once on creation
bool CanEnter(TClient client); // Validate before entering
void OnEnter(TClient client); // Setup on entry
Type Execute(TClient client); // Every Update
void ExecuteFixed(TClient client); // Every FixedUpdate
void ExecuteLate(TClient client); // Every LateUpdate
bool CanExit(TClient client); // Validate before exiting
void OnExit(TClient client); // Cleanup on exit? Transition Guards
- Validate preconditions before entering states
- Prevent invalid state transitions
- Helpful error messages
? State Priority System
- Four priority levels: Low, Normal, High, Critical
- Lower priority cannot interrupt higher priority
- Prevents animation glitches
? State Transition Events
- Subscribe to state changes
- Integrate with UI, audio, analytics
- Decoupled system architecture
? Interruption Control
- Protect critical states from interruption
- Global transitions for emergency states
- Configurable per-state
? Unity Integration
- Real-time Inspector debugging
- Scene view Gizmos
- Enable/disable debug per-instance
? Performance Optimized
- No boxing/unboxing
- Zero allocations in hot paths
- Type-based lookups (no strings)
- Import the GTFSM package into your Unity project
- Files will be in
Assets/DFT_Games/GTFSM/ - Start using by implementing
IGTFSMClient<T>
Scripts/GTFSM.cs- Core FSM managerScripts/Declarations.cs- Interfaces and base classesDemo/- Example implementation (optional)
- Unity 2019.4 or later
- .NET Framework 4.7.1 or later
- No external dependencies
using DFTGames.Tools.GTFSM;
using UnityEngine;
using UnityEngine.AI;
public class Enemy : MonoBehaviour, IGTFSMClient<Enemy>
{
// FSM Required Properties
public GTFSMState<Enemy> CurrentState { get; set; }
public GTFSMState<Enemy> PreviousState { get; set; }
public FSMDebugInfo<Enemy> DebugInfo { get; set; }
// Your game-specific properties
public NavMeshAgent Agent { get; private set; }
public Transform[] patrolPath;
private void Awake()
{
// Cache components
Agent = GetComponent<NavMeshAgent>();
// Initialize debug info (optional)
DebugInfo = new FSMDebugInfo<Enemy>();
}
private void Start()
{
// Register states
GTFSM.RegisterState<Enemy, PatrolState>(this);
GTFSM.RegisterState<Enemy, AttackState>(this);
// Set initial state
GTFSM.SetState(this, typeof(PatrolState));
}
private void Update()
{
GTFSM.Execute(this);
}
private void OnDestroy()
{
GTFSM.Cleanup(this);
}
// Implement notification (optional - for events)
public void NotifyStateTransition(string fromState, string toState)
{
Debug.Log($"State: {fromState} -> {toState}");
}
}using DFTGames.Tools.GTFSM;
using System;
using UnityEngine;
public class PatrolState : GTFSMStateBase<Enemy>
{
public override void OnEnter(Enemy client)
{
base.OnEnter(client);
// Setup patrol behavior
}
public override Type Execute(Enemy client)
{
// Update patrol logic
// Transition to attack when condition met
if (PlayerDetected(client))
return typeof(AttackState);
return null; // Stay in patrol
}
public override void OnExit(Enemy client)
{
// Cleanup patrol behavior
}
}Press Play in Unity. Your Enemy will now use the FSM to manage states!
A Client is any MonoBehaviour that uses the FSM. It must:
- Implement
IGTFSMClient<T>whereTis itself - Store current and previous state references
- Call GTFSM methods in Unity lifecycle
Example:
public class Player : MonoBehaviour, IGTFSMClient<Player>
{
// Implementation
}A State represents a behavior or mode. It must:
- Implement
GTFSMState<TClient>or inherit fromGTFSMStateBase<TClient> - Define behavior in
Execute()method - Return next state Type or null
Example:
public class IdleState : GTFSMStateBase<Player>
{
public override Type Execute(Player client)
{
if (Input.GetKey(KeyCode.W))
return typeof(MoveState);
return null;
}
}The GTFSM class manages:
- State registration per client instance
- State transitions with validation
- Lifecycle method calls
- Priority and guard checking
// Register a state for a client
TState RegisterState<TClient, TState>(TClient client)
where TClient : class, IGTFSMClient<TClient>
where TState : class, GTFSMState<TClient>, new()Example:
GTFSM.RegisterState<Enemy, PatrolState>(this);// Execute current state (call in Update)
void Execute<TClient>(TClient client)
// Execute current state (call in FixedUpdate)
void ExecuteFixed<TClient>(TClient client)
// Execute current state (call in LateUpdate)
void ExecuteLate<TClient>(TClient client)Example:
private void Update()
{
GTFSM.Execute(this);
}// Set state by Type (returns success/fail)
bool SetState<TClient>(TClient client, Type stateType)
// Set state by instance
bool SetState<TClient>(TClient client, GTFSMState<TClient> state)
// Transition to previous state
bool TransitionToPreviousState<TClient>(TClient client)Example:
if (GTFSM.SetState(this, typeof(AttackState)))
{
Debug.Log("Transitioned to attack!");
}// Check if state is registered
bool HasState<TClient>(TClient client, Type stateType)
// Get registered state by Type
GTFSMState<TClient> GetState<TClient>(TClient client, Type stateType)
// Get registered state by name
GTFSMState<TClient> GetState<TClient>(TClient client, string stateName)Example:
if (GTFSM.HasState(this, typeof(FleeState)))
{
GTFSM.SetState(this, typeof(FleeState));
}// Set a global transition (interrupts current state)
void SetGlobalTransition<TClient>(TClient client, Type stateType)
// Clear global transition
void ClearGlobalTransition<TClient>(TClient client)Example:
// Immediately interrupt current state to stun
GTFSM.SetGlobalTransition<Enemy>(this, typeof(StunState));// Clean up all states for a client (call in OnDestroy)
void Cleanup<TClient>(TClient client)Example:
private void OnDestroy()
{
GTFSM.Cleanup(this);
}public interface GTFSMState<TClient>
{
string StateName { get; set; }
StatePriority Priority { get; }
bool AllowInterruption { get; }
void Init(TClient client);
bool CanEnter(TClient client);
void OnEnter(TClient client);
Type Execute(TClient client);
void ExecuteFixed(TClient client);
void ExecuteLate(TClient client);
bool CanExit(TClient client);
void OnExit(TClient client);
}Convenience base class with default implementations:
public abstract class GTFSMStateBase<TClient> : GTFSMState<TClient>
{
// Auto-tracked timer
protected float stateTimer;
// Helper to update timer
protected void UpdateTimer(float deltaTime);
// Virtual properties (override as needed)
public virtual StatePriority Priority => StatePriority.Normal;
public virtual bool AllowInterruption => true;
// Virtual guards (override to add validation)
public virtual bool CanEnter(TClient client) => true;
public virtual bool CanExit(TClient client) => true;
// Must override
public abstract Type Execute(TClient client);
}Prevent invalid transitions by validating preconditions:
public class AttackState : GTFSMStateBase<Enemy>
{
// Check before entering
public override bool CanEnter(Enemy client)
{
if (!client.HasWeapon)
{
Debug.LogWarning("Cannot attack: No weapon!");
return false;
}
if (!client.TargetInRange)
{
Debug.LogWarning("Cannot attack: Target out of range!");
return false;
}
return true;
}
// Check before exiting
public override bool CanExit(Enemy client)
{
// Don't allow exit until attack animation completes
return stateTimer >= MIN_ATTACK_DURATION;
}
}Benefits:
- Automatic validation
- Clear error messages
- Prevents bugs early
Control which states can interrupt others:
public enum StatePriority
{
Low = 0, // Idle, Patrol
Normal = 1, // Move, Attack
High = 2, // Special attacks
Critical = 3 // Death, Cutscenes
}Usage:
public class PatrolState : GTFSMStateBase<Enemy>
{
// Low priority - can be interrupted by anything
public override StatePriority Priority => StatePriority.Low;
}
public class AttackState : GTFSMStateBase<Enemy>
{
// High priority - only High or Critical can interrupt
public override StatePriority Priority => StatePriority.High;
}
public class DeathState : GTFSMStateBase<Enemy>
{
// Critical priority - nothing can interrupt
public override StatePriority Priority => StatePriority.Critical;
}Interruption Rules:
| Current Priority | Can be interrupted by |
|---|---|
| Low | Normal, High, Critical |
| Normal | Normal, High, Critical |
| High | High, Critical |
| Critical | Critical only |
React to state changes from external systems:
public class Enemy : MonoBehaviour, IGTFSMClient<Enemy>
{
// Define your event
public event Action<string, string> OnStateTransition;
// Implement notification
public void NotifyStateTransition(string fromState, string toState)
{
// Fire event
OnStateTransition?.Invoke(fromState, toState);
// Direct handling
Debug.Log($"State changed: {fromState} -> {toState}");
}
private void Start()
{
// Subscribe to transitions
OnStateTransition += OnStateChanged;
}
private void OnStateChanged(string from, string to)
{
// Update UI
UpdateHealthBar(to);
// Play sounds
PlayStateSound(to);
// Track analytics
Analytics.StateTransition(from, to);
}
private void OnDestroy()
{
OnStateTransition -= OnStateChanged;
GTFSM.Cleanup(this);
}
}Use Cases:
- UI updates (health bars, ability icons)
- Audio triggering (footsteps, attack sounds)
- VFX spawning (particle effects)
- Analytics tracking
- Achievement monitoring
Protect critical states from global transitions:
public class CutsceneState : GTFSMStateBase<Player>
{
public override StatePriority Priority => StatePriority.Critical;
// Cannot be interrupted by global transitions
public override bool AllowInterruption => false;
public override Type Execute(Player client)
{
// Cutscene logic...
if (cutsceneComplete)
return typeof(IdleState);
return null;
}
}Force immediate state changes (interrupts):
// Example: Stun effect
public void OnStunned()
{
// Immediately transition from any state
GTFSM.SetGlobalTransition<Enemy>(this, typeof(StunState));
}
// Example: Death
public void OnDeath()
{
GTFSM.SetGlobalTransition<Enemy>(this, typeof(DeathState));
}Notes:
- Global transitions respect
AllowInterruption - Checked before normal state transitions
- Automatically cleared after use
Return to previous states:
public class StunState : GTFSMStateBase<Enemy>
{
public override Type Execute(Enemy client)
{
UpdateTimer(Time.deltaTime);
if (stateTimer >= STUN_DURATION)
{
// Return to whatever state we were in before stun
GTFSM.TransitionToPreviousState(client);
return null; // Don't return a type when using TransitionToPreviousState
}
return null;
}
}Enable real-time state monitoring:
public class Enemy : MonoBehaviour, IGTFSMClient<Enemy>
{
[Header("Debug Settings")]
[SerializeField] private bool enableDebug = true;
[Header("FSM Debug Info")]
[SerializeField] private string currentStateName;
[SerializeField] private float timeInState;
[SerializeField] private int transitionCount;
public FSMDebugInfo<Enemy> DebugInfo { get; set; }
private void Awake()
{
if (enableDebug)
{
DebugInfo = new FSMDebugInfo<Enemy>();
}
}
private void Update()
{
GTFSM.Execute(this);
if (enableDebug && DebugInfo != null)
{
currentStateName = DebugInfo.CurrentStateName;
timeInState = DebugInfo.TimeInCurrentState;
transitionCount = DebugInfo.StateTransitionCount;
}
}
}Inspector Shows:
- Current state name
- Time in current state
- Total transitions
- State history (last 10)
Visualize states in Scene view:
private void OnDrawGizmos()
{
if (!enableDebug || CurrentState == null)
return;
// Draw state name above character
Gizmos.color = Color.green;
Vector3 labelPos = transform.position + Vector3.up * 2f;
#if UNITY_EDITOR
UnityEditor.Handles.Label(labelPos,
$"State: {CurrentState.StateName}\nTime: {timeInState:F1}s");
#endif
// Draw patrol path
if (patrolPath != null)
{
Gizmos.color = Color.yellow;
for (int i = 0; i < patrolPath.Length; i++)
{
if (patrolPath[i] != null)
{
Gizmos.DrawWireSphere(patrolPath[i].position, 0.5f);
if (i < patrolPath.Length - 1)
{
Gizmos.DrawLine(patrolPath[i].position,
patrolPath[i + 1].position);
}
}
}
}
}? Bad:
public override Type Execute(Enemy client)
{
// GetComponent called every frame!
var agent = client.GetComponent<NavMeshAgent>();
agent.SetDestination(target);
}? Good:
public class Enemy : MonoBehaviour
{
public NavMeshAgent Agent { get; private set; }
private void Awake()
{
Agent = GetComponent<NavMeshAgent>(); // Cache once
}
}
public override Type Execute(Enemy client)
{
client.Agent.SetDestination(target); // Direct access
}? Bad:
public override Type Execute(Enemy client)
{
if (playerDetected)
{
if (client.HasWeapon && client.Ammo > 0)
return typeof(AttackState);
}
}? Good:
public class AttackState : GTFSMStateBase<Enemy>
{
public override bool CanEnter(Enemy client)
{
return client.HasWeapon && client.Ammo > 0;
}
}
public override Type Execute(Enemy client)
{
if (playerDetected)
return typeof(AttackState); // Guard handles validation
}// Low - Background behaviors
public class IdleState : GTFSMStateBase<Enemy>
{
public override StatePriority Priority => StatePriority.Low;
}
// Normal - Gameplay actions
public class AttackState : GTFSMStateBase<Enemy>
{
public override StatePriority Priority => StatePriority.Normal;
}
// High - Important animations
public class SpecialAttackState : GTFSMStateBase<Enemy>
{
public override StatePriority Priority => StatePriority.High;
}
// Critical - Must complete
public class DeathState : GTFSMStateBase<Enemy>
{
public override StatePriority Priority => StatePriority.Critical;
public override bool AllowInterruption => false;
}private void OnDestroy()
{
// Unsubscribe from events
if (OnStateTransition != null)
{
OnStateTransition -= OnStateChanged;
}
// Clean up FSM
GTFSM.Cleanup(this);
}? Good:
public class AttackState : GTFSMStateBase<Enemy>
{
private const float ATTACK_DURATION = 2f;
public override Type Execute(Enemy client)
{
UpdateTimer(Time.deltaTime); // Use built-in timer
if (stateTimer >= ATTACK_DURATION)
return typeof(IdleState);
return null;
}
}? Verbose:
public class MyState : GTFSMState<Enemy>
{
public string StateName { get; set; }
public StatePriority Priority => StatePriority.Normal;
public bool AllowInterruption => true;
public void Init(Enemy client) { }
public bool CanEnter(Enemy client) => true;
public void OnEnter(Enemy client) { }
public void ExecuteFixed(Enemy client) { }
public void ExecuteLate(Enemy client) { }
public bool CanExit(Enemy client) => true;
public void OnExit(Enemy client) { }
public Type Execute(Enemy client)
{
// Your logic
}
}? Concise:
public class MyState : GTFSMStateBase<Enemy>
{
public override Type Execute(Enemy client)
{
// Your logic
}
}// Enemy.cs
public class Enemy : MonoBehaviour, IGTFSMClient<Enemy>
{
public GTFSMState<Enemy> CurrentState { get; set; }
public GTFSMState<Enemy> PreviousState { get; set; }
public FSMDebugInfo<Enemy> DebugInfo { get; set; }
public NavMeshAgent Agent { get; private set; }
public Transform[] patrolPath;
public Transform player;
public float detectionRange = 10f;
private void Awake()
{
Agent = GetComponent<NavMeshAgent>();
DebugInfo = new FSMDebugInfo<Enemy>();
}
private void Start()
{
GTFSM.RegisterState<Enemy, PatrolState>(this);
GTFSM.RegisterState<Enemy, AttackState>(this);
GTFSM.SetState(this, typeof(PatrolState));
}
private void Update()
{
GTFSM.Execute(this);
}
private void OnDestroy()
{
GTFSM.Cleanup(this);
}
public void NotifyStateTransition(string from, string to)
{
Debug.Log($"[Enemy] {from} -> {to}");
}
public bool PlayerInRange()
{
return Vector3.Distance(transform.position, player.position) <= detectionRange;
}
}
// PatrolState.cs
public class PatrolState : GTFSMStateBase<Enemy>
{
private int currentWaypoint;
public override StatePriority Priority => StatePriority.Low;
public override bool CanEnter(Enemy client)
{
return client.patrolPath != null && client.patrolPath.Length > 0;
}
public override void OnEnter(Enemy client)
{
base.OnEnter(client);
MoveToNextWaypoint(client);
}
public override Type Execute(Enemy client)
{
// Check for player
if (client.PlayerInRange())
return typeof(AttackState);
// Check if reached waypoint
if (client.Agent.remainingDistance <= 0.5f)
{
MoveToNextWaypoint(client);
}
return null;
}
private void MoveToNextWaypoint(Enemy client)
{
currentWaypoint = (currentWaypoint + 1) % client.patrolPath.Length;
client.Agent.SetDestination(client.patrolPath[currentWaypoint].position);
}
}
// AttackState.cs
public class AttackState : GTFSMStateBase<Enemy>
{
private const float ATTACK_DURATION = 2f;
public override StatePriority Priority => StatePriority.High;
public override void OnEnter(Enemy client)
{
base.OnEnter(client);
client.Agent.isStopped = true;
}
public override Type Execute(Enemy client)
{
UpdateTimer(Time.deltaTime);
// Face player
Vector3 lookDir = client.player.position - client.transform.position;
lookDir.y = 0;
client.transform.rotation = Quaternion.LookRotation(lookDir);
// Return to patrol after attack
if (stateTimer >= ATTACK_DURATION)
return typeof(PatrolState);
return null;
}
public override void OnExit(Enemy client)
{
base.OnExit(client);
client.Agent.isStopped = false;
}
}public class Player : MonoBehaviour, IGTFSMClient<Player>
{
public GTFSMState<Player> CurrentState { get; set; }
public GTFSMState<Player> PreviousState { get; set; }
public FSMDebugInfo<Player> DebugInfo { get; set; }
public CharacterController Controller { get; private set; }
public Animator Animator { get; private set; }
private void Awake()
{
Controller = GetComponent<CharacterController>();
Animator = GetComponent<Animator>();
DebugInfo = new FSMDebugInfo<Player>();
}
private void Start()
{
GTFSM.RegisterState<Player, IdleState>(this);
GTFSM.RegisterState<Player, MoveState>(this);
GTFSM.RegisterState<Player, JumpState>(this);
GTFSM.RegisterState<Player, AttackPlayerState>(this);
GTFSM.SetState(this, typeof(IdleState));
}
private void Update()
{
GTFSM.Execute(this);
}
private void OnDestroy()
{
GTFSM.Cleanup(this);
}
public void NotifyStateTransition(string from, string to)
{
Animator.SetTrigger(to);
}
}
public class IdleState : GTFSMStateBase<Player>
{
public override Type Execute(Player client)
{
if (Input.GetButtonDown("Jump"))
return typeof(JumpState);
if (Input.GetButtonDown("Fire1"))
return typeof(AttackPlayerState);
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
if (h != 0 || v != 0)
return typeof(MoveState);
return null;
}
}
public class MoveState : GTFSMStateBase<Player>
{
public override Type Execute(Player client)
{
if (Input.GetButtonDown("Jump"))
return typeof(JumpState);
if (Input.GetButtonDown("Fire1"))
return typeof(AttackPlayerState);
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
if (h == 0 && v == 0)
return typeof(IdleState);
Vector3 move = new Vector3(h, 0, v) * 5f * Time.deltaTime;
client.Controller.Move(move);
return null;
}
}
public class JumpState : GTFSMStateBase<Player>
{
private const float JUMP_FORCE = 10f;
private float verticalVelocity;
public override StatePriority Priority => StatePriority.High;
public override void OnEnter(Player client)
{
base.OnEnter(client);
verticalVelocity = JUMP_FORCE;
}
public override Type Execute(Player client)
{
UpdateTimer(Time.deltaTime);
verticalVelocity -= 20f * Time.deltaTime;
client.Controller.Move(Vector3.up * verticalVelocity * Time.deltaTime);
if (client.Controller.isGrounded && stateTimer > 0.1f)
return typeof(IdleState);
return null;
}
}
public class AttackPlayerState : GTFSMStateBase<Player>
{
private const float ATTACK_DURATION = 0.5f;
public override StatePriority Priority => StatePriority.High;
public override bool CanExit(Player client)
{
return stateTimer >= ATTACK_DURATION;
}
public override Type Execute(Player client)
{
UpdateTimer(Time.deltaTime);
if (stateTimer >= ATTACK_DURATION)
return typeof(IdleState);
return null;
}
}public class PlayerUIManager : MonoBehaviour
{
public Player player;
public Image staminaBar;
public GameObject attackIcon;
private void Start()
{
player.OnStateTransition += OnPlayerStateChanged;
}
private void OnDestroy()
{
player.OnStateTransition -= OnPlayerStateChanged;
}
private void OnPlayerStateChanged(string from, string to)
{
// Update UI based on state
attackIcon.SetActive(to == "AttackPlayerState");
if (to == "JumpState")
{
staminaBar.fillAmount -= 0.2f;
}
// Play UI animations
LeanTween.scale(attackIcon, to == "AttackPlayerState" ? Vector3.one : Vector3.zero, 0.3f);
}
}Symptoms: State doesn't change when expected
Solutions:
- Check console for warning messages
- Verify
CanExit()returns true on current state - Verify
CanEnter()returns true on target state - Check state priority (lower can't interrupt higher)
- Ensure target state is registered
// Debug transition
if (!GTFSM.SetState(this, typeof(AttackState)))
{
Debug.LogError("Failed to transition to AttackState!");
// Check if registered
if (!GTFSM.HasState(this, typeof(AttackState)))
{
Debug.LogError("AttackState not registered!");
}
}Symptoms: SetGlobalTransition doesn't interrupt current state
Solutions:
- Check if current state has
AllowInterruption = false - Verify target state is registered
- Ensure Execute() is being called
public class DeathState : GTFSMStateBase<Player>
{
// This blocks global transitions
public override bool AllowInterruption => false;
}Symptoms: State changes right after entering
Solutions:
- Increase state priority
- Use
CanExit()to prevent early exits - Set
AllowInterruption = falsefor critical states
public class CutsceneState : GTFSMStateBase<Player>
{
public override StatePriority Priority => StatePriority.Critical;
public override bool AllowInterruption => false;
public override bool CanExit(Player client)
{
return cutsceneComplete;
}
}Symptoms: Multiple enemies interfere with each other's behavior
Solution: This is already fixed in v7.0! Each instance gets its own states. If you're experiencing this:
- Ensure you're using v7.0 or later
- Verify each instance calls
GTFSM.RegisterStateseparately - Check that you're not storing state references globally
Symptoms: Memory usage grows over time
Solution: Always call Cleanup in OnDestroy:
private void OnDestroy()
{
// Unsubscribe from events
OnStateTransition -= MyHandler;
// Clean up FSM
GTFSM.Cleanup(this);
}Symptoms: Debug fields in Inspector don't change
Solutions:
- Ensure
enableDebugis checked - Verify DebugInfo is initialized in Awake
- Call UpdateDebugInfo in Update
[SerializeField] private bool enableDebug = true;
private void Awake()
{
if (enableDebug)
{
DebugInfo = new FSMDebugInfo<Enemy>();
}
}
private void Update()
{
GTFSM.Execute(this);
if (enableDebug && DebugInfo != null)
{
currentStateName = DebugInfo.CurrentStateName;
timeInState = DebugInfo.TimeInCurrentState;
}
}Tested on Unity 2021.3, 1000 AI agents:
| Operation | Time | Allocations |
|---|---|---|
| State Registration | 0.05ms per agent | 1 allocation per state |
| State Transition | 0.002ms | 0 allocations |
| Execute (per frame) | 0.001ms per agent | 0 allocations |
| Global Transition | 0.003ms | 0 allocations |
? No GetComponent Calls
- Before: 2+ calls per frame per agent
- After: 0 calls (cached in Awake)
- Improvement: 100% elimination
? No Boxing/Unboxing
- Before: Object dictionaries caused boxing
- After: Generic dictionaries, zero boxing
- Improvement: 100% elimination
? Per-Instance States
- Before: Shared states caused interference
- After: Isolated state instances
- Improvement: Thread-safe, scalable
? Type-Based Lookups
- Before: String-based dictionary keys
- After: Type-based keys (faster)
- Improvement: 30% faster lookups
-
Cache Component References
// In Awake Agent = GetComponent<NavMeshAgent>();
-
Use Object Pooling for States (if creating thousands)
// Not necessary for most cases - states are reused per instance -
Disable Debug in Builds
#if UNITY_EDITOR DebugInfo = new FSMDebugInfo<Enemy>(); #endif
-
Use ExecuteFixed for Physics
public override void ExecuteFixed(Player client) { // Physics calculations here client.Rigidbody.AddForce(force); }
Copyright 2010-2026 - DFT Games Ltd.
Licensed under Unity Asset Store EULA: http://unity3d.com/legal/as_terms
Summary:
- ? Use in your commercial games
- ? Modify for your projects
- ? Don't redistribute as a package
- ? Don't sell as an asset
- ? Added transition guards (CanEnter/CanExit)
- ? Added state priority system (Low/Normal/High/Critical)
- ? Added state transition event notifications
- ? Added interruption control (AllowInterruption)
- ? Added per-instance debug enable/disable
- ?? Fixed shared state instances bug
- ? Optimized dictionary lookups (Type-based keys)
- ?? Comprehensive documentation and examples
- ?? Improved Unity Inspector integration
- ?? GTFSMStateBase provides sensible defaults
- Documentation: Read this README thoroughly
- Examples: Check the Demo folder for working examples
- Console: Read warning messages for debugging
- Inspector: Enable debug mode to monitor state changes
Q: Can I use this with multiplayer?
A: Yes! Each client instance has its own FSM. For network sync, use the NotifyStateTransition callback to send state changes over the network.
Q: Can states have their own variables? A: Yes! States are classes - add fields as needed:
public class PatrolState : GTFSMStateBase<Enemy>
{
private int currentWaypoint; // State-specific variable
private float patrolSpeed = 3f;
}Q: Can I have nested/hierarchical states? A: Not directly in v7.0, but you can simulate it:
public class CombatState : GTFSMStateBase<Player>
{
private enum CombatMode { Melee, Ranged, Blocking }
private CombatMode currentMode;
}Q: Can I use this with animation state machines? A: Yes! Use the transition event:
public void NotifyStateTransition(string from, string to)
{
animator.SetTrigger(to);
}Q: How many states can I have? A: Unlimited. Performance is O(1) for lookups.
Q: Can I create states at runtime? A: Yes, but states must be registered before use:
GTFSM.RegisterState<Enemy, NewState>(this);Developed by DFT Games Ltd.
Special thanks to the Unity community for feedback and suggestions.
Thank you for using GTFSM!
For updates and support, visit: https://github.com/DFTGames/GTFSM