Custom Swim/Dive Ability

I have started making a custom swim and deep diving ability for the UCC Third person view.

The character currently starts swimming when he enters a collider on the Water layer. He doesn't have any locomotion system yet, and I am not sure how physics work in TPC ability code to simulate the drag of water. Perhaps I can just use the built in physics, but I'm just not sure if the PlayerLocomotion (and other movement) system(s) will conflict with normal physics methods. I suspect that they do, though I could easily be wrong.

I simply want to make him add a slow forward motion according to his input vector when he is swimming and have that same force slowly decelerate as he changes directions so he reacts as if he's underwater. The drifting factor is important, and so is the drag factor, and I'm not sure how to program those physics.

Here is some pseudo code:

Code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Opsive.UltimateCharacterController.Character.Abilities;
using Opsive.UltimateCharacterController.Character.Abilities.Items;

public class Swim : DetectObjectAbilityBase
{

//Input makes the character swim.

    public override void ApplyPosition perhaps?()
    {
    //Add drag and simulate water physics.
    [Pseudo Code]
    Var M = the appropriate input magnitude (Normal, with Speed Change for example);

    Apply force in the forward vector by M.
    Add Drift to the character.
    
        base.ApplyPosition Or whichever method I should override();
    }

//We want to stop swimming when the character exits the water trigger bounds.

    public override void OnTriggerExit(Collider other)
    {
        //Stopping the ability
        if (other.gameObject == m_DetectedObject) {
        StopAbility();
        }

        base.OnTriggerExit(other);
    }

}

Obviously I have not been able to figure out the objects in the namespace quite enough yet, but I think you can understand my desired outcome by the pseudo code.

Thanks for all help offered.


Jacob Siler
 
This post should help with determining the correct way to move the character's position within the ability:

 
It's been a long time coming to send this reply, but I got stuck, bought the agility add-on, and got stuck again. I also have been focusing on other base mechanics of my game.

Anyway, I came back to finish the swim/dive ability as one would, and here is the scenario at the moment:

1598

My testing area looks like this (with the cylinder being a trigger volume set to the swim layer) with the character detecting simply a trigger with the 'swim' layer in order to start the swim ability.

And here is the code:

Code:
public class Swim : DetectObjectAbilityBase
{
    /// <summary>
    /// Setting up the initial variables.
    /// m_ Rigid body is a reference to the characters rigid body component.
    /// Swimspeed is the multiplier for input to make the character swim.
    /// </summary>
    Rigidbody m_Rigidbody;
    public float Swimspeed = 1f;

    /// <summary>
    /// Keeps character from equipping items during the ability.
    /// </summary>
    /// <param name="startingAbility"></param>
    /// <returns></returns>
    public override bool ShouldBlockAbilityStart(Ability startingAbility)
    {
        return startingAbility is ItemAbility;
    }

    //This completes the variable reference for the Rigid body component and turns gravity off for the character.
    public override void Start()
    {

        base.AbilityStarted();


        m_Rigidbody = GetComponent<Rigidbody>(); // Get reference to rigidbody
        m_UseGravity = AbilityBoolOverride.False;
    }

    //If the character exits the trigger that made them swim in the first place, they will stop swimming.
    public override void OnTriggerExit(Collider other)
    {
        //Stopping the ability
        if (other.gameObject == m_DetectedObject)
        {
            m_UseGravity = AbilityBoolOverride.True; //Turn the gravity back on.
            StopAbility();
        }

        base.OnTriggerExit(other);
    }


    //Overriding how the movement is updated, so the swimming logic can take place.
    public override void UpdatePosition()
    {
       
        var motorThrottle = m_CharacterLocomotion.InputVector * Swimspeed;
        m_CharacterLocomotion.MotorThrottle = motorThrottle;
    }

}



Curiously I can't seem to get the character responding to input other than to change direction. I had the character moving previously but for some reason he is not now.
The agility pack helped tremendously to get vaulting and other abilities working instantly, but I am unsure of which scripts to pick apart to unravel swimming.

Would you be able to point me in the right direction?

I will keep digging through each script in the meantime however.
 
Last edited:
As of the later versions of the character controller there is a new property that you can use to move:

m_CharacterLocomotion.AbilityMotor.

The VR Add-On dash ability uses this with the following code:

Code:
        public override void UpdatePosition()
        {
            if (!m_Dash) {
                return;
            }

            var deltaPosition = m_TargetPosition - m_Transform.position;
            var movement = deltaPosition.normalized * m_MoveSpeed;
            m_CharacterLocomotion.AbilityMotor = movement / (m_CharacterLocomotion.TimeScaleSquared * Time.timeScale * m_CharacterLocomotion.FramerateDeltaTime);
        }

Where m_TargetPosition is the position that the character is dashing towards. Hopefully that points you in the right direction.
 
Thank you! Everything is perfect now, except some minor details that I can work out myself.
I was curious though, is there a reason that Debug.Log doesn't work from inside an ability?
I can't seem to enable them so I can make more useful breakpoints. This is why I asked as many questions as I have.:geek:
Probably a very nooby question.
 
Last edited:
Okay, that's good to know. I've been having a persistent error about

'System.Runtime.InteropServices.COMException (0x80040154)' etc. So, maybe that is just blocking them because I have info and warnings enabled.
I think it's just a faulty asset, which I will now uninstall and try that out.
Ah, a quick google search pointed to a newer required driver for my input device to work, which removed the error and debug log is now working. :D
 
Last edited:
It would absolutely be my pleasure. I just need a little bit of help to get the pitch (With input) worked out.
I think I need to override the UpdateRotation, or perhaps the ApplyRotation method. I have been trying some different things though.

Here is the current code without the pitch control:


Code:
/*Current working swim code
The ability needs to be set up to detect the water or a custom swim layer.
in the project settings/physics, make sure the level collisions are turned on.*/

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Opsive.UltimateCharacterController.Character.Abilities;
using Opsive.UltimateCharacterController.Character.Abilities.Items;
using Opsive.UltimateCharacterController.Character;

public class Swim : DetectObjectAbilityBase
{
    /// <summary>
    /// Setting up the initial variables.
    /// m_ Rigid body is a reference to the characters rigid body component.
    /// Swimspeed is the multiplier for input to make the character swim.
    /// </summary>
    Rigidbody m_Rigidbody;
    Animator m_Animator;
    public Collider coll;


    public float Depth=1000;
    public float Swimspeed = 1f;
    public float MaxDepth = 50f;
    public float s_pitch;
    public float depthOffset;


        RaycastHit hit;
        float dist = 1000;

    /// <summary>
    /// Keeps character from equipping items during the ability.
    /// </summary>
    /// <param name="startingAbility"></param>
    /// <returns></returns>
    public override bool ShouldBlockAbilityStart(Ability startingAbility)
    {
        return startingAbility is ItemAbility;
    }

    //This completes the variable reference for the Rigid body component and turns gravity off for the character.
    public override void Start()
    {

        base.AbilityStarted();

        depthOffset = m_CharacterLocomotion.transform.position.y;

        coll = GetComponent<Collider>();
        m_Rigidbody = GetComponent<Rigidbody>(); // Get reference to rigidbody
        //m_UseGravity = AbilityBoolOverride.False;
        base.m_UseGravity = AbilityBoolOverride.False;
    }



    public override void Update()
    {
        base.Update();
        //edit: to draw ray also//
        Debug.DrawRay(m_Rigidbody.transform.position, Vector3.up * Depth, Color.green);

        //Cast a ray to find how deep the character is swimming.

        int mask = 1 << 9;    // Swim on layer 9 in the inspector

        //edit: to draw ray also//
        //Debug.DrawRay(m_Rigidbody.transform.position, Vector3.up * 1000, Color.green);
        //end edit//
        if (Physics.Raycast(m_Rigidbody.transform.position, - Vector3.up, out hit, dist))
        {
            //the ray collided with something, you can interact
            // with the hit object now by using hit.collider.gameObject
            Depth = hit.distance*.1f;
           // Debug.Log(hit.collider.gameObject + " Distance: " + hit.transform.position.y + " Depth:" +Depth);

        }
        else
        {
            //nothing was detected above your gameObject within 1000m.
           // Debug.Log("The Ray didn't hit anything.");
        }
    }

    /// <summary>
    /// Can the ability be stopped?
    /// </summary>
    /// <returns>True if the ability can be stopped.</returns>
    public override bool CanStopAbility()
    {
        return true;
    }

    //If the character exits the trigger that made them swim in the first place, they will stop swimming.
    public override void OnTriggerExit(Collider other)
    {
        Debug.Log(other.gameObject.name);
        //Stopping the ability
        if (other.gameObject == m_DetectedObject)
        {
            // After the character is done swimming reset the rotation and position to realign the character's move direction.
            m_CharacterLocomotion.ResetRotationPosition();
            m_UseGravity = AbilityBoolOverride.False; //Turn the gravity back on.
            base.m_UseGravity = AbilityBoolOverride.True;
            StopAbility();
        }

        base.OnTriggerExit(other);
    }


    //Overriding how the movement is updated, so the swimming logic can take place.
    public override void UpdatePosition()
    {
        var inputVector = m_CharacterLocomotion.InputVector;
       
        Debug.Log(inputVector);

        if (Mathf.Abs(inputVector.y) > 0)
        {
            inputVector.x = inputVector.y * (Vector3.Dot(m_LookSource.LookDirection(true), m_Transform.right) < 0 ? 1 : -1);
        }

        var motorThrottle = inputVector * Swimspeed;
        m_CharacterLocomotion.MotorThrottle = motorThrottle;

/*Tinkering with pitch control attempts

        //m_CharacterLocomotion.SetPosition(new Vector3(m_CharacterLocomotion.transform.position.x, depthOffset, m_CharacterLocomotion.transform.position.z));

        // Get the mouse delta. This is not in the range -1...1
        float h =  Input.GetAxis("Mouse X");
        float v =  Input.GetAxis("Mouse Y")*Time.deltaTime;


        m_CharacterLocomotion.transform.Rotate(v, 0, 0);
            //(Quaternion.Euler(v, 0, 0));
            */

    }

}

You can see from the code that the debug messages and the animator related variables (Aka Depth) aren't really doing anything useful because the raycast doesn't actually work to find the depth from inside the water trigger volume. It is rough because I have been trying so much different stuff.
I think I will change it from an animator with four animations in a 2D blend state to two blend states (One for the surface, and one for below it.) That way I can control if the character is surface swimming, or if he is underwater swimming. Maybe with an added force that gently tries to push him towards the surface.

I am redoing the animation states today, and I will hopefully get the pitch control worked out too.
 
Last edited:
This reminds me of Mario 64's swimming, where if Mario is close to the surface it will push him upwards so his head sticks out.

Has there been any progress? I'd be willing to give this a shot - first I gotta find some swimming animations though!
 
Okay, I just had a chance to check this out. After setting it out like you've shown in the screenshot in the OP, it works partially. However if I walk into it from the ground, I will get catapulted upwards and then eventually get stuck in a falling loop. If I move outside the trigger, it'll stop but fall ability will be activated, and it will refuse to bring my character back down.

Did this happen for you or have I set up something incorrectly?
 
Okay, I just had a chance to check this out. After setting it out like you've shown in the screenshot in the OP, it works partially. However if I walk into it from the ground, I will get catapulted upwards and then eventually get stuck in a falling loop. If I move outside the trigger, it'll stop but fall ability will be activated, and it will refuse to bring my character back down.

Did this happen for you or have I set up something incorrectly?

Hi! It did initially, yes. I sorted it by turning the gravity back on when the character exits the trigger. Strangely though, to do this I needed to set m_gravity to abilitybooloverride.false and erase the other code. I thought this would turn the gravity off but it actually switches it back on properly. This is probably due to the way the abilitybooloverride is actually operating which is set in a different script. I need to inspect and unpeel as many layers of script as possible when I get home. I am not extremely familiar with C# (as a web developer, JS, HTML, and CSS are more my wheelhouse) and I used to scrape by with the former version of opsive only by using the documentation. :ROFLMAO:
The flying up error solved for me with the script I gave you though. So you may need to look at your animations and make sure that the transforms and the root motion work favorably for swimming. I used the frog swim motion off of Mixamo.
That said I haven't given the motor control code enough attention just yet to have it ironed out.

This line most likely holds the answer to controlling the pitch and everything else, after removing the ternary operator and tweaking the logic a bit:

inputVector.x = inputVector.y * (Vector3.Dot(m_LookSource.LookDirection(true), m_Transform.right) < 0 ? 1 : -1);

I am on holiday for a few more days and then I can respond in a more useful way. :)
 
Last edited:
Ah okay, I haven't actually set up any custom animations for swimming, I was mainly just using the falling animations. I'll see where I put my swimming animations and give it a shot.
 
@JacobDSiler Sorry for the late response mate, but here's a video of what my character does when she enters a cylinder of "water", set to layer 9 on the editor and the cylinder's collider is set to trigger. I also played with setting the layer as not a collision layer in the UCC collision layer setting but that had no effect.


As you can see, it seems that she just gets pushed violently out of the cylinder. When she does so, she's unable to fall back down. The inspector is open in the video so you can see what's going on.

Have you had any similar issues like this? I doubt it's animation based because the animations don't have root motion.
 
It's obvious that she just needs clothes to swim properly.

But on a serious note, I'm not too sure since I can't tell how your collision checking is being done.

This what I am using:
1570286182497.png
It may be the same as what you're using, if so let me know. Also, I am using root motion, but it looks like you're getting on well in that regard.

I have had many issues like this, and maybe you need to actually set the gravity abilitybool to true in order to fall again. I'm not too sure without being able to jump in the editor myself.
I'm just excited that the swimming add-on is in the works.
 
Still the same result. What do you have in the way of animation events on your character?
 
None, but I have set up the referenced animator parameters. Which you probably already have since you aren't seeing errors in the console.
 
@JacobDSiler . Hi can you please share how you have done the swimming ability this is what I am looking for. I am not a core programmer. If you can send a download link it will be helpfull.
Thanks
 
Top