Search Unity

Sailing Ship Control

Discussion in 'Scripting' started by podperson, Jul 18, 2006.

  1. podperson

    podperson

    Joined:
    Jun 6, 2006
    Posts:
    1,371
    Below is a control script in progress for a sailing ship.

    Note that I'm probably not using the Input object properly (I'm handling acceleration and decelleration myself, which I could do more easily by just tweaking the input elements, but ... that's not my problem).

    I have a GameObject (PlayerShip) which contains an object hierarcy (frigate) made of lots of pieces. Inside the hierarchy I've inserted another GameObject (rudderControl) and placed the rudder object inside it. I've done this very carefully so that the y-axis of RudderControl passes through the pivot of the rudder. However, setting the localEulerAngles.y of RudderControl causes the rudder to rotate around the wrong axis and then get stuck. This makes no sense. It even happens when I set the rotation to a fixed value (e.g. 45).

    What's more, when I look back at the assembly the RudderControl's axis has moved the "center" of the rudder (which is not where I put it) rendering it useless.

    I'm posting this here, since I am probably doing something wrong, but this looks like bugs or incorrect documentation to me. The Eulers of the PlayerShip work just as expected (but then they're at top level, making me suspect GameObjects might not nest properly).

    Note that the hierarchy is:

    Code (csharp):
    1. PlayerShip (GameObject; where the script is attached)
    2. +-Frigate (??? non geometry container)
    3.     ... (geometry)
    4.     +-RudderControl (GameObject)
    5.         +-Rudder (geometry)
    6.  
    Followup question:

    Is there a way of restricting GameObject.Find to the descendents of a given node? It would be nice if I can write one "ship" behavior that finds the pieces of the ship it belongs to automagically and does what needs to be done with them (I'd like sails to appear, disappear, rotate to reflect wind direction, etc.) but I'd prefer not to have to do it all by hand.

    Code (csharp):
    1. var speed = 1.0;
    2. var acceleration = 1.0;
    3. var maxspeed = 2.0;
    4. var minspeed = -0.25;
    5. var heading = 0.0;
    6. var rudder = 0.0;
    7. var rudderDelta = 2.0;
    8. var maxRudder = 6.0;
    9. var bob = 0.1;
    10. var bobFrequency = 0.2;
    11.  
    12. private var elapsed = 0.0;
    13. private var seaLevel = 0.0;
    14. private var rudderControl;
    15. private var rudderAngle = 0.0;
    16.  
    17. function Update () {
    18.     // Bobbing
    19.     elapsed += Time.deltaTime;
    20.     transform.position.y = seaLevel + bob * Mathf.Sin(elapsed * bobFrequency * (Mathf.PI * 2));
    21.    
    22.     // Steering
    23.     rudder += Input.GetAxis("Horizontal") * rudderDelta * Time.deltaTime;
    24.     if( rudder > maxRudder ){
    25.         rudder = maxRudder;
    26.     } else if ( rudder < -maxRudder ){
    27.         rudder = -maxRudder;
    28.     }
    29.     if( rudderControl ){
    30.         rudderAngle = rudder / maxRudder;
    31.         rudderControl.transform.localEulerAngles.y = (70 * rudderAngle) % 360;
    32.     }
    33.     heading = (heading + rudder * Time.deltaTime * Mathf.Sqrt(speed)) % 360;
    34.     // transform.Rotate(0, rudder * Time.deltaTime, 0);
    35.     transform.eulerAngles.y = heading;
    36.     transform.eulerAngles.z = -rudder;
    37.    
    38.     // Sail
    39.     speed += Input.GetAxis("Vertical") * acceleration * Time.deltaTime;
    40.     if( speed > maxspeed ){
    41.         speed = maxspeed;
    42.     } else if ( speed < minspeed ){
    43.         speed = minspeed;
    44.     }
    45.    
    46.     transform.Translate(0,0,speed * Time.deltaTime);
    47. }
    48.  
    49. function Awake ()
    50. {
    51.     seaLevel = transform.position.y;
    52.     rudderControl = GameObject.Find("rudderControl");
    53. }
     
  2. Samantha

    Samantha

    Joined:
    Aug 31, 2005
    Posts:
    609
    I see you're scripting the physics of the ship's bobbing behavior. You may want to consider using a Spring Joint to handle the bobbing instead. Similarly, you can probably use a Hinge Joint to control the rudder.

    I makde this recommendation because I was in a similar situation with a space ship. While working on a top-down shooter, I was scripting my ship's rotation based on input. I wrote a couple hundred lines, trying various ways to handle the ship's state, and how much rotation to apply. When I learned about the Hinge Joint, I scrapped the entire script, attached a Hinge Joint to my ship, and wrote about 10 new lines. Then my ship rotation worked perfectly.

    It's worth exploring, since it could make your life much easier :)

    Spring Joint: http://unity3d.com/Documentation/Components/class-SpringJoint.html
    Hinge Joint: http://unity3d.com/Documentation/Components/class-HingeJoint.html

    I helped write these pages, so if you have questions, post here or send me a private message!
     
    Lethn likes this.
  3. bigkahuna

    bigkahuna

    Joined:
    Apr 30, 2006
    Posts:
    5,434
    I'm not able to contribute much to this thread for now (no Mac's here until my iMac arrives next week), but I'll be watching your progress. I hope to port my ship (non-sailing) simulator to Unity over the next several weeks/months.

    If you need advice or opinions on ship dynamics, I may be able to help (I'm probably one of the crustiest, old sea dogs here, been sailing navy, merchant and sailing vessels since my shipmate Christopher Columbus retired :wink: ).

    One comment for now: have you considered how you'll simulate other ship motions besides "bobbing" (more properly called "heaving"), ie.: pitch and roll?
     
  4. bigkahuna

    bigkahuna

    Joined:
    Apr 30, 2006
    Posts:
    5,434
    Check this out:

    http://www.cambrianlabs.com/Mattias/DelphiODE/BuoyancyParticles.asp

    The demo is Windows only and done with ODE, but it's a very good example of what can be done with some fairly simple math. If you're able to get to a Windows machine, try loading the "ship2" sample. It illustrates the methods I've seen used for real ship simulators.

    Can anyone tell me if this would be possible to re-create in Unity? (I haven't even gotten my mac yet and I already have a long "wishlist" for future features!)
     
  5. podperson

    podperson

    Joined:
    Jun 6, 2006
    Posts:
    1,371
    All of this is very interesting but doesn't solve or address my fundamental problem which is that:

    a) The position of the GameObject is corrupted and
    b) Setting its EulerAngles behaves completely strangely.

    The bobbing works just fine. I don't see how a spring joint would do me any good since it's just going to resolve to a sine wave anyway (that's what springs do). It's not like it will be more "realistic". Eventually I might use Physics to handle interactions of bouyancy, wind, etc. but that's hardly the issue at hand.

    Also: the movement of the rudder is controlled by the player, not by physical interactions. Making it into a hinge joint won't give me anything.
     
  6. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
  7. podperson

    podperson

    Joined:
    Jun 6, 2006
    Posts:
    1,371
    I haven't tried that and I will. But is there something wrong with what I'm doing? I don't see why the GameObject's position gets "lost", or why I can't transform it (locally) to get the obvious (or at least a non-perverse) result. Setting an object's EulerAngles to a fixed value shouldn't make it spin and get stuck... should it?

    Thanks for the transform.find tip!

    P.S. It occurs to me that I may be misunderstanding the way GameObjects work. I'll have to do more reading.
     
  8. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    Code (csharp):
    1.  
    2.    heading = (heading + rudder * Time.deltaTime * Mathf.Sqrt(speed)) % 360;
    3.  

    This line is probably not a very good idea. Sqrt gives you an undefined number (nan) if it is 0 or less.
    So you want to clamp the value to never be less or equal to zero.

    Code (csharp):
    1.  
    2. speed = Mathf.Max(speed, 0.0001);
    3.  
     
  9. podperson

    podperson

    Joined:
    Jun 6, 2006
    Posts:
    1,371
    Nice catch... but it still doesn't explain the problems I'm having (which occurred before I added the Sqrt to the turnrate calculation).
     
  10. podperson

    podperson

    Joined:
    Jun 6, 2006
    Posts:
    1,371
    Thanks for your help. The rudder is working now. I'm still a bit puzzled why the localEulerAngles failed so spectacularly.

    transform.Find is stated to find children by name -- but it's not finding anything for me... Not sure what's going on there. So I'm still effectively hard-wiring the link to the rudderControl object.

    FYI -- here's the current controller code. Feel free to use it as you see fit.

    Code (csharp):
    1. var speed = 1.0;
    2. var acceleration = 1.0;
    3. var maxspeed = 2.0;
    4. var minspeed = -0.25;
    5. var heading = 0.0;
    6. var rudder = 0.0;
    7. var rudderDelta = 2.0;
    8. var maxRudder = 6.0;
    9. var bob = 0.1;
    10. var bobFrequency = 0.2;
    11.  
    12. private var elapsed = 0.0;
    13. private var seaLevel = 0.0;
    14. private var rudderControl;
    15. private var rudderAngle = 0.0;
    16.  
    17. function signedSqrt( x ){
    18.     var r = Mathf.Sqrt(Mathf.Abs( x ));
    19.     if( x < 0 ){
    20.         return -r;
    21.     } else {
    22.         return r;
    23.     }
    24. }
    25.  
    26. function Update () {
    27.     // Bobbing
    28.     elapsed += Time.deltaTime;
    29.     transform.position.y = seaLevel + bob * Mathf.Sin(elapsed * bobFrequency * (Mathf.PI * 2));
    30.    
    31.     // Steering
    32.     rudder += Input.GetAxis("Horizontal") * rudderDelta * Time.deltaTime;
    33.     if( rudder > maxRudder ){
    34.         rudder = maxRudder;
    35.     } else if ( rudder < -maxRudder ){
    36.         rudder = -maxRudder;
    37.     }
    38.     heading = (heading + rudder * Time.deltaTime * signedSqrt(speed)) % 360;
    39.     // transform.Rotate(0, rudder * Time.deltaTime, 0);
    40.     transform.eulerAngles.y = heading;
    41.     transform.eulerAngles.z = -rudder;
    42.    
    43.     if( rudderControl ){
    44.         rudderAngle = ((-60 * rudder)/maxRudder + heading) % 360;
    45.         //rudderControl.transform.localEulerAngles.y = (70 * rudderAngle) % 360;
    46.         rudderControl.transform.eulerAngles = Vector3(0, rudderAngle, 0);
    47.     }
    48.    
    49.     // Sail
    50.     speed += Input.GetAxis("Vertical") * acceleration * Time.deltaTime;
    51.     if( speed > maxspeed ){
    52.         speed = maxspeed;
    53.     } else if ( speed < minspeed ){
    54.         speed = minspeed;
    55.     }
    56.    
    57.     transform.Translate(0, 0, speed * Time.deltaTime);
    58. }
    59.  
    60. function Awake (){
    61.     seaLevel = transform.position.y;
    62.     rudderControl = GameObject.Find("rudderControl");
    63. }
     
    Frogger007 likes this.
  11. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    Not sure about the transform.Find, never let me down so far.

    Are you sure the name is correct (case sensitive) and the object is a direct child of the transform you are calling it on?
     
  12. podperson

    podperson

    Joined:
    Jun 6, 2006
    Posts:
    1,371
    Yes on both counts.

    When I change to GameObject.find it works. transform.find returns nothing. And the script is attached to the grandparent of the GameObject in question.

    PlayerShip* -> Frigate** -> rudderControl* -> Rudder**

    * GameObject
    ** Mesh
    -> is parent of
     
  13. vasanth

    vasanth

    Joined:
    Dec 18, 2012
    Posts:
    4
    Hi samantha ,
    I would like to work out HingeJoint for my boat object.
    i am doing boat race project and i tying to figure out the simple physics for my boat object. is this suitable to boat , if yes please let me know how to move the boat using hingejoint
     
  14. JohannesNienaber

    JohannesNienaber

    Joined:
    Dec 3, 2012
    Posts:
    7
    Wow! What an awesome script thanks alot dude!
     
  15. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    new record for necro bumping
     
  16. CGFX360

    CGFX360

    Joined:
    Sep 2, 2013
    Posts:
    10
    I know this is old, but i have been trying to use this in my game, and have been having issues with these 2 things:

    1. I want the ship to stop turning after the player stops pressing the horizontal input, so it will just reset to going straight when the player stops pressing A or D instead of just continuing to turn.

    (I could probably figure this out myself, but i thought i'd ask)
    2. When moving forward, it slowly increases to max speed, without the need to continue holding down the W key. And pressing S while moving would stop the ship/back it up.

    I am aware this is not what the script was intended for, and i'm trying to use it in a pirate ship type game. I cant find any other script half as nice, but i just need it to do these things and cannot figure it out. (Im still learning the coding side of unity)

    Thanks for any help :)
     
  17. NullSignal

    NullSignal

    Joined:
    Sep 18, 2013
    Posts:
    45
    I converted this to C# for anyone interested

    Code (CSharp):
    1.  using UnityEngine;
    2. using System.Collections;
    3. public class SailingController : MonoBehaviour {
    4. float speed= 1.0f;
    5. float acceleration= 1.0f;
    6. float maxspeed= 2.0f;
    7. float minspeed= -0.25f;
    8. float heading= 0.0f;
    9. float rudder= 0.0f;
    10. float rudderDelta= 2.0f;
    11. float maxRudder= 180.0f;
    12. float bob= 0.1f;
    13. float bobFrequency= 0.2f;
    14.  
    15. private float elapsed= 0.0f;
    16. private float seaLevel= 0.0f;
    17. private GameObject rudderControl;
    18. private float rudderAngle= 0.0f;
    19.  
    20. float  signedSqrt ( float x ){
    21.      float r= Mathf.Sqrt(Mathf.Abs( x ));
    22.      if( x < 0 ){
    23.          return -r;
    24.      } else {
    25.          return r;
    26.      }
    27. }
    28.  
    29. void  Update (){
    30.      Debug.Log("Sailing script activated");
    31.      // Bobbing
    32.      elapsed += Time.deltaTime;
    33.      float tempY = seaLevel + bob * Mathf.Sin(elapsed * bobFrequency * (Mathf.PI * 2));
    34.      transform.position = new Vector3(transform.position.x, tempY, transform.position.z);
    35.    
    36.      // Steering
    37.      rudder += Input.GetAxis("Horizontal") * rudderDelta * Time.deltaTime;
    38.      if( rudder > maxRudder ){
    39.          rudder = -maxRudder;
    40.      } else if ( rudder < -maxRudder ){
    41.          rudder = maxRudder;
    42.      }
    43.      heading = (heading + rudder * Time.deltaTime * signedSqrt(speed)) % 360;
    44.      // transform.Rotate(0, rudder * Time.deltaTime, 0);
    45.      //transform.eulerAngles.y = heading;
    46.      transform.eulerAngles = new Vector3(transform.eulerAngles.x, heading, transform.eulerAngles.z);
    47.      //transform.eulerAngles.z = -rudder;
    48.      transform.eulerAngles = new Vector3(transform.eulerAngles.x, -rudder, transform.eulerAngles.z);
    49.    
    50.      if( rudderControl != null){
    51.          rudderAngle = ((-60 * rudder)/maxRudder + heading) % 360;
    52.          //rudderControl.transform.localEulerAngles.y = (70 * rudderAngle) % 360;
    53.          rudderControl.transform.eulerAngles = new Vector3(0, rudderAngle, 0);
    54.      }
    55.    
    56.      // Sail
    57.      speed += Input.GetAxis("Vertical") * acceleration * Time.deltaTime;
    58.      if( speed > maxspeed ){
    59.          speed = maxspeed;
    60.      } else if ( speed < minspeed ){
    61.          speed = minspeed;
    62.      }
    63.    
    64.      transform.Translate(0, 0, speed * Time.deltaTime);
    65. }
    66.  
    67. void  Awake (){
    68.      seaLevel = transform.position.y;
    69.      rudderControl = GameObject.Find("rudderControl");
    70. }
    71. }
     
    Frogger007 and Lohrion like this.
  18. John-G

    John-G

    Joined:
    Mar 21, 2013
    Posts:
    1,122
    A small error in:
    Code (CSharp):
    1. transform.eulerAngles = new Vector3(transform.eulerAngles.x, -rudder, transform.eulerAngles.z);
    Should be:
    Code (CSharp):
    1. transform.eulerAngles = new Vector3(transform.eulerAngles.x, transform.eulerAngles.y,  -rudder);
     
    Frogger007 and Lohrion like this.
  19. Frogger007

    Frogger007

    Joined:
    Jun 24, 2014
    Posts:
    338
    How can this script expanded to influence the ship speed by a windzone ?
     
  20. Honorsoft

    Honorsoft

    Joined:
    Oct 31, 2016
    Posts:
    81
    I am experimenting with 'boats' and 'water meshes' in Unity, and found a lot of complex methods to do it. The script posted by carnivoris is pretty good, handles a lot of variables so it obviously gives a realistic boat-physics style. It is way simpler than any others I've seen, which use a lot of rigidbodies, colliders, mesh-modifiers, spring-joints, etc. I came up with a really simple 'sail-boat' method, which gave me great results that looked as good as the complicated physics-based water simulation scripts I've seen, with only a few lines of code.
    There are 3 scripts, one for 'rocking' back and forward, one for 'swaying/twisting' left and right, and one script for ship movement (I guess you can put it all into 1 script, but 3 scripts allow you to work on each aspect separately).
    You could easily add to this because of it's simplicity. For example, a 'windzone' (asked by Frogger007) could be added by having a 'windzone' collider cube (trigger), and when a collision is detected with the boat(e.g. the boat is inside the windzone), it could just add a constant 'push' while the boat is still in the windzone. Anyways, if anyone has any questions I'll check back later, but here's the 3 scripts, just put them on the ship object and attach the camera to the ship too. Make large water-mesh (or just a plane with a water-texture to start with), and that's it. Here's an example project set up to demonstrate (FREE):
    http://www.mediafire.com/file/bd2pohbb794d461/BoatSceneExample1.unitypackage

    //boatRock.cs, boatSway.cs, boatSail.cs

    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. /*
    7. * If you want to slow down or speed up the oscillation, just multiply Time.time by a constant.
    8. * Multiplying Time.time by a constant greater than 1 will speed up the oscillation.
    9. * Multiplying Time.time by a constant less than 1 will slow down the oscillation.
    10. */
    11.  
    12. public class boatRock : MonoBehaviour {
    13. public float floor = -2.0f;
    14. public float ceiling = 2.0f;
    15.  
    16.     // Use this for initialization
    17.     void Start () {
    18.      
    19.     }
    20.  
    21.     // Update is called once per frame
    22.     void Update () {
    23.      
    24.     float tilt = floor + Mathf.PingPong (Time.time, ceiling - floor);
    25.  
    26.     transform.rotation = Quaternion.AngleAxis(tilt*Time.deltaTime, Vector3.left) * transform.rotation;
    27.     }
    28.  
    29. }
    30.  
    31. using System.Collections;
    32. using System.Collections.Generic;
    33. using UnityEngine;
    34.  
    35. /*
    36. * If you want to slow down or speed up the oscillation, just multiply Time.time by a constant.
    37. * Multiplying Time.time by a constant greater than 1 will speed up the oscillation.
    38. * Multiplying Time.time by a constant less than 1 will slow down the oscillation.
    39. */
    40.  
    41. public class boatSway : MonoBehaviour {
    42. public float floor = -1.0f;
    43. public float ceiling = 1.0f;
    44.     // Use this for initialization
    45.     void Start () {
    46.      
    47.     }
    48.  
    49.     // Update is called once per frame
    50.     void Update () {
    51.      
    52.     float tilt = floor + Mathf.PingPong (Time.time, ceiling - floor);
    53.  
    54.     transform.rotation = Quaternion.AngleAxis(tilt*Time.deltaTime, Vector3.up) * transform.rotation;
    55.     }
    56. }
    57.  
    58. using System.Collections;
    59. using System.Collections.Generic;
    60. using UnityEngine;
    61.  
    62. public class boatSail : MonoBehaviour {
    63.  
    64.     public float bSpeed = 10.0f;
    65.  
    66.     // Use this for initialization
    67.     void Start () {
    68.      
    69.     }
    70.  
    71.     // Update is called once per frame
    72.     void Update () {
    73.      
    74.         transform.position += transform.forward * bSpeed * Time.deltaTime;
    75.     }
    76. }
     
    Last edited: Apr 2, 2018