Advertisement
TaylorsRus

State Machine System

Sep 6th, 2023 (edited)
99
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 8.03 KB | None | 0 0
  1. --// StateManager
  2. local Knit = require(game:GetService("ReplicatedStorage").Packages.Knit)
  3. local StateManager = Knit.CreateController {
  4.     Name = "StateManager",
  5. }
  6.  
  7. local ReplicatedStorage = game:GetService("ReplicatedStorage")
  8. local RunService = game:GetService("RunService")
  9. local Player = game:GetService("Players").LocalPlayer
  10.  
  11. local Events = ReplicatedStorage:WaitForChild("Events")
  12. local State = ReplicatedStorage:WaitForChild("State")
  13.  
  14. local StateClass = require(State:WaitForChild("StateClass"))
  15.  
  16. local PromptMachine = Events:WaitForChild("PromptMachine")
  17. local ChangeState = Events:WaitForChild("ChangeState")
  18. local RequestState = Events:WaitForChild("RequestState")
  19.  
  20. local LastChange = nil
  21. local AlreadyCounting = false
  22. local StatesConfigured  = false
  23. local StateMachines = {
  24.     HumanoidStateMachine = {},
  25.     CombatStateMachine = {}
  26. }  
  27.  
  28. local IDLE_REVERT_TIME = 5
  29. local ITERATION_INTERVAL = 1
  30.  
  31. local function SetToDefault(Player)
  32.     for _,StateMachine in StateMachines do
  33.         StateMachine:ChangeState("Idle")
  34.     end
  35. end
  36.  
  37. local function ConfigureStates(Character)
  38.     local Humanoid = Character:WaitForChild("Humanoid")
  39.     local HumanoidStateMachine = StateManager:FetchStateMachine("Humanoid", Player)
  40.     local CombatStateMachine = StateManager:FetchStateMachine("Combat", Player)
  41.     local ConnectionMade = false
  42.    
  43.     RunService.RenderStepped:Connect(function()
  44.         local IsWalking = Humanoid.MoveDirection.Magnitude > 0
  45.         local IsStationary = Humanoid.MoveDirection.Magnitude == 0
  46.         local IsMoving = HumanoidStateMachine:FetchState().Movement
  47.         if (IsWalking and not IsMoving) or IsStationary and IsMoving then
  48.             HumanoidStateMachine:ChangeState("Walking")
  49.         end
  50.     end)
  51.     Humanoid.StateChanged:Connect(function(_, NewState)
  52.         if NewState ~= Enum.HumanoidStateType.Running then
  53.             HumanoidStateMachine:ChangeState(NewState.Name)
  54.         end
  55.     end)   
  56.     --RequestState.OnCli
  57.  
  58.     SetToDefault(Player)
  59.     StatesConfigured = true
  60. end
  61.  
  62. function StateManager.KnitInit()
  63.     for StateMachine,_ in StateMachines do
  64.         StateMachines[StateMachine] = StateClass.new(StateMachine)
  65.         StateMachines[StateMachine]:InitStates()
  66.     end
  67.    
  68.     if Player.Character then ConfigureStates(Player.Character) end
  69.     Player.CharacterAdded:Connect(ConfigureStates)
  70.    
  71.     PromptMachine.OnClientEvent:Connect(function(Machine, State)
  72.         local Machine = StateMachines[Machine.."StateMachine"]
  73.         Machine:ChangeState(State)
  74.     end)
  75.  
  76.     repeat task.wait() until StatesConfigured
  77.     setmetatable(StateManager, {
  78.         __index = function(_, State)
  79.             return function()
  80.                 for _,StateMachine in StateMachines do
  81.                     if StateMachine.States[State] then StateMachine:ChangeState(State) end
  82.                 end
  83.             end
  84.         end,
  85.     })
  86. end
  87.  
  88. function StateManager:FetchStateMachine(StateMachine)
  89.     return StateMachines[StateMachine.."StateMachine"]
  90. end
  91.  
  92. function StateManager:FetchAllMachines()
  93.     return StateMachines
  94. end
  95.  
  96. return StateManager
  97.  
  98. -- //
  99.  
  100. -- // StateClass
  101. local StateClass = {}
  102. StateClass.__index = StateClass
  103.  
  104. local ReplicatedStorage = game:GetService("ReplicatedStorage")
  105. local RunService = game:GetService("RunService")
  106. local Player = game:GetService("Players").LocalPlayer
  107.  
  108. local Knit = require(ReplicatedStorage.Packages.Knit)
  109.  
  110. local State = ReplicatedStorage:WaitForChild("State")
  111.  
  112. local AlreadyCounting = false
  113. local LastChange = nil
  114.  
  115. local ReturnStates = {
  116.     "Ready",
  117.     "Idle"
  118. }
  119.  
  120. local COUNTDOWN_INTERVAL = .5
  121. local IDLE_REVERT_TIME = 5
  122.  
  123. local function IsRequiredState(OldState, NewState, OtherState)
  124.     if not NewState.RequiredStates then return true end
  125.     local RequiredStates = 0
  126.     local FoundStates = 0
  127.     for _ in NewState.RequiredStates do
  128.         RequiredStates += 1
  129.     end
  130.    
  131.     for _,States in NewState.RequiredStates do
  132.         for _,State in ipairs(States) do
  133.             if State == OldState.Name or State == OtherState.Name then
  134.                 FoundStates += 1
  135.             end
  136.         end
  137.     end
  138.    
  139.     if FoundStates >= RequiredStates then
  140.         return true
  141.     end
  142.    
  143.     return false
  144. end
  145.  
  146. local function ValidNewState(OldState, NewState, States)
  147.     if OldState.Name == NewState.Name
  148.         and not table.find(ReturnStates, NewState.Name) then
  149.         return
  150.            
  151.     end
  152.     for StateName,_ in States do
  153.         if StateName == NewState.Name then
  154.             return true
  155.         end
  156.     end
  157.    
  158.     return false
  159. end
  160.  
  161. local function RevertToReturn(NewState)
  162.     if NewState.Movement then
  163.         local Humanoid = Player.Character.Humanoid
  164.         repeat task.wait() until Humanoid.MoveDirection.Magnitude == 0
  165.     elseif not NewState.Movement and not NewState.Holdable then
  166.         while true do
  167.             task.wait(COUNTDOWN_INTERVAL)
  168.             local DeltaTime = os.clock() - LastChange
  169.             if DeltaTime >= IDLE_REVERT_TIME then
  170.                 break
  171.             end
  172.         end
  173.     end
  174. end
  175.  
  176.  
  177. function StateClass.new(StateMachine)
  178.     local self = setmetatable({}, StateClass)
  179.     self.Name = StateMachine
  180.     self.States = {}   
  181.     for _,State in ipairs(State[StateMachine]:GetChildren()) do
  182.         self.States[State.Name] = require(State)
  183.     end
  184.    
  185.     self.ReturnState = "Idle"
  186.     self.State = self.States.Idle
  187.     return self
  188. end
  189.  
  190. function StateClass:InitStates()
  191.     for _,State in self.States do
  192.         if State.Init then
  193.             State.Init()
  194.         end
  195.     end
  196. end
  197.  
  198. function StateClass:ChangeState(State)
  199.     local StateManager = Knit.GetController("StateManager")
  200.     local OtherMachineName = self.Name == "HumanoidStateMachine" and "Combat"  
  201.         or self.Name == "CombatStateMachine" and "Humanoid"
  202.         or error("Couldn't grab state machine: "..self.Name)
  203.     local OtherMachine = StateManager:FetchStateMachine(OtherMachineName)
  204.    
  205.     local OldState = self.State
  206.     local NewState = self.States[State] or error("State enum not found:"..State)
  207.     local OtherState = OtherMachine:FetchState()
  208.    
  209.     local SwappingReturnState = OldState.Name == NewState.Name
  210.         and table.find(ReturnStates, NewState.Name)
  211.    
  212.     if not ValidNewState(OldState, NewState, self.States) then return end  
  213.     if not IsRequiredState(OldState, NewState, OtherState)
  214.         and not SwappingReturnState then
  215.         return
  216.     end
  217.  
  218.     if SwappingReturnState then
  219.         if NewState.Name == "Ready" then
  220.             NewState = self.States.Idle
  221.             self:ChangeReturnState("Idle")
  222.         else
  223.             return
  224.         end
  225.     end
  226.    
  227.     if OldState.Exit  then
  228.         OldState.Exit(NewState)
  229.     end
  230.     if NewState.Enter then
  231.         NewState.Enter(OldState)
  232.     end
  233.    
  234.     self.State = NewState  
  235.     print(self.Name.." State Change:",self.State.Name)
  236.    
  237.     if table.find(ReturnStates, NewState.Name) then
  238.         return
  239.     end
  240.     -- // This return statement means that we are returning to a resting state,
  241.     -- // Such as "Ready" or "Idle", and therefore don't need to RevertToReturn()
  242.    
  243.     LastChange = os.clock()
  244.     if AlreadyCounting or NewState.Holdable then return end
  245.     AlreadyCounting = true
  246.    
  247.     RevertToReturn(NewState)   
  248.     -- // This function yields the code until it is time to revert,
  249.     -- // To the return state (where applicable)
  250.  
  251.     AlreadyCounting = false
  252. end
  253.  
  254. function StateClass:ChangeReturnState(State)
  255.     if table.find(ReturnStates, State) then
  256.         self.ReturnState = State
  257.     end
  258. end
  259.  
  260. function StateClass:FetchState(State)
  261.     return self.State
  262. end
  263.  
  264.  
  265. return StateClass
  266. -- //
  267.  
  268. -- // None State
  269. return {
  270.     Name = script.Name,
  271.     Holdable = false,
  272.     -- // Holdable means that Enter() will occour on InputBegan,
  273.     -- // And Exit() will occour on InputEnded.
  274.    
  275.     RequiredStates = {
  276.         Humanoid = {},
  277.         Combat = {},
  278.     },
  279.     -- // RequiredStates is a list which contains possible states,
  280.     -- // In order to enter the state this list resides in, the
  281.     -- // State corresponding to the sub-lists StateMachine (i.e, Humanoid or Combat),
  282.     -- // Must have already been within that list, ex; If Humanoid contains "Attacking",
  283.     -- // Then in order to enter the state this list resides in, I must be "Attacking".
  284.     -- // This is a matter of OR, not AND. Therefore you must be in one of, not all of.
  285.     -- // In the event states exist for both sub-lists, each state machine must be in
  286.     -- // A state from their corresponding list.
  287.     -- // If this list is nil or does not exist, you can enter the state from any state.
  288.    
  289.     Init = function()
  290.     end,
  291.     -- // Occurs when confiruing states.
  292.    
  293.     Enter = function()
  294.     end,
  295.     --// Occurs when entering this state.
  296.    
  297.     Exit = function()
  298.     end,
  299.     -- Occurs when leaving this state.
  300. }
  301.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement