Trying to simulate a ships rocking motion in the ocean

Hey all…
I have a scene where I have some old style sail ships. In my 3D scene I’d like to have a ship off in the ocean sailing around.

I’d also like make the ship have lifelike motion. Bobbing up and down, ROLL side to side and PITCH front to back. I’m not as worried about YAW. I’ve included an image just to give a visual. Imagine the paper airplane is a ship. :slight_smile:

3027753--226361--pitchrollyaw.png

I was able to easily simulate the bobbing up and down. But, where I’m running into problems is the rotation around an axis. I’ve seen various scripts that rotate, but not finding something that’s close enough to what I’m trying to do.

I would only want the ship to roll/pitch maybe 10 degrees in any direction then rotate back the other direction.

I seen where someone used a mathf.pingpong function.

Vector3 v = new Vector3(0, 0, Mathf.PingPong(Time.time, 60) - 30);
transform.Rotate( v );
Debug.Log(v.x + ", " + v.y + ", " + v.z);

That didn’t work. It’s using floats and the calculations were done on the decimal portion of the number and my ship just spun and spun and spun on the Z axis, then slowly came to a stop and reversed direction.

I looked at the documentation on this function but there was not much there. The Math class itself didn’t seem to have a PingPong function, just Mathf.

Anyone have any suggestions? Guidance?

Thanks so much!

I would just make an AnimationCurve, draw the curve you want, set wrap mode to Loop, then sample it at current time.

Rotate applies the changes on top of the current rotation, so if your v ends up being (0,0,0.1), it’ll just continuously rotate around and around.

What you probably want is to take the starting rotation, store it, and base the rotation on that, instead of whatever the last frame ended up on.

Also a good habit to avoid Euler angles. They’re not going to pose a problem here, really, but they suck in general. Especially since you’re only using one axis, you may as well use .AngleAxis and get more control.

private Quaternion startRotation;
void Start() {
startRotation = transform.rotation;
}
void Update() {
float f = Mathf.PingPong(Time.time, 60) - 30;
transform.rotation = startRotation * Quaternion.AngleAxis(f, Vector3.forward);
}

Thanks I will look into this when I have some free time. I have an interest in ramping my animation skills.

Thanks StarManta, this seemed to accomplish what I was hoping for. The only thing I would want out of this is a way to soften the end of a rotation. Right now it’s a very ridged return to the other direction. But for now, it’s good enough.

I will probably look into LaneFox’s animation solution as well. My mind kinda thinks trying to have a smooth transition would be easier with animation, but… I admit I could be very wrong.

You could try using a sinewave instead of pingpong (Sinewave actually simulates rocking back and forth pretty accurately)

float f = Mathf.Sin(Time.time * rockingSpeedMultiplier) * 30f;

Hmm… Ok, I will give that a shot!

That worked awesome!!!
BUT… Now I face a new problem… Ok, a couple.
Here are the motions I’m trying to accomplish

  • Bobbing up and down
  • Rocking front to back
  • Rocking side to side.
    It’s what ships do.
    I split each motion out into three different functions in one script.
    – BobUpAndDown
    – RockFrontToBack
    – RockSideToSide
    And called them from the Update function. Individually, they all work as I had hoped. Furthermore, I can combine BobUpAndDown together with a rocking motion. It just always works, I guess because it’s not adjusting the rotation. So, no matter what I do in RockFrontToBack or RockSideToSide, it does not mess up the bobbing motion.
    So far so good… Where my problem is, is that I CANNOT combine the RockFrontToBack and RockSideToSide. And now that I think about it, it kinda makes since. Within the same frame I’m trying to adjust the rotation, but the second function will always win out.
    SO… I tried to put in a Boolean switch in my Update… Something like this which would toggle each frame between which direction to rock.
if( sideToSide )
    RockSideToSide( );
else
    RockFrontToBack( );
sideToSide = !sideToSide;

Well, that created a visual mess. Just didn’t work… So, I thought, well… Maybe if I split the functions out into individual script files and attach each script to a ship, MAYBE that will work. It didn’t. I got the same as before, the second script just overwrites the first script calculation…

For reference, here is the current script I’m using.

using System;
using UnityEngine;

public class RockShip : MonoBehaviour
{

    float originalY;
    public float bobbingMultiplier = .5f;
    public float rollMultiplier = .4f;
    private Quaternion startRotation;

    void Start()
    {
        originalY = this.transform.position.y;
        startRotation = transform.rotation;
    }

    void Update()
    {
        BobUpAndDown( );
        RollSideToSide( );
        RollFrontToBack( );
    }

    void BobUpAndDown( )
    {
        transform.position = new Vector3(transform.position.x,
                                         originalY + ((float)Math.Sin(Time.time) * bobbingMultiplier),
                                         transform.position.z);
    }


    void RollSideToSide( )
    {
        float f = Mathf.Sin( Time.time * rollMultiplier ) * 10f;
        transform.rotation = startRotation * Quaternion.AngleAxis( f, Vector3.forward );
    }

    void RollFrontToBack()
    {
        float f = Mathf.Sin(Time.time * rollMultiplier) * 2f;
        transform.rotation = startRotation * Quaternion.AngleAxis(f, Vector3.left);
    }
}

I had put both rotations into one single function with the hopes of trying to use a temporary “Quaternion” variable. The thought was that I could modify the temp quaternion’s x value with one rotation, and then modify the temp quaternion’s z with the other rotation, then apply the entire rotation to the game transform.
That didn’t work out so well either… Not positive on what I was doing wrong, but in theory it seems like it would work. I suspect I was not supposed to be modifying the x and y directly. But, none the less.
So, If anyone of you brilliant minded folks can throw me a bone, it will be greatly appreciated!! :slight_smile:

Having two of the three is pretty damn cool especially in VR.

OH YEAH… Then there was the other problem.

I have several ships all aligned up. How do I randomize the motion of the scripts? Right now they all start at the exact same rotation, same bobbing level. I don’t really want to put a time delay when they start, rather it would be nice to inject some kind of seed in the Mathf.Sin functions. Something to that affect anyway.

I had thought that I could start each ship with a different rotation. Problem is, Sin does not really take into consideration that the ship is already tilting say 10 degrees, it will just extend to the side that much further, and then 10 degrees, would be top dead center for the sign. It just didn’t work.

1 Like

You can combine two Quaternions with *
If you put your two roll functions into one (and make one of them use “f2” instead of “f”, you can do:

        transform.rotation = startRotation * Quaternion.AngleAxis( f, Vector3.forward ) * Quaternion.AngleAxis( f2, Vector3.left );

(If it looks odd, you can try switching the order)

For starters, transform.rotation.x/y/z are NOT the numbers you see in the editor, at all. A quaternion is made of four numbers (note there is also a “w”!) from 0 to 1, and they have almost no superficial relation to the three numbers (Euler angles) you edit in the inspector. Quaternions are like a branch of math unto themselves, and I don’t really understand them either - fortunately Unity’s Quaternion class means that you don’t really need to.

As for working with the Euler angles directly, here’s a longwinded explanation of why you shouldn’t.

You can do exactly that - just use Sin(time + somenumber), and set somenumber to an arbitrary number for each ship.

Thank you. This was what I nees.