When I'm making an object point towards another one, and try to move it with a change in velocity, the object just moves backward a little bit.
My Code:
public Rigidbody riggy;
public float speed = 100f;
public float maxSpeed = 100f;
Vector3 direction;
void Start()
{
Transform player = GameObject.Find("Von").transform;
StartCoroutine(Chase(0, player.transform));
}
void Update()
{
riggy.velocity = transform.forward * speed * Time.deltaTime; //applies velocity
}
IEnumerator Chase(float speed, Transform target) {
while (transform.position != target.position) {
var direction = (target.position - transform.position); //finds the direction of Von
var rotation = Quaternion.LookRotation(direction); // finds the rotation that the object has to move
Debug.DrawLine(transform.position, target.transform.position, Color.green);
transform.rotation = rotation; // moves the object
yield return null;
}
}
A video:
https://youtu.be/1I5bSvLBCuQ
Your script should work, so it's fault of your scene setup. Video does not show much about how your scene is made, so I can just list some possible errors:
Ensure component speed value is not negative and large enough
Rigidbody reference can be on wrong object or setup (structure) invalid
Object "Von" is not the object you want or it's origin point is somewhere else than you think
Possible there is rigidbody inside other rigidbody and this creates weird results
Additional notes:
There is no need to make additional coroutine, you could put everything inside Update and don't worry about "it might be useful if I want to make a whole new enemy", unless it gonna happen for sure. Physics manipulation should be done in FixedUpdate.
Depending what you want to do you might not need rigidbody or make them kinematic, but in the case above you are directly controlling velocity, so it would be good if you read bunny's answer here.
Last note, Time.deltaTime is time between frames inside Update(), not physics steps, read docs about Time.deltaTime and Time.fixedDeltaTime.
Related
I've been told that Rigidbody.MoveRotation is the best way in Unity 3D to rotate the player between fixed positions while still detecting hits. However, while I can move smoothly from fixed position to position with:
if (Vector3.Distance(player.position, targetPos) > 0.0455f) //FIXES JITTER
{
var direction = targetPos - rb.transform.position;
rb.MovePosition(transform.position + direction.normalized * playerSpeed * Time.fixedDeltaTime);
}
I can't find out how to rotate smoothly between fixed positions. I can rotate to the angle I want instantly using Rigidbody.MoveRotation(Vector3 target);, but I can't seem to find a way to do the above as a rotation.
Note: Vector3.Distance is the only thing stopping jitter. Has anyone got any ideas?
First of all MoveRotation doesn't take a Vector3 but rather a Quaternion.
Then in general your jitter might come from overshooting - you might be moving further than the distance between your player and target actually is.
You can avoid that bit by using Vector3.MoveTowards which prevents any overshooting of the target position like e.g.
Rigidbody rb;
float playerSpeed;
Vector3 targetPos;
// in general ONLY g through the Rigidbody as soon as dealing wit Physics
// do NOT go through transform at all
var currentPosition = rb.position;
// This moves with linear speed towards the target WITHOUT overshooting
// Note: It is recommended to always use "Time.deltaTime". It is correct also during "FixedUpdate"
var newPosition = Vector3.MoveTowards(currentPosition, targetPos, playerSpeed * Time.deltaTime);
rb.MovePosition(newPosition);
// [optionally]
// Note: Vector3 == Vector3 uses approximation with a precision of 1e-5
if(rb.position == targetPos)
{
Debug.Log("Arrived at target!");
}
Then you can simply apply this same concept also to rotation by going through the equivalent Quaternion.RotateTowards basically just the same approach
Rigidbody rb;
float anglePerSecond;
Quaternion targetRotation;
var currentRotation = rb.rotation;
var newRotation = Quaternion.RotateTowards(currentRotation, targetRotation, anglePerSecond * Time.deltaTime);
rb.MoveRotation(newRotation);
// [optionally]
// tests whether dot product is close to 1
if(rb.rotation == targetRotation)
{
Debug.Log("Arrived at rotation!");
}
You can go one step further and use a tweeting library to tween between rotations.
DOTween
With that you can call it like this:
rigidbody.DoRotate(target, 1f) to rotate to target in 1 second.
Or even add callbacks.
rigidbody.DoRotate(target, 1f).OnComplete(//any method or lambda you want)
If at some point you want to cancel the tween yuou can save it on a variable and then call tween.Kill();
So, you want to animate the rotation value over time until it reaches a certain value.
Inside the Update method, you can use the Lerp method to keep rotating the object to a point, but you will never really reach this point if you use Lerp. It will keep rotating forever (always closer to the point).
You can use the following:
private bool rotating = true;
public void Update()
{
if (rotating)
{
Vector3 to = new Vector3(20, 20, 20);
if (Vector3.Distance(transform.eulerAngles, to) > 0.01f)
{
transform.eulerAngles = Vector3.Lerp(transform.rotation.eulerAngles, to, Time.deltaTime);
}
else
{
transform.eulerAngles = to;
rotating = false;
}
}
}
So, if the distance between the current object angle and the desired angle is greater than 0.01f, it jumps right to the desired position and stop executing the Lerp method.
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.
Hello guys my Player is walking on the Stone and through the Stone. The Player called Champ has a Box Collider and the Stone has a Mesh Collider. Also the Player has Rigidbody. I tried everthing i found but nothing helped me with my problem.
MovePlayer.cs Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MovePlayer : MonoBehaviour
{
Rigidbody rb;
public float speed = 10f;
private Vector3 moveDirection;
public float rotationSpeed = 0.05f;
void Start()
{
rb = GetComponent<Rigidbody>();
}
void Update()
{
moveDirection = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical")).normalized;
}
void FixedUpdate()
{
rb.MovePosition(rb.position + transform.TransformDirection(moveDirection * speed * Time.deltaTime));
RotatePlayer();
}
void RotatePlayer()
{
if (moveDirection != Vector3.zero)
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(moveDirection.normalized), rotationSpeed);
}
transform.Translate(moveDirection * speed * Time.deltaTime, Space.World);
}
}
Player Settings in Inspector
Stone Settings in Inspector
Scene Preview
Thank you for help guys! :)
So guys i found out the Solution with the help of the guys posted above.
The problem was that my player speed was too high in the Code the speed was on float 10, but i changed the velocity in the Unity Inspector of Player to float 50.
So my first step to solve the problem was to set the speed down to float 10, but i still wanted to move with a speed of 50f...
The Solution for this problem was that in Unity 2020.3.24f1 and higher (probably lower) you can go to Edit>Project Settings>Physics and set the "Default Max Depenetration Velocity" to the speed you want the objects stopping and not going through. In my case i wanted to move with speed = 50f so i needed to change Default Max Depenetration Velocity to 50.
I hope i can help someone with this Answer in future!
Best Wishes
Max G.
Tested your code and collisions seem to be working fine on my end.
Tested it by adding the script to a GameObject with box collider and creating a small level using cubes. Also made a wall that I modified to use mesh-collider instead of box collider. Player collided normally with objects in the scene.
You should double check your Layer collision matrix from Project Settings > Physics whether you've set layers player and wall to collide.
You could also try adding new cube to the scene and setting its layer to wall to see if player collides with it. If it does then the there might be issues with the mesh of the stone.
If not then I would disable animator and Gravity Body components from the player to make sure they're not interfering with the collisions
Rigidbody.MovePosition basically makes the player teleport which can cause unexpected behaviors. It's generally recommended to use Rigidbody.AddForce instead. For precise movement ForceMode.VeloictyChange can be used.
public float maxVelocityChange = 5.0f;
void moveUsingForces(){
Vector3 targetVelocity = moveDirection;
// Doesn't work with the given RotatePlayer implementation.
// targetVelocity = transform.TransformDirection(targetVelocity);
targetVelocity *= speed;
// Apply a force that attempts to reach our target velocity
Vector3 velocity = rb.velocity;
Vector3 velocityChange = (targetVelocity - velocity);
velocityChange.x = Mathf.Clamp(velocityChange.x, -maxVelocityChange, maxVelocityChange);
velocityChange.z = Mathf.Clamp(velocityChange.z, -maxVelocityChange, maxVelocityChange);
velocityChange.y = 0;
rb.AddForce(velocityChange, ForceMode.VelocityChange);
}
In this code you have applied motion twice and the problem is that transform.Translate is used. Remember that Rigidbody class methods are sensitive to colliders and recognize them, but transform is not the same and only applies a point-to-point shift. To solve the problem, I think you will not need a duplicate motion code with translate in the rotate section.
void RotatePlayer()
{
if (moveDirection != Vector3.zero)
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(moveDirection.normalized), rotationSpeed);
}
// removed translate
}
I tested and this script cause lags in physic. It seems to me that it call physic not per 0,2 seconds, but much-much more rarely and because of this object falling much more slower.
Here's a gif to demonstrate what happens: The purple one without script, the blue one with script.
So how can I fix that?
public class PlayerMovement : MonoBehaviour
{
Rigidbody2D rb;
Vector2 movement = new Vector2(0, 0);
public float movementSpeed = 10f;
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
private void Update()
{
movement = new Vector2(Input.GetAxisRaw("Horizontal"), 0);
}
void FixedUpdate()
{
rb.MovePosition(rb.position + movement * movementSpeed * Time.fixedDeltaTime);
}
}
As said MovePosition overrules the movement => don't use it if you want physics (force) based movements and reaction to collisions etc.
You want to use the input only for the horizontal movement but use physics for the vertical. MovePosition affects both directions.
Try directly setting the Rigidbody.velocity instead
private void Update()
{
// Get the current velocity
var velocity = rb.velocity;
// Only overwrite the x velocity
// since the velocity is only applied in the FixedUpdate anyway
// there is no problem setting this already in Update
// And since it is the velocity which is already frame-rate independent
// there is no need for Time.deltaTime
velocity.x = Input.GetAxisRaw("Horizontal") * movementSpeed;
// Assign back the changed velocity
rb.velocity = velocity;
}
There is an issue with the logic of your code if I understand what you want to do.
You seem to be moving in a vertical fashion yet only have the x component of your vector2 from the horizontal input.
new Vector2(Input.GetAxisRaw("Horizontal"), 0);
If the GIF you attached, the object is just in freefall, so that would be a vertical drop, or the y component of the vector2, which you are setting to 0. Think of this as (x,y) or (move horizontally (left/right), move vertically (up/down)).
I would change the above line to:
movement = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
You can now move vertically using vertical input. Something else which would not cause your issue, but is mentioned on the MovePosition docs is to mark your Rigidbody2D as Interpolate. It is the bottom field in the Rigidbody2D component labeled Interpolate, set it from None to Interpolate.
The reason your object is moving slowly without any input is because you still have your Gravity Scale on the Rigidbody set to 1 instead of 0. If you are trying to simulate gravity with this script, add the vertical component to your input vector and set the Gravity Scale field on the Rigidbody to 0. The MovePosition is fighting the current gravity attempting to affect it.
I'm very new in Unity and I'm trying to move a simple square on a classic 2D map (Super Mario like).
I use addForce to jump, it goes as planned.
But now, I'm trying to move my character horizontally.
First, I tried to use tramsform.translation(), I quickly notice that isn't the proper way, cause this method "teleports" the character, and if it moves too quickly, it can teleport behind a wall. I also try with addForce, but I want that my character has a constant speed, and it gives inertia, so it doesn't stop instantly when I release the key. I also try with .MovePosition(), but the character is shaking with this method (apparently, due to gravity).
So, whats the proper way to move a character (Rigidbody2D) horizontaly?
Here's my code with the different try:
using UnityEngine;
using System.Collections;
public class test : MonoBehaviour {
private Rigidbody2D rb;
public Vector2 velocity;
public float jumpforce;
// Use this for initialization
void Start () {
rb = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update () {
Debug.Log(velocity);
if (Input.GetKeyDown("space")){
rb.AddForce(new Vector2(0, jumpforce), ForceMode2D.Impulse);
}
if (Input.GetKey("a")){ // move to the left
rb.AddForce(-velocity * Time.deltaTime, ForceMode2D.Impulse);
//rb.MovePosition(rb.position - velocity * Time.fixedDeltaTime);
//transform.Translate(-velocity);
}
if (Input.GetKey("d")){ // move to the right
rb.AddForce(velocity * Time.deltaTime, ForceMode2D.Impulse);
//rb.MovePosition(rb.position + velocity * Time.fixedDeltaTime);
//transform.Translate(velocity);
}
}
}
Personally, I use a setup like this:
Rigidbody2D rb;
[SerializeField] [Range(0, 1)] float LerpConstant;
//Other stuff here
FixedUpdate() {
float h = Input.GetAxisRaw("Horizontal");
Vector2 movement = new Vector2(h, rb.velocity.y);
rb.velocity = Vector2.Lerp(rb.velocity, movement, LerpConstant);
}
Lerp simply means "take a Vector2 that is 'x' amount from A to B, and return it". So the code creates a movement vector that is your horizontal movement (user input), vertical movement (the vertical movement the rigidbody already has), and then lerp the current velocity to it. By modifying velocity directly, it ensures your movement will stay smooth and constant, so long as you know how to do it right.
If you want a "Physics" based movement then you should apply forces to the Rigidbody. A benefit of acting on the rigidbody is that it will take into account colliding with objects (like walls).
Here is a great intro tutorial (it's 3D but the same concepts apply for 2D). This is the code from that tutorial with some amendments to make it 2D:
using UnityEngine;
using System.Collections;
public class PlayerController : MonoBehaviour {
public float speed; // Here we set a float variable to hold our speed value
private Rigidbody2D rb; // This is to hold the rigidbody component
// Start is called as you start the game, we use it to initially give values to things
void Start ()
{
rb = GetComponent<Rigidbody2D>(); // Here we actually reference the rigidbody.
}
void FixedUpdate ()
{
// We assign values based on our input here:
float moveHorizontal = Input.GetAxis ("Horizontal");
float moveVertical = Input.GetAxis ("Vertical");
// Here we assign those values to a Vector2 variable.
Vector2 movement = new Vector2 (moveHorizontal, moveVertical);
rb.AddForce (movement * speed); // Finally we apply the forces to the rigidbody
}
}
We can alter the manner in which the force acts on the rigidbody by changing the AddForce.ForceMode2D parameter. For example, ForceMode2D.Force will
Add a force to the rigidbody, using its mass.
ForceMode2D.Impulse will
Add an instant force impulse to the rigidbody2D, using its mass.
which is better for things like jumping.
Note - it is better to put physics-based method calls in FixedUpdate, not in Update, because of frame-rate dependency.
Also note - as you are applying a force to an object, it will accelerate because you are acting on a mass (the rigidbody) and decelerate based on other forces (friction etc.) If you want your player to slow to a halt instead of stop dead, think about the forces acting upon the player. Furthermore, if you apply a force to the rigidbody once per FixedUpdate, this will result in the constant speed you want if you choose ForceMode2D.Forceas other forces acting in the opposite direction will balance it out (see below image - credit to http://www.school-for-champions.com/ for the image).
EDIT: Regarding the comment by rutter above, here is an introductory tutorial on 2D Character controllers.