- Home /
Gravity and Grounding
I've been working on the TANKS! Tutorial that Unity came out with and I'm trying to modify it so that the tanks can move on uneven terrain. By default they had rotation in the x- and z-directions turned off so that the tank wouldn't pitch or roll. I turned this off so that tank could move uphill or along the side of a hill without remaining level and if the tank tries to climb a surface that is too steep it can flip. After doing this I found that if the tank is at an angle it will continue to move into the air, and if it is vertical it can climb straight up. To fix this I tried to make it so that if the tank isn't grounded the controls will be turned off, and the only thing affecting the tank will be gravity. The tanks use a Rigidbody, so I can't use isGrounded. I tried using Raycasting but it either makes the tank not move at all, or it does nothing and lets the tank move while it's still in the air.
I'd like some help on how to write the script so that if the tank becomes airborne it will keep its velocity in the x- and z-direction (without the player being able to control it), and have it accelerate due to gravity in the y-direction until it is grounded and the player can control the tank again.
My script so far is: using UnityEngine;
namespace Complete
{
public class TankMovement : MonoBehaviour
{
public int m_PlayerNumber = 1; // Used to identify which tank belongs to which player. This is set by this tank's manager.
public float m_Speed = 12f; // How fast the tank moves forward and back.
public float m_TurnSpeed = 180f; // How fast the tank turns in degrees per second.
public AudioSource m_MovementAudio; // Reference to the audio source used to play engine sounds. NB: different to the shooting audio source.
public AudioClip m_EngineIdling; // Audio to play when the tank isn't moving.
public AudioClip m_EngineDriving; // Audio to play when the tank is moving.
public float m_PitchRange = 0.2f; // The amount by which the pitch of the engine noises can vary.
public float m_MaxRay = 0.1f; // Distance to ground where tank will be "Grounded"
public bool m_isGrounded = true; // True/False, is tank on the ground?
private string m_MovementAxisName; // The name of the input axis for moving forward and back.
private string m_TurnAxisName; // The name of the input axis for turning.
private Rigidbody m_Rigidbody; // Reference used to move the tank.
private float m_MovementInputValue; // The current value of the movement input.
private float m_TurnInputValue; // The current value of the turn input.
private float m_OriginalPitch; // The pitch of the audio source at the start of the scene.
private void Awake ()
{
m_Rigidbody = GetComponent<Rigidbody> ();
}
private void OnEnable ()
{
// When the tank is turned on, make sure it's not kinematic.
m_Rigidbody.isKinematic = false;
// Also reset the input values.
m_MovementInputValue = 0f;
m_TurnInputValue = 0f;
}
private void OnDisable ()
{
// When the tank is turned off, set it to kinematic so it stops moving.
m_Rigidbody.isKinematic = true;
}
private void Start ()
{
// The axes names are based on player number.
m_MovementAxisName = "Vertical" + m_PlayerNumber;
m_TurnAxisName = "Horizontal" + m_PlayerNumber;
// Store the original pitch of the audio source.
m_OriginalPitch = m_MovementAudio.pitch;
}
private void Update ()
{
// Store the value of both input axes.
m_MovementInputValue = Input.GetAxis (m_MovementAxisName);
m_TurnInputValue = Input.GetAxis (m_TurnAxisName);
EngineAudio ();
}
private void EngineAudio ()
{
// If there is no input (the tank is stationary)...
if (Mathf.Abs (m_MovementInputValue) < 0.1f && Mathf.Abs (m_TurnInputValue) < 0.1f)
{
// ... and if the audio source is currently playing the driving clip...
if (m_MovementAudio.clip == m_EngineDriving)
{
// ... change the clip to idling and play it.
m_MovementAudio.clip = m_EngineIdling;
m_MovementAudio.pitch = Random.Range (m_OriginalPitch - m_PitchRange, m_OriginalPitch + m_PitchRange);
m_MovementAudio.Play ();
}
}
else
{
// Otherwise if the tank is moving and if the idling clip is currently playing...
if (m_MovementAudio.clip == m_EngineIdling)
{
// ... change the clip to driving and play.
m_MovementAudio.clip = m_EngineDriving;
m_MovementAudio.pitch = Random.Range(m_OriginalPitch - m_PitchRange, m_OriginalPitch + m_PitchRange);
m_MovementAudio.Play();
}
}
}
private void FixedUpdate ()
{
// Send ray from bottom of tank.
Vector3 m_Ray = transform.TransformDirection (Vector3.down);
float m_RayLength = m_Ray.magnitude;
if (m_RayLength < m_MaxRay)
{
m_isGrounded = false;
}
else if (m_RayLength > m_MaxRay)
{
m_isGrounded = true;
}
// Adjust the rigidbodies position and orientation in FixedUpdate.
Move ();
Turn ();
}
private void Move ()
{
if (m_isGrounded == true)
{
// Create a vector in the direction the tank is facing with a magnitude based on the input, speed and the time between frames.
Vector3 movement = transform.forward * m_MovementInputValue * m_Speed * Time.deltaTime;
// Apply this movement to the rigidbody's position.
m_Rigidbody.MovePosition (m_Rigidbody.position + movement);
}
else if (m_isGrounded == false)
{
Vector3 movement = transform.forward * 0;
m_Rigidbody.MovePosition (m_Rigidbody.position + movement);
}
}
private void Turn ()
{
if (m_isGrounded == true)
{
// Determine the number of degrees to be turned based on the input, speed and time between frames.
float turn = m_TurnInputValue * m_TurnSpeed * Time.deltaTime;
// Make this into a rotation in the y axis.
Quaternion turnRotation = Quaternion.Euler (0f, turn, 0f);
// Apply this rotation to the rigidbody's rotation.
m_Rigidbody.MoveRotation (m_Rigidbody.rotation * turnRotation);
}
else if (m_isGrounded == false)
{
Quaternion turnRotation = Quaternion.Euler (0f, 0f, 0f);
m_Rigidbody.MoveRotation (m_Rigidbody.rotation * turnRotation);
}
}
}
}
FixedUpdate (), Move (), and Turn () are the parts I'm working in.
Answer by felixfors · Jan 03, 2016 at 06:36 AM
you could make your own !isgrounded boolean by doing something like this let me know if this works
if(m_Rigidbody.velocity.y <0 || m_Ridigbody.velocity >0)
{
inAir = true;
}
else // if we dont have any velocity on the y axis
{
inAir = false;
}
Answer by OncaLupe · Jan 03, 2016 at 07:58 AM
You're not actually doing a raycast in your pasted code.
On line 100, you create a Vector3 pointing along the local -y axis, then get it's magnitude on the next line. The magnitude will always be 1 (give or take floating point variances). What you need to do is use Physics.Raycast() to actually perform the test.
http://docs.unity3d.com/ScriptReference/Physics.Raycast.html
private void FixedUpdate ()
{
//Create a ray starting from the center of the tank pointing down along it's local Y axis
Ray m_Ray = new Ray(transform.position, -transform.up);//-transform.up is cleaner than doing TransformDirection()
//Perform raycast with max distance m_MaxRay
if(Physics.Raycast(m_Ray, m_MaxRay))
{
//If raycast returns true, it hit something.
//Can optionally check what we hit by changing the Raycast call to
//include the 'out hit' parameter. See scripting manual.
m_isGrounded = true;
}else{
m_isGrounded = false;
}
// Adjust the rigidbodies position and orientation in FixedUpdate.
Move ();
Turn ();
}
Also, in the Move() method when m_isGrounded is false, you're locking the tank into it's current position by using MovePosition, since you're telling the physics engine to move the rigidbody to it's current location. If the project is using gravity, you should instead not be doing anything if not grounded and let the physics engine do it's work.
Your answer
Follow this Question
Related Questions
Object shattering under a certain weight/force 3 Answers
How to add gravity without a rigidbody 2 Answers
Multiple Cars not working 1 Answer