I am working on a 3D project, in Unity.
I have an object moving in a confined space. The object have a fixed velocity, and it bounces back once they reach the space limit.
I want it to change direction once every n seconds.
The problem I am facing is: How to rotate a 3D vector by a given angle.
In 2D is pretty easy, while in 3D I am not sure how to handle it.
Can someone help me with that?
In 2D this is simple...
In three 3 as well.
You can e.g. simply rotate a Vector3 about any given Euler angles via
var newVector = Quaternion.Euler(x, y, z) * oldVector;
question remains where you get those angles from and whether those are random or you are rather looking for Reflect the vector once the objects reach your defined area constraints.
You can as well simply invert the individual components like e.g.
// Note for simplicity reasons this is not an actual "distance"
// but rather the maximum of each individual axis
public float maxDistance;
private Rigidbody _rigidbody;
private void Start ()
{
_rigidbody = GetComponent<Rigidbody>();
_rigidbody.velocity = Random.insideUnitSphere.normalized;
}
private void FixedUpdate()
{
var vel = _rigidbody.velocity;
var pos = _rigidbody.position;
if(Mathf.Abs(pos.x) >= maxDistance)
{
vel.x *= -1;
}
if(Mathf.Abs(pos.y) >= maxDistance)
{
vel.y *= -1;
}
if(Mathf.Abs(pos.z) >= maxDistance)
{
vel.z *= -1;
}
_rigidbody.velocity = vel;
}
You can change directions by a certain angle on a certain axis, making it similar to 2D.
If you are moving the object with its transform you can easily rotate an object with the Rotate method on the game object transform.
//in a script attached to the game object you want to rotate
transform.Rotate(Vector3.up, Random.Range(90,270), Space.self);
You can now use transform.forward to move the object.
If you want to go deeper into 3D rotation have a look at Quaternions
You can use the Quaternion.operator * to rotate one rotation by another, or to rotate a vector by a rotation.
Consider also looking at the Vector3 class methods such as Reflect and RotateTowards.
Tip: To keep a consistent speed across all directions, consider normalizing the direction vector, (0,1,1) will travel faster than (0,0,1).
You can normalize any vector as follows:
Vector3 direction = Vector3.up + Vector3.forward; //(0,1,1)
direction = direction.normalized; //(0,0.71,0.71)
I managed to do the thing I wanted. Firstly, I computed a Vector3 that was orthogonal to the current transform.forward; then I rotate that angle around itself of a random angle between 0 and 360 degrees, and finally I used that generated vector as axis for the rotation to apply to the velocity of my object.
Here is the code:
public static void ChangeObjectDirection(Rigidbody rb, int angle, float velocity)
{
// get the transform of rb
var t = rb.gameObject.transform;
// get the current velocity direction
var direction = t.forward;
// generate a normalized orthogonal vector wrt the current velocity direction
var orth = OrthogonalVector(direction);
// rotate the orthogonal vector around himself of a random angle between 0 and 360
orth = Quaternion.AngleAxis(Random.Range(0, 360), direction) * orth;
// generate random rotational angle in degrees
var randomAngle = Random.Range(-angle, angle);
// rotate the current velocity direction of randomAngle around the orth vector
direction = Quaternion.AngleAxis(randomAngle, orth) * direction;
// update the velocity direction
t.forward = direction;
rb.velocity = direction * velocity;
}
private static Vector3 OrthogonalVector(Vector3 u)
{
var a = u.x;
var b = u.y;
var c = u.z;
Vector3 v;
if (b == 0 && c == 0)
{
v = new Vector3(0f, 1f, 1f);
}
else if (a == 0 && c == 0)
{
v = new Vector3(1f, 0f, 1f);
}
else if (a == 0 && b == 0)
{
v = new Vector3(1f, 1f, 0f);
}
else
{
if (c != 0)
{
// ax + bx + cz == 0 with x == 1 and y == 1 => z = (-a - b) / c
v = new Vector3(1f, 1f, (-a - b) / c);
}
else
{
// ax + bx + cz == 0 with y == 1 and z == 1 => c = (-b - c) / a
v = new Vector3((-b - c) / a, 1f, 1f);
}
}
v = v.normalized;
return v;
}
Related
I am working in unity/C# and trying to make a function that keeps an character in certain x range (-3 to 3). Below isthe code I got to work. Is there a way to simplify it?
//function creation to limit movement in the x axis
float rangeBoundX(int upperBound, int lowerBound, Vector3 i, float horMoveSpe = 0)
{
//will change velocity to keep the x value in the desired range. - velocity to mvoe away from the upper bound and positive velocity goes away from the lowerBound.
if (i.x > upperBound)
{
horMoveSpe = -1;
}
else if (i.x < lowerBound)
{
horMoveSpe = 1;
}
return horMoveSpe;
}
'private void FixedUpdate()'
{
Vector3 enemyforwardMove = transform.forward * enemySpeed * Time.fixedDeltaTime;
Vector3 horizontalMove = transform.position;
magn = rangeBoundX(3, -3, horizontalMove, magn);
horizontalMove = transform.right * magn * freq;
enemyRB.MovePosition(enemyRB.position + enemyforwardMove + horizontalMove);
}
Yes you can use Math.Clamp to bound any value Like this.
void update(){
float xPos = Mathf.Clamp(transform.position.x, -3, 3);
transform.position = new Vector3(xPos, transform.position.y, transform.position.z);
}
using this code you can bound the position of object horizontaly.
Yes, there is a way to simplify it. You can use Mathf.Clamp method https://docs.unity3d.com/ScriptReference/Mathf.Clamp.html
example usage: float xPos = Mathf.Clamp(xValue, xMin, xMax);
I am working on adding a helicopter to my 2d game and I need it to move in circular motion whilst moving on the x axis as well. Below you can find the code that I am using which uses the mathematical circle equation.
angle += speed * Time.deltaTime; //if you want to switch direction, use -= instead of +=
float x = startPoint.x + Mathf.Cos(angle) * radius;
float y = startPoint.y + Mathf.Sin(angle) * radius;
transform.position = new Vector2(x + 2, y);
The helicopter is rotating correctly but I can't figure out how I can make it move along the x axis. Concept image of how it should work below:
1) Make an empty game object
2) Parent your box to the empty game object
3) rotate the box around the empty game object
4) move the empty game object to the side
If you want to avoid adding an empty parent, you can keep track of the center of rotation separately, rotate around it, and move it over time.
public class hello_rotate : MonoBehaviour
{
float angle = 0;
float radius = 1;
float speed = 10;
float linear_speed = 1;
Vector2 centerOfRotation;
// Start is called before the first frame update
void Start()
{
centerOfRotation = transform.position;
}
// Update is called once per frame
void Update()
{
centerOfRotation.x = centerOfRotation.x + linear_speed * Time.deltaTime;
angle += speed * Time.deltaTime; //if you want to switch direction, use -= instead of +=
float x = centerOfRotation.x + Mathf.Cos(angle) * radius;
float y = centerOfRotation.y + Mathf.Sin(angle) * radius;
transform.position = new Vector2(x + 2, y);
}
}
I am making the Bouncing Ball using Processing. It works fine when I use the ball object once, but when I use it twice like ball1 & ball2, the balls appear on top of each other making the delusion that it is just one ball bouncing, although I'm setting their primary location and velocity a random number. So, where is the problem? (first argument is for velocity and the second is for x Coordinate)
Main class:
Ball ball1 = new Ball(int(random(0, 2)),int(random(width)));
Ball ball2 = new Ball(int(random(0, 2)),int(random(width)));
void setup() {
// Windows configurations
size(640, 360);
background(50);
}
void draw() {
// Draw the circle
ball1.display();
// Circle movements
ball1.movements();
// Movement limits
ball1.movementLimits();
// Draw the circle
ball2.display();
// Circle movements
ball2.movements();
// Movement limits
ball2.movementLimits();
}
Ball class:
float xCoordinates;
float yCoordinates;
float xVelocity;
float yVelocity;
final float gravity = 0.1;
class Ball {
Ball(int Velocity, int Coordinates) {
xCoordinates = Coordinates;
yCoordinates = height / 6;
if (Velocity == 0)
xVelocity = 2;
else
xVelocity = -2;
if (Velocity == 0)
yVelocity = 2;
else
yVelocity = -2;
}
void movementLimits() {
if (xCoordinates - 10 <= 0 || xCoordinates + 10 >= width)
xVelocity *= -1;
if (yCoordinates + 10 >= height)
yVelocity *= -0.9;
if (yCoordinates - 10 <= 0)
yVelocity *= -1;
}
void movements() {
xCoordinates += xVelocity;
yCoordinates += yVelocity;
yVelocity += gravity;
}
void display() {
background(50);
fill(255);
stroke(255);
circle(xCoordinates, yCoordinates, 20);
}
}
Problem 1:
Both of your objects are shaing the same cooridinates and velocity. They are stored globally so when one object changes it, the change is used by the other object as well. To fix this you should give your Ball class properties to hold the coordinates and velocities.
class Ball{
float x;
float y;
float dx;
float dy
public Ball(float x, float y, float dx, float dy){
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
}
}
Problem 2:
In the display function in Ball, you call background(50);. This will basicly cover the entire screan with the new background; over any previous balls which includes ball1. However, if you remove this line you'll get a kind of cool effect cause by all previous ball drawing sticking around. You should move the background(50); line to the beginning of the draw function. This way, you draw the two balls, draw over them with gray, then redraw the two balls in their new positions.
i'm trying to get the tangent vector for each point on the circle , i tried to use the derivative for the circle equation, but the result looks off in the viewport, so i'm wondering if i can find some help here
the code
public void OnDrawGizmos(){
step = (360.0f * Mathf.Deg2Rad) / numberOfPoints ;
CreateVertices();
}
void CreateVertices()
{
Points.Clear();
Normals.Clear();
Tangents.Clear();
float dynamicAngle = 0.0f;
for (int i = 0; i <= numberOfPoints; i++)
{
Vector3 point;
point.x = transform.position.x + Radius * Mathf.Cos(dynamicAngle);
point.y = transform.position.y + Radius * Mathf.Sin(dynamicAngle);
point.z = transform.position.z;
dynamicAngle = dynamicAngle + step;
if (i >= 1)
{
Gizmos.color = Color.red;
Gizmos.DrawLine(Points[i - 1], point);
Gizmos.color = Color.white;
}
Points.Add(point);
CalculateNormals(dynamicAngle ,point);
CalculateTangents(dynamicAngle,i , point);
}
}
void CalculateNormals(float dynamicAngle , Vector3 point)
{
Vector3 Normal = (point - transform.position).normalized;
Gizmos.color = Color.magenta;
Gizmos.DrawLine(Normal, point);
Gizmos.color = Color.white;
Normals.Add(Normal);
}
void CalculateTangents(float dynamicAngle,int i ,Vector3 point)
{
Vector3 tangent;
tangent = new Vector3(-Normals[i].y, Normals[i].x, 0);
tangent.Normalize();
Gizmos.color = Color.blue;
Gizmos.DrawLine( point, tangent);
Gizmos.color = Color.white;
Tangents.Add(tangent);
}
Blue is the tangents purple is the normals, as you can see they are not perpendicular:
To understand my issue better here is a gif from unity viewport:
Since you already calculated the normals you can use the cross product to get the corresponding tangents
Vector3 up = new Vector3(0, 0, 1); // up side of your circle
Vector3 tangent = Vector3.Cross(normal, up);
If you only need to use circles on a specific plane you can also use this simplification
Vector3 tangent = new Vector3(-normal.y, normal.x, 0);
Edit:
The normals and tangents are direction vectors. They point from point in the direction the normal / tangent whould point. To draw the tangent, you have to pass the correct start and end points of the line by using
Gizmos.DrawLine(point, point + tangent);
If you move the GameObject away from the origin you will notice that the normals also get deformed, this has the same reason.
You are using 2D parametric equations:
x = x0 + r*cos(a)
y = y0 + r*sin(a)
z = z0
a = <0,2*Pi>
Tangent is unit circle coordinate with center (0,0,0) but shifted by 90 degrees:
tx = cos(a (+/-) pi/4)
ty = sin(a (+/-) pi/4)
tz = 0
Similarly bi-tangent is:
bx = (+/-) cos(a)
by = (+/-) sin(a)
bz = 0
and finally normal is
nx = 0
ny = 0
nz = (+/-) 1
The signs depends on your coordinate system conventions and movement direction.
So I'm trying to create a realistic trampoline jump instead of the player falling through the trampoline and then slingshotting back up, whilst instead allowing the player to instantly shoot upon contact with the trampoline and come down a relative gravity.
Where am I going wrong and what can I do to fix it?
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(CharacterController))]
public class small_bounce_script: MonoBehaviour {
public float speed = 6.0F;
public float jumpSpeed = 8.0F;
public float gravity = 20.0F;
private Vector3 moveDirection = Vector3.zero;
private Vector3 bounce = Vector3.zero;
void Update() {
CharacterController controller = GetComponent<CharacterController>();
if (controller.isGrounded) {
if (bounce.sqrMagnitude > 0) {
moveDirection = bounce;
bounce = Vector3.zero;
} else {
moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
moveDirection = transform.TransformDirection(moveDirection);
moveDirection *= speed;
}
if (Input.GetButton("Jump"))
moveDirection.y = jumpSpeed;
}
moveDirection.y -= gravity * Time.deltaTime;
controller.Move(moveDirection * Time.deltaTime);
}
void OnTriggerEnter(Collider other) {
Debug.Log ("Controller collider hit");
Rigidbody body = other.attachedRigidbody;
// Only bounce on static objects...
if ((body == null || body.isKinematic) && other.gameObject.controller.velocity.y < -1f) {
float kr = 0.5f;
Vector3 v = other.gameObject.controller.velocity;
Vector3 n = other.normal;
Vector3 vn = Vector3.Dot(v,n) * n;
Vector3 vt = v - vn;
bounce = vt -(vn*kr);
}
}
}
A trampoline reacts like a spring device. Let's assume gravity is in Y direction and the trampoline surface is positionied in the X,Z plane.
Then your Y coordinate y is proportional to a sine function during OnTriggerStay. Velocity v in Y direction as 1st derivative of y is then a cosine function, while X and Z velocity remain constant.
y (t) = yMax * sin (f * t)
v (t) = yMax * f * cos (f * t)
Considering conservation of energy, we have:
E = 0.5 * m * vMax² = 0.5 * k * yMax²
=> yMax = ± SQRT (k / m) * vMax
vMax := speed in Y direction when hitting the trampoline. ± because for landing and starting
yMax := maximum amplitude when v == 0, i.e. hwo deep should the player sink before returning
k := spring constant defining trampoline behaviour i.e. how strong it is
m := player's mass
f := SQRT (k / m)
So all you need to do is playing around with the spring constant and have something like this in your Update method:
Vector3 velocity = rigidbody.velocity;
float elapsedTime = Time.time - timestampOnEnter;
velocity.y = YMax * FConst * Mathf.cos (FConst * elapsedTime);
rigidbody.velocity = velocity;
Member var timestampOnEnter is taken in OnTriggerEnter, FConst is the constant we called f in the maths part.