How to move rigidbody object up or down smoothly in unity3d - c#

I have start a project and I attached a rigidbody to my player for apply some force on it.So when I run my project then in FixedUpdate() function I apply a force on player to moving it in forward direction.So when I pressed leftarrow or rightarrow it perform 'tilt' means rotate it's wings.
But now I want to move my player up or down smoothly by pressing Uparrow or Downarrow,and when I press both uparrow and downarrow then it must be affect on player.
Here is my code.Please help me. :(
void FixedUpdate ()
{
rb.AddForce(transform.forward *1000f);
float movedir = Input.GetAxis ("Horizontal");
Vector3 movement = new Vector3 (movedir, 0.0f, 0.0f);
rb.velocity = movement * movingspeed;
rb.rotation = Quaternion.Euler (0.0f, 0.0f, rb.velocity.x * -3f);//perform tilt
if (Input.GetKeyDown (KeyCode.UpArrow))
{
//code for smoothly move up from current position to = current position + 2f
}
if (Input.GetKeyDown (KeyCode.DownArrow))
{
//code for smoothly move down from current position to = current position - 2f
}
}

Your question is not really clear. Move smoothly could mean many things.
In general you should use RigidBody.MovePosition and Rigidbody.MoveRotation to set a Rigidbody's transforms instead of rb.rotation and rb.position in order to get "smooth" movements:
Use Rigidbody.MovePosition to move a Rigidbody, complying with the Rigidbody's interpolation setting.
If Rigidbody interpolation is enabled on the Rigidbody, calling Rigidbody.MovePosition results in a smooth transition between the two positions in any intermediate frames rendered. This should be used if you want to continuously move a rigidbody in each FixedUpdate.
Set Rigidbody.position instead, if you want to teleport a rigidbody from one position to another, with no intermediate positions being rendered.
So as you can see depending on your settings using Rigidbody.MovePosition might already result in a "smooth" movement.
rb.MovePosition(transform.position + Vector3.up * 2.0f);
Also you use Input.GetKeyDown so it works like a trigger ... it is not called continously like Input.GetKey
If you want to move continously while the key stays pressed use e.g.
// set e.g. in the inspector
public float verticalMoveSpeed;
// ...
if (Input.GetKey(KeyCode.UpArrow))
{
rb.MovePosition(transform.position + Vector3.up * verticalMoveSpeed * Time.deltaTime);
}
if (Input.GetKey(KeyCode.DownArrow))
{
rb.MovePosition(transform.position - Vector3.up * verticalMoveSpeed * Time.deltaTime);
}
If you want to trigger the movement only with GetKeyDown instead you could also do something like e.g.
// set e.g. in the inspector
public float verticalMoveSpeed;
// ...
if (Input.GetKeyDown(KeyCode.UpArrow))
{
StartCoroutine(MoveVertical(2.0f, verticalMoveSpeed));
}
else if (Input.GetKeyDown(KeyCode.DownArrow))
{
StartCoroutine(MoveVertical(-2.0f, verticalMoveSpeed));
}
// ...
private IEnumerator MoveVertical(float distance, float speed)
{
var originalY = transform.position.y;
var targetY = originalY + distance;
var currentY = originalY;
do
{
rb.MovePosition(new Vector 3(transform.position.x, currentY, transform.positiom.z);
// Update currentY to the next Y position
currentY = Mathf.Clamp(currentY + speed * Time.deltaTime, originalY, targetY);
yield return null;
}
while(currentY < originalY);
// make sure you didn't move to much on Y
rb.MovePosition(new Vector3(transform.position.x, targetY, transform.position,z));
}
Than there are two options to prevent concurrent routines:
use a flag. This also prevents the routine from beeing interrupted/called twice/called concurrent
privtae bool isMovingVertical;
// ...
if (Input.GetKeyDown(KeyCode.UpArrow) && !isMovingVertical )
{
StartCoroutine(MoveVertical(2.0f, verticalMoveSpeed));
}
else if (Input.GetKeyDown(KeyCode.DownArrow) && !isMovingVertical )
{
StartCoroutine(MoveVertical(-2.0f, verticalMoveSpeed));
}
// ...
private IEnumerator MoveVertical(float distance, float speed)
{
isMovingVertical = true;
// ...
isMovingVertical = false;
}
use StopAllCoroutines to interrupt a running routine (attention this might lead to "infinite" moves in one direction - at least without you preventing it with additional checks)
if (Input.GetKeyDown(KeyCode.UpArrow))
{
StopAllCoroutines();
StartCoroutine(MoveVertical(2.0f, verticalMoveSpeed));
}
if (Input.GetKeyDown(KeyCode.DownArrow))
{
StopAllCoroutines();
StartCoroutine(MoveVertical(-2.0f, verticalMoveSpeed));
}

Related

Why is my character's horizontal velocity lost when jumping?

GOAL
I'm relatively new to Unity and I want my character to be able to run and jump at the same time causing the character to go up diagonally.
PROBLEM
However after making some adjustments to give the character some acceleration, the jump seems to clear all existing velocity, meaning the character goes up and then to the side instead of both at the same time:
(I apologise if its a bit hard to see)
CODE
This is my character movement script:
Rigidbody2D rb;
BoxCollider2D bc;
[Header("Run")]
float xInput = 0f;
public float maxRunSpeed;
public float acceleration;
[Space]
[Header("Jump")]
public float jumpHeight;
public float lowJumpHeight;
public float fallSpeed;
public float airControl;
[Space]
public LayerMask groundLayer;
public bool onGround;
[Space]
public Vector2 bottomOffset;
public Vector2 boxSize;
public float coyoteTime;
void Start() {
// Gets a reference to the components attatched to the player
rb = GetComponent<Rigidbody2D>();
bc = GetComponent<BoxCollider2D>();
}
void Update() {
Jump();
// Takes input for running and returns a value from 1 (right) to -1 (left)
xInput = Math.Sign(Input.GetAxisRaw("Horizontal"));
}
// Applies a velocity scaled by runSpeed to the player depending on the direction of the input
// Increaces the velocity by accerleration until the max velocity is reached
void FixedUpdate() {
rb.velocity = Math.Abs(rb.velocity.x) < Math.Abs(xInput) * maxRunSpeed ? rb.velocity + new Vector2(acceleration * xInput, rb.velocity.y) * Time.deltaTime : new Vector2(xInput * maxRunSpeed, rb.velocity.y);
}
void Jump() {
// Checks whether the player is on the ground and if it is, replenishes coyote time, but if not, it starts to tick it down
coyoteTime = onGround ? 0.1f : coyoteTime - Time.deltaTime;
// Draws a box to check whether the player is touching objects on the ground layer
onGround = Physics2D.OverlapBox((Vector2)transform.position + bottomOffset, boxSize, 0f, groundLayer);
// Adds an upwards velocity to player when there is still valid coyote time and the jump button is pressed
if (Input.GetButtonDown("Jump") && coyoteTime > 0) {
rb.velocity = Vector2.up * jumpHeight;
}
// Increases gravity of player when falling down or when the jump button is let go mid-jump
if (rb.velocity.y < 0 ) {
rb.velocity += Vector2.up * Physics2D.gravity.y * (fallSpeed - 1) * Time.deltaTime;
} else if (rb.velocity.y > 0 && !Input.GetButton("Jump")) {
rb.velocity += Vector2.up * Physics2D.gravity.y * (lowJumpHeight - 1) * Time.deltaTime;
}
}
Sorry for there being a lot of unecessary code, it's just im not sure what's causing the issue so i don't want to remove anything. Hopefully my comments make some sense?
This is happening because you're setting the velocity of your rigidbody directly with rb.velocity = Vector2.up * jumpHeight. So that will wipe all existing velocity.
If you want to just add a force to the velocity rather than replacing it entirely, you can do that with methods like Rigidbody2D.AddForce.
While in all other cases you keep your velocity values and only slightly modify them you are hard overwriting the absolute velocity in
rb.velocity = Vector2.up * jumpHeight;
erasing any velocity on X.
You could simply keep whatever you have on the other axis and only overwrite Y like
var velocity = rb.velocity;
velocity.y = jumpHeight;
rb.velocity = velocity;
or
rb.velocity = new Vector2(rb.velocity.x, jumpHeight);
basically the same way you do it also in FixedUpdate for the horizontal direction.

Move Character from point A to B smoothly unity

So, Hey guys I am new to unity. I have a small doubt. I have a player who is a child of the topcube in a 3 stackcube placed upon eachother.
These cubes have a target position to move once the user clicks on them.
Like imagine there are 3 points in my world. POINT A with location coordinates as(0,0,1),POINT B with (0,0,2),POINT C with (0,0,3) and the 3 stack cube is place on (0,0,0) with the player attached as a child to topcube in that 3stackcube.
All these points(A,B,C) has a script called targetpoint with a variable bool isFilled(default as false) in them which becomes true when one of the cube reaches to its target position.
Further im checking whenever the cubes reaches their target position make isFilled true and check to see if there is a child attached if yes get the animator of the child and trigger jump animation. The jump animation is an inplace animation.
So I want to programmatically move my character +1 towards the direction he is facing (if he is facing z move + 1 in z, if x move +1 in x like this)when the cube he is attached reached its target position while also playing jump animation.
I did a code. it doesnt seem to be working. And sorry for huge paragraphs. Im totally new to coding and asking doubts. Any help will be helpful thanks.
[SerializeField] public List<targetpoint> waypoints;
[SerializeField] float moveSpeed = 2f;
[SerializeField] AudioClip[] surferSounds;
[SerializeField] GameObject particleToPlay;
int waypointIndex = 0;
float t;
//Cached Reference
AudioSource audioSource;
//State
public bool move = false;
void Start()
{
transform.position = this.transform.position;
t = 0f;
}
void FixedUpdate()
{
if (move == true)
{
MoveTarget();
}
}
void MoveTarget()
{
//Time.timeScale = 0.1f;
if (waypointIndex <= waypoints.Count - 1)
{
var targetPosition = waypoints[waypointIndex].transform.position;
transform.position = Vector3.MoveTowards(transform.position, targetPosition, moveSpeed * Time.deltaTime);
if (transform.position == targetPosition)
{
//Debug.Log(t);
if (waypoints[waypointIndex].isFilled == false)
{
waypoints[waypointIndex].isFilled = true;
AudioClip clip = surferSounds[UnityEngine.Random.Range(0, surferSounds.Length)];
var storeToDestroy = Instantiate(particleToPlay, targetPosition, Quaternion.identity);
Destroy(storeToDestroy , 5f);
audioSource.PlayOneShot(clip);
move = false;
}
else if(waypoints[waypointIndex].isFilled == true)
{
waypointIndex++;
targetPosition = waypoints[waypointIndex].transform.position;
transform.position = Vector3.MoveTowards(transform.position, targetPosition, moveSpeed * Time.deltaTime);
}
if (this.gameObject.transform.childCount > 0)
{
var storeChild = gameObject.transform.GetChild(1).gameObject;
StartCoroutine(GravityAndJump(storeChild,storeChild.transform.position+1*transform.forward,1f));
}
else
{
return;
}
}
}
}
IEnumerator GravityAndJump(GameObject child, Vector3 newPosition , float time)
{
var elapsedTime = 0f;
var startingPosition = child.transform.position;
while(elapsedTime < time)
{
child.GetComponent<Animator>().SetTrigger("shouldJump?");
child.transform.position = Vector3.Lerp(startingPosition, newPosition, (elapsedTime / time));
elapsedTime += Time.deltaTime;
yield return null;
}
//storeChild.GetComponent<Animator>().SetFloat("JumpSpeed", 1f);
//yield return new WaitForSeconds(1f);
//gameObject.GetComponentInChildren<Rigidbody>().useGravity = true;
}
}
So I want to programmatically move my character +1 towards the direction he is facing (if he is facing z move + 1 in z, if x move +1 in x like this)
You can get the forward direction for a GameObject using with transform.forward you can use this to calculate target position in front of the GameObject.
Set target position some distance in front of the transform
targetPosition = transform.position + (transform.forward * distance);
Move towards target position at some speed.
transform.position = Vector3.Lerp(transform.position, targetPosition, Time.deltaTime * followSpeed);
Determining arrival to targetPosition
When it comes to determining if transform has arrive to target destination you should measure the distance instead of comparing that the vectors are the same.
So replace this:
if (transform.position == targetPosition){}
With something like this:
if(Vector3.Distance(transform.position, targetPosition) < .001f){
transform.position = targetPosition;
}
Unless you strictly set two vectors to same values it's likely that they will never be considered equal due to how floating point numbers work.

How to implement lerp for smooth movement

I want achieve moving object on x axis only with lerp to get smoothly movement.
this is picture what i need
I don't know how i can implement lerp to this code to get smooth movement between these values, it now works but it teleport the player and that is not smooth movement what i want to achieve
This is my working code that teleports player:
void Start()
{
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Vector3 desiredPos = new Vector3(transform.position.x + 1.5f, transform.position.y, transform.position.z);
transform.position = desiredPos;
}
if (Input.GetMouseButtonDown(1))
{
Vector3 desiredPos = new Vector3(transform.position.x -1.5f, transform.position.y, transform.position.z);
transform.position = desiredPos;
}
}
I want to implement this but i don't understand how to do it .. When i put all code into update the player don't even move.. It only works for me when i copy paste all the code from docs, but how i can move the time from start method to update and always do the same to achieve to get smooth movement for player when going left and right i don't know really please help me guys..
This is the code that works but i don't know how to change it for my example..
https://docs.unity3d.com/ScriptReference/Vector3.Lerp.html
There are multiple ways. I would not use Translate as this gives you little control here but rather e.g. MoveTowards which makes sure you have no over shooting at the end. Use this for a linear movement with a given moveSpeed:
// set move speed in Units/seconds in the Inspector
public float moveSpeed = 1f;
private Vector3 desiredPos;
private bool isMoving;
private void Update()
{
if (!isMoving && Input.GetMouseButtonDown(0))
{
desiredPos = transform.position + Vector3.right * 1.5f;
isMoving = true;
}
if (!isMoving && Input.GetMouseButtonDown(1))
{
desiredPos = transform.position - Vector3.right * 1.5f;
isMoving = true;
}
if(isMoving)
{
transform.position = Vector3.MoveTowards(transform.position, desiredPos, moveSpeed * Time.deltaTime);
// this == is true if the difference between both
// vectors is smaller than 0.00001
if(transform.position == desiredPos)
{
isMoving = false;
// So in order to eliminate any remaining difference
// make sure to set it to the correct target position
transform.position = desiredPos;
}
}
}
Or as you asked use Vector3.Lerp like e.g.
// a factor between 0 and 1
[Range(0, 1)] public float lerpFactor;
...
transform.position = Vector3.Lerp(transform.position, desiredPos, lerpFactor);
lerpFactor has to be a value between 0 and 1 where in our case 0 would meen the object never moves and 1 it directly jumps to the target position. In other words the closer you set it to 0 the slower it will reach the target, the closer you set it to 1 the faster it will reach the target.
a lot of people do this to get "smooth" movements but what actually happens is e.g. if you set 0.5 for lerpFactor then every frame the object is placed in the middle between current position and target position.
That looks somehow smooth, moves very fast at the beginning and very very slow at the end ... but: It actually never really reaches the target position but just gets very slow.
For your case that is fine since anyway we compare the current and target position using == with a precision of 0.00001. One just has to have in mind how Lerp works.
But with this you won't have any control neither over the move speed nor the duration.
If you want overall more controll (as I do) I would recommend to use a Coroutine (it is not absolutely neccessary and you could do the same in Update as well but in my eyes Coroutines are better to maintain and keep track of).
Than you could also make a smooth eased-in and eased-out movement with an always fixed duration regardless how far the distance is
// set desired move duration in seconds
public float moveDuration = 1;
private bool isMoving;
privtae void Update()
{
if (!isMoving && Input.GetMouseButtonDown(0))
{
StartCoroutine(transform.position + Vector3.right * 1.5f, moveDuration);
}
if (!isMoving && Input.GetMouseButtonDown(1))
{
StartCoroutine(transform.position - Vector3.right * 1.5f, moveDuration);
}
}
private IEnumerator Move(Vector3 targetPosition, float duration)
{
if(isMoving) yield break;
isMoving = true;
var startPosition = transform.position;
var passedTime = 0f;
do
{
// This would move the object with a linear speed
var lerpfactor = passedTime / duration;
// This is a cool trick to simply ease-in and ease-out
// the move speed
var smoothLerpfactor = Mathf.SmoothStep(0, 1, lerpfactor);
transform.position = Vector3.Lerp(startPosition, targetPosition, smoothLerpfactor);
// increase the passedTime by the time
// that passed since the last frame
passedTime += Time.deltaTime;
// Return to the main thread, render this frame and go on
// from here in the next frame
yield return null;
} while (passedTime < duration);
// just like before set the target position just to avoid any
// under shooting
transform.position = targetPosition;
isMoving = false;
}
and you could still extend this example to also take the dtsnace to move into account like e.g.
var actualDuration = duration * Vector3.Distance(startPosition, targetPosition);
and then later everywhere use actualDuration.
Use transform.Translate instead:
public float moveSpeed = 3f;
void Update ()
{
//Moves Left and right along x Axis
transform.Translate(Vector3.right * Time.deltaTime * Input.GetAxis("Horizontal")* moveSpeed);
}

Back and forth movement using Rigidbody2D movePosition

I have two force fields I & II (Image here - https://i.stack.imgur.com/yXAhQ.png). They both have an attached script with a bool 'repel', which is toggled on MouseDown(). These force fields need to either attract or repel a spaceship directly in its path. The actual movement of the ship is done by raycasting right & down from the ship. These raycasts check the 'repel' bool of the force fields and the ship is then either moved away or towards the force fields. All gameObjects are Kinematic.
I need to be able to move the ship back and forth between the force fields by toggling their 'repel' bools. This was working fine by using transform.Translate to move the ship. However, collisions were buggy, so I decided to use Rigidbody2D.MovePosition instead.
Now, the ship can move towards ForceField I and when it detects ForceField II, it changes its course along a vertical line, which is what I want. BUT, it can no longer move towards or away from ForceField I when it detects it along the X axis ray. So, the ship can now only move up and down. How do I keep the ship moving between the force fields?
Here's the code attached to the ship -
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class shipRays_test : MonoBehaviour {
public float rayDistance = 100;
public Vector2 Xspeed;
public Vector2 Yspeed;
private polarityScript polarityright;
private polarityScript polaritydown;
private Rigidbody2D rb;
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
void FixedUpdate ()
{
Debug.DrawRay (transform.position, Vector2.right * rayDistance, Color.red);
RaycastHit2D rightHit = Physics2D.Raycast (transform.position, Vector2.right, rayDistance);
Debug.DrawRay (transform.position, Vector2.down * rayDistance, Color.green);
RaycastHit2D downHit = Physics2D.Raycast (transform.position, Vector2.down, rayDistance);
if (rightHit.collider != null)
{
if (rightHit.collider.CompareTag ("forceField"))
{
polarityright = rightHit.collider.gameObject.GetComponent<polarityScript > ();
if (polarityright.repel)
{
rb.MovePosition (rb.position - Xspeed * Time.fixedDeltaTime);
}
else if (!polarityright.repel)
{
rb.MovePosition (rb.position + Xspeed * Time.fixedDeltaTime);
}
}
}
if (downHit.collider != null)
{
if (downHit.collider.CompareTag ("forceField"))
{
polaritydown= downHit.collider.gameObject.GetComponent<polarityScript >();
if (polaritydown.repel)
{
rb.MovePosition (rb.position + Yspeed * Time.fixedDeltaTime);
}
else if (!polaritydown.repel)
{
rb.MovePosition (rb.position - Yspeed * Time.fixedDeltaTime);
}
}
}
}
}
Apparently Rigidbody.MovePosition() stores the value passed and interpolates over frames, each invocation overwriting the previous value, during which time Rigidbody.position does not change. This isn't noted in the documentation at all (or at least not well), but there's the rub.
To fix this you need to create a variable to hold the desired new position yourself, adding to it as your logic flow and only at the end calling MovePosition().
So you'd have to set things up like this:
Vector3 newPos = rb.position;
if (rightHit.collider != null)
{
//...
{
newPos -= Xspeed * Time.fixedDeltaTime;
}
//...
}
if (downHit.collider != null)
{
//...
{
newPos += Yspeed * Time.fixedDeltaTime;
}
//...
}
rb.MovePosition(newPos);

Character vibrates when movement starts unity

I'm working through a book and have come to an issue. it's a top down shooting game that has the player rotate with the mouse and move with the keyboard. problem is when testing if either the mouse or the keyboard set off movement the image vibrates. If i push the arrow keys it moves in a circle the longer I hold the key the wider the circle. Below is the script I'm working with.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class PlayerBehaviour : MonoBehaviour
{
//movement modifier applied to directional movement
public float playerSpeed = 2.0f;
//current player speed
private float currentSpeed = 0.0f;
/*
* Allows us to have multiple inputs and supports keyboard,
* joystick, etc.
*/
public List<KeyCode> upButton;
public List<KeyCode> downButton;
public List<KeyCode> leftButton;
public List<KeyCode> rightButton;
//last movement made
private Vector3 lastMovement = new Vector3();
// Update is called once per frame
void Update()
{
//rotates ship to face mouse
Rotation();
//moves ship
Movement();
}
void Rotation()
{
//finds mouse in relation to player location
Vector3 worldPos = Input.mousePosition;
worldPos = Camera.main.ScreenToWorldPoint(worldPos);
/*
get x and y screen positions
*/
float dx = this.transform.position.x - worldPos.x;
float dy = this.transform.position.y - worldPos.y;
//find the angle between objects
float angle = Mathf.Atan2(dy, dx) * Mathf.Rad2Deg;
/*
* The transform's rotation property uses a Quaternion,
* so we need to convert the angle in a Vector
* (The Z axis is for rotation for 2D).
*/
Quaternion rot = Quaternion.Euler(new Vector3(0, 0, angle + 90));
// Assign the ship's rotation
this.transform.rotation = rot;
}
// Will move the player based off of keys pressed
void Movement()
{
// The movement that needs to occur this frame
Vector3 movement = new Vector3();
// Check for input
movement += MoveIfPressed(upButton, Vector3.up);
movement += MoveIfPressed(downButton, Vector3.down);
movement += MoveIfPressed(leftButton, Vector3.left);
movement += MoveIfPressed(rightButton, Vector3.right);
/*
* If we pressed multiple buttons, make sure we're only
* moving the same length.
*/
movement.Normalize();
// Check if we pressed anything
if (movement.magnitude > 0)
{
// If we did, move in that direction
currentSpeed = playerSpeed;
this.transform.Translate(movement * Time.deltaTime * playerSpeed, Space.World);
lastMovement = movement;
}
else
{
// Otherwise, move in the direction we were going
this.transform.Translate(lastMovement * Time.deltaTime * currentSpeed, Space.World);
// Slow down over time
currentSpeed *= .9f;
}
}
/*
* Will return the movement if any of the keys are pressed,
* otherwise it will return (0,0,0)
*/
Vector3 MoveIfPressed(List<KeyCode> keyList, Vector3 Movement)
{
// Check each key in our list
foreach (KeyCode element in keyList)
{
if (Input.GetKey(element))
{
/*
* It was pressed so we leave the function
* with the movement applied.
*/
return Movement;
}
}
// None of the keys were pressed, so don't need to move
return Vector3.zero;
}
}
I studied your code for a while and could not find anything wrong with it. So I tested it myself and it works perfectly.
So I suppose there's something wrong with your scene. You might for example have your player object be a child of some object that rotates your axes: this would cause problems I suppose.
Have a new, empty scene. Add a new GameObject (a 3D cube for example, or a 2D sprite) and assign PlayerBehaviour to it. Now test: it should work perfectly.

Categories

Resources