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)
Related
In my 2D game, the player Prefab has children which are weapons (like swords) so that when the player moves, the weapons translate in world space with his movements while maintaining a constant local position (until of course it is time to attack). The weapons automatically point towards nearby enemies, and swing when they get close enough.
I want the weapon to follow a swing arc by rotating around a pivot point defined at half of the weapon's range in the direction of the enemy. Once the weapon starts the swing, the arc's local position and rotation should remain unchanged and no longer care about the enemy position while the world arc will obviously translate with the player. The weapon should follow this arc purely relative to the player.
RotateAround seems to only work in world space, and thus causes strange problems when trying to rotate around an object in world space while the weapon's world position (as well as my desired pivot point's world position) would be translating with the player. Also, the point that I need it to rotate around needs to be relative to the local space, since when the player moves, the weapon needs to maintain its local arc while also translating with the player.
I also tried using Vector3.Slerp on the weapon's transform.localPosition, which seemed like it would be the perfect solution, but I can't seem to get the arc to match what I envision a good round swing would look like.
The attack consists of three parts: Backswing, Foreswing, and Recovery. The part that I care most about is the foreswing arc, as the others can be acheived easily with simply local rotations and lerping.
const int BACKSWING = 0;
const int FORESWING = 1;
const int RECOVER = 2;
float[] timeFrac = { .15f, .25f, .6f };
float[] rotations = { 120f, -240f, 120f };
float backSwingDistMultiplier = .5f;
//Swing Attack
public override IEnumerator Attack(float startAngle) {
var totalAttackTime = GetAttackTime();
var backSwingDist = backSwingDistMultiplier * Range;
var startPos = transform.localPosition;
var slerpCenterDiff = PerpDir(dir).normalized;
//Interpolation arrays
float[] swingTimes = { timeFrac[BACKSWING] * totalAttackTime,
timeFrac[FORESWING] * totalAttackTime,
timeFrac[RECOVER] * totalAttackTime };
float[] startAngles = { startAngle,
startAngle + rotations[BACKSWING],
startAngle + rotations[BACKSWING] + rotations[FORESWING] };
Vector3[] swingPositions = { startPos - (dir - slerpCenterDiff ) * backSwingDist,
startPos + dir * Range + slerpCenterDiff * backSwingDist };
Vector3[] slerpCenters = { (startPos + swingPositions[BACKSWING]) * .5f + slerpCenterDiff ,
((swingPositions[BACKSWING] + swingPositions[FORESWING]) * .5f) + slerpCenterDiff };
Vector3[] slerpStarts = { startPos - slerpCenters[BACKSWING],
swingPositions[BACKSWING] - slerpCenters[FORESWING]};
Vector3[] slerpEnds = { swingPositions[BACKSWING] - slerpCenters[BACKSWING],
swingPositions[FORESWING] - slerpCenters[FORESWING]};
timer = 0;
float percentDone;
//A swing attack has backswing, foreswing, and recovery
for (int swing = 0; swing <= 2; swing++) {
while (timer < swingTimes[swing]) {
percentDone = timer / swingTimes[swing];
//Backswing and Foreswing will slerp
if (swing < RECOVER) {
transform.localPosition = Vector3.Slerp(slerpStarts[swing], slerpEnds[swing], percentDone);
transform.localPosition += slerpCenters[swing];
} else { //Recover will lerp
transform.localPosition = Vector3.Lerp(swingPositions[FORESWING], startPos, percentDone);
}
transform.localRotation = Quaternion.Euler(0, 0,
startAngles[swing] + rotations[swing] * percentDone);
timer += Time.deltaTime;
yield return null;
}
transform.localRotation = Quaternion.Euler(0, 0, startAngles[swing] + rotations[swing]);
timer -= swingTimes[swing];
}
transform.localPosition = startPos;
}
I would just make an animation out of it, but I need the range to be dynamic, which is next to impossible to achieve with keyframes.
I was able to get my desired outcome by defining the arc centers in localPosition first:
var backswingArcCenter = (startPos + swingPositions[BACKSWING]) * 0.5f;
var foreswingArcCenter = (swingPositions[BACKSWING] + swingPositions[FORESWING]) * 0.5f;
and then calling RotateAround using that position added to the player's world space
if (swing == FORESWING) {
transform.RotateAround(player.transform.position + foreswingArcCenter,
Vector3.forward, Time.deltaTime / swingTimes[swing] * 270f);
} else if (swing == BACKSWING) {
transform.RotateAround(player.transform.position + backswingArcCenter,
Vector3.forward, Time.deltaTime / swingTimes[swing] * -180f);
}
I tried to make a game similar to a game called pick a lock game. However, I was struggle with spawning randomly a point around the border of circle but not in the circle. For more details, I want to spawn a gameObject at the border of a circle only but not every where in the circle.
Please help me :<
Thank you very much and have a good day!
p/s: I just a beginner to unity engine and syntax. It'll be great if you give me some advice of how to self-learning unity. Thank you so much
First of all, I really like Dot Product. I took a random place around the reference object (which is in this case transform.position). Then take direction between randomPos and reference object. Calculated Dot Product with Vector3.Dot(). Take the angle between randomPos and our transform.forward with the result of Dot Product. Calculate the x and z values with Cos and Sin functions. Dot Product Angle always gives 0 to 180 values so I gave randomness to z-axis with multiply dotProductAngle with (Random.value > 0.5f ? 1f : -1f). If you don't do this, the given random position will be always in front of the player.
3D Space
private void SpawnSphereOnEdgeRandomly3D()
{
float radius = 3f;
Vector3 randomPos = Random.insideUnitSphere * radius;
randomPos += transform.position;
randomPos.y = 0f;
Vector3 direction = randomPos - transform.position;
direction.Normalize();
float dotProduct = Vector3.Dot(transform.forward, direction);
float dotProductAngle = Mathf.Acos(dotProduct / transform.forward.magnitude * direction.magnitude);
randomPos.x = Mathf.Cos(dotProductAngle) * radius + transform.position.x;
randomPos.z = Mathf.Sin(dotProductAngle * (Random.value > 0.5f ? 1f : -1f)) * radius + transform.position.z;
GameObject go = Instantiate(_spherePrefab, randomPos, Quaternion.identity);
go.transform.position = randomPos;
}
Example in 3D
2D Space
private void SpawnSphereOnEdgeRandomly2D()
{
float radius = 3f;
Vector3 randomPos = Random.insideUnitSphere * radius;
randomPos += transform.position;
randomPos.y = 0f;
Vector3 direction = randomPos - transform.position;
direction.Normalize();
float dotProduct = Vector3.Dot(transform.forward, direction);
float dotProductAngle = Mathf.Acos(dotProduct / transform.forward.magnitude * direction.magnitude);
randomPos.x = Mathf.Cos(dotProductAngle) * radius + transform.position.x;
randomPos.y = Mathf.Sin(dotProductAngle * (Random.value > 0.5f ? 1f : -1f)) * radius + transform.position.y;
randomPos.z = transform.position.z;
GameObject go = Instantiate(_spherePrefab, randomPos, Quaternion.identity);
go.transform.position = randomPos;
}
Example in 2D
I hope this helps you out 👍
You need to try something out and show were you get stuck.
I would start trying to understand point rotation with a raw language to understand what is happening, for example, point rotation in plain c#:
// Rotate B around A by angle theta clockwise
private static (double x, double y) Rotate(
(double x, double y) A,
(double x, double y) B,
double theta)
{
double s = Math.Sin(theta);
double c = Math.Cos(theta);
// translate point back to origin:
B.x -= A.x;
B.y -= A.y;
// rotate point clockwise
double xnew = B.x * c - B.y * s;
double ynew = B.x * s + B.y * c;
// translate point back:
B.x = xnew + A.x;
B.y = ynew + A.y;
return B;
}
Then you can check some basic vectorial operations and their math background which is not that hard and give a look to what can be done with the unity API:
Vector3.Reflect and
Vector3.RotateRowards
Sure you can figure something out.
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'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
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.