RAM Swimming (River Swimming!)

River swimming requires the swimming add-on and has only been tested with RAM 2019.



My friend and fellow developer, nathanj, made a height checking script for RAM which allows the swim ability to dynamically detect the height of a swimmable RAM surface. This is done by adding a mesh collider to the RAM spline and setting up a few parameters.

I have been chatting with him and working on a script to check the current of the river, and apply it to the character.

Yes! The character is now swimming in a river.

Here are a few scripts. Optimizations and further changes are taking place because we both use these in our current game projects.

Step 1)
Here is a script for checking the RAM flow and putting it in an exposed variable so that the character can take advantage of it:

RamFlowAffect.cs
C#:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RamFlowAffect : MonoBehaviour
{
    public float buoyancy = 30;
    public float viscosity = 2;
    public float viscosityAngular = 0.4f;

    public LayerMask layer = 16;

    private new Collider collider;

    private new Rigidbody rigidbody;
    static RamSpline[] ramSplines;
    static LakePolygon[] lakePolygons;

    Vector3 lowestPoint;

    public GameObject checkObject;
    [HideInInspector] public Vector3 checkTarget;

    public bool debug = false;
    [HideInInspector] public Vector3 currentForce;


    private void Awake()
    {
        rigidbody = gameObject.GetComponent<Rigidbody>();
        if (!checkObject)
        {
            Debug.LogWarning("No object assigned to be checked in RAMFlowAffect. " + gameObject.transform.name + " will be assigned, unexpected behaviour may occur.");
            checkObject = gameObject;
        }
    }

    private void FixedUpdate()
    {

        WaterPhysics();

    }


    public void WaterPhysics()
    {

        Ray ray = new Ray();
        ray.direction = Vector3.up;
        RaycastHit hit;



        bool backFace = Physics.queriesHitBackfaces;
        Physics.queriesHitBackfaces = true;

        checkTarget = checkObject.transform.position;
        lowestPoint = checkTarget;//volumePoints[0];

    ray.origin = lowestPoint;

        if (Physics.Raycast(ray, out hit, 100, layer))
        {
            Vector3 velocity = rigidbody.velocity;

            var hitY = hit.point.y;


            RamSpline ramSpline = hit.collider.GetComponent<RamSpline>();
            LakePolygon lakePolygon = hit.collider.GetComponent<LakePolygon>();
            if (ramSpline != null)
            {
                Mesh meshRam = ramSpline.meshfilter.sharedMesh;
                int verticeId1 = meshRam.triangles[hit.triangleIndex * 3];

                Vector3 verticeDirection = ramSpline.verticeDirection[verticeId1];

                Vector2 uv4 = meshRam.uv4[verticeId1];

                verticeDirection = verticeDirection * uv4.y - new Vector3(verticeDirection.z, verticeDirection.y, -verticeDirection.x) * uv4.x;

                currentForce = new Vector3(verticeDirection.x, 0, verticeDirection.z) * ramSpline.floatSpeed;


                if (debug)
                    Debug.DrawRay(lowestPoint, Vector3.up * buoyancy * (hitY) * 5, Color.blue);
                if (debug)
                    Debug.DrawRay(transform.position, velocity * -1 * viscosity * 5, Color.magenta);
                if (debug)
                    Debug.DrawRay(transform.position, velocity * 5, Color.grey);
                if (debug)
                    Debug.DrawRay(transform.position, rigidbody.angularVelocity * 5, Color.black);
            }
            else if (lakePolygon != null)
            {
                Mesh meshLake = lakePolygon.meshfilter.sharedMesh;
                int verticeId1 = meshLake.triangles[hit.triangleIndex * 3];


                Vector2 uv4 = -meshLake.uv4[verticeId1];

                //Debug.Log(uv4);
                Vector3 verticeDirection = new Vector3(uv4.x, 0, uv4.y);// Vector3.forward * uv4.y + new Vector3(0, 0, 1) * uv4.x;

                currentForce = new Vector3(verticeDirection.x, 0, verticeDirection.z) * lakePolygon.floatSpeed;

                if (debug)
                    Debug.DrawRay(transform.position + Vector3.up, verticeDirection * 5, Color.red);

                if (debug)
                    Debug.DrawRay(lowestPoint, Vector3.up * buoyancy * (hitY) * 5, Color.blue);
                if (debug)
                    Debug.DrawRay(transform.position, velocity * -1 * viscosity * 5, Color.magenta);
                if (debug)
                    Debug.DrawRay(transform.position, velocity * 5, Color.grey);
                if (debug)
                    Debug.DrawRay(transform.position, rigidbody.angularVelocity * 5, Color.black);

            }

            if (debug)
                Debug.Log("The current reading is: " + currentForce);
        }


        Physics.queriesHitBackfaces = backFace;
    }

    void OnDrawGizmosSelected()
    {
        if (!debug)
            return;

        if (lowestPoint != null)
        {
            Gizmos.color = Color.blue;
            Gizmos.DrawSphere(lowestPoint, .08f);
        }

    }

}
The script is modified from one that comes with RAM 2019. It won't do anything, if you don't actually have RAM 2019 installed in your project.
This script can be used for other objects too, if you need to simply check the flow at a given game object's position and have access to the raw vector data. However, if you put the script on a UCC character it will automatically configure without further set up in the case for using the swimming add-on like I am describing.

Step 2)
Modify swim.cs to get the flow information from your RamFlowAffect component.

First, paste this code at the end of the member variable delcrations/initializations:
Code:
//This will hold a private reference to the flow affect script
private RamFlowAffect m_RamFlowAffect;

Next, paste this line within the Awake method:
m_RamFlowAffect = m_GameObject.GetComponent<RamFlowAffect>();

Then, paste this snippet to the end of the ApplyBouyancy method:
Code:
//Apply RamFlowAffect
            if (m_RamFlowAffect == null)
                Debug.LogWarning("No RAMFlowAffect Component Detected on "+ m_Neck.root.name + ".");
            else
                m_CharacterLocomotion.AbilityMotor += m_RamFlowAffect.currentForce * Time.timeScale * m_CharacterLocomotion.TimeScale * Time.deltaTime;

Save the script, and everything should be configured correctly for default settings as long as you have followed the steps.

Here's a video to give you some idea of what it should allow you to do:


And finally, a video tutorial to walk you through setting it up.


If you have questions or comments, let me know!
I hope this helps you in your game development.
 
Last edited:
thanks for the video but im still getting

Assets\Opsive\UltimateCharacterController\Add-Ons\Swimming\Scripts\Swim.cs(72,17): error CS0246: The type or namespace name 'RamFlowAffect' could not be found (are you missing a using directive or an assembly reference?)

the ramflowaffect is in asset folder
 
@leafN thank you for patiently waiting. I missed your post notification.

So, do you have a using directive at the top of your file? Otherwise, you are missing an assembly reference are you familiar with using them? It may need one in the folder with the ram flow effect that you include in your compilation dependencies. The assembly reference for the Opsive scripts folder can then include the assembly reference in your asset folder.

Let me know if this is making sense, or if I'm missing the mark with it.
 
Top