API Docs for:
Show:

File: gameplay\movement\wall-grappling\System.js

/**
 This system uses the physics system to determine whether an entity is grappling a wall. For an entity to grapple a wall, it needs
 to push against a physics entity with a 'wall' collision category and a vertical surface. The grappling entity needs to be a dynamic
 physics object, additionally it requires the GroundedMovement and WallGrappling components to be present. An entity can only grapple
 a wall while it's in the air. This system triggers animations on entities. A 'grappleLeft' animation is played if an entity is grappling
 a wall on the left, similarily, for the right side a 'grappleRight' animation will be played.
 @class WallGrapplingSystem
 @constructor
 @param entitySystemManager {Manager} The entity system manager whose entities this system will be working on.
 @param physicsSystem {PhysicsSystem}
 @param collisionMasks {Object} An object holding bit masks used for determining collision categories.
 */
function WallGrapplingSystem(entitySystemManager, physicsSystem, collisionMasks){

    //System variables.
    var WALL_GRAPPLING_ANIMATION_PRIORITY = 2,
    
    //Keep this value at 2. This way, grappling lasts for one full frame, from a physics system update
    //through this system's update to this system's update in the next frame.
    //If this value is higher, then the wall sliding logic might prevent the entity from wall jumping,
    //since it resets the vertical velocity. If this value is lower (or if you'd just use a boolean value
    //instead of a counter), then the entity would be wall grappling between the physics update to this system's update only,
    //which means that for the input system callbacks the entity would never be grappling.
    //There's more on this in the update function.
        WALL_GRAPPLING_DURATION_IN_FRAMES = 2,
        
    //Animation names.
        grappleLeft = 'grappleLeft',
        grappleRight = 'grappleRight';
    
    var collisionResolvedEventCallback = (function(){
        //Reusable vectors.
        var rightVector = new Vector2D(1, 0),
            collisionNormal = new Vector2D(),
            velocity = new Vector2D(),
            temporaryPosition = new Vector2D();
        
        return function(contactData){
            var entityA = contactData.getEntityA(),
                entityB = contactData.getEntityB(),
                
                movable = entityA.get(Movable),
                groundedMovement = entityA.get(GroundedMovement),
                wallGrappling = entityA.get(WallGrappling),
                collidable = entityB.get(Collidable),
                position, dotProduct;
                
            if(movable && groundedMovement && !groundedMovement.isOnGround() && wallGrappling && (collidable._category & collisionMasks.wall)){
                
                contactData.collisionData.getCollisionNormal(collisionNormal);
                
                dotProduct = rightVector.dot(collisionNormal);
                
                //Check if the entity grappled the left wall while in air.
                if(dotProduct > 0.95){
                    
                    //Displace the grappling entity slightly to make sure that it's no longer touching the wall.
                    //This enforces the player to push against the wall to grapple and prevents entities from sticking to walls.
                    //The position has to always be present, because this function is a physics system callback.
                    position = entityA.get(Position);
                    position.getPosition(temporaryPosition);
                    position.setPosition(temporaryPosition.x + 0.1, temporaryPosition.y);
                    
                    //The entity can grapple a wall only if it's moving downwards.
                    movable.getVelocity(velocity);
                    if(velocity.y >= 0){
                        wallGrappling._grapplingLeftCounter = WALL_GRAPPLING_DURATION_IN_FRAMES;
                    }
                    
                //Check if the entity grappled the right wall while in air.
                }else if(dotProduct < -0.95){
                    
                    //Displace the grappling entity slightly to make sure that it's no longer touching the wall.
                    //This enforces the player to push against the wall to grapple and prevents entities from sticking to walls.
                    //The position has to always be present, because this function is a physics system callback.
                    position = entityA.get(Position);
                    position.getPosition(temporaryPosition);
                    position.setPosition(temporaryPosition.x - 0.1, temporaryPosition.y);
                    
                    //The entity can grapple a wall only if it's moving downwards.
                    movable.getVelocity(velocity);
                    if(velocity.y >= 0){
                        wallGrappling._grapplingRightCounter = WALL_GRAPPLING_DURATION_IN_FRAMES;
                    }
                }
            }
            /*This code is commented, because i don't expect a dynamic entity to be able to grapple another dynamic entity.
             *It's here though, if such a need occurs in the future.
             *
            movable = entityB.get(Movable);
            groundedMovement = entityB.get(GroundedMovement);
            wallGrappling = entityB.get(WallGrappling);
            collidable = entityA.get(Collidable);
                
            if(movable && groundedMovement && !groundedMovement.isOnGround() && wallGrappling && (collidable._category & collisionMasks.wall)){
                
                contactData.collisionData.getCollisionNormal(collisionNormal);
                //Invert the collision normal, since it's given from entityA's perspective.
                collisionNormal.invert();
                
                dotProduct = rightVector.dot(collisionNormal);
                
                //Check if the entity grappled the left wall while in air.
                if(dotProduct > 0.95){
                    
                    //Displace the grappling entity slightly to make sure that it's no longer touching the wall.
                    //This enforces the player to push against the wall to grapple and prevents entities from sticking to walls.
                    //The position has to always be present, because this function is a physics system callback.
                    position = entityB.get(Position);
                    position.getPosition(temporaryPosition);
                    position.setPosition(temporaryPosition.x + 0.1, temporaryPosition.y);
                    
                    //The entity can grapple a wall only if it's moving downwards.
                    movable.getVelocity(velocity);
                    if(velocity.y >= 0){
                        wallGrappling._grapplingLeftCounter = WALL_GRAPPLING_DURATION_IN_FRAMES;
                    }
                    
                //Check if the entity grappled the right wall while in air.
                }else if(dotProduct < -0.95){
                    
                    //Displace the grappling entity slightly to make sure that it's no longer touching the wall.
                    //This enforces the player to push against the wall to grapple and prevents entities from sticking to walls.
                    //The position has to always be present, because this function is a physics system callback.
                    position = entityB.get(Position);
                    position.getPosition(temporaryPosition);
                    position.setPosition(temporaryPosition.x - 0.1, temporaryPosition.y);
                    
                    //The entity can grapple a wall only if it's moving downwards.
                    movable.getVelocity(velocity);
                    if(velocity.y >= 0){
                        wallGrappling._grapplingRightCounter = WALL_GRAPPLING_DURATION_IN_FRAMES;
                    }
                }
            }*/
        };
    })();
    
    physicsSystem.subscribe('collisionResolved', collisionResolvedEventCallback);
    
    var wallGrapplingEntities = entitySystemManager.createAspect([WallGrappling, Movable]);
    
    /**
     @method update
     */
    this.update = (function(){
        
        var velocity = new Vector2D();
        
        function updateWallGraplingEntity(entity){
            var wallGrappling = entity.get(WallGrappling),
                movable = entity.get(Movable),
                animation = entity.get(Animation);
                
            //It's necessary that these counters are updated before the sliding logic.
            //That way, given that wall grappling lasts 2 frames, as mentioned at the top of this file,
            //if an entity is pushing against a wall, then first, in the physics system, the collision callback
            //will set the wall grappling counters to 2. Then this update function will first lower the counters, then
            //it'll update the velocity which will cause the entity to slide along the wall. Then for the next frame the counters
            //will remain at 1 which will cause the "isGrappling" functions to return true, as expected.
            //Then this update function will get called and if the entity wasn't grappling for the frame, for
            //example the entity jumped off a wall and thus the grappling logic in the physics callback didn't kick in,
            //then the counters will be reduced to 0 and thus the sliding logic won't kick in.
            //This way, the sliding logic won't reset the vertical velocity gained via a wall jump.
            wallGrappling._grapplingLeftCounter--;
            wallGrappling._grapplingRightCounter--;
            
            //Set the entity's velocity to the wall sliding velocity if the entity is grappling a wall.
            if(wallGrappling.isGrapplingLeft()){
                movable.getVelocity(velocity);
                movable.setVelocity(velocity.x, wallGrappling._slidingSpeed);
                
                if(animation){
                    animation.play(grappleRight, WALL_GRAPPLING_ANIMATION_PRIORITY);
                }
            }else if(wallGrappling.isGrapplingRight()){
                movable.getVelocity(velocity);
                movable.setVelocity(velocity.x, wallGrappling._slidingSpeed);
                
                if(animation){
                    animation.play(grappleLeft, WALL_GRAPPLING_ANIMATION_PRIORITY);
                }
            }
        }
        
        return function(){
            wallGrapplingEntities.iterate(updateWallGraplingEntity);
        };
    })();
    
    /**
     @method destroy
     */
    this.destroy = function(){        
        physicsSystem.unsubscribe('collisionResolved', collisionResolvedEventCallback);
        
        wallGrapplingEntities.destroy();
    }
}