Unity - creating a hovercar by counteracting gravity - c#

I wish to implement a way to create a hoverbike.
my current code to hover is
readonly float yForce = 80;
Physics.Raycast(hoverbike.transform.position, Vector3.down, out hit);
Debug.Log(hit.distance);
if (hit.distance < 10 && hit.distance > 0)
{
if (hoverbike.velocity.y < 0.1)
{
hoverbike.AddForce(0, yForce, 0, ForceMode.Acceleration);
Debug.Log("applying force!");
}
}
This works, but not well, the vehicle bounces up and down. I also tried to subtract the exact same force as the bike's y velocity, but the vehicle slowly drifted down, and did not go up to my desired height of 10 units from the ground. How can I achieve this?
Simply counteracting its current velocity is easy, but how do I make it float back up to the desired height?

It is much easier to simply turn off gravity than to constantly be fighting against it; this frequent readjustment is likely the cause of your bounciness. Upon bike activation you can take the object's y velocity calculations fully into your own hands as so:
public class Bike : MonoBehaviour
{
private Rigidbody hoverbike;
private bool isBikeActive = false;
[SerializeField] private float verticalSpeedMultiplier = 1f;
[SerializeField] private float hoverHeight = 10f;
[SerializeField] private float hoverTolerance = 0.5f;
[SerializeField] private float maximumVerticalVelocity = 10f;
private void Awake()
{
hoverbike = GetComponent<Rigidbody>();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space)) { ToggleBike(); }
if (isBikeActive)
{
Physics.Raycast(hoverbike.transform.position, Vector3.down, out RaycastHit hit);
Vector3 modifiedVelocity = hoverbike.velocity;
if ((hit.distance > hoverHeight - hoverTolerance) && (hit.distance < hoverHeight + hoverTolerance))
{
modifiedVelocity.y = 0f;
}
else
{
modifiedVelocity.y = -(hit.distance - hoverHeight) * verticalSpeedMultiplier;
modifiedVelocity.y = Mathf.Clamp(modifiedVelocity.y, -maximumVerticalVelocity, maximumVerticalVelocity);
}
Debug.Log($"Distance from ground: {hit.distance}, Bike Velocity.y: {modifiedVelocity}");
hoverbike.velocity = modifiedVelocity;
}
}
private void ToggleBike()
{
isBikeActive = !isBikeActive;
hoverbike.useGravity = !isBikeActive;
}
}
Your bike will now always try to move towards the point hoverHeight units above the object below it, until it is within hoverTolerance from that point. It will also move much more smoothly towards this point, moving faster the further away it is from the intended height.
If you wish for the bike to still bob up and down a little, this can be achieved by modifying the hoverHeight slowly over time, perhaps through use of a Sine function.

Hovering is (essentially) a visual effect
Make the collider extend below the vehicle so that when it rests on the ground the bike appears to be hovering at the desired height. The physics engine only does physics. It doesn't care about what those colliders are, it just wants them to behave in a physics-y way, and if that means falling until it reaches the ground, then let them fall until they reach the ground. Take advantage of the physics engine instead of going around it and then trying to solve the bugs created by going around the physics engine.

First, apply your dampening force scaled by the downward velocity, then apply an additional force scaled by how far it needs to travel back upwards. Keep track of how much force/acceleration you apply through this process and cap that amount at some constant.
readonly float yForce = 80f; // requires tuning
readonly float dampenFactor = 0.8f; // requires tuning
readonly float offsetFactor = 0.5f; // requires tuning
readonly float targetHeight = 10f
Physics.Raycast(hoverbike.transform.position, Vector3.down, out hit);
Debug.Log(hit.distance);
if (hit.distance < targetHeight && hit.distance > 0)
{
float availableForce = yForce;
// cancel out downward velocity
if (hoverbike.velocity.y < 0)
{
// Cap out upward force based on yForce
float cappedDampenForce = Mathf.Min(dampenFactor * -hoverbike.velocity.y,
availableForce);
// How much force is available for the offset?
availableForce -= cappedDampenForce;
hoverbike.AddForce(Vector3.up * cappedDampenForce, ForceMode.Acceleration);
Debug.Log("applied dampening force");
}
// Find upward force scaled by distance left to target height, and cap that amount
float cappedOffsetForce = Mathf.Min(offsetFactor * (targetHeight - hit.distance),
availableForce);
hoverbike.AddForce(Vector3.up * cappedOffsetForce, ForceMode.Acceleration);
Debug.Log("applied offset force");
}

Related

How to reduce 'speed' of a rigidbody without changing the distance it has to cover?

I am moving a rigidbody using rb.AddForce(force,ForceMode.Impulse) where force is the target position the rigidbody have to reach.
Now the speed it goes directly depends on the distance it has to cover.
Let's say the time taken to reach the target position is 3sec. I need the rigidbody to cover the same target pos in 5sec.
I dont want to change the timescale as it affects my gameflow
On Changing the velocity of rigidbody it fails to reach the target position
Some basic physics/math:
velocity = change-in-position / travel-time
force = mass * change-in-velocity / acceleration-time
For ease, we're going to call change-in-position as distance, and change-in-velocity/acceleration-time as acceleration
Now, since the acceleration-time component is effectively zero because you're using Impulse, we're going to remove it from the equation (in math terms, we set it at '1')
force = mass * change-in-velocity
Assuming your object starts at zero velocity, we can simplify change-in-velocity to just velocity
force = mass * velocity
force = mass * distance / travel-time
To bring that back into Unity code:
var mass = rb.mass;
var distance = destination.position - transform.position;
var travelTime = 5f; // seconds
var force = mass * distance / travelTime;
rb.AddForce(force, ForceMode.Impulse);
Note that this assumes a frictionless transfer and constant velocity.
If you ignore gravity, this code solves the problem, here I changed the drag according to weight and distance, it may be a little bit away from the destination at the end, the reason should be higher drag friction.
public void ForceToTarget(Transform target, float time = 1f)
{
var rb = GetComponent<Rigidbody>();
var vector = target.position - transform.position;
var distance = vector.magnitude;
rb.drag = distance/time;
rb.AddForce(vector*rb.mass*distance/time, ForceMode.Impulse);
}
If you want precise control over your speed, then stop using ForceMode.Impulse because other physics effects like drag will make your answers wrong. Instead, just set the speed yourself. You can do this with a Coroutine to control timing and ForceMode.VelocityChange to control the speed. Basically, just look at where you are, where the target is, how much time is left, and apply the speed directly.
private bool canMove = true;
public void MoveTo(Vector3 targetPosition, float targetTime)
{
if(canMove)
{
StartCoroutine(MoveToCoroutine(targetPosition,targetTime));
}
}
private IEnumerator MoveToCoroutine(Vector3 targetPosition, float time)
{
canMove = false;
while(time > 0)
{
var positionDelta = transform.position - targetPosition;
var targetSpeed = positionDelta / time;
var speedDelta = targetSpeed - rb.velocity;
rb.AddForce(speedDelta , ForceMode.VelocityChange);
yield return null;
time -= Time.deltaTime;
}
// Bring the object to a stop before fully releasing the coroutine
rb.AddForce(-rb.velocity, ForceMode.VelocityChange);
canMove = true;
}
I wrote this here into the text editor, no IDE and haven't tested it, but I'm pretty sure this'll do what you want.
Assuming you're using the target position as-is then larger vectors will cause larger force to be applied than smaller vectors. Similarly, if using a direction vector as-is then as the rb gets closer to the target the magnitute of the vector gets smaller and thus less force is applied.
To get a constant speed use the direction to the target and Normalise it instead. Regardless of the distance the direction vector will always have a magnitude of 1 so you can multiply it by any value to accurately control the speed of the object:
Rigidbody rb;
public Transform target;
public float dist;
public float speed = 2f; // or whatever
public float targetDistance = 40f; // or whatever
private void Start()
{
rb = GetComponent<Rigidbody>();
StartCoroutine("IMove");
}
IEnumerator IMove()
{
dist = Vector3.Distance(transform.position, target.position);
while (dist > targetDistance)
{
dist = Vector3.Distance(transform.position, target.position);
rb.AddForce(Vector3.Normalize(target.position - transform.position) * speed, ForceMode.Impulse);
yield return new WaitForFixedUpdate();
}
}
Without getting too much into the physics and maths, if you want it to travel slower but the same distance you need to reduce the gravity on it and the initial force.
Note in this example I am assuming the weight is 1 to make the calculation a bit easier for force.
public class TrevelSpeedAdjusted
{
public float speedFactor = 1;
void FixedUpdate()
{
// Reduce the gravity on the object
rb.AddForce(-Physics.gravity * rigidbody.mass * (1 - speedFactor));
}
public float AddAdjustedForce(Vector3 force, ForceMode forceMode)
{
rb.AddForce(force * speedFactor, forceMode);
}
}
So you can try DoTween package to do this pretty easily and its very convenient to use a package instead of using Unity's inbuilt system.
With doTween use this:
DOMove(Vector3 to, float duration, bool snapping) condition to tween your physics Gameobject to a given target position in the duration you require.
Here's documentation you can refer to if you want: http://dotween.demigiant.com/documentation.php
Let me give you an example:
Install the doTween Package. http://dotween.demigiant.com/download
Import it to unity.
Go to your script where you want to achieve the functionality you mentioned on your question and add this header "using DG.Tweening".
Now get access of your RigidBody.
For Example Lets say: I have a cube gameobject with rigidbidy and this script attached.
The Cube Initial Position is at 0,0,0.
And I want it to move to 5,5,5 in 3 seconds or 5 seconds as per your questions request. And lets say I want this to happen when I click SpaceBar on keyboard.
So I would simply do.
Rigidbody rb;
void Start()
{
rb= GetComponent<Rigibody>();
}
void Update()
{
if(Input.GetButtonDown(Keycode.Space))
{
MoveCube(new Vector3(5,5,5),5);
}
}
void MoveCube(Vector3 inTargetPosition , float durationToReachTheTarget)
{
//What this line does is first take in the target position you want your physics object to reach as it first parameter, Then takes in the duration in which you want it to reach there.
rb.DoMove(inTargetPosition,durationToReachTheTarget);
}
This should help you. But remember this is only if you okay with adding an extra package. Personally this package is very good and I would recommend you this.

Unity - Vector3.MoveTowards does not move at constant speed

I'm trying to design a spitball/poison type projectile that will travel until it reaches the point clicked on screen and then destroys itself.
The problem is that contrary to what almost everyone says, Vector3.Movetowards is not moving the ball at a constant speed. If the target location is close to the launcher it moves slowly, if it is further away it moves much faster.
public float speed = 5f;
public Vector3 target = new Vector3();
void Update()
{
transform.position = Vector3.MoveTowards(transform.position, target,speed * Time.deltaTime);
}
Adding the launcher script
private void Shooting()
{
if (Input.GetMouseButton(0))
{
if (Time.time >= shotTime)
{
GameObject poison = Instantiate(Projectile, shotPoint.position, transform.rotation);
Vector3 target = Camera.main.ScreenToWorldPoint(Input.mousePosition);
target.z = 10f;
poison.GetComponent<PoisonBall>().target = target;
shotTime = Time.time + timeBetweenShots;
}
}
}
Is the balls start position also at z=10f? Otherwise the difference in the speed most probably is a perspective issue in a 2D game and results in your bullet traveling in the Z direction but from your camera perspective you don't see that.
=> The closer you will be to the target the more movement will only happening on the Z axis, the further away the more movement is happening on X and Y.
Make sure to also do
var spawn = shotPoint.position;
spawn.z = 10f;
GameObject poison = Instantiate(Projectile, spawn, transform.rotation);
Or alternatively keep target.z = 0 (just remove the line target.z = 10f as per default the ScreenToWorld uses the Z component of given vector as depth and you are passing in a 2D vector with 0 depth anyway) and instead use Vector2.MoveTowards which will ignore ant depth on Z.

How to continue moving when I decrease speed

I am creating a game that is set in Zero-gravity. I am using a script that can be found here https://github.com/brihernandez/ArcadeSpaceFlightExample
The movement is controlled by a throttle that can be increased/decreased using the mouse wheel or 'w' and 's' keys.
This code controls the mouse wheel and is the same for the 'w' and 's' keys.
private void UpdateMouseWheelThrottle()
{
throttle += Input.GetAxis("Mouse ScrollWheel");
throttle = Mathf.Clamp(throttle, 0.0f, 1.0f);
}
The object that is accelerated by the throttle stops moving when I decrease the throttle and does not drift or continue moving as it should in zero gravity.
This is the code that controls the physics.
public class PlayerPhysics: MonoBehaviour
{
[Tooltip("X: Lateral thrust\nY: Vertical thrust\nZ: Longitudinal Thrust")]
public Vector3 linearForce = new Vector3(100.0f, 100.0f, 100.0f);
[Tooltip("X: Pitch\nY: Yaw\nZ: Roll")]
public Vector3 angularForce = new Vector3(100.0f, 100.0f, 100.0f);
[Range(0.0f, 1.0f)]
[Tooltip("Multiplier for longitudinal thrust when reverse thrust is requested.")]
public float reverseMultiplier = 1.0f;
[Tooltip("Multiplier for all forces. Can be used to keep force numbers smaller and more readable.")]
public float forceMultiplier = 100.0f;
public Rigidbody Rigidbody { get { return rbody; } }
private Vector3 appliedLinearForce = Vector3.zero;
private Vector3 appliedAngularForce = Vector3.zero;
private Rigidbody rbody;
private Player player;
void Awake()
{
rbody = GetComponent<Rigidbody>();
if (rbody == null)
{
Debug.LogWarning(name + ": ShipPhysics has no rigidbody.");
}
player = GetComponent<Player>();
}
void FixedUpdate()
{
if (rbody != null)
{
rbody.AddRelativeForce(appliedLinearForce * forceMultiplier, ForceMode.Force);
rbody.AddRelativeTorque(appliedAngularForce * forceMultiplier, ForceMode.Force);
}
}
public void SetPhysicsInput(Vector3 linearInput, Vector3 angularInput)
{
appliedLinearForce = MultiplyByComponent(linearInput, linearForce);
appliedAngularForce = MultiplyByComponent(angularInput, angularForce);
}
private Vector3 MultiplyByComponent(Vector3 a, Vector3 b)
{
Vector3 ret;
ret.x = a.x * b.x;
ret.y = a.y * b.y;
ret.z = a.z * b.z;
return ret;
}
}
And here is the code that instantiates both.
public class Player: MonoBehaviour
{
public static Player Playerdrone { get { return player; } }
private static Player player;
private PlayerInput input;
private PlayerPhysics physics;
public Vector3 Velocity { get { return physics.Rigidbody.velocity; } }
public float Throttle { get { return input.throttle; } }
// Start is called before the first frame update
void Awake()
{
input = GetComponent<PlayerInput>();
physics = GetComponent<PlayerPhysics>();
}
// Update is called once per frame
void Update()
{
physics.SetPhysicsInput(new Vector3(input.strafe, 0.0f, input.throttle), new Vector3(input.pitch, input.yaw, input.roll));
}
}
I cannot get the Rigidboy to keep drifting when I decrease the throttle, my option is to either abandon the throttle concept and use a simple "Addforce" but I would prefer to keep it.
I have edited the script to have a similar function to the 'Roll' that used 'w' and 's' but moves me forwards and backward. I still find myself decelerating when I let go of the input.
Lowering drag and angular causes the object to spin out of control due to the mouse following function. The mouse does not center the camera and instead causes the camera to keep rotating and tracking the mouse. Changing the mouse script will cause it's own issues since it controls the pitch and yaw of the object.
private void SetStickCommandsUsingMouse()
{
Vector3 mousePos = Input.mousePosition;
// Figure out most position relative to center of screen.
// (0, 0) is center, (-1, -1) is bottom left, (1, 1) is top right.
pitch = (mousePos.y - (Screen.height * 0.5f)) / (Screen.height * 0.5f);
yaw = (mousePos.x - (Screen.width * 0.5f)) / (Screen.width * 0.5f);
// Make sure the values don't exceed limits.
pitch = -Mathf.Clamp(pitch, -1.0f, 1.0f);
yaw = Mathf.Clamp(yaw, -1.0f, 1.0f);
}
The object that is accelerated by the throttle stops moving when I decrease the throttle and does not drift or continue moving as it should in zero gravity.
The Rigidbody component has properties that can be changed in the Inspector (documented here) (and also via script (documented here)):
Mass: The mass of the object (in kilograms by default).
Drag: How much air resistance affects the object when moving from forces. 0 means no air resistance, and infinity makes the object
stop moving immediately.
It sounds like your Drag value might be too large (?).
Although this example project uses "space mechanics" (effectively with no air resistance), the Drag property can still be used to tune the movement and "drift behavior" of the rigidbody, and to control how long it will keep decelerating after the throttle value went down to 0.
Alternatively, you could try to experiment with other ForceMode parameters for the Add*Force() calls: ForceMode.Acceleration or ForceMode.VelocityChange (which is ignoring the mass of the rigidbody).
To make your game more "realistic", you could instead set the Drag property to 0 (no air resistance like in real space -> infinite drift), and try to actively decelerate the ship by experimenting with retrograde rockets/thrusters, by adding a force in backward direction. However, that's probably quite impractical for an arcade game, I guess.
There are also the global Physics settings of the project, which can be found under Edit -> Project settings -> Physics (documented here). There, you'll also find many important physics settings (like Gravity, for example).
(It can also be interesting to have a look at the official docs of the PhysX physics engine, which Unity uses internally for its 3D physics, to understand how things actually work under the hood...)

I want an Object lerping in the y-axis to move along the Z-Axis?

I'm currently making a runner type game that is not randomly generated in Unity. The player, camera and background stay in the same place (the player movement is clamped) and the hazards come towards the player along the negative Z-Axis with this simple lines of code:
public float speed;
private Rigidbody rb;
void Start () {
rb = GetComponent<Rigidbody> ();
rb.velocity = transform.forward * -speed;
}
And it works fine. Now I want to add asteroids that not only come towards the player, but they move up and down (and repeat) and I tried doing this with Lerp(). I'm not a pro about Lerp so I used a code that I found online that works:
public Transform startPos, endPos;
public bool repeatable = true;
public float speed = 1.0f;
public float duration = 3.0f;
float startTime, totalDistance;
IEnumerator Start () {
startTime = Time.time;
while(repeatable) {
yield return RepeatLerp(startPos.position, endPos.position, duration);
yield return RepeatLerp(endPos.position, startPos.position, duration);
}
}
void Update () {
totalDistance = Vector3.Distance(startPos.position, endPos.position);
if(!repeatable) {
float currentDuration = (Time.time - startTime) * speed;
float journeyFraction = currentDuration / totalDistance;
this.transform.position = Vector3.Lerp(startPos.position, endPos.position, journeyFraction);
}
}
public IEnumerator RepeatLerp(Vector3 a, Vector3 b, float time) {
float i = 0.0f;
float rate = (1.0f / time) * speed;
while (i < 1.0f) {
i += Time.deltaTime * rate;
this.transform.position = Vector3.Lerp(a, b, i);
yield return null;
}
}
What I did is create 2 Empty Objects for the start and end positions and then I put the hazard and two lerping points under another Empty Object, let's call it Parent (So Parent > -Hazard -Start - End). Then I added the moving script along the Z-Axis to the parent empty and it kinda works but is like jumping. So the lerping works but it does not move along the Z-Axis until the lerping function is done, the the object jumps to the new Position in the Z-Axis. So it looks very jumpy and glitchy.
How can I do this so is a smooth movement along the Z-Axis and the lerp still works along the Y?
Thanks!!
PS: I know I could just move the character and camera etc, but for now I would like to keep it this way.
The coroutine RepeatLerp acts across several frames, but receives input of type Vector3. The actual position of a reference point will change over time, but the argument values a and b do not change until the coroutine terminates.
So the immediate solution is simple: instead of passing Vector3, pass reference to Transform, and grab its new position each frame. Change your function to this:
public IEnumerator RepeatLerp(Transform a, Transform b, float time)
{
float i = 0.0f;
float rate = (1.0f / time) * speed;
while (i < 1.0f)
{
i += Time.deltaTime * rate;
this.transform.position = Vector3.Lerp(a.position, b.position, i);
yield return null;
}
}
And in Start funtion the invocation will now be:
yield return RepeatLerp(startPos, endPos, duration);
yield return RepeatLerp(endPos, startPos, duration);
This will solve your problem, however, having said that, I strongly suggest you reconsider the way you move your object. You have a mix of Rigidbody semantics with moving objects using transform, which is a really bad practice. The engine is not designed to do that and it may cause great performance issues in the future.
In general, if you have colliders on your objects (and it looks like you have) you should move your objects around using only Rigidbody and applying velocity / forces to it. Maybe have a look at Rigidbody.MovePosition if you want to use Lerp, but even better use Rigidbody.velocity, like for example (the code below is just schematic):
if (transform.position.x > highLimit) rigidbody.velocity -= 2*pulsingSpeed;
if (transform.position.x < lowLimit) rigidbody.velocity += 2*pulsingSpeed;

Unity - Using a rigidbody for motorcycle - how do I turn?

I'm new to Unity and rigidbodies, and I thought I'd learn by trying to make a 3D Tron Light-cycle game. I've made my player vehicle using a combination of cylinders, spheres, and rectangles, seen below:
I used a rigid body on the elongated sphere, and used the following code:
public float accel = 1.0f;
// Use this for initialization
void Start () {
cycleSphere = GetComponent<Rigidbody>();
}
void FixedUpdate () {
cycleSphere.velocity = Vector3.forward * accel;
}
That moves the vehicle forwards. I'm not sure if there's a better way to do this, but if there is, please do say so.
I've attached the Main camera to the vehicle, and disabled X rotation to prevent it and the camera from rolling.
Now I would like to get it to turn by pressing the A and D buttons. Unlike the 90 degree turning of the original Tron light-cycles, I wanted it to turn like a regular vehicle.
So I tried this:
void Update () {
if (Input.GetKey (KeyCode.A)) {
turning = true;
turnAnglePerFixedUpdate -= turnRateAngle;
} else if (Input.GetKey (KeyCode.D)) {
turning = true;
turnAnglePerFixedUpdate += turnRateAngle;
} else {
turning = false;
}
}
void FixedUpdate () {
float mag = cycleSphere.velocity.magnitude;
if (!turning) {
Quaternion quat = Quaternion.AngleAxis (turnAnglePerFixedUpdate, transform.up);// * transform.rotation;
cycleSphere.MoveRotation (quat);
}
cycleSphere.velocity = Vector3.forward * accel;
}
While the above code does rotate the vehicle, it still moves in the last direction it was in - it behaves more like a tank turret. Worse, pressing either A or D too much would cause it to rotate in the desired direction and, after a short while, go nuts, rotating this way and that, taking the camera with it.
What did I do wrong and how can I fix it?
First of all I would recommend you to change from Input.GetKey to Input.GetAxis which will gracefully increase or decrease it's value when the key is pressed. This will give you the option to normalize the force vector applied as your velocity. Then based on that vector you have to adapt your force input so that the "front wheel" will "drag" the rest of the body to some other direction ( left or right ). This is not the ideal "real world physics behavior" because the forward force is slightly bigger than the side ( left or right ) force.
code example :
// member fields
float sideForceMultiplier = 1.0f;
float frontForceMultiplier = 2.0f;
Vector3 currentVeloticy = Vector3.zero;
void Update()
{
Vector3 sideForce = (sideForceMultiplier * Input.GetAxis("horizontal")) * Vector3.right;
Vector3 frontForce = frontForceMultiplier * Vector3.forward;
currentVelocity = (sideForce + fronForce).Normalize;
}
void FxedUpdate()
{
cycleSphere.velocity = currentVelocity * accel;
}

Categories

Resources