{"id":27125438,"url":"https://github.com/dubit/unity-fsm","last_synced_at":"2025-04-07T14:53:20.360Z","repository":{"id":65664846,"uuid":"139430254","full_name":"dubit/unity-fsm","owner":"dubit","description":"A lightweight finite state machine implementation that supports custom and conditional transitions","archived":false,"fork":false,"pushed_at":"2020-01-02T11:51:56.000Z","size":13,"stargazers_count":93,"open_issues_count":2,"forks_count":14,"subscribers_count":8,"default_branch":"development","last_synced_at":"2023-08-03T20:22:29.570Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dubit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-07-02T10:48:49.000Z","updated_at":"2023-06-15T18:13:23.000Z","dependencies_parsed_at":"2023-02-03T05:25:11.124Z","dependency_job_id":null,"html_url":"https://github.com/dubit/unity-fsm","commit_stats":null,"previous_names":[],"tags_count":2,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dubit%2Funity-fsm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dubit%2Funity-fsm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dubit%2Funity-fsm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dubit%2Funity-fsm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dubit","download_url":"https://codeload.github.com/dubit/unity-fsm/tar.gz/refs/heads/development","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247675602,"owners_count":20977376,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-04-07T14:53:19.776Z","updated_at":"2025-04-07T14:53:20.332Z","avatar_url":"https://github.com/dubit.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# unity-fsm\n\n# What is it?\nA lightweight finite state machine implementation that supports custom and conditional transitions\n\n## What are the requirements?\n * Unity 2018.x\n\n## How to use it.\n\nFull example:\n```c#\nvar fsm = new FiniteStateMachine\u003cstring\u003e(\"closed\", \"open\", \"locked\");\nfsm.AddTransition(\"closed\", \"open\", OPEN_COMMAND)\n   .AddTransition(\"closed\", \"locked\", LOCK_COMMAND, customTransition) // using a custom transition\n   .AddTransition(\"locked\", \"closed\", UNLOCK_COMMAND, () =\u003e user.HasKey()) // using a condition\n   .AddTransition(\"open\", \"closed\", CLOSE_COMMAND)\n   .OnEnter(open, () =\u003e Debug.Log(\"The door is now open!\"))\n   .OnExit(closed, HandleDoorIsNoLongerClosed);\n```\n\nA finite state machine is composed of `STATES` and `TRANSITIONS`, and is controled by `COMMANDS`\n\nImagine the example diagram modelling a door:\n```\n          \"CLOSE\"               \"LOCK\"\n    +--------\u003e---------+  +--------\u003e---------+\n    |                  |  |                  |\n +----+              +------+              +------+\n |OPEN|              |CLOSED|              |LOCKED|\n +----+              +------+              +------+\n    |                  |  |                  |\n    +--------\u003c---------+  +--------\u003c---------+\n           \"OPEN\"                \"UNLOCK\"\n```\n\nHere we have 3 states, `OPEN`, `CLOSED`, and `LOCKED`, with 4 transitions, controlled by 4 commands `OPEN`, `CLOSE`, `LOCK` and `UNLOCK`.\n\n\nNow let's break it down\n### Step 1: Create it\nFirst let's create the fsm object using it's constructor that takes any number of `STATES`. A state can be any IComparable. In this case we will use strings.\n```c#\nvar fsm = new FiniteStateMachine\u003cstring\u003e(\"closed\", \"open\", \"locked\");\n```\n\nwe could also use the static helper `FromEnum`, that will take an enum and use every value as a state\n```c#\nvar fsm =  FiniteStateMachine\u003cDoorStates\u003e.FromEnum();\n```\n\n### Step 2: Configure it\nNext we can configure the finite state machine's transitions using a fluent interface to add the transitions.\n\n`AddTransition` takes, a from `STATE`, to `STATE` and a `COMMAND` that will trigger it.\nIt literally reads like, _when in state X, I can move to state Y with the command Z.\nIt also optionally takes a condition check function or a custom transition for further control\n\n```c#\nfsm.AddTransition(\"closed\", \"open\", OPEN_COMMAND)\n   .AddTransition(\"closed\", \"locked\", LOCK_COMMAND, customTransition) // using a custom transition\n   .AddTransition(\"locked\", \"closed\", UNLOCK_COMMAND, () =\u003e user.HasKey()) // using a condition\n   .AddTransition(\"open\", \"closed\", CLOSE_COMMAND);\n```\n\n#### Condition functions\nThe condition function is useful for guarding transitions that depend on state outside the domain of the thing you are modelling.\nIn this case \"does the user have the key\" is a peice of state that does not belong to the door, but should control whether or not the door can be unlocked.\n\n#### Custom transitions\nImplement your own custom transition by extending the abstract class `Transition\u003cTState\u003e`. Here you can control the transition between the states, such as animating objects, or the timing of the transition.\n\n\n### Step 3 (optional): Add your own hooks\nOptional, but quite likely required will be to add your own hooks. Again using the fluent interface, you can get callbacks when you enter, or exit a specific state, or go between 2 specific states, or just any change\n```c#\nfsm\n    // Called on every state change\n    .OnChange((fromState, toState) =\u003e { Debug.Log(\"State has changed from fromState to toState\"); });\n    // Called on when we go from open to closed\n    .OnChange(\"locked\", \"open\", () =\u003e { Debug.Log(\"The door was unlcocked\"); });\n    // Called on entry to the specified state\n    .OnEnter(\"open\", () =\u003e Debug.Log(\"The door is open everyone!\"))\n    // Called on exit from the specified state\n    .OnExit(\"closed\", HandleDoorIsNoLongerClosed);\n```\n\n### Step 4: Begin and start issuing commands\n\n```c#\nfsm.Begin(closed);\nfsm.IssueCommand(openCommand); // door should now be open\nfsm.IssueCommand(lockCommand); // nothing will happen (no transition from open using lock command)\n\n\n// get the current state\nfsm.CurrentState; // will equal \"open\"\n```\n\n## Motivation\nThe door example above could be modelled quite easily with a class containing 2 boolean values for our states `isLocked` and `isOpen`. \nThen a bunch of functions `Open()` `Close()` `Lock()` `Unlock` for our commands. The problem is the user can then call `Lock()` and `Open()` and the state would look like this:\n\n```c#\nisLocked = true;\nisOpen = true;\n```\n\nThis is not a valid state for our door model. We would have to add checks in all those functions. It's not a lot in this case but in other situations it can turn into a lot of over complicated code. Using the FSM, we can configure which transitions are allowed, and it cannot possibly get into an invalid state, because that state doesn't exist!  \n\nTo recreate this model we would need checks in all of our command functions and to expose and implement the required event hooks.\nThere are a lot of situations where we can just replace this with a fsm.\n\n### FAQs\n**Can I use this to control my game states?**\n\nThis is not a solution for high level so called \"game state\" management. It can be used to control that but normally those require fully connected graphs. and integration with asset loaded. It's not recommended. The term _state_ is a misnomer in this context anyway.\n\n**Is there an easy way to add commands to go to any state from any state?**\n \nNo, that would negate the need to have a finite state machine. By using this you are describing how a domain is allowed to change state. If you want to go anywhere from anywhere, you probably just want a single enum variable and the ability to set it.\n\n\n## Releasing\n* Use [gitflow](https://nvie.com/posts/a-successful-git-branching-model/)\n* Create a release branch for the release\n* On that branch, bump version number in package json file, any other business (docs/readme updates)\n* Merge to master via pull request and tag the merge commit on master.\n* Merge back to development.\n\n## DUCK\n\nThis repo is part of DUCK (dubit unity component kit)\nDUCK is a series of repos containing reusable component, utils, systems \u0026 tools. \n\nDUCK packages can be added to a project as git submodules or by using [Unity Package Manager](https://docs.unity3d.com/Manual/upm-git.html). \n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdubit%2Funity-fsm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdubit%2Funity-fsm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdubit%2Funity-fsm/lists"}