I'm doing a beginner project in Unity and want to use Vector3.Lerp to do animation for the dash movement. Here is my code and I'm struggling to see why it doesn't work.
void Update()
{
if (Input.GetKeyDown(KeyCode.E))
{
Vector3 start = transform.position;
Vector3 dash = new Vector3(start.x + m_DashDist, start.y);
transform.position = Vector3.Lerp(start, dash, Time.deltaTime);
}
}
How Lerp Works
The last argument you pass to the Lerp function is a float value (mostly between 0f and 1f).
If this value is 0f, Lerp will return the first argument (let's say _dashStart).
If this value is 1f, Lerp will return the second argument (let's say _dashEnd).
If this value is 0.5f, Lerp will return the "mid" between the first and the second argument.
As you can see, this function interpolates between these two arguments depending on the third argument.
This function has to be called every frame and the float value (the 3rd argument) needs to be incremented by Time.deltaTime every frame in your case.
But your if block is only executed once, when you press E. For instance, you can set a bool to true when the E key has been pressed. Here is the further code – hopefully with no mistakes:
public float dashTime = 0.1f;
private float _currentDashTime = 0f;
private bool _isDashing = false;
private Vector3 _dashStart, _dashEnd;
void Update()
{
if (Input.GetKeyDown(KeyCode.E))
{
if (_isDashing == false)
{
// dash starts
_isDashing = true;
_currentDashTime = 0;
_dashStart = transform.position;
_dashEnd = new Vector3(_dashStart.x + m_DashDist, _dashStart.y);
}
}
if (_isDashing)
{
// incrementing time
_currentDashTime += Time.deltaTime;
// a value between 0 and 1
float perc = Mathf.Clamp01(_currentDashTime / dashTime);
// updating position
transform.position = Vector3.Lerp(_dashStart, _dashEnd, perc);
if (_currentDashTime >= dashTime)
{
// dash finished
_isDashing = false;
transform.position = _dashEnd;
}
}
}
There are different ways of using Lerp. I mostly use this approach.
Related
Rotating an object around the same pivot gives different results
So I'm trying to make a door that opens when clicked on, and I have a door that works good as is, but for some reason when I try to make it work for another door using RotateAround, it moves along the Z axis when closing. The code is identical to the other door that works and works when opening this door, so I have no clue as to why it's having trouble closing for this one. The code I'm using to rotate them is as follows
IEnumerator CloseDoor()
{
float timer = 1f;
float speed = 30f;
Vector3 pivot = new Vector3(door.transform.position.x, door.transform.position.y,
door.transform.position.z + 1);
while (timer > 0)
{
door.transform.RotateAround(pivot, Vector3.down, speed * Time.deltaTime);
yield return new WaitForSeconds(0.001f);
timer -= Time.deltaTime;
}
}
IEnumerator OpenDoor()
{
float timer = 1f;
float speed = 30f;
Vector3 pivot = new Vector3(door.transform.position.x, door.transform.position.y,
door.transform.position.z + 1);
while (timer > 0)
{
door.transform.RotateAround(pivot, Vector3.up, speed * Time.deltaTime);
yield return new WaitForSeconds(0.001f);
timer -= Time.deltaTime;
}
}
I'll start with some overall advices, regarding your code.
Don't use Time.deltaTime with new WaitForSeconds(...). Time.deltaTime is an amout of time, that passed between Update() calls. But your logic is not inside Update(). You use own time inervals with new WaitForSecons(...), but if the specified amout of time is less then Time.deltaTime, then it will be executed every Time.deltaTime after all Update() calls. It works good for you only because your time interval 0.001f is low enough to be executed every Update(). When your argument in new WaitForSeconts(...) becomes more them Time.deltaTime, the rotation speed of the door becomes too low to door be opened completely. To made your code more clear and safe, return null with yield instruction. In this case, you can be shure, that coroutine will be executed every Update(). More info here.
Your code works with transform positioning, and probably you have physics in your game. All changes with physical object supposed to be done in the FixedUpdate(). In your case your cant return new WaitForFixedUpdate() in yield instruction and use Time.fixedDeltaTime with it.
So speaking about main question. In your code, you are doing pretty unclear and unsafe thing, like hardcoding pivot with global position offset here:
Vector3 pivot = new Vector3(door.transform.position.x, door.transform.position.y,
door.transform.position.z + 1);
Probably, not all doors will have same rotation, and for some of them offset, just with z coordinate will be wrong. Also it becomes wrong after door opening, because the position and rotation of the door changed when you rotating it around some point, that is not the center of the door. So you should base on local transform point, like this:
public class Door : MonoBehaviour
{
private bool _doorOpened = false;
private bool _doorOpening = false;
[SerializeField] // to see in the inspector.
private Vector3 _localDoorRotatePoint = new Vector3(0.5f, 0f, 0f);
// Update is called once per frame
void Update()
{
if (!_doorOpening && Input.GetKeyDown(KeyCode.E))
{
if (!_doorOpened)
{
StartCoroutine(OpenDoor());
}
else
{
StartCoroutine(CloseDoor());
}
}
}
IEnumerator CloseDoor()
{
_doorOpening = true;
var timer = 1f;
var speed = 30f;
// in my case _localDoorRotate point is (0.5f, 0f, 0). In your case it will be like (0, 0, 1f) or something like this.
// remember, that this point is in local transform coordinates, and in scales with this transform scale vector.
var pivot = transform.TransformPoint(_localDoorRotatePoint);
while (timer > 0)
{
transform.RotateAround(pivot, Vector3.down, speed * Time.fixedDeltaTime);
yield return new WaitForFixedUpdate();
timer -= Time.fixedDeltaTime;
}
_doorOpening = false;
_doorOpened = false;
}
IEnumerator OpenDoor()
{
_doorOpening = true;
var timer = 1f;
var speed = 30f;
// in my case _localDoorRotate point is (0.5f, 0f, 0f). In your case it will be like (0f, 0f, 1f) or something like this.
// remember, that this point is in local transform coordinates, and in scales with this transform scale vector.
var pivot = transform.TransformPoint(_localDoorRotatePoint);
while (timer > 0)
{
transform.RotateAround(pivot, Vector3.up, speed * Time.fixedDeltaTime);
yield return new WaitForFixedUpdate();
timer -= Time.fixedDeltaTime;
}
_doorOpening = false;
_doorOpened = true;
}
}
Helpfull links, that can help you with understanding this code:
Transform.TransformPoint
Transform.up
this code does so when I press the A key or the B key, the elevator stops upwards on the first or second floor.
The question is
How do I modify the code that when the elevator is on the second floor, for example, and I press the A key again to make the elevator go down to the first floor?
Question two
If I replace
if (Input.GetKey (KeyCode.A)) {}
with this
if (Input.GetKeyUp (KeyCode.Keypad1)) {}
so the code does not work. Why?
Thank you for advice
Sorry for the bad English. Here is the code
public GameObject lift;
private bool keyHHit=false;
private bool keyHHitB=false;
void Update ()
{
if (Input.GetKey(KeyCode.B))
{
keyHHit=true;
}
if( keyHHit==true)
{
if(transform.localPosition.y >= 14.52)
{
transform.Translate(new Vector3(0, 0, 0) * 2 * Time.deltaTime, Space.Self);
}
else
{
transform.Translate(new Vector3(0, 2, 0) * 2 * Time.deltaTime, Space.Self);
}
}
if (Input.GetKey(KeyCode.A))
{
keyHHitB=true;
}
if( keyHHitB==true)
{
if(transform.localPosition.y >= 8.52)
{
transform.Translate(new Vector3(0, 0, 0) * 2 * Time.deltaTime, Space.Self);
}
else
{
transform.Translate(new Vector3(0, 2, 0) * 2 * Time.deltaTime, Space.Self);
//transform.Translate(Vector3.up * 0.05f);
}
}
}
So if I understand you correctly
you have two or 3 floors (doesn't matter, could be more later).
Once you press a button you want to move towards the target floor until you reach there. "Lock" input meanwhile
Once you reached a floor "unlock" Input again and allow to also go down if the current floor is above the target floor
I would use a Coroutine and Vector3.Lerp for this.
First lets have a class for floors so it is easier to add additional floors later
[Serializable]
public class FloorSetting
{
public KeyCode Key;
public Vector3 Position;
}
Then simply have an array of all floors target positions and according keys
// Configure these floors in the Inspector
// simply add the entries you need and adjust buttons and target positions
[SerializeField] private FloorSetting[] floors;
[SerializeField] private float moveUnitsPerSecond = 1f;
// This flag is for blocking any Input while moving
// thus preventing concurrent routines
private bool isMoving;
private void Update()
{
// if already moving do nothing
if(isMoving) return;
// check if any of the configured keys was pressed and start moving
// towards the according position
foreach(var floor in floors)
{
// You want to use GetKeyDown to only get the first press event
// you don't care about continues press since you will keep moving automatically until reaching the floor
if(Input.GetKeyDown(floor.Key))
{
StartCoroutine(MoveRoutine(floor.Position));
}
}
}
private IEnumerator MoveRoutine(Vector3 targetPosition)
{
// block concurrent routine
if(isMoving) yield break;
isMoving = true;
// Get the duration of movement
var startPosition = transform.position;
var duration = Vector3.Distance(startPosition, targetPosition) / moveUnitsPerSecond;
// Move smoothly towards the target position and add some easing
var timePassed = 0f;
while(timePassed <= duration)
{
// A interpolation factor between 0 and 1
var factor = timePassed / duration;
// optionally add ease-in and out
factor = Mathf.SmoothStep(0, 1, factor);
// Set the position to an interpolated position between start and target depending on the factor
transform.position = Vector3.Lerp(startPosition, targetPosition, factor);
// increase by the time passed since last frame
timePassed += Time.deltaTime;
// Tells Unity to "pause" the routine here, render this frame
// and continue from here in the next frame
yield return null;
}
// Just to be sure in he end set it to hard position
transform.position = targetPosition;
// optionally add some cooldown in seconds
yield return new WaitForSeconds(1f);
// Release the lock so next move can start now
isMoving = false;
}
If you don't need the easing you could as well also implement it lot easier using Vector3.MoveTowards like e.g.
while(!Mathf.Approximately(Vector3.Distance(transform.position, targetPosition), 0)
{
transform.position = Vector3.MoveTowards(transform.position, targetPosition, moveUnitsPerSecond);
yield return null;
}
transform.position = targetPosition;
Note: Typed on smartphone but I hope the idea gets clear
I'm Instantiating tiles as a path, on every four instantiating's tiles, they should start instantiating towards right, after four instantiating's, they go straight again.
to check this logic I, made a variable spawnOffset and incrementing +1 each frame.
if spawnOffset % 4 == 0 change direction
but, i'm not getting a change in direction in regular intervals, when i debug, the frame skips and so is the logic
public GameObject go;
public Transform Playertransform;
public Vector3 tileSpawnOffset = Vector3.zero;
private Vector3 direction = Vector3.forward;
public int SpawnOffset = -3;
private bool turnRight = false;
void Update()
{
SpawnPath();
ChangeDirection();
}
void SpawnPath()
{
if (Playertransform.position.z > SpawnOffset)
{
tileSpawnOffset += direction;
Instantiate(go, this.transform.position + tileSpawnOffset, this.transform.rotation, this.transform);
SpawnOffset++;
}
}
void ChangeDirection()
{
if (SpawnOffset % 4 == 0)
{
turnRight = !turnRight;
}
direction = turnRight == true ? Vector3.right : Vector3.forward;
}
and but when i tried with time instead
float time = 0f;
void ChangeDirection()
{
time += Time.deltaTime;
if (time > 1)
{
turnRight = !turnRight;
time = 0;
}
direction = turnRight == true ? Vector3.right : Vector3.forward;
}
it works perfectly fine. so, how could i fix it. I, don't want use time, i want to change direction exactly after 4 tiles spawned
Since your always calling both SpawnPath(); ChangeDirection(); everyframe if (Playertransform.position.z > SpawnOffset) is ever false twice in a row your turnright bool gets flipped regardless every frame your on a multiple of 4. You don't need to call ChangeDirection if you didn't spawn a new tile. if you just remove that call from update and add it at right after your increment SpawnOffset its probably going to fixe it.
I can write somehow this code for optimization?
If not use coroutines, when I click on space the next jump has more force and so on.
If use rb.MovePosition, the character will move as if 15 fps. I know, change Time in settings. But I want to know if exist another method...
private void Update() {
if(Input.GetKeyDown(KeyCode.Space)) {
StopAllCoroutines();
StartCoroutine(Jump());
}
}
private IEnumerator Jump() {
if(rb.bodyType != RigidbodyType2D.Dynamic) {
rb.bodyType = RigidbodyType2D.Dynamic;
}
rb.constraints = RigidbodyConstraints2D.FreezePositionY;
_pos = transform.position;
for (float t = 0; t < 1; t += Time.deltaTime * 4f)
{
transform.position = Vector3.Lerp(transform.position, new Vector3(transform.position.x, _pos.y + .35f, transform.position.z), t);
yield return null;
}
rb.constraints = RigidbodyConstraints2D.None;
}
Rigidbodies exist so you don't need to directly adjust an object's transform. Since you have a Rigidbody2d you can just set the velocity directly:
public float jumpSpeed = 5f; // Whatever feels right
private void FixedUpdate() {
if(Input.GetKeyDown(KeyCode.Space)) {
rb.velocity = Vector2.up * jumpSpeed;
}
}
(Edited to use velocity instead of AddForce)
I created a object in unity
GameObject monsterclone =
(GameObject)Instantiate(monsterPrefab, floorPosition, Quaternion.identity);
This object should move in a wave style from a limit1 to limit2.
Then move back from limit2 to limit1.
Y position as well x position have to change in a specific way.
Vector3 nPos = mfloorPos + new Vector3(2f, 0f, 0f);
Vector3 oPos = mfloorPos + new Vector3(-2f, 0f, 0f);
How can I do it?
I can't exactly write the code without knowing more specific but i think this question is already asked any this link will help you out MOVE OBJECT as wave
EDIT:
I think the flat up and float down functionality will work for you for moving one point to another
example:
var floatup;
function Start(){
floatup = false;
}
function Update(){
if(floatup)
floatingup();
else if(!floatup)
floatingdown();
}
function floatingup(){
transform.position.y += 0.3 * Time.deltaTime;
yield WaitForSeconds(1);
floatup = false;
}
function floatingdown(){
transform.position.y -= 0.3 * Time.deltaTime;;
yield WaitForSeconds(1);
floatup = true;
}
example taken from
float amplitudeX = -25.0f;
float amplitudeY = 5.0f;
float omegaX = 0.5f;
float omegaY = 4.0f;
float index;
void Update () {
index += Time.deltaTime;
float x = amplitudeX*Mathf.Cos (omegaX*index);
float y = Mathf.Abs (amplitudeY*Mathf.Sin (omegaY*index));
if(transform.position.x > 24){
transform.eulerAngles = new Vector3(270, -90, 0);
}
if(transform.position.x < -24){
transform.eulerAngles = new Vector3(270, 90, 0);
}
transform.localPosition= new Vector3(x,y,20);
}
If this is a consitant wave and not dependant on speed I would use an animation to create a literal wave curve of the Position.Y value (much in the same principle as Ravindra Shekhawat has explained.) you can find out more about animation here.
Here is some code (untested) that you could go off. It is in c# so I hope it proves no issue with putting in to JavaScript.
bool monsterMoving = false;
void Update(){
//check monster moving to start updating position
if(monsterMoving == true){
//moving animation controls up and down "wave" movement
animation.CrossFade('moving');
//Lerp changes position
Transform.Lerp(transform.Position, oPos, Time.deltaTime);
if (transform.Position == oPos) {
//We are at destination, stop movement
monsterMoving = false;
}
} else {
// if monster stopped moving, return to idle animation (staying still)
animation.CrossFade('idle');
}
}
// function to send a new position to the monster object
void MoveTo(Vector3 newPos){
oPos = newPos;
monsterMoving = true;
}