protected override void OnUpdate()
        {
            //Prepare all components without the correct data.
            for (int i = 0; i < data.Length; i++)
            {
                //Add the collision info. (NOTE: this does mean no
                //archetyping CollisionInfo in!)
                CollisionInfo cols = new CollisionInfo();
                cols.faceDirection = 1;
                PostUpdateCommands.AddComponent(data.entities[i], cols);

                //Configure inverseMaxSlope to be truly inverse of maxSlopeAngle.
                //CharacterControllerComponent controller = data.characterController[i];
                //If inverseMaxSlope is set, don't do the calculation again.
                //if (controller.maxSlopeInverse == default(float)) {
                //	controller.maxSlopeInverse = 90 / controller.maxSlopeAngle;
                //	PostUpdateCommands.SetSharedComponent(data.entities[i], controller);
                //}
            }
        }
        //@TODO: Replace raycast calls with CombCast2D jobs. All will have same length, but can filter by distance to same effect.
        //@TODO: Variable down direction. Not just good for gravity games, but also for SONIC!

        protected override void OnUpdate()
        {
            //Slightly more efficient to use GetExistingManager.
            //As long as you make sure it actually is existing before update!
            EntityManager em = World.Active.GetExistingManager <EntityManager>();

            NativeArray <RaycastHit2D> hCast;
            NativeArray <RaycastHit2D> vCast;

            for (int i = 0; i < data.Length; i++)
            {
                //Working copy of the movement value.
                float2 moveAmount = data.movement[i].Value;
                //For when we need an unaltered copy later.
                float2 moveAmountPreChange = moveAmount;

                //Layer mask.
                LayerMask mask = data.collisionMask[i].SolidMask;
                //@NEW: This is my implementation of semi-solid platforms.
                {
                    bool checkSemiSolid = data.collisions[i].below | moveAmount.y < 0;
                    checkSemiSolid &= em.HasComponent(data.entities[i], typeof(FallThroughSemiSolid));
                    mask           |= math.select(
                        0,
                        data.collisionMask[i].SemiSolidMask,
                        checkSemiSolid
                        );
                }
                //EXPLANATION: This merges in a second layer mask conditionally.
                //Specifically, if grounded or going down.
                //The intuitive thing would be to occlude going-up ONLY when
                //checking vertical, but doing that makes for some problems when
                //trying to jump through semi-solid slopes.
                //And we have the grounded check because you technically are
                //going up while climbing slopes--so otherwise, we would fall
                //straight through upward slopes, or even weirder behavior!


                //float maxSlopeAngleInverse;

                //Create a new collision info to be populated.
                CollisionInfo collisions = new CollisionInfo();

                //Covered in a prior system.
                //UpdateRaycastOrigins();

                //Descending slope check.
                //@TODO: Apparently, a better solution is to do an initial groundward cast
                //and run this method if (groundward_cast.hit).
                //The result of this could also replace collisions.below in the above select statement.
                if (moveAmount.y < 0)
                {
                    //I'm not sure if I copied the sliding-down code correctly when I first
                    //followed the Sebastian Lague tutorials, so I've no guarantee that this
                    //implementation shown here matches his implementation.
                    //Works fine without, in any case.
                    CheckIfSlidingDownSlope(
                        ref moveAmount,
                        ref collisions,
                        data.raycastController[i].bottomCorners,
                        data.characterController[i].maxSlopeAngle,
                        data.raycastController[i].skinWidth,
                        mask
                        );
                    CheckIfDescendingSlope(
                        ref moveAmount,
                        data.characterController[i].maxSlopeAngle,
                        ref collisions,
                        data.raycastController[i].skinWidth,
                        data.raycastController[i].bottomCorners,
                        mask,
                        collisions.slidingDownSlope
                        );
                }
                //Needs to be before setting the move direction, lest the X collision check
                //toward the slope rather than in the direction of any possible collisions (i.e. our move direction).

                //Store last move direction
                if (moveAmount.x != 0)
                {
                    collisions.faceDirection = (int)math.sign(moveAmount.x);
                }
                //Collisions here
                //Do horizontal collisions unconditionally.
                HorizontalCollisions(
                    ref moveAmount, ref collisions,
                    data.raycastController[i],
                    data.characterController[i].maxSlopeAngle,
                    moveAmountPreChange,
                    mask
                    );
                //Do vertical collisions only if moving vertically.
                if (moveAmount.y != 0)
                {
                    VerticalCollisions(
                        ref moveAmount,
                        ref collisions,
                        data.raycastController[i],
                        mask
                        );
                    CounteractJointCatching(
                        ref moveAmount,
                        ref collisions,
                        data.raycastController[i].skinWidth,
                        data.raycastController[i].bottomCorners,
                        mask
                        );
                }

                collisions.below |= em.HasComponent(data.entities[i], typeof(ForceGrounded));

                //Apply components' new data.
                float3 positionOutput = data.position[i].Value;
                positionOutput.x += moveAmount.x;
                positionOutput.y += moveAmount.y;
                data.position[i]  = new Position {
                    Value = positionOutput
                };
                //Movement component's job is done, and it will be blanked by MoveResetSystem.
                //Output this for next frame.
                data.collisions[i] = collisions;
            }
        }
 void CheckIfSlidingDownSlope(
     ref float2 moveAmount,
     ref CollisionInfo collisions,
     in float2x2 bottomCorners,            //@TODO: Do variables passed as in interfere with cache friendliness?