enum MovementTransferOnJump { None, // The jump is not affected by velocity of floor at all. InitTransfer, // Jump gets its initial velocity from the floor, then gradualy comes to a stop. PermaTransfer, // Jump gets its initial velocity from the floor, and keeps that velocity until landing. PermaLocked // Jump is relative to the movement of the last touched floor and will move together with that floor.}

// We will contain all the jumping related variables in one helper class for clarity.class CharacterMotorJumping { // Can the character jump? var enabled : boolean = true;

// How high do we jump when pressing jump and letting go immediately var baseHeight : float = 1.0;

// We add extraHeight units (meters) on top when holding the button down longer while jumping var extraHeight : float = 4.1;

// How much does the character jump out perpendicular to the surface on walkable surfaces? // 0 means a fully vertical jump and 1 means fully perpendicular. var perpAmount : float = 0.0;

// How much does the character jump out perpendicular to the surface on too steep surfaces? // 0 means a fully vertical jump and 1 means fully perpendicular. var steepPerpAmount : float = 0.5;

// For the next variables, @System.NonSerialized tells Unity to not serialize the variable or show it in the inspector view. // Very handy for organization!

// Are we jumping? (Initiated with jump button and not grounded yet) // To see if we are just in the air (initiated by jumping OR falling) see the grounded variable. @System.NonSerialized var jumping : boolean = false;

@System.NonSerialized var holdingJumpButton : boolean = false;

// the time we jumped at (Used to determine for how long to apply extra jump power after jumping.) @System.NonSerialized var lastStartTime : float = 0.0;

// Find out how much we need to push towards the ground to avoid loosing grouning // when walking down a step or over a sharp change in slope. var pushDownOffset : float = Mathf.Max(controller.stepOffset, Vector3(currentMovementOffset.x, 0, currentMovementOffset.z).magnitude); if (grounded) currentMovementOffset -= pushDownOffset * Vector3.up;

// Reset variables that will be set by collision function movingPlatform.hitPlatform = null; groundNormal = Vector3.zero;

// Calculate the velocity based on the current and previous position. // This means our velocity will only be the amount the character actually moved as a result of collisions. var oldHVelocity : Vector3 = new Vector3(velocity.x, 0, velocity.z); movement.velocity = (tr.position - lastPosition) / Time.deltaTime; var newHVelocity : Vector3 = new Vector3(movement.velocity.x, 0, movement.velocity.z);

SendMessage("OnFall", SendMessageOptions.DontRequireReceiver); // We pushed the character down to ensure it would stay on the ground if there was any. // But there wasn't so now we cancel the downwards offset to make the fall smoother. tr.position += pushDownOffset * Vector3.up; } // We were not grounded but just landed on something else if (!grounded && IsGroundedTest()) { grounded = true; jumping.jumping = false; SubtractNewPlatformVelocity();

SendMessage("OnLand", SendMessageOptions.DontRequireReceiver); }

// Moving platforms support if (MoveWithPlatform()) { // Use the center of the lower half sphere of the capsule as reference point. // This works best when the character is standing on moving tilting platforms. movingPlatform.activeGlobalPoint = tr.position + Vector3.up * (controller.center.y - controller.height*0.5 + controller.radius); movingPlatform.activeLocalPoint = movingPlatform.activePlatform.InverseTransformPoint(movingPlatform.activeGlobalPoint);

if (grounded) { // When going uphill, the CharacterController will automatically move up by the needed amount. // Not moving it upwards manually prevent risk of lifting off from the ground. // When going downhill, DO move down manually, as gravity is not enough on steep hills. velocity.y = Mathf.Min(velocity.y, 0); }

// When jumping up we don't apply gravity for some time when the user is holding the jump button. // This gives more control over jump height by pressing the button longer. if (jumping.jumping && jumping.holdingJumpButton) { // Calculate the duration that the extra jump force should have effect. // If we're still less than that duration after the jumping time, apply the force. if (Time.time < jumping.lastStartTime + jumping.extraHeight / CalculateJumpVerticalSpeed(jumping.baseHeight)) { // Negate the gravity we just applied, except we push in jumpDir rather than jump upwards. velocity += jumping.jumpDir * movement.gravity * Time.deltaTime; } }

// Make sure we don't fall any faster than maxFallSpeed. This gives our character a terminal velocity. velocity.y = Mathf.Max (velocity.y, -movement.maxFallSpeed); }

if (grounded) { // Jump only if the jump button was pressed down in the last 0.2 seconds. // We use this check instead of checking if it's pressed down right now // because players will often try to jump in the exact moment when hitting the ground after a jump // and if they hit the button a fraction of a second too soon and no new jump happens as a consequence, // it's confusing and it feels like the game is buggy. if (jumping.enabled && canControl && (Time.time - jumping.lastButtonDownTime < 0.2)) { grounded = false; jumping.jumping = true; jumping.lastStartTime = Time.time; jumping.lastButtonDownTime = -100; jumping.holdingJumpButton = true;

private function SubtractNewPlatformVelocity () { // When landing, subtract the velocity of the new ground from the character's velocity // since movement in ground is relative to the movement of the ground. if (movingPlatform.enabled && (movingPlatform.movementTransfer == MovementTransferOnJump.InitTransfer || movingPlatform.movementTransfer == MovementTransferOnJump.PermaTransfer) ) { // If we landed on a new platform, we have to wait for two FixedUpdates // before we know the velocity of the platform under the character if (movingPlatform.newPlatform) { var platform : Transform = movingPlatform.activePlatform; yield WaitForFixedUpdate(); yield WaitForFixedUpdate(); if (grounded && platform == movingPlatform.activePlatform) yield 1; } movement.velocity -= movingPlatform.platformVelocity; }}

function CalculateJumpVerticalSpeed (targetJumpHeight : float) { // From the jump height and gravity we deduce the upwards speed // for the character to reach at the apex. return Mathf.Sqrt (2 * targetJumpHeight * movement.gravity);}

// Update is called once per framefunction Update () { // Get the input vector from kayboard or analog stick var directionVector = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));

if (directionVector != Vector3.zero) { // Get the length of the directon vector and then normalize it // Dividing by the length is cheaper than normalizing when we already have the length anyway var directionLength = directionVector.magnitude; directionVector = directionVector / directionLength;

// Make sure the length is no bigger than 1 directionLength = Mathf.Min(1, directionLength);

// Make the input vector more sensitive towards the extremes and less sensitive in the middle // This makes it easier to control slow speeds when using analog sticks directionLength = directionLength * directionLength;