TwoStick ECS Sample: Adding deflection to enemies

Hi there,

I’m currently attempting to learn how to use ECS and I’ve downloaded the ECS sample to play around with. I currently have the goal of trying to get bullets to deflect off of certain enemies.

I haven’t touched the code much; just enough to add in the information that I believe would be relevant. I’ve added in a ComponentDataArray of Heading2D objects:

/// <summary>
        /// All player shots.
        /// </summary>
        struct PlayerShotData
            public int Length;
            public ComponentDataArray<Shot> Shot;
            [ReadOnly] public ComponentDataArray<Position2D> Position;
            [ReadOnly] public ComponentDataArray<PlayerShot> PlayerShotMarker;
            public ComponentDataArray<Heading2D> Direction;

I’ve done this in the hope that I could get the heading of each player shot. In the ShotSpawnSystem, it seems as though the shots are given a heading so I didn’t think it to be that far a stretch to grab that data from the entity.

The problem comes when I’m manipulating the headings. I’ve split the collision job into two jobs; one for when the bullets hit the player (Which is the unmodified shot collision code) and another job for when the bullet hits the enemy. This is the relevant struct:

        struct EnemyCollisionJob : IJobParallelFor
            public float CollisionRadiusSquared;

            public ComponentDataArray<Health> Health;
            [ReadOnly] public ComponentDataArray<Position2D> Positions;
            [ReadOnly] public ComponentDataArray<EnemyType> Types;
            public ComponentDataArray<Heading2D> ShotHeadings;

            public ComponentDataArray<Shot> Shots;

            [ReadOnly] public ComponentDataArray<Position2D> ShotPositions;

            public void Execute(int index)
                float damage = 0.0f;

                float2 receiverPos = Positions[index].Value;
                for (int si = 0; si < Shots.Length; ++si)
                    float2 shotPos = ShotPositions[si].Value;
                    float2 delta = shotPos - receiverPos;
                    float distSquared =, delta);
                    if (distSquared <= CollisionRadiusSquared)
                        var shot = Shots[si];

                        damage += shot.Energy;

                        if (Types[index].CanDeflect == 1)
                            Heading2D heading = ShotHeadings[si];
                            heading.Value *= -1;
                            ShotHeadings[si] = heading;
                            shot.TimeToLive = 0.0f;

                        Shots[si] = shot;

                var h = Health[index];
                if (Types[index].CanDeflect == 0 && Types[index].CanAbsorb == 0)
                    h.Value = math.max(h.Value - damage, 0.0f);
                Health[index] = h;

The issue that I receive is that when accessing the headings array via the use of my shot index, I get an index out of bounds exception. This leads me to my questions:

  1. My understanding is that PlayerShotData filters and grabs all of the objects that specifically have the components that are listed in the struct. Is this true or does it grab everything that may have a subset of these components? (E.g. could something that only had a position end up injected in somehow?).
  2. If my above assumption is correct, that it does filter out objects like I believe it does, why would the length of the Shot component array and the Direction component array be different? A Shot entity has a direction added to it in the ShotSpawnSystem, however when I’m attempting to iterate through the shots, it seems that I go out of bounds with my index when referencing the Direction array.

I’m very much new to ECS so I’m expecting this is just something simple that I’m missing.

Thanks for looking.

It seems that to modify the shot headings, I needed to put this tag above ShotPositions when I declared it in the job struct:

public ComponentDataArray<Heading2D> ShotHeadings;

I’m currently under the impression that this is required because the job is expecting a parallel for and the data is being modified, thus this stops race conditions.