Unity movement near point - c#

I have spaceships that chase each other. They currently move to their target exactly, but I want to replicate physics you might see on water or in zero gravity, where the object would overshoot its target, turn and move back toward it. Possibly hovering around the target back and forth. I've tried addforce and addrelativeforce, and velocity, but those don't seem to give me the desired effect. Any ideas?
This is my code...
Vector3 dir = (new Vector3(x, y, 0) - transform.parent.position).normalized;
float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
Quaternion q = Quaternion.AngleAxis(angle, Vector3.forward);
float heading = Mathf.Atan2(dir.x, dir.y);
transform.parent.rotation = Quaternion.Slerp(transform.parent.rotation, Quaternion.Inverse(Quaternion.Euler(0f, 0f, heading * Mathf.Rad2Deg)), Time.deltaTime * 12f);
//transform.parent.position += dir * speed * Time.deltaTime;
//rb.AddForce(dir);
rb.velocity = new Vector3(dir.x * speed, dir.y * speed, 0);

Here's how I achieve that AI in a 2D space shooter:
void FixedUpdate()
{
var target = Player != null ? PlayerObject : Target; // If player is dead, go for secondary target
Vector2 dir = -(Vector2)(transform.position - target.transform.position + targetOffset); // Direction
// dir.magnitude is the distance to target
if (dir.magnitude > reaction * rangeModifier) // If I'm far away from the target, slowly rotate towards it
{
// calculate angle toward target and slowly rotate child ship object toward it in the speed of turnRate
float attackAngle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg - 90;
ship.transform.rotation = Quaternion.Slerp(ship.transform.rotation, Quaternion.AngleAxis(attackAngle, Vector3.forward), turnRate * Time.deltaTime);
}
else // If I'm close to the target just keep going straight ahead and fire until I'm too far away again
{
My.weapon.Shoot(dir);
}
_rb.velocity = ship.transform.up.normalized * My.Movementspeed; // Add velocity in the direction of the ship's rotation
}
This will give you an AI that goes in eight (8) shapes around the target. If you have many objects running this code I recommend adding a random offset to the target, to simulate swarming and more realistic flight aim.
I added comments to explain some details of the code.

Related

Move 2D object at constant speed and turn towards touch point

I've been trying for a while to get a 2D player to work kind of like a bullet that is always moving forward (forward being in this case the local X axis for the GameObject, as that's the way that the character is facing) and only changes direction when you touch a point on the screen, in which case it should smoothly start turning towards that point.
One problem I have is that I can't manage to keep the character moving smoothly at a constant speed in the last direction it was facing before, and the other problem that I'm finding is that the character is turning around the wrong axis and instead of rotating based on the Z axis, it's always rotating on the Y axis, which makes the sprite become invisible to the camera.
Here's the code that I have right now:
Vector3 lastTouchPoint;
private void Start()
{
lastTouchPoint = transform.position;
}
void Update()
{
if (Input.touchCount > 0)
{
// The screen has been touched so store the touch
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Stationary || touch.phase == TouchPhase.Moved)
{
// If the finger is on the screen, move the object smoothly to the touch position
lastTouchPoint = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10));
}
}
transform.position = Vector3.Lerp(transform.position, lastTouchPoint, Time.deltaTime);
//Rotate towards point
Vector3 targetDir = lastTouchPoint - transform.position;
transform.LookAt(lastTouchPoint);
}
Thanks in advance!
keep the character moving smoothly at a constant speed
You probably didn't understand what Lerp actually is: This interpolates between the two positions on the given factor where 0 means fully the first position, 1 means fully the last position and e.g. 0.5f would mean in the center between both positions.
This results in faster speeds if the positions are further apart and becomes slower and slower the smaller the distance between both positions becomes. In some cases especially with a factor that small as in your case the object might even never actually reach the target position.
Using this with a dynamic factor of Time.deltaTime makes no sense as this value changes every frame and jitters somewhere around 0,017 (assumin 60 FPS).
You could rather use Vector3.MoveTowards with a fixed constant speed
// set via the Inspector
public float speedInUnitsPerSecond = 1;
...
transform.position = Vector3.MoveTowards(transform.position, lastTouchPoint, Time.deltaTime * speedInUnitsPerSecond);
if you want to keep moving but stop once the touched position is reached.
If you rather wanted to continue moving in the according direction no matter what you could rather store the direction instead of a position and use a straight forward Transform.Translate
// set via the Inspector
public float speedInUnitsPerSecond = 1;
private Vector2 lastDirection;
privtae void Update()
{
...
// If the finger is on the screen, move the object smoothly to the touch position
var touchPosition = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10));
lastDirection = (touchPosition - transform.position).normalized;
...
// move with constant speed in the last direction
transform.Translate(lastDirection * Time.deltaTime * speedInUnitsPerSecond);
...
}
the character is turning around the wrong axis and instead of rotating based on the Z axis, it's always rotating on the Y axis
Note that Transform.LookAt has an optional second parameterworldUp which by default is Vector3.up so a rotation around the global Y axis!
Since you rather want a rotation around the Z axis you should pass
transform.LookAt(lastTouchPoint, Vector3.forward);
I don't know your setup ofcourse but also note that
LookAt
Rotates the transform so the forward vector points at worldPosition.
As you describe it it is also possible that you don't want the objects forward vector to point towards the target position but actually rather the objects right (X) vector!
You can do this by rather simply directly setting the transform.right like e.g.
transform.right = (lastTouchPoint - transform.position).normalized;
or
transform.right = lastDirection;
Btw it would actually be enough to set this rotation only once, namely the moment it changes so in
if (touch.phase == TouchPhase.Stationary || touch.phase == TouchPhase.Moved)
{
// If the finger is on the screen, move the object smoothly to the touch position
lastTouchPoint = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10));
transform.right = (lastTouchPoint - transform.position).normalized;
}
or
if (touch.phase == TouchPhase.Stationary || touch.phase == TouchPhase.Moved)
{
// If the finger is on the screen, move the object smoothly to the touch position
var touchPosition = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10));
lastDirection = (touchPosition - transform.position).normalized;
transform.right = lastDirection;
}
I ended up finding the answer to my own problem using code to rotate smoothly from another post. Here's the code:
Vector3 lastTouchPoint;
Vector3 direction;
Vector3 vectorToTarget;
//Character controller variables
public float moveSpeed = 5f;
public float angularSpeed = 3f;
private void Start()
{
lastTouchPoint = transform.position;
}
void Update()
{
if (Input.touchCount > 0)
{
// The screen has been touched so store the touch
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began)
{
// If the finger is on the screen, move the object smoothly to the touch position
lastTouchPoint = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10));
direction = lastTouchPoint - transform.position;
vectorToTarget = lastTouchPoint - transform.position;
}
}
transform.position += direction.normalized * moveSpeed * Time.deltaTime;
float angle = Mathf.Atan2(vectorToTarget.y, vectorToTarget.x) * Mathf.Rad2Deg;
Quaternion q = Quaternion.AngleAxis(angle, Vector3.forward);
transform.rotation = Quaternion.Slerp(transform.rotation, q, Time.deltaTime * angularSpeed);
}

Unity3D - Rotating object towards point and rolling

i've got a gameobject, which moves straight ahead and can turn left, right, up and down using this function:
void moveTowardsPoint(Vector3 targetPoint)
{
//forward movement
var movementSpeed = Time.deltaTime * speed;
transform.position += transform.forward * movementSpeed;
//rotation
Vector3 dir = targetPoint - transform.position;
Quaternion targetRotation = Quaternion.LookRotation(dir);
var turnSpeed = Time.deltaTime * 2f;
transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, turnSpeed);
}
i want to make this object roll, proportionally to how much it turns left or right. for 20° turn, i want to roll by 20° aswell (angles relative to my startangle)
would actually be even nicer, if i can set a roll limit and it would turn lets say by 30° and roll by 15°.
Here is a topdown view of how this behaviour looks like:

Quaternion.Slerp on X and Z axis without Y axis

I am trying to rotate the Player about X, Y, and Z axis. The Y axis should not move from last angle. Example, if I rotate 45 degree's to the left, the player should not rotate back to 0. The players X and Z axis rotate a maximum of 30 degrees, then when Input is no longer in use, settle to 0.
Through trial and error, I have finally gotten my Y angle to not Slerp back to 0. However, X and Z, still consider Y to be 0 degree's. The player is rotated (assume 45 degree's to the left), but movement along X and Z is as if Y is 0 degree's.
I've been reading articles and threads, and watching video's across multiple domains, including but not limited StackOverflow, Unity forums, Unity API, and YouTube video's.
Video of Current Game - notice the engine exhaust - X and Z never change to the new normal of the Camera view / Player Y direction.
void Update()
{
if(!controller.isGrounded)
{
//Three degree's
moveDirection = new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Thrust"), Input.GetAxis("Vertical"));
moveDirection *= speed;
//rotate around Y-Axis
transform.Rotate(0, Input.GetAxis("Yaw") * rotationSpeed, 0);
float currentY = transform.eulerAngles.y; //save Y for later
//rotation around X and Z
float tiltAroundX = Input.GetAxis("Vertical") * tiltAngle;
float tiltAroundZ = -1 * (Input.GetAxis("Horizontal") * tiltAngle);
Quaternion targetRotation = Quaternion.Euler(tiltAroundX, currentY, tiltAroundZ);
Vector3 finalRotation = Quaternion.Slerp(transform.rotation, targetRotation, smooth).eulerAngles;
finalRotation.y = currentY; //reintroduce Y
transform.rotation = Quaternion.Euler(finalRotation);
controller.Move(moveDirection * Time.deltaTime);
}
After further research that lead me along different avenues, I discovered that there were two issues. Both issue's revolved around the fact that the Z-axis was never being normalized to the new Y-axis degree after rotation. #Ruzihm, solved the issue of Rotation. I solved the then visible issue of movement. Which became readily visible once rotation was working properly.
In essence, the Z-axis (transform.forward) must be recalculated after any change in the Y-axis rotation (Vector3.up). Once you have the new normal (transform.forward), the movement vector needed to flattened to the plane to keep the player from diving into the surface of the world. Thank you #Ruzihm for all your assistance.
Here is the new code:
//Three degree's
moveDirection = new Vector3(Input.GetAxis("Horizontal"),
Input.GetAxis("Thrust"),
Input.GetAxis("Vertical"));
//Normalize the movement direction and flatten the Plane
moveDirection = transform.TransformDirection(moveDirection);
moveDirection = Vector3.ProjectOnPlane(moveDirection, Vector3.up);
moveDirection *= speed;
// collect inputs
float yaw = Input.GetAxis("Yaw") * rotationSpeed;
float pitch = Input.GetAxis("Vertical") * tiltAngle;
float roll = -1 * (Input.GetAxis("Horizontal") * tiltAngle);
// Get current forward direction projected to plane normal to up (horizontal plane)
Vector3 forwardCurrent = transform.forward
- Vector3.Dot(transform.forward, Vector3.up) * Vector3.up;
// Debug to view forwardCurrent
Debug.DrawRay(transform.position, forwardCurrent * 2, Color.white);
// create rotation based on forward
Quaternion targetRotation = Quaternion.LookRotation(forwardCurrent);
// rotate based on yaw, then pitch, then roll.
// This order prevents changes to the projected forward direction
targetRotation = targetRotation * Quaternion.AngleAxis(yaw, Vector3.up);
// Debug to see forward after applying yaw
Debug.DrawRay(transform.position, targetRotation * Vector3.forward, Color.red);
targetRotation = targetRotation * Quaternion.AngleAxis(pitch, Vector3.right);
targetRotation = targetRotation * Quaternion.AngleAxis(roll, Vector3.forward);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, smooth);
controller.Move(moveDirection * Time.deltaTime);
There seem to be some incorrect assumptions about the order of rotations that apply when working with Euler angles. Roll is applied, then pitch, then finally yaw. This means that keeping the same yaw then setting the roll and pitch to zero (or even just changing roll) can completely change the flattened direction you're facing.
It may help to rotate by yaw, flatten the forward direction (aka project it to a completely horizontal plane) Then create a rotation based off that (using Quaternion.LookRotation) which you can then rotate by each axis manually.
if(!controller.isGrounded)
{
//Three degree's
moveDirection = new Vector3(Input.GetAxis("Horizontal"),
Input.GetAxis("Thrust"),
Input.GetAxis("Vertical"));
moveDirection *= speed;
// collect inputs
float yaw = Input.GetAxis("Yaw") * rotationSpeed;
float pitch = Input.GetAxis("Vertical") * tiltAngle;
float roll = -1 * (Input.GetAxis("Horizontal") * tiltAngle);
// Get current forward direction projected to plane normal to up (horizontal plane)
Vector3 forwardCurrent = transform.forward
- Vector3.Dot(transform.forward,Vector3.up) * Vector3.up;
// Debug to view forwardCurrent
Debug.DrawRay(transform.location, forwardCurrent, Color.white, 0f, false);
// create rotation based on forward
Quaternion targetRotation = Quaternion.LookRotation(forwardCurrent);
// rotate based on yaw, then pitch, then roll.
// This order prevents changes to the projected forward direction
targetRotation = targetRotation * Quaternion.AngleAxis(yaw, Vector3.up);
// Debug to see forward after applying yaw
Debug.DrawRay(transform.location, targetRotation * Vector3.forward, Color.red, 0f, false);
targetRotation = targetRotation * Quaternion.AngleAxis(pitch, Vector3.right);
targetRotation = targetRotation * Quaternion.AngleAxis(roll, Vector3.forward);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, smooth);
//debug new forward/up
Debug.DrawRay(transform.location, Transform.forward, Color.blue, 0f, false);
Debug.DrawRay(transform.location, Transform.up, Color.green, 0f, false);
controller.Move(moveDirection * Time.deltaTime);
}
This may be considered a partial answer because being able to determine a "flattened forward" direction and reorder the process of applying component rotations is useful to answering your question but may not be enough to get the full effect you want depending on the details.
As a sidenote, you may want to consider using Quaternion.RotateTowards instead of Quaternion.Slerp if you want to ensure that it will actually reach the target rotation instead of infinitely approach it.

Unity, rotate an object in axis and back to starting point in the same direction

I want to rotate an object in the Y direction at constant speed. When stopped I want to rotate back to Quaternion.identity in the same direction.
public bool spin;
public float speed;
private void Update() {
if (spin) {
transform.Rotate (-Vector3.up, Time.deltaTime * speed, Space.World);
} else if (transform.rotaion != Quaternion.identity) {
transform.rotation = Quaternion.RotateTowards (transform.rotation, Quaternion.identity, Time.deltaTime * speed);
}
}
This works great but it spins back on opposite direction. How do you force it to keep spinning on original direction to Quaternion.identity?
This works great but it spins back on opposite direction.
RotateTowards will take the shortest path to its target - whichever direction that may be.
How do you force it to keep spinning on original direction to Quaternion.identity?
Reverse direction when rotation passes 180 degrees (or halfway):
var dir = transform.eulerAngles.y < 180f ? 1f : -1f;
Quaternion.RotateTowards(rotation, Quaternion.identity, Time.deltaTime * speed * dir);

Unity enemy ai always firing bullets high over the player

Got an issue where the enemy will fire at the player, but always seems to go high or to the side of the player even when the player is stationary and isn't moving. Am I doing something wrong in my code which creates this wild issue or is it just a random annoying bug?
Using the same script for the player albeit it under a different name works, which leads me to believe the issue lies within the fire point. Under the player's script I fire like so:
// Get the place the player has clicked
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// Holds information regarding the mouseclick
RaycastHit hitInfo;
// Now work out if we fire or not
if (Physics.Raycast(ray, out hitInfo))
{
if(hitInfo.distance < maxRange)
{
FireAtPoint(hitInfo.point);
Whereas in the enemy script it is just done through the player's position.
// Holds information regarding the mouseclick
RaycastHit hitInfo;
// Now work out if we fire or not
if (Physics.Raycast(player.transform.position,transform.forward, out hitInfo))
{
Is this underlying issue in the Physics.Raycast call then?
Rest of code for reference:
//More above this but doesn't influence the firing
if (Physics.Raycast(player.transform.position,transform.position, out hitInfo))
{
if (hitInfo.distance < maxRange)
{
FireAtPoint(hitInfo.point);
}
}
private void FireAtPoint(Vector3 point)
{
// Get the velocity to fire out at
var velocity = FiringVelocity(point, angle);
Rigidbody rg = Instantiate(bulletPrefab.gameObject, firePoint.position, firePoint.rotation).GetComponent<Rigidbody>();
EnemyBulletController newProjectile = rg.GetComponent<EnemyBulletController>();
newProjectile.speed = velocity;
}
private Vector3 FiringVelocity(Vector3 destination, float angle)
{
// Get the direction of the mouse click from the player, then get the height differential.
Vector3 direction = destination - transform.position;
float height = direction.y;
height = 0;
// Get the distance in a float of the vector3
float distance = direction.magnitude;
// Turn the firing angle into radians for calculations, then work out any height differential
float AngleRadians = angle * Mathf.Deg2Rad;
direction.y = distance * Mathf.Tan(AngleRadians);
distance += height / Mathf.Tan(AngleRadians);
// Calculate the velocity magnitude
float velocity = Mathf.Sqrt(distance * Physics.gravity.magnitude / Mathf.Sin(2 * AngleRadians));
// Return the normalized vector to fire at.
return velocity * direction.normalized;
}
Picture for reference:
Your equation for computing the velocity looks doubtful. Let's re-derive it:
The equations of free-fall motion under constant gravity are:
After rearranging by substituting the first into the second, we find an expression for the firing velocity:
This is different to what you have, as you are missing the h/d term; said term also gives a constraint on the allowed values of θ:
(Basically means that if you fire directly at the target the bullet would never reach due to gravity)
There are many other problems with your code; just to list three:
Why set height to zero?
Why add a correction to distance? The correction has no physical interpretation.
The fix suggested by #BasillePerrnoud
Amended code:
private Vector3 FiringVelocity(Vector3 destination, float angle)
{
Vector3 direction = destination - transform.position;
float height = direction.y;
float distance = Mathf.Sqrt(direction.x * direction.x + direction.z * direction.z); // *horizontal* distance
float radians = angle * Mathf.Deg2Rad;
float hOverd = height / distance;
float tanAngle = Mathf.Tan(radians);
if (tanAngle <= hOverd)
// throw an exception or return an error code, because no solution exists for v
float cosAngle = Mathf.Cos(radians);
direction.Y = distance / cosAngle;
float velocity = Mathf.Sqrt((distance * Physics.gravity.magnitude) /
(2 * cosAngle * cosAngle * (tanAngle - hOverd)));
return velocity * direction.normalized;
}
I think you use Raycast wrongly. According to the doc, the second argument is the direction, not the destination:
if (Physics.Raycast(player.transform.position,transform.position, out hitInfo))
Should be
if (Physics.Raycast(transform.position, player.transform.position -
transform.position, out hitInfo))
That would explain why it is not firing at the right moment and why the direction is not accurate (since hitInfo is wrong)

Categories