Пример #1
0
        /// <summary>
        ///     Before to start, let's review some of the Svelto.ECS terms:
        ///     - Entity:
        ///     it must be a real and concrete entity that you can explain in terms of game design. The name of each
        ///     entity should reflect a specific concept from the game design domain
        ///     - Engines (Systems):
        ///     Where all the logic lies. Engines operates on Entity Components
        ///     - IEntityComponent:
        ///     It's an Entity Component which can be used with Pure ECS
        ///     - IEntityViewComponent:
        ///     structs implementing this are used to wrap Objects that come from OOP libraries. You will never use it unless
        ///     you are forced to mix your ECS code with OOP code because of external libraries or platforms. These special
        ///     "Hybrid" component can hold only interfaces
        ///     - Implementors:
        ///     The EntityViewComponent exposed interfaces must be implemented by Implementors that are actually the
        ///     Objects you need to wrap.
        ///     - EntityDescriptors:
        ///     Gives a way to formalise your Entity, it also defines the components that must
        ///     be generated once the Entity is built
        /// </summary>
        void CompositionRoot(UnityContext contextHolder)
        {
            //the UnitySumbmissionEntityViewScheduler is the scheduler that is used by the EnginesRoot to know
            //when to submit the entities. Custom ones can be created for special cases.
            var unityEntitySubmissionScheduler = new UnityEntitiesSubmissionScheduler("survival");

            //The Engines Root is the core of Svelto.ECS. You shouldn't inject the EngineRoot,
            //therefore the composition root class must hold a reference or it will be garbage collected.
            _enginesRoot = new EnginesRoot(unityEntitySubmissionScheduler);
            //The EntityFactory can be injected inside factories (or engine acting as factories) to build new entities
            //dynamically
            var entityFactory = _enginesRoot.GenerateEntityFactory();
            //The entity functions is a set of utility operations on Entities, including removing an entity. I couldn't
            //find a better name so far.
            var entityFunctions             = _enginesRoot.GenerateEntityFunctions();
            var entityStreamConsumerFactory = _enginesRoot.GenerateConsumerFactory();

            //wrap non testable unity static classes, so that can be mocked if needed (or implementation can change in general, without changing the interface).
            IRayCaster rayCaster = new RayCaster();
            ITime      time      = new Time();
            //GameObjectFactory allows to create GameObjects without using the Static method GameObject.Instantiate.
            //While it seems a complication it's important to keep the engines testable and not coupled with hard
            //dependencies
            var gameObjectFactory = new GameObjectFactory();

            //Player related engines. ALL the dependencies must be solved at this point through constructor injection.
            var playerShootingEngine       = new PlayerGunShootingEngine(rayCaster, time);
            var playerMovementEngine       = new PlayerMovementEngine(rayCaster);
            var playerAnimationEngine      = new PlayerAnimationEngine();
            var playerDeathEngine          = new PlayerDeathEngine(entityFunctions, entityStreamConsumerFactory);
            var playerInputEngine          = new PlayerInputEngine();
            var playerGunShootingFXsEngine = new PlayerGunShootingFXsEngine(entityStreamConsumerFactory);
            //Spawner engines are factories engines that can build entities
            var playerSpawnerEngine      = new PlayerSpawnerEngine(gameObjectFactory, entityFactory);
            var restartGameOnPlayerDeath = new RestartGameOnPlayerDeathEngine();

            //Player engines
            _enginesRoot.AddEngine(playerMovementEngine);
            _enginesRoot.AddEngine(playerAnimationEngine);
            _enginesRoot.AddEngine(playerShootingEngine);
            _enginesRoot.AddEngine(playerInputEngine);
            _enginesRoot.AddEngine(playerGunShootingFXsEngine);
            _enginesRoot.AddEngine(playerDeathEngine);
            _enginesRoot.AddEngine(playerSpawnerEngine);
            _enginesRoot.AddEngine(restartGameOnPlayerDeath);

            //Factory is one of the few OOP patterns that work very well with ECS. Its use is highly encouraged
            var enemyFactory = new EnemyFactory(gameObjectFactory, entityFactory);
            //Enemy related engines
            var enemyAnimationEngine = new EnemyChangeAnimationOnPlayerDeath();
            var enemyDamageFX        = new EnemySpawnEffectOnDamage(entityStreamConsumerFactory);
            var enemyAttackEngine    = new EnemyAttackEngine(time);
            var enemyMovementEngine  = new EnemyMovementEngine();
            //Spawner engines are factories engines that can build entities
            var enemySpawnerEngine = new EnemySpawnerEngine(enemyFactory, entityFunctions);
            var enemyDeathEngine   = new EnemyDeathEngine(entityFunctions, entityStreamConsumerFactory, time
                                                          , new WaitForSubmissionEnumerator(
                                                              unityEntitySubmissionScheduler));

            //enemy engines
            _enginesRoot.AddEngine(enemySpawnerEngine);
            _enginesRoot.AddEngine(enemyAttackEngine);
            _enginesRoot.AddEngine(enemyMovementEngine);
            _enginesRoot.AddEngine(enemyAnimationEngine);
            _enginesRoot.AddEngine(enemyDeathEngine);
            _enginesRoot.AddEngine(enemyDamageFX);

            //abstract engines
            var applyDamageEngine        = new ApplyDamageToDamageableEntitiesEngine(entityStreamConsumerFactory);
            var cameraFollowTargetEngine = new CameraFollowingTargetEngine(time);
            var deathEngine = new DispatchKilledEntitiesEngine();

            //abstract engines (don't need to know the entity type)
            _enginesRoot.AddEngine(applyDamageEngine);
            _enginesRoot.AddEngine(deathEngine);
            _enginesRoot.AddEngine(cameraFollowTargetEngine);

            //hud and sound engines
            var hudEngine         = new HUDEngine(entityStreamConsumerFactory);
            var damageSoundEngine = new DamageSoundEngine(entityStreamConsumerFactory);
            var scoreEngine       = new UpdateScoreEngine(entityStreamConsumerFactory);

            //other engines
            _enginesRoot.AddEngine(damageSoundEngine);
            _enginesRoot.AddEngine(hudEngine);
            _enginesRoot.AddEngine(scoreEngine);

            var unsortedEngines = new SurvivalUnsortedEnginesGroup(new FasterList <IStepEngine>(
                                                                       new IStepEngine[]
            {
                playerMovementEngine,
                playerInputEngine,
                playerGunShootingFXsEngine,
                playerSpawnerEngine,
                playerAnimationEngine,
                enemySpawnerEngine,
                enemyMovementEngine,
                cameraFollowTargetEngine,
                hudEngine,
                restartGameOnPlayerDeath
            }
                                                                       ));

            var unsortedDamageEngines = new DamageUnsortedEngines(new FasterList <IStepEngine>(
                                                                      new IStepEngine[]
            {
                applyDamageEngine,
                damageSoundEngine,
                deathEngine
            }
                                                                      ));

            //Svelto ECS doesn't provide a tick system, hence it doesn't provide a solution to solve the order of execution
            //However it provides some option if you want to use them like the SortedEnginesGroup.
            _enginesRoot.AddEngine(new TickEnginesGroup(new FasterList <IStepEngine>(new IStepEngine[]
            {
                unsortedEngines
                , playerShootingEngine
                , enemyDamageFX
                , enemyAttackEngine
                , unsortedDamageEngines
                , playerDeathEngine
                , enemyDeathEngine
                , scoreEngine
            })));

            BuildGUIEntitiesFromScene(contextHolder, entityFactory);
        }
Пример #2
0
/// <summary>
/// Before to start a review of Svelto.ECS terminologies:
/// - Entity:
///     it must be a real and concrete entity that you can explain
///     in terms of game design. The name of each entity should reflect
///     a specific concept from the game design domain
/// - IComponents:
///     Components must be seen as data holders. There are implementing
///     exceptions, but logically it must be still see as a group
///     of readable or writeable data.
///     In Svelto.ECS components are always interfaces declaring
///     Setters and Getters of ValueTypes. DispatchOnSet
///     and DispatchOnChange must not be seen as events, but
///     as pushing of data instead of data polling, similar
///     to the concept of DataBinding.
/// - Implementors:
///     Being components interfaces, they must be implemented through
///     Implementors. The relation Implementors to Components
///     is not 1:1 so that you can, if logic, group several
///     components in one implementor. This allows to easily
///     share data between components. Implementors also act
///     as bridge between the platform and Svelto.ECS.
///     Since Components can hold only value types, Implementors
///     are the objects that can interact directly with the platform
///     objects, I.E.: RigidBody, Transform and so on.
///     Note: IComponents must hold only valuetypes for
///     code design purposes and not optmization purposes.
///     The reason is that all the logic must lie in the engines
///     so Components cannot hold references to instances that can
///     expose functions with logic.
/// - Engines:
///     Where all the logic lies. Engines operates on EntityViews
/// - EntityViews:
///     EntityViews maps EntityComponents. The Engines can't
///     access directly to each entity (as a single set of components), but
///     through a component sets defined by EntityView.
///     They act as a component filters and expose only the entity components
///     that the Engine is interested in.
///     EntityViews are actually defined by the need of the Engine so they
///     come together with the engine and in the same namespace of the engine.
/// - EntityStructs:
///     In order to write Data Oriented Cache Friendly code, Svelto.ECS
///     also support EntityStructs. Please check other examples to
///     understand how to use them. However know that this kind of
///     optimizations is very limited to special circumstances
///     so the flexibility of EntityViews is most of the times what you need.
/// - EntityDescriptors:
///     Gives a way to formalize your Entity in svelto.ECS, it also
///     groups the EntityViews that must be generated once the
///     Entity is built
/// </summary>
        void SetupEnginesAndEntities()
        {
            //The Engines Root is the core of Svelto.ECS. You must NEVER inject the EngineRoot
            //as it is, therefore the composition root must hold a reference or it will be
            //GCed.
            //the UnitySumbmissionEntityViewScheduler is the scheduler that is used by the EnginesRoot to know
            //when to inject the EntityViews. You shouldn't use a custom one unless you know what you
            //are doing or you are not working with Unity.
            _enginesRoot = new EnginesRoot(new UnitySumbmissionEntityViewScheduler());
            //Engines root can never be held by anything else than the context itself to avoid leaks
            //That's why the EntityFactory and EntityFunctions are generated.
            //The EntityFactory can be injected inside factories (or engine acting as factories)
            //to build new entities dynamically
            _entityFactory = _enginesRoot.GenerateEntityFactory();
            //The entity functions is a set of utility operations on Entities, including
            //removing an entity. I couldn't find a better name so far.
            var entityFunctions = _enginesRoot.GenerateEntityFunctions();
            //GameObjectFactory allows to create GameObjects without using the Static
            //method GameObject.Instantiate. While it seems a complication
            //it's important to keep the engines testable and not
            //coupled with hard dependencies references (read my articles to understand
            //how dependency injection works and why solving dependencies
            //with static classes and singletons is a terrible mistake)
            GameObjectFactory factory = new GameObjectFactory();
            //the ISequencer is one of the 3 official ways available in Svelto.ECS
            //to communicate. They are mainly used for two specific cases:
            //1) specify a strict execution order between engines (engine logic
            //is executed horizontally instead than vertically, I will talk about this
            //in my articles). 2) filter a data token passed as parameter through
            //engines. The ISequencer is also not the common way to communicate
            //between engines
            Sequencer playerDamageSequence     = new Sequencer();
            Sequencer enemyDamageSequence      = new Sequencer();
            Sequencer enemyWaveSequence        = new Sequencer();
            Sequencer enemySpawnSequence       = new Sequencer();
            Sequencer bonusHealthSequence      = new Sequencer();
            Sequencer bonusAmmoSequence        = new Sequencer();
            Sequencer bonusHealthSpawnSequence = new Sequencer();
            Sequencer bonusAmmoSpawnSequence   = new Sequencer();
            Sequencer ammoUpdateSequence       = new Sequencer();
            Sequencer pushPowerSequence        = new Sequencer();

            //wrap non testable unity static classes, so that
            //can be mocked if needed.
            IRayCaster rayCaster = new RayCaster();
            ITime      time      = new Time();

            //Player related engines. ALL the dependecies must be solved at this point
            //through constructor injection.
            var playerHealthEngine    = new HealthEngine(playerDamageSequence);
            var playerShootingEngine  = new PlayerGunShootingEngine(enemyDamageSequence, ammoUpdateSequence, rayCaster, time);
            var playerMovementEngine  = new PlayerMovementEngine(rayCaster, time);
            var playerAnimationEngine = new PlayerAnimationEngine();
            var playerDeathEngine     = new PlayerDeathEngine(entityFunctions);

            //Enemy related engines
            var enemyAnimationEngine = new EnemyAnimationEngine();
            //HealthEngine is a different object for the enemy because it uses a different sequence
            var enemyHealthEngine   = new HealthEngine(enemyDamageSequence);
            var enemyAttackEngine   = new EnemyAttackEngine(playerDamageSequence, time);
            var enemyMovementEngine = new EnemyMovementEngine();
            var enemySpawnerEngine  = new EnemySpawnerEngine(enemyWaveSequence, enemySpawnSequence, factory, _entityFactory);
            var enemyDeathEngine    = new EnemyDeathEngine(entityFunctions, time);
            var enemyWaveEngine     = new EnemyWaveEngine(enemyWaveSequence);
            //bonus engines
            var bonusHealthEngine        = new BonusHealthEngine(bonusHealthSequence);
            var bonusCollectedEngine     = new BonusCollectedEngine(entityFunctions);
            var bonusAnimationEngine     = new BonusAnimationEngine();
            var bonusHealthSpawnerEngine = new BonusHealthSpawnerEngine(bonusHealthSpawnSequence, factory, _entityFactory);
            var bonusAmmoSpawnerEngine   = new BonusAmmoSpawnerEngine(bonusAmmoSpawnSequence, factory, _entityFactory);
            var bonusAmmoEngine          = new BonusAmmoEngine(bonusAmmoSequence);
            var bonusSoundEngine         = new BonusSoundEngine();
            //powers engines
            var pushPowerEngine  = new PushPowerEngine(pushPowerSequence, time);
            var powerSoundEngine = new PowerSoundEngine();
            //hud and sound engines
            var hudEngine         = new HUDEngine(time);
            var enemyCountEngine  = new EnemyHUDEngine();
            var damageSoundEngine = new DamageSoundEngine();
            var scoreEngine       = new ScoreEngine();


            //The ISequencer implementaton is very simple, but allows to perform
            //complex concatenation including loops and conditional branching.
            #region "Setting Sequences"
            playerDamageSequence.SetSequence(
                new Steps              //sequence of steps, this is a dictionary!
            {
                {                      //first step
                    enemyAttackEngine, //this step can be triggered only by this engine through the Next function
                    new To             //this step can lead only to one branch
                    {
                        //this is the only engine that will be called when enemyAttackEngine triggers Next()
                        new IStep[] { playerHealthEngine }
                    }
                },
                {                       //second step
                    playerHealthEngine, //this step can be triggered only by this engine through the Next function
                    new To              //once the playerHealthEngine calls step, all these engines will be called at once
                                        //depending by the condition a different set of engines will be triggered. The
                                        //order of the engines triggered is guaranteed.
                    {
                        //these engines will be called when the Next function is called with the DamageCondition.damage set
                        { DamageCondition.Damage, new IStep[] { hudEngine, damageSoundEngine } },
                        //these engines will be called when the Next function is called with the DamageCondition.dead set
                        { DamageCondition.Dead, new IStep[] {
                              hudEngine, damageSoundEngine,
                              playerMovementEngine, playerAnimationEngine,
                              enemyAnimationEngine, playerDeathEngine
                          } }
                    }
                }
            }
                );

            enemyDamageSequence.SetSequence(
                new Steps
            {
                {
                    playerShootingEngine,
                    new To
                    {
                        //in every case go to enemyHealthEngine
                        new IStep[] { enemyHealthEngine }
                    }
                },
                {
                    enemyHealthEngine,
                    new To
                    {
                        { DamageCondition.Damage, new IStep[] { enemyAnimationEngine, damageSoundEngine } },
                        { DamageCondition.Dead, new IStep[] { scoreEngine, enemyMovementEngine, enemyAnimationEngine,
                                                              enemySpawnerEngine, enemyCountEngine, damageSoundEngine, enemyDeathEngine } },
                    }
                }
            }
                );
            enemyWaveSequence.SetSequence(
                new Steps
            {
                {
                    enemySpawnerEngine,
                    new To
                    {
                        enemyWaveEngine,
                    }
                },
                {
                    enemyWaveEngine,
                    new To
                    {
                        { WaveCondition.Next, new IStep[] { enemySpawnerEngine, enemyCountEngine, hudEngine } },
                        { WaveCondition.Last, new IStep[] { enemySpawnerEngine, enemyCountEngine, hudEngine } },
                        { WaveCondition.Stop, new IStep[] { hudEngine } },
                    }
                }
            }
                );
            enemySpawnSequence.SetSequence(
                new Steps
            {
                {
                    enemySpawnerEngine,
                    new To
                    {
                        enemyMovementEngine,
                    }
                }
            }
                );
            bonusHealthSequence.SetSequence(
                new Steps
            {
                {
                    bonusHealthEngine,
                    new To
                    {
                        { new IStep[] { playerHealthEngine, hudEngine, bonusSoundEngine, bonusAnimationEngine,
                                        bonusCollectedEngine } }
                    }
                }
            }
                );
            bonusHealthSpawnSequence.SetSequence(
                new Steps
            {
                {
                    bonusHealthSpawnerEngine,
                    new To
                    {
                        bonusSoundEngine,
                    }
                }
            }
                );
            bonusAmmoSpawnSequence.SetSequence(
                new Steps
            {
                {
                    bonusAmmoSpawnerEngine,
                    new To
                    {
                        bonusSoundEngine,
                    }
                }
            }
                );
            ammoUpdateSequence.SetSequence(
                new Steps
            {
                {
                    playerShootingEngine,
                    new To
                    {
                        hudEngine,
                    }
                }
            }
                );
            bonusAmmoSequence.SetSequence(
                new Steps
            {
                {
                    bonusAmmoEngine,
                    new To
                    {
                        { new IStep[] { playerShootingEngine, hudEngine, bonusSoundEngine, bonusAnimationEngine,
                                        bonusCollectedEngine } }
                    }
                }
            }
                );
            pushPowerSequence.SetSequence(
                new Steps
            {
                {
                    pushPowerEngine,
                    new To
                    {
                        { PowerCondition.Start, new IStep[] { powerSoundEngine, hudEngine } },
                        { PowerCondition.Stop, new IStep[] { powerSoundEngine } },
                    }
                }
            }
                );
            #endregion
            //Mandatory step to make engines work
            //Player engines
            _enginesRoot.AddEngine(playerMovementEngine);
            _enginesRoot.AddEngine(playerAnimationEngine);
            _enginesRoot.AddEngine(playerShootingEngine);
            _enginesRoot.AddEngine(playerHealthEngine);
            _enginesRoot.AddEngine(new PlayerInputEngine());
            _enginesRoot.AddEngine(new PlayerGunShootingFXsEngine());
            //enemy engines
            _enginesRoot.AddEngine(enemySpawnerEngine);
            _enginesRoot.AddEngine(enemyWaveEngine);
            _enginesRoot.AddEngine(enemyAttackEngine);
            _enginesRoot.AddEngine(enemyMovementEngine);
            _enginesRoot.AddEngine(enemyAnimationEngine);
            _enginesRoot.AddEngine(enemyHealthEngine);
            _enginesRoot.AddEngine(enemyDeathEngine);
            //bonus engines
            _enginesRoot.AddEngine(bonusHealthEngine);
            _enginesRoot.AddEngine(bonusAmmoEngine);
            _enginesRoot.AddEngine(bonusCollectedEngine);
            _enginesRoot.AddEngine(bonusAnimationEngine);
            _enginesRoot.AddEngine(bonusHealthSpawnerEngine);
            _enginesRoot.AddEngine(bonusAmmoSpawnerEngine);
            //powers engines
            _enginesRoot.AddEngine(powerSoundEngine);
            _enginesRoot.AddEngine(pushPowerEngine);
            //other engines
            _enginesRoot.AddEngine(new CameraFollowTargetEngine(time));
            _enginesRoot.AddEngine(damageSoundEngine);
            _enginesRoot.AddEngine(bonusSoundEngine);
            _enginesRoot.AddEngine(hudEngine);
            _enginesRoot.AddEngine(scoreEngine);
            _enginesRoot.AddEngine(enemyCountEngine);
        }