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): PlayerShip (GameObject; where the script is attached) +-Frigate (??? non geometry container) ... (geometry) +-RudderControl (GameObject) +-Rudder (geometry) 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): var speed = 1.0; var acceleration = 1.0; var maxspeed = 2.0; var minspeed = -0.25; var heading = 0.0; var rudder = 0.0; var rudderDelta = 2.0; var maxRudder = 6.0; var bob = 0.1; var bobFrequency = 0.2; private var elapsed = 0.0; private var seaLevel = 0.0; private var rudderControl; private var rudderAngle = 0.0; function Update () { // Bobbing elapsed += Time.deltaTime; transform.position.y = seaLevel + bob * Mathf.Sin(elapsed * bobFrequency * (Mathf.PI * 2)); // Steering rudder += Input.GetAxis("Horizontal") * rudderDelta * Time.deltaTime; if( rudder > maxRudder ){ rudder = maxRudder; } else if ( rudder < -maxRudder ){ rudder = -maxRudder; } if( rudderControl ){ rudderAngle = rudder / maxRudder; rudderControl.transform.localEulerAngles.y = (70 * rudderAngle) % 360; } heading = (heading + rudder * Time.deltaTime * Mathf.Sqrt(speed)) % 360; // transform.Rotate(0, rudder * Time.deltaTime, 0); transform.eulerAngles.y = heading; transform.eulerAngles.z = -rudder; // Sail speed += Input.GetAxis("Vertical") * acceleration * Time.deltaTime; if( speed > maxspeed ){ speed = maxspeed; } else if ( speed < minspeed ){ speed = minspeed; } transform.Translate(0,0,speed * Time.deltaTime); } function Awake () { seaLevel = transform.position.y; rudderControl = GameObject.Find("rudderControl"); }
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!
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?
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!)
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.
To find a game object in the children you can use transform.Find instead of GameObject.Find. http://unity3d.com/Documentation/ScriptReference/Transform.Find.html have you tried assigning euler angles like this instead: Code (csharp): transform.eulerAngles = Vector3(0, heading, -rudder);
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.
Code (csharp): heading = (heading + rudder * Time.deltaTime * Mathf.Sqrt(speed)) % 360; 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): speed = Mathf.Max(speed, 0.0001);
Nice catch... but it still doesn't explain the problems I'm having (which occurred before I added the Sqrt to the turnrate calculation).
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): var speed = 1.0; var acceleration = 1.0; var maxspeed = 2.0; var minspeed = -0.25; var heading = 0.0; var rudder = 0.0; var rudderDelta = 2.0; var maxRudder = 6.0; var bob = 0.1; var bobFrequency = 0.2; private var elapsed = 0.0; private var seaLevel = 0.0; private var rudderControl; private var rudderAngle = 0.0; function signedSqrt( x ){ var r = Mathf.Sqrt(Mathf.Abs( x )); if( x < 0 ){ return -r; } else { return r; } } function Update () { // Bobbing elapsed += Time.deltaTime; transform.position.y = seaLevel + bob * Mathf.Sin(elapsed * bobFrequency * (Mathf.PI * 2)); // Steering rudder += Input.GetAxis("Horizontal") * rudderDelta * Time.deltaTime; if( rudder > maxRudder ){ rudder = maxRudder; } else if ( rudder < -maxRudder ){ rudder = -maxRudder; } heading = (heading + rudder * Time.deltaTime * signedSqrt(speed)) % 360; // transform.Rotate(0, rudder * Time.deltaTime, 0); transform.eulerAngles.y = heading; transform.eulerAngles.z = -rudder; if( rudderControl ){ rudderAngle = ((-60 * rudder)/maxRudder + heading) % 360; //rudderControl.transform.localEulerAngles.y = (70 * rudderAngle) % 360; rudderControl.transform.eulerAngles = Vector3(0, rudderAngle, 0); } // Sail speed += Input.GetAxis("Vertical") * acceleration * Time.deltaTime; if( speed > maxspeed ){ speed = maxspeed; } else if ( speed < minspeed ){ speed = minspeed; } transform.Translate(0, 0, speed * Time.deltaTime); } function Awake (){ seaLevel = transform.position.y; rudderControl = GameObject.Find("rudderControl"); }
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?
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
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
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
I converted this to C# for anyone interested Code (CSharp): using UnityEngine; using System.Collections; public class SailingController : MonoBehaviour { float speed= 1.0f; float acceleration= 1.0f; float maxspeed= 2.0f; float minspeed= -0.25f; float heading= 0.0f; float rudder= 0.0f; float rudderDelta= 2.0f; float maxRudder= 180.0f; float bob= 0.1f; float bobFrequency= 0.2f; private float elapsed= 0.0f; private float seaLevel= 0.0f; private GameObject rudderControl; private float rudderAngle= 0.0f; float signedSqrt ( float x ){ float r= Mathf.Sqrt(Mathf.Abs( x )); if( x < 0 ){ return -r; } else { return r; } } void Update (){ Debug.Log("Sailing script activated"); // Bobbing elapsed += Time.deltaTime; float tempY = seaLevel + bob * Mathf.Sin(elapsed * bobFrequency * (Mathf.PI * 2)); transform.position = new Vector3(transform.position.x, tempY, transform.position.z); // Steering rudder += Input.GetAxis("Horizontal") * rudderDelta * Time.deltaTime; if( rudder > maxRudder ){ rudder = -maxRudder; } else if ( rudder < -maxRudder ){ rudder = maxRudder; } heading = (heading + rudder * Time.deltaTime * signedSqrt(speed)) % 360; // transform.Rotate(0, rudder * Time.deltaTime, 0); //transform.eulerAngles.y = heading; transform.eulerAngles = new Vector3(transform.eulerAngles.x, heading, transform.eulerAngles.z); //transform.eulerAngles.z = -rudder; transform.eulerAngles = new Vector3(transform.eulerAngles.x, -rudder, transform.eulerAngles.z); if( rudderControl != null){ rudderAngle = ((-60 * rudder)/maxRudder + heading) % 360; //rudderControl.transform.localEulerAngles.y = (70 * rudderAngle) % 360; rudderControl.transform.eulerAngles = new Vector3(0, rudderAngle, 0); } // Sail speed += Input.GetAxis("Vertical") * acceleration * Time.deltaTime; if( speed > maxspeed ){ speed = maxspeed; } else if ( speed < minspeed ){ speed = minspeed; } transform.Translate(0, 0, speed * Time.deltaTime); } void Awake (){ seaLevel = transform.position.y; rudderControl = GameObject.Find("rudderControl"); } }
A small error in: Code (CSharp): transform.eulerAngles = new Vector3(transform.eulerAngles.x, -rudder, transform.eulerAngles.z); Should be: Code (CSharp): transform.eulerAngles = new Vector3(transform.eulerAngles.x, transform.eulerAngles.y, -rudder);
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): using System.Collections; using System.Collections.Generic; using UnityEngine; /* * If you want to slow down or speed up the oscillation, just multiply Time.time by a constant. * Multiplying Time.time by a constant greater than 1 will speed up the oscillation. * Multiplying Time.time by a constant less than 1 will slow down the oscillation. */ public class boatRock : MonoBehaviour { public float floor = -2.0f; public float ceiling = 2.0f; // Use this for initialization void Start () { } // Update is called once per frame void Update () { float tilt = floor + Mathf.PingPong (Time.time, ceiling - floor); transform.rotation = Quaternion.AngleAxis(tilt*Time.deltaTime, Vector3.left) * transform.rotation; } } using System.Collections; using System.Collections.Generic; using UnityEngine; /* * If you want to slow down or speed up the oscillation, just multiply Time.time by a constant. * Multiplying Time.time by a constant greater than 1 will speed up the oscillation. * Multiplying Time.time by a constant less than 1 will slow down the oscillation. */ public class boatSway : MonoBehaviour { public float floor = -1.0f; public float ceiling = 1.0f; // Use this for initialization void Start () { } // Update is called once per frame void Update () { float tilt = floor + Mathf.PingPong (Time.time, ceiling - floor); transform.rotation = Quaternion.AngleAxis(tilt*Time.deltaTime, Vector3.up) * transform.rotation; } } using System.Collections; using System.Collections.Generic; using UnityEngine; public class boatSail : MonoBehaviour { public float bSpeed = 10.0f; // Use this for initialization void Start () { } // Update is called once per frame void Update () { transform.position += transform.forward * bSpeed * Time.deltaTime; } }