private IEnumerator ChangeToNewStateRoutine(StateMapping newState) { isInTransition = true; foreach (StateMapping currentState in currentStates) { if (currentState != null) { var exitRoutine = currentState.Exit(); if (exitRoutine != null) { yield return(StartCoroutine(exitRoutine)); } } } currentStates = new List <StateMapping>(); currentStates.Add(newState); if (newState != null) { var enterRoutine = newState.Enter(); if (enterRoutine != null) { yield return(StartCoroutine(enterRoutine)); } } isInTransition = false; }
private IEnumerator WaitForPreviousTransition(StateMapping nextState) { while (_isInTransition) { yield return(null); } ChangeState(nextState.State); }
private IEnumerator ChangeToNewStateRoutine(StateMapping newState, StateTransition transition) { destinationState = newState; //Chache this so that we can overwrite it and hijack a transition if (currentState != null) { if (currentState.hasExitRoutine) { exitRoutine = currentState.ExitRoutine(); if (exitRoutine != null && transition != StateTransition.Overwrite) //Don't wait for exit if we are overwriting { yield return(engine.StartCoroutine(exitRoutine)); } exitRoutine = null; } else { currentState.ExitCall(); } currentState.Finally(); } lastState = currentState; currentState = destinationState; if (currentState != null) { if (currentState.hasEnterRoutine) { enterRoutine = currentState.EnterRoutine(); if (enterRoutine != null) { yield return(engine.StartCoroutine(enterRoutine)); } enterRoutine = null; } else { currentState.EnterCall(); } //Broadcast change only after enter transition has begun. if (Changed != null) { Changed((T)currentState.state); } } isInTransition = false; }
private IEnumerator ChangeToNewStateRoutine(StateMapping newState, StateTransition transtiion) { _destinationState = newState; if (_currentState != null) { if (_currentState.hasExitRoutine) { _exitRoutine = _currentState.ExitRoutine(); if (_exitRoutine != null && transtiion != StateTransition.Overwrite) { yield return(_engine.StartCoroutine(_exitRoutine)); } _exitRoutine = null; } else { _currentState.ExitCall(); } _currentState.Finally(); } _lastState = _currentState; _currentState = _destinationState; if (_currentState != null) { if (_currentState.hasEnterRoutine) { _enterRoutine = _currentState.EnterRoutine(); if (_enterRoutine != null) { yield return(_engine.StartCoroutine(_enterRoutine)); } _enterRoutine = null; } else { _currentState.EnterCall(); } if (Changed != null) { Changed(_currentState.State); } } _isInTransition = false; }
private IEnumerator RemoveConcurrentStateRoutine(StateMapping newState) { currentStates.Remove(newState); if (newState != null) { var exitRoutine = newState.Exit(); if (exitRoutine != null) { yield return(StartCoroutine(exitRoutine)); } } }
private IEnumerator AddConcurrentStateRoutine(StateMapping newState) { currentStates.Add(newState); if (newState != null) { var enterRoutine = newState.Enter(); if (enterRoutine != null) { yield return(StartCoroutine(enterRoutine)); } } }
IEnumerator ChangeToNewStateRoutine(StateMapping newState) { destinationState = newState; // Chache this so that we can overwrite it and hijack a transition if (currentState != null) { exitRoutine = currentState.Exit(); if (exitRoutine != null) { yield return(StartCoroutine(exitRoutine)); } exitRoutine = null; currentState.Finally(); } currentState = destinationState; if (currentState != null) { enterRoutine = currentState.Enter(); if (enterRoutine != null) { yield return(StartCoroutine(enterRoutine)); } enterRoutine = null; // Broadcast change only after enter transition has begun. if (Changed != null) { Changed(currentState.state); } } isInTransition = false; }
public void Initialize <T>(MonoBehaviour entity) { //Define States var values = Enum.GetValues(typeof(T)); stateLookup = new Dictionary <Enum, StateMapping>(); for (int i = 0; i < values.Length; i++) { var mapping = new StateMapping((Enum)values.GetValue(i)); stateLookup.Add(mapping.state, mapping); } //Reflect methods var methods = entity.GetType().GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic); //Bind methods to states var separator = "_".ToCharArray(); for (int i = 0; i < methods.Length; i++) { var names = methods[i].Name.Split(separator); //Ignore functions without an underscore if (names.Length <= 1) { continue; } Enum key; try { key = (Enum)Enum.Parse(typeof(T), names[0]); } catch (ArgumentException) { //Some things (evetns, properties) generate automatic method. Ignore these for (int j = 0; j < ignoredNames.Length; j++) { if (names[0] == ignoredNames[j]) { goto SkipWarning; } } Debug.LogWarning("Method with name " + methods[i].Name + " could not resolve a matching state. Check method spelling"); continue; SkipWarning: continue; } var targetState = stateLookup[key]; switch (names[1]) { case "Enter": if (methods[i].ReturnType == typeof(IEnumerator)) { targetState.Enter = CreateDelegate <Func <IEnumerator> >(methods[i], entity); } else { var action = CreateDelegate <Action>(methods[i], entity); targetState.Enter = () => { action(); return(null); }; } break; case "Exit": if (methods[i].ReturnType == typeof(IEnumerator)) { targetState.Exit = CreateDelegate <Func <IEnumerator> >(methods[i], entity); } else { var action = CreateDelegate <Action>(methods[i], entity); targetState.Exit = () => { action(); return(null); }; } break; case "Update": targetState.Update = CreateDelegate <Action>(methods[i], entity); break; case "LateUpdate": targetState.LateUpdate = CreateDelegate <Action>(methods[i], entity); break; case "FixedUpdate": targetState.FixedUpdate = CreateDelegate <Action>(methods[i], entity); break; } } }
public StateMachine(StateMachineRunner engine, BaseState stateScript, string[] states) { _engine = engine; _stateScript = stateScript; _stateLookup = new Dictionary <string, StateMapping> (); foreach (var state in states) { var mapping = new StateMapping(state); _stateLookup.Add(state, mapping); } // Reflect methods var methods = _stateScript.GetType().GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic); for (int i = 0; i < methods.Length; i++) { if (methods[i].GetCustomAttributes(typeof(CompilerGeneratedAttribute), true).Length != 0) { continue; } int lastIdx = methods[i].Name.LastIndexOf('_'); if (lastIdx < 0) { continue; } string name = methods[i].Name.Substring(0, lastIdx); if (!states.Contains(name)) { continue; } StateMapping targetState = _stateLookup[name]; switch (methods[i].Name.Substring(lastIdx + 1)) { case "Enter": if (methods[i].ReturnType == typeof(IEnumerator)) { targetState.hasEnterRoutine = true; targetState.EnterRoutine = CreateDelegate <Func <IEnumerator> > (methods[i], _stateScript); } else { targetState.hasEnterRoutine = false; targetState.EnterCall = CreateDelegate <Action> (methods[i], _stateScript); } break; case "Exit": if (methods[i].ReturnType == typeof(IEnumerator)) { targetState.hasExitRoutine = true; targetState.ExitRoutine = CreateDelegate <Func <IEnumerator> > (methods[i], _stateScript); } else { targetState.hasExitRoutine = false; targetState.ExitCall = CreateDelegate <Action> (methods[i], _stateScript); } break; case "Finally": targetState.Finally = CreateDelegate <Action> (methods[i], _stateScript); break; case "Update": targetState.Update = CreateDelegate <Action> (methods[i], _stateScript); break; case "LateUpdate": targetState.LateUpdate = CreateDelegate <Action> (methods[i], _stateScript); break; case "FixedUpdate": targetState.FixedUpdate = CreateDelegate <Action> (methods[i], _stateScript); break; } } _currentState = new StateMapping(null); }
public void ChangeState(string newState, StateTransition transition) { if (_stateLookup == null) { throw new Exception("States have not been configured, please call initialized before trying to set state"); } if (!_stateLookup.ContainsKey(newState)) { throw new Exception("No state with the name " + newState.ToString() + " can be found. Please make sure you are called the correct type the statemachine was initialized with"); } StateMapping nextState = _stateLookup[newState]; // Cancel any queued changes if (_queuedChange != null) { _engine.StopCoroutine(_queuedChange); _queuedChange = null; } switch (transition) { case StateTransition.Safe: if (_isInTransition) { //Already exiting current state on our way to our previous target state if (_exitRoutine != null) { _destinationState = nextState; return; } if (_enterRoutine != null) { _queuedChange = WaitForPreviousTransition(nextState); _engine.StartCoroutine(_queuedChange); return; } } break; case StateTransition.Overwrite: if (_currentTransition != null) { _engine.StopCoroutine(_currentTransition); _currentTransition = null; } if (_exitRoutine != null) { _engine.StopCoroutine(_exitRoutine); _exitRoutine = null; } if (_enterRoutine != null) { _engine.StopCoroutine(_enterRoutine); _enterRoutine = null; } break; } if ((_currentState != null && _currentState.hasExitRoutine) || nextState.hasEnterRoutine) { _isInTransition = true; _currentTransition = ChangeToNewStateRoutine(nextState, transition); _engine.StartCoroutine(_currentTransition); } else //Same frame transition, no coroutines are present { if (_currentState != null) { _currentState.ExitCall(); _currentState.Finally(); } _lastState = _currentState; _currentState = nextState; if (_currentState != null) { _currentState.EnterCall(); if (Changed != null) { Changed(_currentState.State); } } _isInTransition = false; } }
public void ChangeState(Enum newState, StateTransition transition = StateTransition.Safe) { if (stateLookup == null) { throw new Exception("States have not been configured, please call initialized before trying to set state"); } if (!stateLookup.ContainsKey(newState)) { throw new Exception("No state with the name " + newState.ToString() + " can be found. Please make sure you are called the correct type the statemachine was initialized with"); } var nextState = stateLookup[newState]; // Uncomment this to block loop transitions // if (currentState == nextState) return; // Cancel any queued changes. if (queuedChange != null) { StopCoroutine(queuedChange); queuedChange = null; } switch (transition) { // case StateMachineTransition.Blend: // Do nothing - allows the state transitions to overlap each other. This is a dumb idea, as previous state might trigger new changes. // A better way would be to start the two couroutines at the same time. IE don't wait for exit before starting start. // How does this work in terms of overwrite? // Is there a way to make this safe, I don't think so? // break; case StateTransition.Safe: if (isInTransition) { if (exitRoutine != null) // We are already exiting current state on our way to our previous target state { // Overwrite with our new target destinationState = nextState; return; } if (enterRoutine != null) // We are already entering our previous target state. Need to wait for that to finish and call the exit routine. { // Damn, I need to test this hard queuedChange = WaitForPreviousTransition(nextState); StartCoroutine(queuedChange); return; } } break; case StateTransition.Overwrite: if (currentTransition != null) { StopCoroutine(currentTransition); } if (exitRoutine != null) { StopCoroutine(exitRoutine); } if (enterRoutine != null) { StopCoroutine(enterRoutine); } if (currentState != null) { currentState.Finally(); } currentState = null; // We need to set current state to null so that we don't trigger it's exit routine break; } isInTransition = true; currentTransition = ChangeToNewStateRoutine(nextState); StartCoroutine(currentTransition); }
public StateMachine(StateMachineRunner engine, MonoBehaviour component) { this.engine = engine; this.component = component; //Define States var values = Enum.GetValues(typeof(T)); if (values.Length < 1) { throw new ArgumentException("Enum provided to Initialize must have at least 1 visible definition"); } stateLookup = new Dictionary <object, StateMapping>(); for (int i = 0; i < values.Length; i++) { var mapping = new StateMapping((Enum)values.GetValue(i)); stateLookup.Add(mapping.state, mapping); } //Reflect methods var methods = component.GetType().GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic); //Bind methods to states var separator = "_".ToCharArray(); for (int i = 0; i < methods.Length; i++) { if (methods[i].GetCustomAttributes(typeof(CompilerGeneratedAttribute), true).Length != 0) { continue; } var names = methods[i].Name.Split(separator); //Ignore functions without an underscore if (names.Length <= 1) { continue; } Enum key; try { key = (Enum)Enum.Parse(typeof(T), names[0]); } catch (ArgumentException) { //Not an method as listed in the state enum continue; } var targetState = stateLookup[key]; switch (names[1]) { case "Enter": if (methods[i].ReturnType == typeof(IEnumerator)) { targetState.hasEnterRoutine = true; targetState.EnterRoutine = CreateDelegate <Func <IEnumerator> >(methods[i], component); } else { targetState.hasEnterRoutine = false; targetState.EnterCall = CreateDelegate <Action>(methods[i], component); } break; case "Exit": if (methods[i].ReturnType == typeof(IEnumerator)) { targetState.hasExitRoutine = true; targetState.ExitRoutine = CreateDelegate <Func <IEnumerator> >(methods[i], component); } else { targetState.hasExitRoutine = false; targetState.ExitCall = CreateDelegate <Action>(methods[i], component); } break; case "Finally": targetState.Finally = CreateDelegate <Action>(methods[i], component); break; case "Update": targetState.Update = CreateDelegate <Action>(methods[i], component); break; case "LateUpdate": targetState.LateUpdate = CreateDelegate <Action>(methods[i], component); break; case "FixedUpdate": targetState.FixedUpdate = CreateDelegate <Action>(methods[i], component); break; case "OnCollisionEnter": targetState.OnCollisionEnter = CreateDelegate <Action <Collision> >(methods[i], component); break; } } //Create nil state mapping currentState = new StateMapping(null); }
public void ChangeState(T newState, StateTransition transition) { if (stateLookup == null) { throw new Exception("States have not been configured, please call initialized before trying to set state"); } if (!stateLookup.ContainsKey(newState)) { throw new Exception("No state with the name " + newState.ToString() + " can be found. Please make sure you are called the correct type the statemachine was initialized with"); } var nextState = stateLookup[newState]; if (currentState == nextState) { return; } //Cancel any queued changes. if (queuedChange != null) { engine.StopCoroutine(queuedChange); queuedChange = null; } switch (transition) { //case StateMachineTransition.Blend: //Do nothing - allows the state transitions to overlap each other. This is a dumb idea, as previous state might trigger new changes. //A better way would be to start the two couroutines at the same time. IE don't wait for exit before starting start. //How does this work in terms of overwrite? //Is there a way to make this safe, I don't think so? //break; case StateTransition.Safe: if (isInTransition) { if (exitRoutine != null) //We are already exiting current state on our way to our previous target state { //Overwrite with our new target destinationState = nextState; return; } if (enterRoutine != null) //We are already entering our previous target state. Need to wait for that to finish and call the exit routine. { //Damn, I need to test this hard queuedChange = WaitForPreviousTransition(nextState); engine.StartCoroutine(queuedChange); return; } } break; case StateTransition.Overwrite: if (currentTransition != null) { engine.StopCoroutine(currentTransition); } if (exitRoutine != null) { engine.StopCoroutine(exitRoutine); } if (enterRoutine != null) { engine.StopCoroutine(enterRoutine); } //Note: if we are currently in an EnterRoutine and Exit is also a routine, this will be skipped in ChangeToNewStateRoutine() break; } if ((currentState != null && currentState.hasExitRoutine) || nextState.hasEnterRoutine) { isInTransition = true; currentTransition = ChangeToNewStateRoutine(nextState, transition); engine.StartCoroutine(currentTransition); } else //Same frame transition, no coroutines are present { if (currentState != null) { currentState.ExitCall(); currentState.Finally(); } lastState = currentState; currentState = nextState; if (currentState != null) { currentState.EnterCall(); if (Changed != null) { Changed((T)currentState.state); } } isInTransition = false; } }