So I have a script to move gameobjects around. When I move a gameObject one time it's smooth. But when I move it a second time the movement very slow and looks abit buggy.
The first input in the movement script is the object, then the position it needs to move to and the speed as last parameter. All coordinates are based on the localposition. I use wait because I want to wait before executing the second movement.
I tried to move other objects twice as well but they all end up moving very slow/buggy.
I do not want to run this in Update that's why I use coroutine.
Here's my code:
IEnumerator MovementGentryOne()
{
StartCoroutine(Movement(GentryOne, MovementCoords.GentryOneBasin, gentryspeed));
yield return new WaitForSeconds(2);
StartCoroutine(Movement(GentryOneArm, MovementCoords.GentryArmMoved, gentryspeed));
yield return new WaitForSeconds(2);
StartCoroutine(Movement(GentryOnePicker, MovementCoords.GentryPickerPick, gentryspeed));
yield return new WaitForSeconds(4);
//this one is not working smooth.
StartCoroutine(Movement(GentryOnePicker, MovementCoords.GentryPickerStart, gentryspeed));
yield return null;
}
private IEnumerator Movement(GameObject toMove, Vector3 position, float time)
{
float elapsedTime = 0;
while (elapsedTime < time)
{
toMove.transform.localPosition = Vector3.Lerp(toMove.transform.localPosition, position, (elapsedTime / time));
elapsedTime += Time.deltaTime;
yield return null;
}
toMove.transform.localPosition = position;
}
Anyone an idea what is going wrong?
Kind regards
I found the answer the method never breaks out of the while loop. So I edited the while statement to:
toMove.transform.localPosition != position
Related
So this is for an aim-down sight motion in a FPS game. When the player right clicks, this coroutine is fired up which should move the player's right hand from its "idle" position to its "aiming" position.
Once the aiming position is reached, hitbox is activated, and an animation plays.
public float ChargeTime;
public Vector3 TargetPosition;
public override IEnumerator Attack(PlayerController player)
{
float elapsedTime = 0f;
Vector3 currentPos = player.RightHand.transform.localPosition;
while (elapsedTime < ChargeTime)
{
elapsedTime += Time.deltaTime;
player.RightHand.transform.localPosition = Vector3.Lerp(currentPos, TargetPosition, (elapsedTime / ChargeTime));
yield return null;
}
player.RightHand.transform.localPosition = TargetPosition;
player.Hitbox.SetActive(true);
player.RightHandAnimator.SetBool("IsHolding", true);
yield return null;
}
I've been going over and over this code and everything should be working as intended. Debug.Logs will show me I've reached this code exactly once, that I'm looping through this while() function properly, that the t variable of the Lerp does increase properly from 0 to 1, and that the hitbox properly triggers at the end of while()... but the hand simply refuses to move.
I've tripled and quadrupled checked that the RightHand variable is indeed referencing the proper object, and that no other code ever interferes (there is nothing else that makes that specific object move).
Am I not understanding how Vector3.Lerp works? What am I missing?
Thanks!
EDIT: The "override" is because this class derives from "Weapon" which has a virtual Attack coroutine, which gets overriden by different types of Weapon subclasses.
Looks to me like there is an Animator on your player that affects the right hand position - at least player.RightHandAnimator leads me to that thought ;)
The Animator is one of the last things to be executed within a frame and as soon as it holds any keyframe in any state on the player.RightHand.transform.localPosition it will always overrule whatever changes you make in code!
you could in the meantime turn it off and do e.g.
public override IEnumerator Attack(PlayerController player)
{
player.RightHandAnimator.enabled = false;
var rightHand = player.RightHand.transform;
var currentPos = rightHand.localPosition;
for(var elapsedTime = 0f; elapsedTime < ChargeTime; elapsedTime += Time.deltaTime)
{
rightHand.localPosition = Vector3.Lerp(currentPos, TargetPosition, elapsedTime / ChargeTime);
yield return null;
}
rightHand.localPosition = TargetPosition;
player.Hitbox.SetActive(true);
player.RightHandAnimator.enabled = true;
player.RightHandAnimator.SetBool("IsHolding", true);
}
What actually happens? Does the hand not move at all?
Your code looks ok to me. Does the hand move at the end of the loop at this line..?
player.RightHand.transform.localPosition = TargetPosition;
If not then, the problem is that the field TargetPosition is wrong. You can ignore the lerp and just try and get that one line working properly, and once it is, your lerp should also work fine.
You could try changing it from simply from a public Vector3 to a Transform and drag a reference to a blank gameobject into it, and use the transform.position field of it in the lerp (the gameobject will be at the position you want the hand to goto) That would make more sense to me, but maybe not for your game. I dunno.
This is a turn based game where the enemy character runs and hits the player's characters and then returns to its start position. The enemy's target object (one of the player's characters) is updated after each turn. In the first turn everything is correct and the character runs in a straight line towards the target. However in the following turns when the target changes it runs towards the previous target's position first before moving to the correct target and then it doesn't come back to start position straight away. Like I said, everything is correct in the first turn but when the target changes these behaviours occur.
public Vector3 startPos;
public Vector3 targetPos;
void Start()
{
startPos = transform.position;
targetPos = playerCharacter1.transform.position;
}
void Update()
{
Move(targetPos);
}
public void Move(Vector3 targetPos)
{
StartCoroutine(MoveOverTime());
IEnumerator MoveOverTime()
{
while (transform.position != targetPos)
{
transform.position = Vector3.MoveTowards(transform.position, targetPos, 0.04f * Time.deltaTime);
animator.Play("Run");
yield return null;
}
animator.Play("Attack");
yield return new WaitForSeconds(0.2f);
if (transform.localScale.x > 0f)
{
transform.localScale = new Vector3(-0.175f, 0.175f, 1f);
}
else
{
transform.localScale = new Vector3(0.175f, 0.175f, 1f);
}
while (transform.position != startPos)
{
transform.position = Vector3.MoveTowards(transform.position, startPos, 0.04f * Time.deltaTime);
animator.Play("Run");
yield return null;
}
if (transform.localScale.x < 0f)
{
transform.localScale = new Vector3(0.175f, 0.175f, 1f);
}
else
{
transform.localScale = new Vector3(-0.175f, 0.175f, 1f);
}
yield return null;
}
}
Moving targetPos to a public global value alone is not a good solution!
With the code you provided you are indeed creating a new CoRoutine with every Update loop (as pointed out by #Fredrik Schon). The reason that making targetPos global seems to "work" is that all of those coroutines are using that new location as the target. However you are still creating and executing many coroutines that will only be stopped when they finally reach transform.position == targetPos.
There are a number of things you should fix here:
Don't compare Vector3s (transform.position) like you are as they are floating point numbers. Use instead:
if (Vector3.Distance(transform.position, targetPos) < 0.001f)
...
OR
if (Vector3.Distance(transform.position, targetPos) < Vector3.kEpsilon)
See Unity example on this page: https://docs.unity3d.com/ScriptReference/Vector3.MoveTowards.html
Create only 1 coroutine and have it do the work for you. StartCoRoutine returns a Coroutine value that can be used to stop that specific CoRoutine. (https://docs.unity3d.com/ScriptReference/MonoBehaviour.StopCoroutine.html)
Coroutine moveRoutine;
...
if (moveRoutine == null)
moveRoutine = StartCoroutine(MoveOverTime());
At the end of your MoveOverTime coroutine, just set moveRoutine = null; and then you will ensure that if another Coroutine is needed one will be started. Or alternatively you could keep track of if you have a coroutine using a boolean that you set. Same principle.
Better yet, only start 1 Coroutine during your Start() and modify it to not quit. There is a small overhead in creating coroutines. If you know you plan to have 1 running the whole time your MonoBehaviour is alive, why not just start one at the beginning and add conditions to have it sleep (yield return new WaitForSeconds(0.5f); when you don't want it do do anything. Or have it check if it must do any work at all and just yield return null; immediately if not.
Consider caching your WaitForSeconds. This is just an object like any other, so you can do the following:
WaitForSeconds attackPause;
void Start()
{
attackPause = new WaitForSeconds(0.2f);
}
Then in your coroutine instead of yield return new WaitForSeconds(0.2f); use yield return attackPause;
I am learning Unity from a Swift SpriteKit background where moving a sprite's x Position is as straight forward as an running an action as below:
let moveLeft = SKAction.moveToX(self.frame.width/5, duration: 1.0)
let delayAction = SKAction.waitForDuration(1.0)
let handSequence = SKAction.sequence([delayAction, moveLeft])
sprite.runAction(handSequence)
I would like to know an equivalent or similar way of moving a sprite to a specific position for a specific duration (say, a second) with a delay that doesn't have to be called in the update function.
gjttt1's answer is close but is missing important functions and the use of WaitForSeconds() for moving GameObject is unacceptable. You should use combination of Lerp, Coroutine and Time.deltaTime. You must understand these stuff to be able to do animation from Script in Unity.
public GameObject objectectA;
public GameObject objectectB;
void Start()
{
StartCoroutine(moveToX(objectectA.transform, objectectB.transform.position, 1.0f));
}
bool isMoving = false;
IEnumerator moveToX(Transform fromPosition, Vector3 toPosition, float duration)
{
//Make sure there is only one instance of this function running
if (isMoving)
{
yield break; ///exit if this is still running
}
isMoving = true;
float counter = 0;
//Get the current position of the object to be moved
Vector3 startPos = fromPosition.position;
while (counter < duration)
{
counter += Time.deltaTime;
fromPosition.position = Vector3.Lerp(startPos, toPosition, counter / duration);
yield return null;
}
isMoving = false;
}
Similar Question: SKAction.scaleXTo
The answer of git1 is good but there is another solution if you do not want to use couritines.
You can use InvokeRepeating to repeatedly trigger a function.
float duration; //duration of movement
float durationTime; //this will be the value used to check if Time.time passed the current duration set
void Start()
{
StartMovement();
}
void StartMovement()
{
InvokeRepeating("MovementFunction", Time.deltaTime, Time.deltaTime); //Time.deltaTime is the time passed between two frames
durationTime = Time.time + duration; //This is how long the invoke will repeat
}
void MovementFunction()
{
if(durationTime > Time.time)
{
//Movement
}
else
{
CancelInvoke("MovementFunction"); //Stop the invoking of this function
return;
}
}
You can use co-routines to do this. To do this, create a function that returns type IEnumerator and include a loop to do what you want:
private IEnumerator foo()
{
while(yourCondition) //for example check if two seconds has passed
{
//move the player on a per frame basis.
yeild return null;
}
}
Then you can call it by using StartCoroutine(foo())
This calls the function every frame but it picks up where it left off last time. So in this example it stops at yield return null on one frame and then starts again on the next: thus it repeats the code in the while loop every frame.
If you want to pause for a certain amount of time then you can use yield return WaitForSeconds(3) to wait for 3 seconds. You can also yield return other co-routines! This means the current routine will pause and run a second coroutine and then pick up again once the second co-routine has finished.
I recommend checking the docs as they do a far superior job of explaining this than I could here
I am learning Unity from a Swift SpriteKit background where moving a sprite's x Position is as straight forward as an running an action as below:
let moveLeft = SKAction.moveToX(self.frame.width/5, duration: 1.0)
let delayAction = SKAction.waitForDuration(1.0)
let handSequence = SKAction.sequence([delayAction, moveLeft])
sprite.runAction(handSequence)
I would like to know an equivalent or similar way of moving a sprite to a specific position for a specific duration (say, a second) with a delay that doesn't have to be called in the update function.
gjttt1's answer is close but is missing important functions and the use of WaitForSeconds() for moving GameObject is unacceptable. You should use combination of Lerp, Coroutine and Time.deltaTime. You must understand these stuff to be able to do animation from Script in Unity.
public GameObject objectectA;
public GameObject objectectB;
void Start()
{
StartCoroutine(moveToX(objectectA.transform, objectectB.transform.position, 1.0f));
}
bool isMoving = false;
IEnumerator moveToX(Transform fromPosition, Vector3 toPosition, float duration)
{
//Make sure there is only one instance of this function running
if (isMoving)
{
yield break; ///exit if this is still running
}
isMoving = true;
float counter = 0;
//Get the current position of the object to be moved
Vector3 startPos = fromPosition.position;
while (counter < duration)
{
counter += Time.deltaTime;
fromPosition.position = Vector3.Lerp(startPos, toPosition, counter / duration);
yield return null;
}
isMoving = false;
}
Similar Question: SKAction.scaleXTo
The answer of git1 is good but there is another solution if you do not want to use couritines.
You can use InvokeRepeating to repeatedly trigger a function.
float duration; //duration of movement
float durationTime; //this will be the value used to check if Time.time passed the current duration set
void Start()
{
StartMovement();
}
void StartMovement()
{
InvokeRepeating("MovementFunction", Time.deltaTime, Time.deltaTime); //Time.deltaTime is the time passed between two frames
durationTime = Time.time + duration; //This is how long the invoke will repeat
}
void MovementFunction()
{
if(durationTime > Time.time)
{
//Movement
}
else
{
CancelInvoke("MovementFunction"); //Stop the invoking of this function
return;
}
}
You can use co-routines to do this. To do this, create a function that returns type IEnumerator and include a loop to do what you want:
private IEnumerator foo()
{
while(yourCondition) //for example check if two seconds has passed
{
//move the player on a per frame basis.
yeild return null;
}
}
Then you can call it by using StartCoroutine(foo())
This calls the function every frame but it picks up where it left off last time. So in this example it stops at yield return null on one frame and then starts again on the next: thus it repeats the code in the while loop every frame.
If you want to pause for a certain amount of time then you can use yield return WaitForSeconds(3) to wait for 3 seconds. You can also yield return other co-routines! This means the current routine will pause and run a second coroutine and then pick up again once the second co-routine has finished.
I recommend checking the docs as they do a far superior job of explaining this than I could here
I am learning Unity from a Swift SpriteKit background where moving a sprite's x Position is as straight forward as an running an action as below:
let moveLeft = SKAction.moveToX(self.frame.width/5, duration: 1.0)
let delayAction = SKAction.waitForDuration(1.0)
let handSequence = SKAction.sequence([delayAction, moveLeft])
sprite.runAction(handSequence)
I would like to know an equivalent or similar way of moving a sprite to a specific position for a specific duration (say, a second) with a delay that doesn't have to be called in the update function.
gjttt1's answer is close but is missing important functions and the use of WaitForSeconds() for moving GameObject is unacceptable. You should use combination of Lerp, Coroutine and Time.deltaTime. You must understand these stuff to be able to do animation from Script in Unity.
public GameObject objectectA;
public GameObject objectectB;
void Start()
{
StartCoroutine(moveToX(objectectA.transform, objectectB.transform.position, 1.0f));
}
bool isMoving = false;
IEnumerator moveToX(Transform fromPosition, Vector3 toPosition, float duration)
{
//Make sure there is only one instance of this function running
if (isMoving)
{
yield break; ///exit if this is still running
}
isMoving = true;
float counter = 0;
//Get the current position of the object to be moved
Vector3 startPos = fromPosition.position;
while (counter < duration)
{
counter += Time.deltaTime;
fromPosition.position = Vector3.Lerp(startPos, toPosition, counter / duration);
yield return null;
}
isMoving = false;
}
Similar Question: SKAction.scaleXTo
The answer of git1 is good but there is another solution if you do not want to use couritines.
You can use InvokeRepeating to repeatedly trigger a function.
float duration; //duration of movement
float durationTime; //this will be the value used to check if Time.time passed the current duration set
void Start()
{
StartMovement();
}
void StartMovement()
{
InvokeRepeating("MovementFunction", Time.deltaTime, Time.deltaTime); //Time.deltaTime is the time passed between two frames
durationTime = Time.time + duration; //This is how long the invoke will repeat
}
void MovementFunction()
{
if(durationTime > Time.time)
{
//Movement
}
else
{
CancelInvoke("MovementFunction"); //Stop the invoking of this function
return;
}
}
You can use co-routines to do this. To do this, create a function that returns type IEnumerator and include a loop to do what you want:
private IEnumerator foo()
{
while(yourCondition) //for example check if two seconds has passed
{
//move the player on a per frame basis.
yeild return null;
}
}
Then you can call it by using StartCoroutine(foo())
This calls the function every frame but it picks up where it left off last time. So in this example it stops at yield return null on one frame and then starts again on the next: thus it repeats the code in the while loop every frame.
If you want to pause for a certain amount of time then you can use yield return WaitForSeconds(3) to wait for 3 seconds. You can also yield return other co-routines! This means the current routine will pause and run a second coroutine and then pick up again once the second co-routine has finished.
I recommend checking the docs as they do a far superior job of explaining this than I could here