I have a spaceship that has a location, destination and a rotation. When it has a new destination it moves forward all the while rotating clockwise until it is facing its destination.
Code:
public void Move()
{
Vector requiredDirection = destination - origin;
requiredDirection.Normalize();
Vector directionNow = new Vector((float)Math.Cos(rotation), (float)Math.Sin(rotation));
float x = Math.Abs(requiredDirection.X - directionNow.X);
float y = Math.Abs(requiredDirection.Y - directionNow.Y);
if ((x > rotationSpeed) || (y > rotationSpeed))
{
rotation += rotationSpeed;
}
shipPosition += directionNow * speed;
}
My problem is that the ship will only rotate in the one direction until it is facing its target, I need it to rotate in the direction that would be the shortest route.
I'm really at a loss as to where to begin, this is my first real attempt at Vectors.
The angle from directionNow to requiredDirection is given by Math.Atan2(requiredDirection.Y,requiredDirection.X) - Math.Atan2(directionNow.Y,directionNow.X). That will be positive to rotate counterclockwise, negative to rotate clockwise.
Related
I do not understand how this code works and I am seeking an explanation. This code is inside an update function, updating the Objects location constantly.
By "facing the mouse direction" I mean that the object is like the earth orbiting around the sun for example, but you choose where it is currently located on it's rotation line around the sun.
public GameObject player;
private Vector3 v3Pos;
private float angle;
private readonly float distance = 0.16f;
private void Update()
{
v3Pos = Input.mousePosition;
v3Pos.z = (player.transform.position.z -
Camera.main.transform.position.z);
v3Pos = Camera.main.ScreenToWorldPoint(v3Pos);
v3Pos -= player.transform.position;
angle = Mathf.Atan2(v3Pos.y, v3Pos.x) * Mathf.Rad2Deg;
if (angle < 0.0f) { angle += 360.0f; }
transform.localEulerAngles = new Vector3(0, 0, angle);
float xPos = Mathf.Cos(Mathf.Deg2Rad * angle) * distance;
float yPos = Mathf.Sin(Mathf.Deg2Rad * angle) * distance;
transform.localPosition = new Vector3(player.transform.position.x +
xPos * 4, player.transform.position.y + yPos * 4, 0);
}
I found this code in a video which makes an object (like a gun) rotate around the player and follow the mouse simultaneously but I don't understand how it works. How does it work? Also, I don't know where the video is at any more but I will find it if necessary.
It warps the attached gameobject so many units in the direction of the mouse cursor from the player (locally for some reason), and also turns to face its right side in that direction (locally for some reason).
Individual sections explained in the comments below:
public GameObject player;
private Vector3 v3Pos;
private float angle;
private readonly float distance = 0.16f;
private void Update()
{
// mouse position in screen space
// current value = (mouse x, mouse y, 0) )
Vector3 v3Pos = Input.mousePosition;
// sets the z coordinate to be the difference from the camera to the player
// along the forward world axis.
// current value = (mouse x, mouse y, camera->player distance along forward)
v3Pos.z = (player.transform.position.z - Camera.main.transform.position.z);
// v3Pos now means the position of the cursor, projected onto plane the player
// is on parallel to camera plane
// This means it's a world space positioning of the mouse
v3Pos = Camera.main.ScreenToWorldPoint(v3Pos);
// v3Pos now means the direction from the player to the mouse position
// in world space.
// Despite the name, now a direction, not a position!
v3Pos -= player.transform.position;
// finds the signed angle from right to the direction v3Pos represents.
// in the range (-180, 180]. positive = counterclockwise
angle = Mathf.Atan2(v3Pos.y, v3Pos.x) * Mathf.Rad2Deg;
// converts negative angle into an equivalent positive angle.
if (angle < 0.0f) { angle += 360.0f; }
// sets the forward axis angle of the transform this
// MonoBehaviour is attached to as the same angle.
// Since we measured from the right to the direction of the mouse,
// this turns the right side to face the mouse.
// This is done in local space for some reason, can't tell from code.
transform.localEulerAngles = new Vector3(0, 0, angle);
// finds x and y coordinates of a point in the same direction as
// v3Pos the mouse from the player but at distance
// Could use v3Pos but with a z=0, normalized then * distance but
// recalculating with trig works too.
// basic trig refresher
// cos of angle from right gives unit circle x coordinate
// sin of angle from right gives unit circle y coordinate
float xPos = Mathf.Cos(Mathf.Deg2Rad * angle) * distance;
float yPos = Mathf.Sin(Mathf.Deg2Rad * angle) * distance;
// sets the player's local position to be the position of the player
// adjusted by the direction and distance of the point.
// Also done in local space, can't tell why from code.
// Weird to use something else's world position as the local position
// for something else.
transform.localPosition = new Vector3(player.transform.position.x
+ xPos * 4, player.transform.position.y + yPos * 4, 0);
}
The alternate calculation mentioned above could be done like this:
Vector3 v3Pos = Input.mousePosition;
v3Pos.z = (player.transform.position.z - Camera.main.transform.position.z);
v3Pos = Camera.main.ScreenToWorldPoint(v3Pos);
v3Pos -= player.transform.position;
angle = Mathf.Atan2(v3Pos.y, v3Pos.x) * Mathf.Rad2Deg;
if (angle < 0.0f) { angle += 360.0f; }
transform.eulerAngles = new Vector3(0, 0, angle);
Vector3 v3DirFlat = v3Pos;
v3DirFlat.z = 0f;
// keeping both * distance and * 4
v3DirFlat = v3DirFlat.normalized * distance * 4;
transform.position = player.transform.position + v3DirFlat;
The steps are
// Get the mouse position on the screen
v3Pos = Input.mousePosition;
// Bring that point down until it is level with the player
v3Pos.z = (player.transform.position.z - Camera.main.transform.position.z);
// Find that point in world space coordinates
v3Pos = Camera.main.ScreenToWorldPoint(v3Pos);
// Find the vector from the player to that point
v3Pos -= player.transform.position;
// Calculate the angle between that vector and the X axis
angle = Mathf.Atan2(v3Pos.y, v3Pos.x) * Mathf.Rad2Deg;
// ensure the values are between 0 and 360
if (angle < 0.0f) { angle += 360.0f; }
// Set the item's rotation to that angle, so it faces the right direction
transform.localEulerAngles = new Vector3(0, 0, angle);
// Find the new position of the item on the orbit circle
float xPos = Mathf.Cos(Mathf.Deg2Rad * angle) * distance;
float yPos = Mathf.Sin(Mathf.Deg2Rad * angle) * distance;
// Set the item to it's new position
transform.localPosition = new Vector3(player.transform.position.x + xPos * 4, player.transform.position.y + yPos * 4, 0);
I'm not sure why the coordinates in that last step are multiplied by 4, they should already be positioned on a circle with radius distance
I crafted a model to illustrate what I am trying to calculate. Given a line (vector) between two anchor points, I want to place one or more game objects a certain distance tangential to the midpoint of that vector and at designated angles (radians?) along a circle that is perpendicular to the vector.
In this illustration, an imaginary circle is placed at midpoint and perpendicular to the line between Anchor 1 and Anchor 2. I want to calculate the Vector3 positions of three points (P1, P2, and P3). The intent is to place objects at each of those points. The entire assembly will a gameobject that can rotate in space. (There will be a single game object with each object a child.)
I have scoured StackOverflow and the Unity communities and cannot find examples that help me make those three placements.
Any ideas?
Instead of trig, consider vector math and quaternions. Use cross products to find the 0 angle offset and quaternions to rotate it according to the angle. See comments for explanation.
public void PlaceObjects(Transform anchor1, Transform anchor2, float r,
List<Transform> objs, List<float> angles)
{
// lists must be non-null and same size
Debug.Assert(objs != null);
Debug.Assert(angles != null);
Debug.Assert(objs.Count == angles.Count);
// Find midpoint and axis of rotation
Vector3 midpoint = 0.5f * (anchor1.position + anchor2.position);
Vector3 axis = (anchor2.position - anchor1.position).normalized;
// What direction should the the "zero" offset be based on?
// Base it on the local up of the "assembly parent" this script is attached to?
// or Vector3.up if "0 angle" should approximate world up?
Vector3 upDirection = transform.up;
// Of directions perpendicular to the axis find the closest to upDirection
// See https://stackoverflow.com/a/57698547/1092820 for more information
Vector3 axisRight = Vector3.Cross(upDirection, axis);
if (axisRight == Vector3.zero)
{
// upDirection & axis are colinear, no unique "up-ish" exists.
// Just give up and don't move anything.
return;
}
Vector3 zeroOffsetDir = Vector3.Cross(axis, axisRight);
for (int i = 0 ; i < objs.Count ; i++)
{
Transform obj = objs[i];
float angle = angles[i];
// Find a rotation that describes how to rotate a "0 angle" offset into the one
// the current object needs
Quaternion rot = Quaternion.AngleAxis(angle, axis);
// Find the offset by rotating the "zero" offset, then extending it by the radius
Vector3 offset = r * (rot * zeroOffsetDir);
// Set the object's position based on its offset and the location of the midpoint
obj.position = midpoint + offset;
// Optionally, set the object's rotation based on its current forward and the new up:
// obj.rotation = Quaternion.LookRotation(obj.forward, offset);
}
}
For those interested, here's my Component adaptation of #Ruzihm's solution. The PlaceObjectsOnCirclePerpendicularToVectorLine component is placed on an empty game object.
using System.Collections.Generic;
using UnityEngine;
public class PlaceObjectsOnCirclePerpendicularToVectorLine : MonoBehaviour
{
// Adapted from https://stackoverflow.com/a/63308834/13052746, by Ruzihm.
public Transform anchor1;
public Transform anchor2;
public float radius;
public List<Transform> objs;
public List<float> angles;
private Vector3 anchor1PriorPosition;
private Vector3 anchor2PriorPosition;
// Start is called before the first frame update
void Start()
{
PlaceObjects(
anchor1,
anchor2,
radius,
objs,
angles);
}
// Update is called once per frame
void Update()
{
PlaceObjects(
anchor1,
anchor2,
radius,
objs,
angles);
}
public void PlaceObjects(
Transform anchor1,
Transform anchor2,
float radius,
List<Transform> objs,
List<float> angles)
{
if (anchor1PriorPosition == anchor1.position &&
anchor2PriorPosition == anchor2.position)
{
// The anchors haven't moved.
return;
} else
{
anchor1PriorPosition = anchor1.position;
anchor2PriorPosition = anchor2.position;
}
// lists must be non-null and same size
Debug.Assert(objs != null);
Debug.Assert(angles != null);
Debug.Assert(objs.Count == angles.Count);
// Find midpoint and axis of rotation
Vector3 midpoint = 0.5f * (anchor1.position + anchor2.position);
Vector3 axis = (anchor2.position - anchor1.position).normalized;
// What direction should the the "zero" offset be based on?
// Base it on the local up of the "assembly parent" this script is attached to?
// or Vector3.up if "0 angle" should approximate world up?
Vector3 upDirection = transform.up;
// Of directions perpendicular to the axis find the closest to upDirection
// See https://stackoverflow.com/a/57698547/1092820 for more information
Vector3 axisRight = Vector3.Cross(upDirection, axis);
//if (axisRight == Vector3.zero)
//{
// // upDirection & axis are colinear, no unique "up-ish" exists.
// // Just give up and don't move anything.
// return;
//}
Vector3 zeroOffsetDir = Vector3.Cross(axis, axisRight);
for (int i = 0; i < objs.Count; i++)
{
Transform obj = objs[i];
float angle = angles[i];
// Find a rotation that describes how to rotate a "0 angle" offset into the one
// the current object needs
Quaternion rot = Quaternion.AngleAxis(angle, axis);
// Find the offset by rotating the "zero" offset, then extending it by the radius
Vector3 offset = radius * (rot * zeroOffsetDir);
// Set the object's position based on its offset and the location of the midpoint
obj.position = midpoint + offset;
}
}
}
Here's what it looks like in the Scene view:
And here's what the component looks like in the inspector:
Again, brilliant, #Ruzihm! Exactly what I wanted!
Any tips on how to move an object back and forth sinusoidally (like a pendulum, but in a linear path) along a specified 3D vector? I've got the sinusoidal motion and the vector, but I can't figure out how to combine the two.
The following are the two pieces of code I have; the vector is specified using angles from the origin.
I'm very new to coding, so please forgive me for any mistakes in the code.
This moves the object in the sinusoidal path about the origin - this is the motion I want to achieve along the 3D vector.
float rodPositionZsin = pathLength * Mathf.Sin(Time.time) + position;
transform.position = new Vector3(0, 0, rodPositionZsin);
This will move the object along the vector in the X and Y dimensions, but I'm stumped for what to do in the Z.
float Xangle = 20;
float Yangle = 50;
float Zangle = 30;
//Position Transformations
float rodPositionZsin = pathLength * Mathf.Sin(Time.time) + position;
float rodPositionY = Mathf.Cos(Yangle*Mathf.PI/180)*pathLength;
float rodPositionX = Mathf.Sin(Xangle * Mathf.PI / 180)*pathLength;
float rodPositionZ = Mathf.Tan(Zangle * Mathf.PI / 180) * pathLength;
transform.position = Vector2.MoveTowards(transform.position, new Vector2(rodPositionX, rodPositionY), pathLength * Mathf.Sin(Time.time));
rodPositionX = transform.position.x;
rodPositionY = transform.position.y;
rodPositionZ = rodPositionZsin + transform.position.z;
transform.position = new Vector3(rodPositionX, rodPositionY, rodPositionZsin);
if you have a vector, you just need to scale it by a sine curve, then set the object's position to that scaled vector.
so in (untested) pseudocode:
Vector3 scaledVector = originalVector* Mathf.Sin(Time.time);
youGameObject.transform.position = scaledVector
You can then add phase, frequency, and amplitude terms in your sine function to change the frequency of oscillation, how far along that vector, and the start position of the oscillation if you want to customize it further.
Edit:
Here’s how to add these.
http://jwilson.coe.uga.edu/EMT668/EMT668.Folders.F97/Feller/sine/assmt1.html
a * sin(b*x +c) + offset.
Where a is amplitude (max distance travelled)
B is wavelength (1/frequency of oscillation)
C is phase ( starting pos) and offset is to move the whole oscillation pattern along the vector ( make it happen away from origin center)
I'm trying to make a simple car simulator, and when I use the transform.Rotate() to rotate the wheels foreword (x axis), and the transform.localEulerAngles() to rotate in the turning direction (y axis), only the localEulerAngles() works. when I use only one method, the wheel foreword rotation (x axis) works, but I can't manage to make them both work. Do you have any ideas how to make them work together?
float ro = 20f; // 20 degrees turn
//to preserve the x and z values of rotation
Vector3 rot = wheel.gameObject.transform.rotation.eulerAngles;
//rotates the wheels angle
wheel.gameObject.transform.localEulerAngles = new Vector3(rot.x, ro, rot.z);
float vel = wheel.rpm * 2 * Mathf.PI / 60 * Time.deltaTime * Mathf.Rad2Deg;
//rotates the wheels forward
wheel.gameObject.transform.Rotate(vel, 0, 0);
You should not be using localEulerAngles when you are having two different rotations, because when you have 2 movements if you use localEulerAngles you change the local angles with the movements you are providing themselves so you loose the reference of where the angular position of your axis is at.
You can indicate to the rotate method to rotate respect to the world, transform.rotate rotates the transform of your object relative to the world so you can handle simultaneus rotations together because world axis do not change.
Find below the code snippet that I tried, and the picture of my axis orientation which is not exactly the same as yours.
public class WheelTurning : MonoBehaviour
{
float rpm=10f;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
turn(direction.left);
}
if (Input.GetKeyDown(KeyCode.D))
{
turn(direction.right);
}
float vel = this.rpm * 2 * Mathf.PI / 60 * Time.deltaTime * Mathf.Rad2Deg;
rotate(vel);
}
enum direction
{
left,
right
}
void turn(direction dir)
{
Vector3 rot = this.gameObject.transform.rotation.eulerAngles;
//rotates the wheels angle
float rotation = dir == direction.left ? 20f : -20f;
gameObject.transform.Rotate(new Vector3(0, rotation, 0), Space.World);
}
void rotate (float speed)
{
gameObject.transform.Rotate(0, speed, 0);
}
}
However it is much more operative to use quaternions, try to check that out.
Hope that helps!
I do not get your point. If you want to turn a wheel it will alwiys be respect to its previous rotational postion. With that reference you can choose the value you like in the rotation vector Vector3(0, rotation, 0). If you want an absolute reference you can make a parent object and rotate this. Objects have the same world axis and when rotated or transformed, the children "suffer" the same transformation. You have to meke sure that the posistion in the childre is 0,0,0 as this is the distance respect to the parent position that will be the rotation pivot.
void setRotation()
{
Vector3 rot = this.gameObject.transform.rotation.eulerAngles;
GameObject wheelParent = gameObject.transform.parent.gameObject;
Vector3 rotationVector = new Vector3(0, 30, 0);//absolute turn set
Quaternion rotation = Quaternion.Euler(rotationVector);
wheelParent.transform.localRotation = rotation;
}
Hope that helps.
I'm trying to write a script in Unity which creates a type of radial menu around an object the player is directly facing, but the number of buttons in the menu is a variable.
I've generated the angles to the main menu the objects are supposed to appear at easily enough...
// int buttonCount = number of buttons
float buttonWidth = 360 / buttonCount;
for (int i = 1; i <= buttonCount; i++)
{
float maxAngle = buttonWidth * i;
float minAngle;
if (i == 0)
{
minAngle = 0f;
}
else if (i == buttonCount)
{
minAngle = 360 - buttonWidth;
}
else
{
minAngle = buttonWidth * (i - 1);
}
float buttonAngle = (minAngle + maxAngle) / 2;
}
...but now I'm trying to position the button objects at the corresponding angles around the central menu object and I don't know how?
This function takes as parameters the object you want the buttons to go around, the player gameobject so that you can orient the new buttons toward the player, the angle you want the button to be at, and the radius (distance the button will be from the buttonCenter). Its output is the button position in world space. You can call it for each button you want to add.
Vector3 positionButton(GameObject buttonCenter, GameObject player, float angle, float radius) {
//get the up and right vectors from the player object so we can orient the buttons
Vector3 up = player.transform.up;
Vector3 right = player.transform.right;
angle = Mathf.Deg2Rad * angle; //convert degrees to radians. radians=degrees * 2pi / 360
//cos(angle) give an x coordinate, on a unit circle centered around 0
//sin(angle) is the y coordinate on the unit circle
//take those values, multiply them by the up and right vectors to orient them to the player,
//multiply by the radius to move them the correct distance from the buttoncenter,
//and add the buttoncenter position so they circle around the correct point
Vector3 buttonPos =buttonCenter.transform.position + (radius * right * Mathf.Cos(angle)) + (radius* up * Mathf.Sin(angle));
return buttonPos;
}
First, define an origin and distance for each button from it. As you have the angles, you can apply trigonometry which should allow you to find the coordinate of a point given an angle, distance and origin point. The point will be defined by cos() and sin() of the angle.
Have a look at the 2nd section here