[HELP] Swimming/diving script

Hi

I’ve been playing around with adding swimming + diving to a 3rd person game and it seems that EVERY f***ing time make any thing on it something else is getting in the way. So i trashed all my code now to try start on a fresh but im really lost in what i should do and there for turning to the forum here.

most of you are most likely known to wow and how you swim in there and its something like that i wanna have but there is a lot there is lots of other problems im running into.

the swimming should be a little like this: http://forum.unity3d.com/threads/121494-Released-WoW-Character-Movement-Camera but i really don’t feel like using money on that package (i’m broke) and i do not know if this have all my requirements.

I’ve made this drawing in boredom to show the concerns and problems I’ve been running into: https://www.dropbox.com/s/nq75tfd753fq17r/swimming-lol.png (please look at it and you’ll get the idea)

The requirements is:
no gravity while diving AND swim the way the camera is facing
gravity should apply if you’re in places like underwater caverns
swimming in the water surface that then goes into diving if the camera is facing down (like in the old counter strike maps that had water in them)
then hitting the buttom you should not be able to move into walking/isgrounded but keep on swimming/diving

i really need to sample codes to this since I’ve been stared blankly into my own crap and looked all over Google for at least 2 weeks without progress so pretty please - samples and/or explanations!

for moving my character at this point i’m using a slightly modified charactermotor script remade and change into C#, and for camera movement i’m using a FPS/freelook script (sources below) since it should work a little like GTA.

I would really prefer to add swimming/diving to the charactermotor so but i’m willing at this point to redo the most of the character movement system to be able to add a proper swim/dive system.

So if there is any suggestions please post them for the love of angry code blocks! and feel free to add to/modify my script here below if you think you can help me add some useful code.

Thanks A LOT in advance and deep respect!

  • Jackie

Camera:

using UnityEngine;
using System.Collections;

public class MMOCameraController : MonoBehaviour
{
    public Transform target;

    public float targetHeight;
    public float distance = 5.0f;
    public float offsetFromWall = 0.1f;

    public float maxDistance = 20;
    public float minDistance = 0f;

    public float xSpeed = 200.0f;
    public float ySpeed = 200.0f;

    public int yMinLimit = -90;
    public int yMaxLimit = 90;

    public int zoomRate = 40;

    public float rotationDampening = 5.0f;
    public float zoomDampening = 5.0f;

    public LayerMask collisionLayers = -1;

    public string joystickAxisX;
    public string joystickAxisY;

    public int invertX;
    public int invertY;

    [HideInInspector]
    public bool guiMode = false;

    private float xDeg = 0.0f;
    private float yDeg = 0.0f;
    private float currentDistance;
    private float desiredDistance;
    private float correctedDistance;

    void Start()
    {
        Vector3 angles = transform.eulerAngles;
        xDeg = angles.x;
        yDeg = angles.y;

        currentDistance = distance;
        desiredDistance = distance;
        correctedDistance = distance;

        // Make the rigid body not change rotation 
        if (rigidbody)
            rigidbody.freezeRotation = true;
    }

    void Update()
    {
        Quaternion rotation = Quaternion.Euler(yDeg, xDeg, 0);
        Vector3 vTargetOffset;
        vTargetOffset = new Vector3(0, -targetHeight, 0);
        RaycastHit collisionHit;
        Vector3 position = target.position - (rotation * Vector3.forward * desiredDistance + vTargetOffset);
        Vector3 trueTargetPosition = new Vector3(target.position.x, target.position.y + targetHeight, target.position.z);

        // if there was a collision, correct the camera position and calculate the corrected distance 
        bool isCorrected = false;
        if (Physics.Linecast(trueTargetPosition, position, out collisionHit, collisionLayers))
        {
            // calculate the distance from the original estimated position to the collision location,
            // subtracting out a safety "offset" distance from the object we hit.  The offset will help
            // keep the camera from being right on top of the surface we hit, which usually shows up as
            // the surface geometry getting partially clipped by the camera's front clipping plane.
            correctedDistance = Vector3.Distance(trueTargetPosition, collisionHit.point) - offsetFromWall;
            isCorrected = true;
        }

        // For smoothing, lerp distance only if either distance wasn't corrected, or correctedDistance is more than currentDistance 
        currentDistance = !isCorrected || correctedDistance > currentDistance ? Mathf.Lerp(currentDistance, correctedDistance, Time.deltaTime * zoomDampening) : correctedDistance;

        // keep within legal limits
        currentDistance = Mathf.Clamp(currentDistance, minDistance, maxDistance);
    }

    /** 
     * Camera logic on LateUpdate to only update after all character movement logic has been handled. 
     */
    void LateUpdate()
    {
        // check for collision using the true target's desired registration point as set by user using height 
        Vector3 vTargetOffset;
        
        // Don't do anything if target is not defined 
        if (!target)
            return;

        // If either mouse buttons are down, let the mouse govern camera position 
        //if (Input.GetMouseButton(0) ^ Input.GetMouseButton(1))
        //{
        if (!guiMode)
        {
            xDeg += Input.GetAxis("Mouse X") * xSpeed * 0.1f * invertX;
            yDeg -= Input.GetAxis("Mouse Y") * ySpeed * 0.1f * invertY;
            xDeg += Input.GetAxis(joystickAxisX) * xSpeed * invertX;
            yDeg -= Input.GetAxis(joystickAxisY) * ySpeed * invertY;
        }
        //}
        // otherwise, ease behind the target if any of the directional keys are pressed 
        //else if (Input.GetAxis("Vertical") != 0 || Input.GetAxis("Horizontal") != 0)
        //{
            float targetRotationAngle = target.eulerAngles.y;
            float currentRotationAngle = transform.eulerAngles.y;
            //xDeg = Mathf.LerpAngle(currentRotationAngle, targetRotationAngle, rotationDampening * Time.deltaTime);
        //}

        yDeg = ClampAngle(yDeg, yMinLimit, yMaxLimit);

        // set camera rotation 
        Quaternion rotation = Quaternion.Euler(yDeg, xDeg, 0);
        
        // calculate the desired distance 
        if (!guiMode)
        {
            desiredDistance -= Input.GetAxis("Mouse ScrollWheel") * Time.deltaTime * zoomRate * Mathf.Abs(desiredDistance);
        }
        desiredDistance = Mathf.Clamp(desiredDistance, minDistance, maxDistance);
        correctedDistance = desiredDistance;

        // calculate desired camera position
        vTargetOffset = new Vector3(0, -targetHeight, 0);
        

        // recalculate position based on the new currentDistance 
        Vector3 position = target.position - (rotation * Vector3.forward * currentDistance + vTargetOffset);

        transform.rotation = rotation;
        //if (Input.GetMouseButton(1))
        //{
            target.rotation = new Quaternion(0, rotation.y, 0, rotation.w);
        //}
            position.y += 1;
        transform.position = position;
    }

    private static float ClampAngle(float angle, float min, float max)
    {
        if (angle < -360)
            angle += 360;
        if (angle > 360)
            angle -= 360;
        return Mathf.Clamp(angle, min, max);
    }
}

Motor:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

// Require a character controller to be attached to the same game object
[RequireComponent(typeof(CharacterController))]
[AddComponentMenu("Character/Character Motor")]


public class CharacterMotor : MonoBehaviour
{
    // Does this script currently respond to input?
    bool canControl = true;
    bool useFixedUpdate = true;

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

    // The current global direction we want the character to move in.
    [System.NonSerialized]
    public Vector3 inputMoveDirection = Vector3.zero;

    // Is the jump button held down? We use this interface instead of checking
    // for the jump button directly so this script can also be used by AIs.
    [System.NonSerialized]
    public bool inputJump = false;

    [System.Serializable]
    public class CharacterMotorMovement
    {
        // The maximum horizontal speed when moving
        public float maxForwardSpeed = 3.0f;
        public float maxSidewaysSpeed = 2.0f;
        public float maxBackwardsSpeed = 2.0f;

        // Curve for multiplying speed based on slope(negative = downwards)
        public AnimationCurve slopeSpeedMultiplier = new AnimationCurve(new Keyframe(-90, 1), new Keyframe(0, 1), new Keyframe(90, 0));

        // How fast does the character change speeds?  Higher is faster.
        public float maxGroundAcceleration = 30.0f;
        public float maxAirAcceleration = 20.0f;

        // The gravity for the character
        public float gravity = 20f;
        public float maxFallSpeed = 20.0f;

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

        // The last collision flags returned from controller.Move
        [System.NonSerialized]
        public CollisionFlags collisionFlags;

        // We will keep track of the character's current velocity,
        [System.NonSerialized]
        public Vector3 velocity;

        // This keeps track of our current velocity while we're not grounded
        [System.NonSerialized]
        public Vector3 frameVelocity = Vector3.zero;

        [System.NonSerialized]
        public Vector3 hitPoint = Vector3.zero;

        [System.NonSerialized]
        public Vector3 lastHitPoint = new Vector3(Mathf.Infinity, 0, 0);
    }

    public CharacterMotorMovement movement = new CharacterMotorMovement();

    public 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.
    [System.Serializable]
    public class CharacterMotorJumping
    {
        // Can the character jump?
        public bool enabled = true;

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

        // We add extraHeight units(meters) on top when holding the button down longer while jumping
        public float extraHeight = 2f;

        // 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.
        public float perpAmount = 0.0f;

        // 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.
        public float steepPerpAmount = 0.5f;

        // 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 ifwe are just in the air(initiated by jumping OR falling) see the grounded variable.
        [System.NonSerialized]
        public bool jumping = false;

        [System.NonSerialized]
        public bool holdingJumpButton = false;

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

        [System.NonSerialized]
        public float lastButtonDownTime = -100.0f;

        [System.NonSerialized]
        public Vector3 jumpDir = Vector3.up;
    }

    public CharacterMotorJumping jumping = new CharacterMotorJumping();

    [System.Serializable]
    public class CharacterMotorMovingPlatform
    {
        public bool enabled = true;

        public MovementTransferOnJump movementTransfer = MovementTransferOnJump.PermaTransfer;

        [System.NonSerialized]
        public Transform hitPlatform;

        [System.NonSerialized]
        public Transform activePlatform;

        [System.NonSerialized]
        public Vector3 activeLocalPoint;

        [System.NonSerialized]
        public Vector3 activeGlobalPoint;

        [System.NonSerialized]
        public Quaternion activeLocalRotation;

        [System.NonSerialized]
        public Quaternion activeGlobalRotation;

        [System.NonSerialized]
        public Matrix4x4 lastMatrix;

        [System.NonSerialized]
        public Vector3 platformVelocity;

        [System.NonSerialized]
        public bool newPlatform;
    }

    public CharacterMotorMovingPlatform movingPlatform = new CharacterMotorMovingPlatform();

    [System.Serializable]
    public class CharacterMotorSliding
    {
        // Does the character slide on too steep surfaces?
        public bool enabled = true;

        // How fast does the character slide on steep surfaces?
        public float slidingSpeed = 10.0f;

        // How much can the player control the sliding direction?
        // ifthe value is 0.5 the player can slide sideways with half the speed of the downwards sliding speed.
        public float sidewaysControl = 1.0f;

        // How much can the player influence the sliding speed?
        // ifthe value is 0.5 the player can speed the sliding up to 150% or slow it down to 50%.
        public float speedControl = 0.4f;
    }

    public CharacterMotorSliding sliding = new CharacterMotorSliding();

    //[System.Serializable]
    //public class CharacterMotorSwimming
    //{
    //    public bool enable;

    //    public bool disableJumping = true;


    //    [System.NonSerialized]
    //    public bool isSwimming;

    //    [System.NonSerialized]
    //    public bool isDiving;
    //}

    //public CharacterMotorSwimming swimming = new CharacterMotorSwimming();


    [System.NonSerialized]
    public bool grounded = true;

    [System.NonSerialized]
    public Vector3 groundNormal = Vector3.zero;

    private Vector3 lastGroundNormal = Vector3.zero;

    private Transform tr;

    private CharacterController controller;

    void Awake()
    {
        controller = GetComponent<CharacterController>();
        tr = transform;
    }

    private void UpdateFunction()
    {
        // We copy the actual velocity into a temporary variable that we can manipulate.
        Vector3 velocity = movement.velocity;
        
        // Update velocity based on input
        velocity = ApplyInputVelocityChange(velocity);

        if (TooSteep())
        {
            inputJump = false;
        }


        // Apply gravity and jumping force
        velocity = ApplyGravityAndJumping(velocity);

        // Moving platform support
        Vector3 moveDistance = Vector3.zero;
        if(MoveWithPlatform())
        {
            Vector3 newGlobalPoint = movingPlatform.activePlatform.TransformPoint(movingPlatform.activeLocalPoint);
            moveDistance = (newGlobalPoint - movingPlatform.activeGlobalPoint);
            if(moveDistance != Vector3.zero)
                controller.Move(moveDistance);

            // Support moving platform rotation as well:
            Quaternion newGlobalRotation = movingPlatform.activePlatform.rotation * movingPlatform.activeLocalRotation;
            Quaternion rotationDiff = newGlobalRotation * Quaternion.Inverse(movingPlatform.activeGlobalRotation);

            var yRotation = rotationDiff.eulerAngles.y;
            if(yRotation != 0)
            {
                // Prevent rotation of the local up vector
                tr.Rotate(0, yRotation, 0);
            }
        }

        // Save lastPosition for velocity calculation.
        Vector3 lastPosition = tr.position;

        // We always want the movement to be framerate independent.  Multiplying by Time.deltaTime does this.
        Vector3 currentMovementOffset = velocity * Time.deltaTime;

        // 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.
        float pushDownOffset = Mathf.Max(controller.stepOffset, new 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;

        // Move our character!
        movement.collisionFlags = controller.Move(currentMovementOffset);
        movement.lastHitPoint = movement.hitPoint;
        lastGroundNormal = groundNormal;

        if(movingPlatform.enabled  movingPlatform.activePlatform != movingPlatform.hitPlatform)
        {
            if(movingPlatform.hitPlatform != null)
            {
                movingPlatform.activePlatform = movingPlatform.hitPlatform;
                movingPlatform.lastMatrix = movingPlatform.hitPlatform.localToWorldMatrix;
                movingPlatform.newPlatform = true;
            }
        }

        // 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.
        Vector3 oldHVelocity = new Vector3(velocity.x, 0, velocity.z);
        movement.velocity = (tr.position - lastPosition) / Time.deltaTime;
        Vector3 newHVelocity = new Vector3(movement.velocity.x, 0, movement.velocity.z);

        // The CharacterController can be moved in unwanted directions when colliding with things.
        // We want to prevent this from influencing the recorded velocity.
        if(oldHVelocity == Vector3.zero)
        {
            movement.velocity = new Vector3(0, movement.velocity.y, 0);
        }
        else
        {
            float projectedNewVelocity = Vector3.Dot(newHVelocity, oldHVelocity) / oldHVelocity.sqrMagnitude;
            movement.velocity = oldHVelocity * Mathf.Clamp01(projectedNewVelocity) + movement.velocity.y * Vector3.up;
        }

        if(movement.velocity.y < velocity.y - 0.001)
        {
            if(movement.velocity.y < 0)
            {
                // Something is forcing the CharacterController down faster than it should.
                // Ignore this
                movement.velocity.y = velocity.y;
            }
            else
            {
                // The upwards movement of the CharacterController has been blocked.
                // This is treated like a ceiling collision - stop further jumping here.
                jumping.holdingJumpButton = false;
            }
        }

        // We were grounded but just loosed grounding
        if(grounded  !IsGroundedTest())
        {
            grounded = false;

            // Apply inertia from platform
            if(movingPlatform.enabled 
                (movingPlatform.movementTransfer == MovementTransferOnJump.InitTransfer ||
                movingPlatform.movementTransfer == MovementTransferOnJump.PermaTransfer)
            )
            {
                movement.frameVelocity = movingPlatform.platformVelocity;
                movement.velocity += movingPlatform.platformVelocity;
            }

            SendMessage("OnFall", SendMessageOptions.DontRequireReceiver);
            // We pushed the character down to ensure it would stay on the ground ifthere 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.5f + controller.radius);
            movingPlatform.activeLocalPoint = movingPlatform.activePlatform.InverseTransformPoint(movingPlatform.activeGlobalPoint);

            // Support moving platform rotation as well:
            movingPlatform.activeGlobalRotation = tr.rotation;
            movingPlatform.activeLocalRotation = Quaternion.Inverse(movingPlatform.activePlatform.rotation) * movingPlatform.activeGlobalRotation;
        }
    }

    void FixedUpdate()
    {
        if(movingPlatform.enabled)
        {
            if(movingPlatform.activePlatform != null)
            {
                if(!movingPlatform.newPlatform)
                {
                    // unused: Vector3 lastVelocity = movingPlatform.platformVelocity;

                    movingPlatform.platformVelocity = (
                        movingPlatform.activePlatform.localToWorldMatrix.MultiplyPoint3x4(movingPlatform.activeLocalPoint)
                        - movingPlatform.lastMatrix.MultiplyPoint3x4(movingPlatform.activeLocalPoint)
                    ) / Time.deltaTime;
                }
                movingPlatform.lastMatrix = movingPlatform.activePlatform.localToWorldMatrix;
                movingPlatform.newPlatform = false;
            }
            else
            {
                movingPlatform.platformVelocity = Vector3.zero;
            }
        }

        if(useFixedUpdate)
            UpdateFunction();
    }

    void Update()
    {
        if(!useFixedUpdate)
            UpdateFunction();
    }

    private Vector3 ApplyInputVelocityChange(Vector3 velocity)
    {
        if(!canControl)
            inputMoveDirection = Vector3.zero;

        // Find desired velocity
        Vector3 desiredVelocity;
        if (grounded  TooSteep())
        {
            canControl = false;
            // The direction we're sliding in
            desiredVelocity = new Vector3(groundNormal.x, 0, groundNormal.z).normalized;
            // Find the input movement direction projected onto the sliding direction
            var projectedMoveDir = Vector3.Project(inputMoveDirection, desiredVelocity);
            // Add the sliding direction, the spped control, and the sideways control vectors
            desiredVelocity = desiredVelocity + projectedMoveDir * sliding.speedControl + (inputMoveDirection - projectedMoveDir) * sliding.sidewaysControl;
            // Multiply with the sliding speed
            desiredVelocity *= sliding.slidingSpeed;
        }
        else
        {
            desiredVelocity = GetDesiredHorizontalVelocity();
            canControl = true;
        }

        if(movingPlatform.enabled  movingPlatform.movementTransfer == MovementTransferOnJump.PermaTransfer)
        {
            desiredVelocity += movement.frameVelocity;
            desiredVelocity.y = 0;
        }

        if(grounded)
            desiredVelocity = AdjustGroundVelocityToNormal(desiredVelocity, groundNormal);
        else
            velocity.y = 0;

        // Enforce max velocity change
        float maxVelocityChange = GetMaxAcceleration(grounded) * Time.deltaTime;
        Vector3 velocityChangeVector = (desiredVelocity - velocity);
        if(velocityChangeVector.sqrMagnitude > maxVelocityChange * maxVelocityChange)
        {
            velocityChangeVector = velocityChangeVector.normalized * maxVelocityChange;
        }
        // ifwe're in the air and don't have control, don't apply any velocity change at all.
        // ifwe're on the ground and don't have control we do apply it - it will correspond to friction.
        if(grounded || canControl)
            velocity += velocityChangeVector;

        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);
        }

        return velocity;
    }

    private Vector3 ApplyGravityAndJumping(Vector3 velocity)
    {
        if(!inputJump || !canControl)
        {
            jumping.holdingJumpButton = false;
            jumping.lastButtonDownTime = -100;
        }

        if(inputJump  jumping.lastButtonDownTime < 0  canControl)
            jumping.lastButtonDownTime = Time.time;

        if(grounded)
            velocity.y = Mathf.Min(0, velocity.y) - movement.gravity * Time.deltaTime;
        else
        {
            velocity.y = movement.velocity.y - movement.gravity * Time.deltaTime;

            // 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.
                // ifwe'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 ifthe jump button was pressed down in the last 0.2 seconds.
            // We use this check instead of checking ifit's pressed down right now
            // because players will often try to jump in the exact moment when hitting the ground after a jump
            // and ifthey 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;

                // Calculate the jumping direction
                if(TooSteep())
                    jumping.jumpDir = Vector3.Slerp(Vector3.up, groundNormal, jumping.steepPerpAmount);
                else
                    jumping.jumpDir = Vector3.Slerp(Vector3.up, groundNormal, jumping.perpAmount);

                // Apply the jumping force to the velocity. Cancel any vertical velocity first.
                velocity.y = 0;
                velocity += jumping.jumpDir * CalculateJumpVerticalSpeed(jumping.baseHeight);

                // Apply inertia from platform
                if(movingPlatform.enabled 
                    (movingPlatform.movementTransfer == MovementTransferOnJump.InitTransfer ||
                    movingPlatform.movementTransfer == MovementTransferOnJump.PermaTransfer)
                )
                {
                    movement.frameVelocity = movingPlatform.platformVelocity;
                    velocity += movingPlatform.platformVelocity;
                }

                SendMessage("OnJump", SendMessageOptions.DontRequireReceiver);
            }
            else
            {
                jumping.holdingJumpButton = false;
            }
        }

        return velocity;
    }

    void OnControllerColliderHit(ControllerColliderHit hit)
    {
        if(hit.normal.y > 0  hit.normal.y > groundNormal.y  hit.moveDirection.y < 0)
        {
            if((hit.point - movement.lastHitPoint).sqrMagnitude > 0.001 || lastGroundNormal == Vector3.zero)
                groundNormal = hit.normal;
            else
                groundNormal = lastGroundNormal;

            movingPlatform.hitPlatform = hit.collider.transform;
            movement.hitPoint = hit.point;
            movement.frameVelocity = Vector3.zero;
        }
    }

    void OnTriggerEnter(Collider col)
    {
        if (col.CompareTag("Water"))
        {
            swimming.isSwimming = true;
        }
    }

    void OnTriggerExit(Collider col)
    {
        if (col.CompareTag("Water"))
        {
            swimming.isSwimming = false;
        }
    }

    private IEnumerator 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)
            {
                Transform platform = movingPlatform.activePlatform;
                yield return new WaitForFixedUpdate();
                yield return new WaitForFixedUpdate();
                if(grounded  platform == movingPlatform.activePlatform)
                    yield break;
            }
            movement.velocity -= movingPlatform.platformVelocity;
        }
    }

    private bool MoveWithPlatform()
    {
        return (movingPlatform.enabled
             (grounded || movingPlatform.movementTransfer == MovementTransferOnJump.PermaLocked)
             movingPlatform.activePlatform != null
        );
    }

    private Vector3 GetDesiredHorizontalVelocity()
    {
        // Find desired velocity
        Vector3 desiredLocalDirection = tr.InverseTransformDirection(inputMoveDirection);
        float maxSpeed = MaxSpeedInDirection(desiredLocalDirection);
        if(grounded)
        {
            // Modify max speed on slopes based on slope speed multiplier curve
            var movementSlopeAngle = Mathf.Asin(movement.velocity.normalized.y) * Mathf.Rad2Deg;
            maxSpeed *= movement.slopeSpeedMultiplier.Evaluate(movementSlopeAngle);
        }
        return tr.TransformDirection(desiredLocalDirection * maxSpeed);
    }

    private Vector3 AdjustGroundVelocityToNormal(Vector3 hVelocity, Vector3 groundNormal)
    {
        Vector3 sideways = Vector3.Cross(Vector3.up, hVelocity);
        return Vector3.Cross(sideways, groundNormal).normalized * hVelocity.magnitude;
    }

    private bool IsGroundedTest()
    {
        return (groundNormal.y > 0.01);
    }

    float GetMaxAcceleration(bool grounded)
    {
        // Maximum acceleration on ground and in air
        if(grounded)
            return movement.maxGroundAcceleration;
        else
            return movement.maxAirAcceleration;
    }

    float CalculateJumpVerticalSpeed(float targetJumpHeight)
    {
        // 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);
    }

    bool IsJumping()
    {
        return jumping.jumping;
    }

    bool IsSliding()
    {
        return (grounded  sliding.enabled  TooSteep());
    }

    bool IsTouchingCeiling()
    {
        return (movement.collisionFlags  CollisionFlags.CollidedAbove) != 0;
    }

    bool IsGrounded()
    {
        return grounded;
    }

    bool TooSteep()
    {
        return (groundNormal.y <= Mathf.Cos(controller.slopeLimit * Mathf.Deg2Rad));
    }

    Vector3 GetDirection()
    {
        return inputMoveDirection;
    }

    void SetControllable(bool controllable)
    {
        canControl = controllable;
    }

    // Project a direction onto elliptical quater segments based on forward, sideways, and backwards speed.
    // The function returns the length of the resulting vector.
    float MaxSpeedInDirection(Vector3 desiredMovementDirection)
    {
        if(desiredMovementDirection == Vector3.zero)
            return 0;
        else
        {
            float zAxisEllipseMultiplier = (desiredMovementDirection.z > 0 ? movement.maxForwardSpeed : movement.maxBackwardsSpeed) / movement.maxSidewaysSpeed;
            Vector3 temp = new Vector3(desiredMovementDirection.x, 0, desiredMovementDirection.z / zAxisEllipseMultiplier).normalized;
            float length = new Vector3(temp.x, 0, temp.z * zAxisEllipseMultiplier).magnitude * movement.maxSidewaysSpeed;
            return length;
        }
    }

    void SetVelocity(Vector3 velocity)
    {
        grounded = false;
        movement.velocity = velocity;
        movement.frameVelocity = Vector3.zero;
        SendMessage("OnExternalVelocity");
    }
}

Controller (not so important):

using UnityEngine;
using System.Collections.Generic;

[RequireComponent(typeof(CharacterController))]
[RequireComponent(typeof(CharacterMotor))]

public class MMOController : MonoBehaviour
{
    public string axisX = "NONE";
    public string axisY = "NONE";
    public AnimationClip walkAnimations;
    public AnimationClip runAnimations;
    public AnimationClip jumpAnimations;
    public AnimationClip doubleJumpAnimations;
    public AnimationClip sitAnimations;
    public AnimationClip standAnimations;
    public AnimationClip swimAnimations;
    public AnimationClip idleSwimAnimations;

    private InputManager inputManager;
    private CharacterController character;
    private CharacterMotor motor;
    private MMOCameraController mmocamera;

    private float currentMoveSpeedMultiplier;
    private bool guibuttonpressed = false;

    private bool isflying;
    private bool isswimming;
    private GUIManager guiManager;
    public float runspeedmultiplier = 3;
    public float walkspeedmultiplier = 1;

    public void Awake()
    {
        currentMoveSpeedMultiplier = runspeedmultiplier;
        motor = (CharacterMotor)this.GetComponent(typeof(CharacterMotor));
        inputManager = new InputManager();
        axisX = inputManager.axisX;
        axisY = inputManager.axisY;
        guiManager = GameObject.Find("Main Camera").GetComponent<GUIManager>();
        mmocamera = GameObject.Find("Main Camera").GetComponent<MMOCameraController>();
        //foreach (InputHandler i in inputManager.InputKeys)
        //{
        //    Debug.Log(i.Command.ToString());
        //}
    }

    void OnGUI()
    {
        Event e = Event.current;
        if (e.isKey)
        {
            Debug.Log(e.keyCode);
        }
    }

    public void Update()
    {
        inputManager.CheckControleKyes();
        Vector3 dir = new Vector3();
        if ((guiManager.showCharacterMenu || guiManager.showInventory || guiManager.showMainMenu || guiManager.showQuestlog || guiManager.showSocialMenu || guiManager.showSpellBook || guiManager.showTalents || guiManager.showWorldMap) || guibuttonpressed)
        {
            mmocamera.guiMode = true;
            guiManager.showGUI = true;
        }
        else if ((!guiManager.showCharacterMenu || !guiManager.showInventory || !guiManager.showMainMenu || !guiManager.showQuestlog || !guiManager.showSocialMenu || !guiManager.showSpellBook || !guiManager.showTalents || !guiManager.showWorldMap)  guiManager.showGUI)
        {
            guiManager.showGUI = false;
            mmocamera.guiMode = false;
        }
        else
        {
            guiManager.showGUI = false;
            mmocamera.guiMode = false;
        }

        if (guiManager.showOptionsMenu || guiManager.showKeyBindingsMenu || guiManager.showLogOutMenu || guiManager.showQuitMenu)
        {
            if (Input.GetKeyDown(KeyCode.Escape))
            {
                guiManager.showKeyBindingsMenu = false;
                guiManager.showOptionsMenu = false;
            }
            motor.inputMoveDirection = Vector3.zero;
            return;
        }

        if (guiManager.showMainMenu)
        {
            if (Input.GetKeyDown(KeyCode.Escape))
            {
                guiManager.showMainMenu = !guiManager.showMainMenu;
            }
            motor.inputMoveDirection = Vector3.zero;
            return;
        }

        //Menu statements
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            guiManager.showMainMenu = !guiManager.showMainMenu;
        }
        if (inputManager.CheckInput("ToggleGuiMode", KeyStages.Down))
        {
            if (guiManager.showCharacterMenu || guiManager.showInventory || guiManager.showMainMenu || guiManager.showQuestlog || guiManager.showSocialMenu || guiManager.showSpellBook || guiManager.showTalents || guiManager.showWorldMap)
            {
                guibuttonpressed = false;
                mmocamera.guiMode = false;
                guiManager.showCharacterMenu = false;
                guiManager.showGUI = false;
                guiManager.showInventory = false;
                guiManager.showMainMenu = false;
                guiManager.showQuestlog = false;
                guiManager.showSocialMenu = false;
                guiManager.showSpellBook = false;
                guiManager.showTalents = false;
                guiManager.showWorldMap = false;
            }
            else
            {
                guibuttonpressed = !guibuttonpressed;
                mmocamera.guiMode = !mmocamera.guiMode;
                guiManager.showGUI = !guiManager.showGUI;
            }
        }
        if (inputManager.CheckInput("Inventory", KeyStages.Down))
        {
            guiManager.showInventory = !guiManager.showInventory;
        }
        if (inputManager.CheckInput("Quest", KeyStages.Down))
        {
            guiManager.showQuestlog = !guiManager.showQuestlog;
        }
        if (inputManager.CheckInput("Character", KeyStages.Down))
        {
            guiManager.showCharacterMenu = !guiManager.showCharacterMenu;
        }
        if (inputManager.CheckInput("SpellBook", KeyStages.Down))
        {
            guiManager.showSpellBook = !guiManager.showSpellBook;
        }
        if (inputManager.CheckInput("WorldMap", KeyStages.Down))
        {
            guiManager.showWorldMap = !guiManager.showWorldMap;
        }
        if (inputManager.CheckInput("Social", KeyStages.Down))
        {
            guiManager.showSocialMenu = !guiManager.showSocialMenu;
        }
        if (inputManager.CheckInput("Talents", KeyStages.Down))
        {
            guiManager.showTalents = !guiManager.showTalents;
        }

        //Movement statements
        if (inputManager.CheckInput("MoveForward", KeyStages.Press) || Input.GetAxis(axisY) < -0.04f)
        {
            dir.z += 1;
        }
        if (inputManager.CheckInput("MoveBackward", KeyStages.Press) || Input.GetAxis(axisY) > 0.04f)
        {
            dir.z -= 1;
        }
        if (inputManager.CheckInput("StrafeLeft", KeyStages.Press) || Input.GetAxis(axisX) < -0.04f)
        {
            dir.x -= 1;
        }
        if (inputManager.CheckInput("StrafeRight", KeyStages.Press) || Input.GetAxis(axisX) > 0.04f)
        {
            dir.x += 1;
        }
        
        if (inputManager.CheckInput("ToggleRun/Walk", KeyStages.Down))
        {
            if (currentMoveSpeedMultiplier == runspeedmultiplier)
            {
                currentMoveSpeedMultiplier = walkspeedmultiplier;
            }
            else
            {
                currentMoveSpeedMultiplier = runspeedmultiplier;
            }
        }

        if (inputManager.CheckInput("Interact", KeyStages.Down))
        {
            //rayCast some stuff here to find a interact script...
        }

        motor.inputJump = inputManager.CheckInput("Jump", KeyStages.Down);
        //if (motor.swimming.isSwimming)
        //{
        //    dir.y = Mathf.Sin(mmocamera.transform.rotation.ToEulerAngles().x) * -1;
        //    Debug.Log(dir.y);
        //}
        
            motor.inputMoveDirection = this.transform.TransformDirection(dir * currentMoveSpeedMultiplier);
    }

    void OnApplicationQuit()
    {
        if (guiManager.movementUsesJoystickAxis == true)
        {
            inputManager.SaveConfig(axisX, axisY);
        }
        else
        {
            inputManager.SaveConfig("NONE", "NONE");
        }
    }
}

Up…

Up…

So no buoyancy physics while swimming? Use itween and a set of swimming motions. Mixamo has 3 or 4 you can buy for about $30 each. Or try out Unity 4 Beta if you aren’t too far into your game development.

from my understanding from what “buoyancy physics” is then indeed no buoyancy physics on many items, but some items should have it tho, and i prefer not to buy packages and i’ve already looked at itween for other purposes but cant really figure out how to use it probably. one thing i’ve tried to play with is a ray cast up and see if the water plane is above the player but haven’t got any luck with this :/…

Why not use a trigger when entering the water to set the gravity. The same state can be used to tell your player code not to walk while its on the bottom.

I could give some small examples in JS if you like.

the problem with triggers is if i want underground caves (as seen in my awful drawing) the trigger will cover it and make the character swim instead of walking there… I’ve tried using plane triggers but sadly they’re not to much use… and a raycast wont hit the plane in at times for some strange reason… but you’re welcome to link the JS example, might help others in a less complicated situation… :slight_smile:

Can you use nested trigger volumes and some simple logic?

if ( isGrounded ) { /* walking logic */ }
if ( isGrounded  jumpPressed ) { /* do something that causes isGrounded to be false */ }
if ( inWater ) {
  movementMagnitude = Input.GetAxis("Move Z"); 
  movementVector = movementMagnitude * Camera.main.forward;
}
if ( !isGrounded  jumpPressed  inWater ) { /* swimming or something */ }
if ( !inWater ) { /* apply normal gravity */ }
if ( inWater ) { /* apply water gravity */ }

function OnTriggerEnter( other : Collider ) {
  if ( other.GetComponent( WaterVolume ) ) {
    inWater = true;
  }
  if ( other.GetComponent( AirVolume ) ) {
    inWater = false;
  }
}
function OnTriggerExit( other : Collider ) {
  if ( other.GetComponent( AirVolume ) ) {
    inWater = true;
  }
  if ( other.GetComponent( WaterVolume ) ) {
    inWater = false;
  }
}

If you put an AirVolume completely inside a WaterVolume that should correctly calculate when you’re in or out of water.

aaah a trigger in a trigger, didn’t even think about that in one sec, might work fine but wouldn’t there be an easier way to keep track of this? in the end there would be a lot of work to make the triggers and place them correctly etc… I may see a problem using raycast and plane trigger to keep track of it. But thanks for the finger point

Further suggestions please, would help very much :slight_smile: