I'm coding my games boss behaviour and in the final stage of the battle the boss is supposed to charge towards the player and then move back to its original position. Wait for 5 seconds and then do the same.
I tried to achieve this using coroutines and Vector2.MoveTowards() but am not getting the desired effect, first off the boss does not "Move Towards" the player but instantly appears at the targetPosition and then just stays there, does not move back.
Below is my code:
private Vector2 chargeTarget;
private Vector2 tankStartPosition;
void Start()
{
chargeTarget = new Vector2(-5.0f, transform.position.y);
tankStartPosition = transform.position;
}
void Update()
{
if (Time.time > nextCharge)
{
StartCoroutine(TankCharge());
nextCharge = Time.time + chargeRate;
}
}
IEnumerator TankCharge()
{
transform.position = Vector2.MoveTowards(tankStartPosition, chargeTarget, Time.deltaTime * chargeSpeed);
transform.position = Vector2.MoveTowards(chargeTarget, tankStartPosition, Time.deltaTime * returnSpeed);
}
Any idea what I am doing wrong here? And how to get my desired action?
Thank you
Calling MoveTowards once only moves the game object once during that iteration of the game loop. Calling MoveTowards once doesn't move the game object all the way to its target (unless the maxDistanceDelta parameter is big enough to move the game object to its target in one iteration).
If the boss is instantly appearing at the target, I'm guessing your chargeSpeed is too big.
What you want to do is call MoveTowards once per Update cycle. However, the way you're doing your coroutine, the coroutine will only move the game object once and then exit. Normally coroutines will have a loop within them (otherwise the coroutine will exit after running once). Something like this:
IEnumerator TankCharge()
{
while (Vector3.Distance(transform.position, chargeTarget.position) > Mathf.Epsilon)
{
// Adjust this so this game object doesn't move the entire
// distance in one iteration
float distanceToMove = Time.deltaTime * chargeSpeed;
transform.position = Vector3.MoveTowards(transform.position, chargeTarget.position, distanceToMove)
yield return null;
}
}
However, for your situation, you don't really need a coroutine. You can just do this directly in Update()
private bool returnToStart = false;
private float timer;
void Update
{
float distanceToMove = Time.deltaTime * chargeSpeed;
if (timer <= 0)
{
if (!returnToStart)
{
transform.position = Vector3.MoveTowards(transform.position, chargeTarget.position, distanceToMove)
// Target reached? If so, start moving back to the original position
if (Vector3.Distance(transform.position, chargeTarget.position) <= Mathf.Epsilon)
{
returnToStart = true;
this.timer = this.chargeRate;
}
}
else
{
transform.position = Vector3.MoveTowards(transform.position, tankStartPosition.position, distanceToMove)
// Original position reached? If so, start moving to the target
if (Vector3.Distance(transform.position, tankStartPosition.position) <= Mathf.Epsilon)
{
returnToStart = false;
this.timer = this.chargeRate;
}
}
}
else
{
this.timer -= Time.time;
}
}
Related
I'm making a top down shooter game in unity using the old input system and I ran into a problem. I use the arrow keys to shoot but I'm not sure how to fire in multiple directions at the same time/frame.
Code:
void Update()
{
currentTime += Time.deltaTime;
//input section
if (Input.GetKey(KeyCode.UpArrow))
{
ShootUp();
}
if (Input.GetKey(KeyCode.DownArrow))
{
ShootDown();
}
if (Input.GetKey(KeyCode.LeftArrow))
{
ShootLeft();
}
if (Input.GetKey(KeyCode.RightArrow))
{
ShootRight();
}
if (Input.GetKey(KeyCode.UpArrow) && Input.GetKey(KeyCode.RightArrow))
{
ShootUp();
ShootRight();
}
}
I thought that looking at both arrow keys at the same time, I would just call both functions as well. Its does not work however.
I tried google but it's pretty hard to find info on the old input system having the same problem.
Edit for clarification
public void SpawnBullet(Vector3 direction, Quaternion rotation)
{
//fire rate correction
if (currentTime < currentFireRate) { return; };
currentTime = 0;
//bullet spawning and firing
Bullet instanceOfBullet = Instantiate(BulletPrefab);
instanceOfBullet.transform.position = transform.position;
instanceOfBullet.direction = direction;
instanceOfBullet.transform.rotation = rotation;
instanceOfBullet.tag = "Player Bullet";
}
void ShootUp()
{
SpawnBullet(Vector3.up, Quaternion.Euler(0,0,0));
//Debug.Log("bullet");
}
Updated answer
The problem is that currentTime becomes 0 al in the first method, and in the next method, being less than currentFireRate, the method stops by not instantiating anything.
To solve this, I created a Coroutine that takes an Action as a parameter, that is any code snippet.
Start this coroutine after checking if currentTime is less than currentFireRate, and give it as an update the code instruction that assigns currentTime the value 0.
The coroutine first waits for a frame, and then invokes the action, executing the indicated code (in our case set currentTime equal to 0).
In this way, if you press two keys while executing the method to instantiate the bullet twice, the value of currentTime will not set to 0 before the end of the frame and therefore both will be executed.
void Update()
{
currentTime += Time.deltaTime;
//input section
if (Input.GetKey(KeyCode.UpArrow))
{
ShootUp();
}
if (Input.GetKey(KeyCode.DownArrow))
{
ShootDown();
}
if (Input.GetKey(KeyCode.LeftArrow))
{
ShootLeft();
}
if (Input.GetKey(KeyCode.RightArrow))
{
ShootRight();
}
}
public void SpawnBullet(Vector3 direction, Quaternion rotation)
{
//fire rate correction
if (currentTime < currentFireRate) { return; };
StartCoroutine(DoActionAfterAFrame(() => currentTime = 0));
//bullet spawning and firing
Bullet instanceOfBullet = Instantiate(BulletPrefab);
instanceOfBullet.transform.position = transform.position;
instanceOfBullet.direction = direction;
instanceOfBullet.transform.rotation = rotation;
instanceOfBullet.tag = "Player Bullet";
}
void ShootUp()
{
SpawnBullet(Vector3.up, Quaternion.Euler(0, 0, 0));
}
IEnumerator DoActionAfterAFrame(System.Action _action)
{
yield return new WaitForEndOfFrame();
_action.Invoke();
}
I hope I have helped you :)
Okay, so first of all. All of these should go into a seperate method and then called in Update. Secondly, Input.GetAxis use that, and write a method which would take a Vector2 direction and instantiate a bullet towards that direction.
private void Update()
{
currentTime += Time.deltaTime;
Shooting();
}
private void Shooting()
{
var x = Input.GetAxis("Horizontal");
var y = Input.GetAxis("Vertical");
Vector2 direction = new Vector2(x, y);
}
private void Shoot(Vector2 direction)
{
// Instantiate bullets and fire them towards direction.
}
Google Input.GetAxis method, and Vector2 in case you do not know that.
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);
}
I make effect for my enemy if the player is near to the enemy decrease the player speed its work fine but if there's one of the same enemies in the filed has the same script or work if there's more all of them are near to the player not work if there's more then one of the same enemy one near to the the player and the other not here's the question
how to make enemy does the effect if there's more then one of the same enemy that has the same effect and only one near the player or more ?
here is my effect script
void Update () {
if (Vector3.Distance (target.position, transform.position) < 20) {
// if the enemy near effect
player.speed = 5f;
} else {
player.speed = 10f;
}
Here is a solution that doesn't require you to add or change the location of scripts and/or add any more components to the object.
By using a short buffer of time and only removing the slow if the "enemy" was previously slowing, you can do without the extra steps.
This is not the BEST way of doing this, some great suggestions in the comments, but it is A way of doing this.
Tested and confirmed to work in Unity.
private float timeUntilSlowEnds = 0;
private bool isSlowing = false;
void Update ()
{
if (Vector3.Distance (target.position, transform.position) < 20)
{
//1/4 second of slow time
timeUntilSlowEnds = 0.25f;
isSlowing = true;
}
if(timeUntilSlowEnds <= 0)
{
if(isSlowing)
{
//only reset player speed if ending slow
isSlowing = false;
player.speed = 10.0f;
}
}
else
{
//slow the player each frame and decrease the timer
player.speed = 5.0f;
timeUntilSlowEnds -= Time.deltaTime;
}
}
Here is a Video also - http://tinypic.com/r/mmagki/9
Here is my start () function
void Start()
{
target = GameObject.FindGameObjectWithTag("Player").transform;
}
and update() function
void Update()
{
transform.LookAt(target);
float step = speed * Time.deltaTime;
distance = (transform.position - target.position).magnitude;
//Debug.Log("Now distance -" + distance);
if (distance < 20)
{
// print("In Range");
transform.GetComponent<Animation>().Play("attack", PlayMode.StopAll);
if (isAttacking == false)
{
isAttacking = true;
Hit.playerHealth -= Random.Range(20f, 25f) * Time.deltaTime;
Hit.playerHealth -= Random.Range(20f, 25f);
// StartCoroutine(MyCoroutine(4));
// print("Player Health Status = " + Hit.playerHealth);
if (Hit.playerHealth <= 0)
{
// print("Player dead");
}
}
else
{
}
}
else
{
// print("Out of Range");
transform.position = Vector3.MoveTowards(transform.position, target.position, step);
transform.GetComponent<Animation>().Play("walk", PlayMode.StopAll);
}
}
My Zombie (Enemy) is approaching Player, when Zombie gets hit with the wall, he should go to gate.
What i did, as soon as Zombie (Set Trigger = checked) hit with the wall, i have changed the reference of 'target' to Object with tag 'gate'.
Now the Zombie is not moving towards gate object (i have set tag 'gate' also). he is still moving towards player only. Not able to change the reference of target.
void OnTriggerEnter(Collider col)
{
if (col.gameObject.tag == "wall")
{
target = GameObject.FindGameObjectWithTag("gate").transform;
Debug.Log("Yes its a onTrigger Enter function , hitting with wall");
}
}
It looks like you don't have a collider on your gate. Adding a collider to the gate should do the trick.
first verify the tags of all are seted correctly, then you need to add a rigidbody component at least in one of the objects as Unity Docs say: OnTriggerEnter . I recomend to add it to the zombie and set UseGravity to false. and with this it should work... sorry for bad english.
I'm doing a 2D platform game where the player only climbs (y-axis).
What I'm trying to do is when I reach near to the top edge, my camera goes up a little bit so I can see more of the level.
I have this, but it's not working:
Public Transform player;
Void Start() { }
Void Update()
{
if (player.transform.position > Screen.height - 50)
{
transform.position.y += speed * Time.deltaTime;
}
}
Other Way is like this But not working more than once, i might need to set move = false; but dont know how without interupting Lerp:
float moveTime = 1f; // In seconds
float moveTimer = 0.0f; // Used for keeping track of time
bool moving = false; // Flag letting us know if we're moving
public float heightChange = 7.0f; // This is the delta
// These will be assigned when a collision occurs
Vector3 target; // our target position
Vector3 startPos; // our starting position
void Start()
{
}
void Update()
{
// If we're currently moving and the movement hasn't finished
if (moving && moveTimer < moveTime)
{
// Accumulate the frame time, making the timer tick up
moveTimer += Time.deltaTime;
// calculate our ratio ("t")
float t = moveTimer / moveTime;
transform.position = Vector3.Lerp(startPos, target, t);
}
else
{
// We either haven't started moving, or have finished moving
}
}
void OnTriggerEnter2D(Collider2D other)
{
if (!moving)
{
// We set the target to be ten units above our current position
target = transform.position + Vector3.up * heightChange;
// And save our start position (because our actual position will be changing)
startPos = transform.position;
// Set the flag so that the movement starts
moving = true;
}
}
}
you are comparing two different things here. Screen.height is in pixels while player position is in world units. so lets assume your screen is 1024x768 you are checking if your player is above 718 which is a huge amount in world units.
What you have to do is convert one to the others unit and compare then for example with http://docs.unity3d.com/ScriptReference/Camera.ScreenToWorldPoint.html
Another thing i noticed is that this script is named player so i assume this is controlling your player object in which case
transform.position.y += speed * Time.deltaTime;
would only change your players position. To change the position of the main camera you could use:
Camera.main.transform.position += new Vector3(0f, speed * Time.deltaTime, 0f);
And lastly, always post the errors you get when asking questions, explain what you expect to happen and what actually happens.
You could try lerping the between positions eg.
transform.position = new trasform.lerp(transform.position, player.transform.position, speed*Time.DeltaTime);
just use the if statement to trigger a bool that then does the lerp, so the camera will move to what you need it to and not just when the player hits a certain point. Then when the camera is finished mooving, reset the bool ready for next time