In the asteroid's example, the values handed to components created inside a state are essentially simple defaults. In my case, these values would have to be generated. To generate these values, I need access to other components (for example, I need a position created based no the viewable scene). I don't do it now, but I could see a component needing info from another entity (teleport to nearest friendly unit) Should I do this in the state machine? If I don't, where should I do them? If I do it in the state machine isn't it indirectly coupling the component to other components? Isn't this the role of the system?
Another issue is I sometimes have a "short term transition". Meaning I need something to happen once and then be removed or not updated. For example, triggering an animation and then I can remove that component. This can't wait for a "state change" because the overall state of the entity may not change again immediately. Basically, I need to kick something off and then not execute it again. I have come up with a few options:1. Add a component dedicated to doing this job and use a "needsUpdate" boolean on that component to inform the processing system that it should take action. And then when it does, set this to false so it ignores it in subsequent loops until something changes it to true again.2. have the system remove the component for this kind of action when it's done doing it's update for that loop. Removes the need for needsUpdate as the component will be executed and removed on the next loop of the associated system. Systems like this are just basic helpers to do one off tasks shared by a lot of different entities. (Like fade out before the entity is removed)The above is really about "transitions" between states. Example, during game play the user might collect a shield power up for their ship. When this is added to the entity as a component, an animation "raising the shield" should be triggered. This is a set it and forget it type operation. Should I just create a "RaiseShield" component (Probably added by the ShieldComponent state machine in it's initial "Activating" state) that is added to the entity and processed like above? This might come with a one-off sound and also trigger adding a visual node to the entity to represent the shield (again, a one-off fade-in animation type of thing). After this is done, the shield should be switched to activated.
Figured I'd take a crack at answering some of your questions:In the asteroid's example, the values handed to components created inside a state are essentially simple defaults. In my case, these values would have to be generated. To generate these values, I need access to other components (for example, I need a position created based no the viewable scene). I don't do it now, but I could see a component needing info from another entity (teleport to nearest friendly unit) Should I do this in the state machine? If I don't, where should I do them? If I do it in the state machine isn't it indirectly coupling the component to other components? Isn't this the role of the system?I could be wrong but from what I understand, Finite State Machines (FSM) require the content of their states to be fixed (hence the term finite state). In terms of Ash's FSM framework, I take that to mean the properties/values inside a state's components are not meant to change once created and assigned (to said state). The examples you've given (position based on a viewable scene and teleporting to nearest friendly unit) seem to describe situations where the properties of a state's components will have to change. That seems to make them poor candidates for a FSM.
Another issue is I sometimes have a "short term transition". Meaning I need something to happen once and then be removed or not updated. For example, triggering an animation and then I can remove that component. This can't wait for a "state change" because the overall state of the entity may not change again immediately. Basically, I need to kick something off and then not execute it again. I have come up with a few options:1. Add a component dedicated to doing this job and use a "needsUpdate" boolean on that component to inform the processing system that it should take action. And then when it does, set this to false so it ignores it in subsequent loops until something changes it to true again.2. have the system remove the component for this kind of action when it's done doing it's update for that loop. Removes the need for needsUpdate as the component will be executed and removed on the next loop of the associated system. Systems like this are just basic helpers to do one off tasks shared by a lot of different entities. (Like fade out before the entity is removed)The above is really about "transitions" between states. Example, during game play the user might collect a shield power up for their ship. When this is added to the entity as a component, an animation "raising the shield" should be triggered. This is a set it and forget it type operation. Should I just create a "RaiseShield" component (Probably added by the ShieldComponent state machine in it's initial "Activating" state) that is added to the entity and processed like above? This might come with a one-off sound and also trigger adding a visual node to the entity to represent the shield (again, a one-off fade-in animation type of thing). After this is done, the shield should be switched to activated.Transitions between states can be difficult to pull off but there are a couple ways to tackle it.One approach using Finite State Machines is to model what happens before entering a state (transition in) and modeling what happens after leaving/exiting said state (transition out). In a project I did a while back that meant tweening component properties to achieve various effects. The solution I came up with isn't exactly trivial but it worked for my use case. Ash has this notion of Nodes which are distinct groupings of components matched to a specific system type. You can track when a node is added or removed from a System via "nodeAdded" and "nodeRemoved" signals(analogous to events) triggered by the Engine. While I doubt it was the architect's intention, these work well enough for one-off operations. I set up "enter" or "exit" transitions by adding and removing "Transition" components that houses tween information. Then a TransitionSystem triggers FSM state changes upon a tween's completion. It may or may not be of use to you but feel free to check out my implementation here).
You could also try to reframing some aspects of the problem. Take the shield example, instead of making it a component of the ship entity consider making it an entity onto itself. When the ship and a shield power-up collide, the system responsible for handling collisions, would spawn a shield entity with its own series of FSM states independent of the ship (shieldActivateState, shieldTakeDamageState, shieldDeactivateState etc.). Upon creation, the shield entity could be composed of component instances from the ship entity (essentially having the ship and shield entities share state; position for example) but redefine others (shield has a hitbox that is distinct from the ship so it would need unique components to define it).The ship entity's FSM would still have a "shielded" state, but the work of managing that state is offloaded to the shield entity.
That is an interesting theory. What you are saying is that finite state machines not only have the components be equal but the data in every component should be equal. But, let me create a very simple case. Let's say the asteroid ship didn't want to re-spawn at exactly the same location. This is a change on the data in a component, not the component combination. This seems to be the point of data driven design to me. So the playing state, when you switch to it, might adjust the position of the new ship to one of several re-spawn locations. Or, you might even want to re-spawn where you died, not at the original location. Saying all data in an FSM system must be identical means you shouldn't adjust the position of the ship either. Meaning once you are in the "playing" state and the player moves the ship, technically, it's no longer in the beginning "playing" state. So, really, it should transition to a less restrictive state of "playerControlled". That is obviously "extreme". And I don't think that's what you intend with this response.
63 public function createSpaceship():Entity {
64 var spaceship : Entity = new Entity();
65 var fsm : EntityStateMachine = new EntityStateMachine( spaceship );
66
67
68 fsm.createState( "playing" )
69 .add( Motion ).withInstance( new Motion( 0, 0, 0, 15 ) )
70 .add( MotionControls ).withInstance( new MotionControls( Keyboard.LEFT, Keyboard.RIGHT, Keyboard.UP, 100, 3 ) )
71 .add( Gun ).withInstance( new Gun( 8, 0, 0.3, 2 ) )
72 .add( GunControls ).withInstance( new GunControls( Keyboard.SPACE ) )
73 .add( Collision ).withInstance( new Collision( 9 ) )
74 .add( Display ).withInstance( new Display( new SpaceshipView() ) );
75
76
77 var deathView : SpaceshipDeathView = new SpaceshipDeathView();
78
79 fsm.createState( "destroyed" )
80 .add( DeathThroes ).withInstance( new DeathThroes( 5 ) )
81 .add( Display ).withInstance( new Display( deathView ) )
82 .add( Animation ).withInstance( new Animation( deathView ) );
83
84
85 spaceship.add( new Spaceship( fsm ) ).add( new Position( 10 + Math.random(620), 10 + Math.random(460), 0 ) );
86
87 fsm.changeState( "playing" );
88
89 engine.addEntity(spaceship);
90
91
92 return spaceship;
93 }
Yes, you misinterpreted that. In Ash, a state is a set of components that should be added when that state is entered and removed when that state is exited unless required by the next state. The state only cares about these components. It will not touch components that are not part of its state. So a state machine handles a subset of the components on an entity. When you exit a state it removes all the components that are part of that state's definition in the state machine, and then adds all the components that are part of the next state (with the caveat that if a specific component is in both states it will not be removed and added again but will simply remain). You can have any number of state machines operating in parallel on one entity. Each will handle the components they care about and will ignore all the others.Were I creating Ash again I would not include the state machines in the core library. There are three reasons for this -1. They are heavily engineered compared to the rest of the engine2. They are not core functionality3. They do not cover all needs for state management
The reason they are in Ash is because in the past many developers asked me how to do a state machine in Ash. These developers were under the impression that state machines are not possible in ECS architectures like Ash, when the reality is the whole engine, and every entity, and every component is a state machine at different levels of detail.- You change the state of an engine by changing the entities and systems in it- You change the state of an entity by changing the components in it- You change the state of a component by changing the data in its properties
When using the built-in FSM, sometimes you need to do additional work when transitioning to a new state. You can so this in various ways including- Write some additional code that you run immediately after changing the state (i.e. the next line of code after the call to changeState)- Create transition states that track perform the transition requirements and then change to the final state when the transition is complete- Use the nodeAdded signal in a NodeList or the componentAdded signal in an Entity to reset the data in a component- In a system, detect the initial state of a component and treat it differently, performing "first frame" logic on the entity
You can attach any component to any entity. But there is no guarantee that that will make sense within the context of your game and how you have architected its needs. At any time, to get the effect you want you will have to add one or more specific components to specific entities. If you design your components such that, to get the effect you want, a shield component should be added to an entity separate to the ship that is simply how that component is designed to be used.
I hope this clarifies some issues and doesn't just confuse things further.