How do I make Rigidbody2D.MovePosition move a gameobject in local space? - c#

I found a way to find what the title says for Rigidbody but not for Rigidbody2D, since the original method involves using Transform.TransformDirection(), which only functions on Vector3 while Rigidbody2D.MovePosition functions on Vector2. I essentially need a bullet to move forward, with two more bullets moving forward but rotated at a 45 degree angle difference.
How would i go about doing this?

Your question reminded me of a game I made for a game jam a while ago so I checked the code, and it seems I used Quaternion.AngleAxis to rotate the bullets.
I'm assuming you have a reference to the prefab you want to clone (in this example, it's projectilePrefab), as well as a firePoint Transform that represents the position you want to shoot from and the rotation of the middle projectile.
// Middle Bullet
GameObject mBullet = Instantiate(projectilePrefab, firePoint.position, firePoint.rotation);
var mRb = mBullet.GetComponent<Rigidbody2D>();
middleRb.AddForce(mRb.transform.up * velocity, ForceMode2D.Impulse);
// Left Bullet
GameObject lBullet = Instantiate(projectilePrefab, firePoint.position, firePoint.rotation);
// Rotate here
lBullet.transform.up = Quaternion.AngleAxis(-45, Vector3.forward) * firePoint.transform.up;
var lRb = lBullet.GetComponent<Rigidbody2D>();
lRb.AddForce(lBullet.transform.up * velocity, ForceMode2D.Impulse);
// Right Bullet
GameObject rBullet = Instantiate(projectilePrefab, firePoint.position, firePoint.rotation);
// Rotate here
rBullet.transform.up = Quaternion.AngleAxis(45, Vector3.forward) * firePoint.transform.up;
var lRb = lBullet.GetComponent<Rigidbody2D>();
lRb.AddForce(lBullet.transform.up * velocity, ForceMode2D.Impulse);
Let me know if you run into any issues with this code, I can't test it right now.

I'm assuming you have a bullet prefab, and that you are instantiating 3 bullets at once, but want 2 of them to be at -45 and 45 degrees respectively.
//bullet is whatever prefab you have
var bl = Instantiate(bullet);
var bl = Instantiate(bullet);
bl.transform.rotation = //Set rotation here to 45 deg
var bl = Instantiate(bullet);
bl.transform.rotation = //Set rotation here to -45 deg

Between Vector2 and Vector3 exists an implicit conversion and vise versa. Both types can more or less be used exchangeable which will either create a Vector3 where z is simply 0 or create a Vector2 only using the x and y and simply ignoring the z.
You can simply pass a Vector3 as parameter to Rigidbody2D.MovePosition and it will implicitly convert it to a Vector2 ignoring the Z component.

Related

How to tilt an object depending on the direction of the player

How do I correctly calculate the rotation angle so that the object dodges the player? It means that whichever side the player comes from, the object must turn away from the player depending on its direction.
I want the effect like in the video but without Joint, only rotation angle: https://www.youtube.com/watch?v=GJhiR3SOyXs
I need it for spherical map. And right now it looks like this.
calculate the direction Vector from player to the object.
calculate the axis of rotation by calculating the cross product of the direction Vector and the world up. Doing so creates a vector that is orthogonal to both, which is what we need.
calculate the angle by how much to rotate. This is done by clamping the distance (magnitude of direction) from 0 to the maximum effect distance and dividing it by the effect distance. Doing so creates a value from 0 to 1. However we need a value from 1 to 0, so there is no effect when the player is far away from the object and the maximum when close. To do so you simply subtract the initial value from 1. By multiplying the result with the max angle we calculate an angle in the range of 0 to maxAngle.
Finally we calculate the object rotation by multiplying the initial rotation with the rotation around the axis.
[SerializeField] Transform player;
[SerializeField] float effectMaxDistance=1;
[SerializeField] float maxAngle=50;
Quaternion initialRotation;
void Start(){
initialRotation = transfrom.rotation;
}
void Update(){
Vector3 dir = player.position - transform.position;
Vector3 axis = Vector3.Cross(dir, Vector3.up);
float angle = (1-(Mathf.Clamp(dir.magnitude, 0 effectMaxDistance) / effectMaxDistance)) * maxAngle;
transform.rotation = initialRotation * Quaternion.AngleAxis(angle, axis);
}
Note that cross products and Quaternion multiplications are not commutative and need to be done in this exact order!
Bonus answer:
If you're on a sphere you need to use the normal of the ground as the up direction. Since you've probably initially rotated the tree the right way up, you could do the following:
[SerializeField] Transform player;
[SerializeField] float effectMaxDistance=1;
[SerializeField] float maxAngle=50;
Quaternion initialRotation;
Vector3 initialUp;
void Start(){
initialRotation = transfrom.rotation;
initialUp = transfrom.up;
}
void Update(){
Vector3 dir = player.position - transform.position;
Vector3 axis = Vector3.Cross(dir, initialUp);
float angle = (1-(Mathf.Clamp(dir.magnitude, 0 effectMaxDistance) / effectMaxDistance)) * maxAngle;
transform.rotation = initialRotation * Quaternion.AngleAxis(angle, axis);
}

Need to rotate object on z axis to point at/look at mouse on xy plane

I am making a 3d side scroller. need to rotate gun on z axis to face mouse on x,y plane. This is What I have so far. Tried lookat and angle. They sort of worked but would not point correctly. only half of the screen. I just think I am missing something.
void FixedUpdate()
{
screenPosition = Input.mousePosition;
screenPosition.z = 1; //mainCamera.nearClipPlane + 1;
worldPosition = mainCamera.ScreenToWorldPoint(screenPosition);
worldPosition.z = 0;
//Angle?????
transform.rotation = Quaternion.Slerp(rb.transform.rotation,
Quaternion.Euler(0,0,angle), 0.7f);
}
In order to get the angle there is no need to convert to world space.
Indeed actually it is way easier to go the other way round and convert your objects position into screen space.
Then you can simply use the Mathf.Atan2 on the pixel space delta
var mousePosition = Input.mousePosition;
var objectScreenPosition = mainCamera.WorldToScreenPoint(rb.position);
var direction = mousePosition - objectScreenPosition; // No need to normalize btw
var angle = Mathf.Atan2(direction.x, direction.y) * Mathf.Rad2Deg;
// since this seems to be a rigidbody I would rather use this
rb.MoveRotation(Quaternion.Slerp(rb.rotation, Quaternion.Euler(0, 0, angle), 0.7f);
// NOTE: Or in case your rb is actually Rigidbody2D then rather simply
rb.MoveRotation(Mathf.Lerp(rb.rotation, angle, 0.7f));

Unity 3D - How to gllobaly rotate one object based on second

I have got a very large problem with rotation in Unity. What I want:
I have two 3D objects. Just one is for player manipulating, second object Transform.rotation and Transform.position is dependent on object number one with scale of 1/10. It means if I will move first object from (0,0,0) to (10,30,90) then obj.2 will move from (0,0,0) to (1,3,9). It's simple. But I have got LARGE problem with rotation.
I can't make rotation on normal transform because it's based on "local position".
Below I present my problem with simplest 2D object situation:
As you can see when I rotate red object +90 degrees the second object rotate +9 degrees and the axes become different in relation to the world. After more transformations in 3D world it make a large mess. For example after some transformations if I will want to rotate 3D object from me (like using accelerator on motocycle) on first make second object rotating from left to right (because it's based on object axis).
Of course using Transform.Rotate instead of Transform.localRotate (or Transform.EulerAngles instead of Transform.localEulerAngles) is not a solutions because it's means only if objects are childrens (it this this case are not).
WHAT I FOUND:
Using Transform.Rotate(Xdegree,Ydegree,Zdegree, Space.World) is solution for rotating second object !
What I need:
Xdegree, Ydegree and Zdegree from first (manipulated by player) object.
Transform.EulerAngles and Transform.Rotation DOESN'T work because it's returns "local objects" rotations.
So... I know that if 3D obj.2 rotation is (0;30;0) and i use obj2.Rotate(45,0,0) then the obj.2 rotation will be (~37.76;~39.23;~26.56) and it's okay. But I dont know how to convert the other way (from "local" rotation XYZ to degrees that I can use on Transform.Rotate() (of course I will divided this values (xyz) by 10 at the end because I have got 1/10 moving scale))
If you need one GameObject to have 1/10 of the rotation and position of another, you could use something like:
//the player-controlled cube
public Transform t1;
//the 1/10 cube
public Transform t2;
void Update(){
//set the position of t2 to 1/10 of the position of t1
t2.position = 0.1f * t1.position;
//get the axis and angle of t1's rotation
t1.rotation.ToAngleAxis(out float angle, out Vector3 axis);
//t2 should be rotated in the same direction (axis), but with 1/10th of the angle
t2.rotation = Quaternion.AngleAxis(angle * 0.1f, axis);
}
Edit: To allow resetting delta rotation and changing targets, you could do something like this. Note: this glitches when it wraps more than a full circle, I'm not an expert on Quaternions so you'd have to figure it out yourself.
//the player-controlled cube
public Transform t1;
//the 1/10 cube
public Transform t2;
private Vector3 t1originalPosition;
private Quaternion t1originalRotation;
private Vector3 t2originalPosition;
private Quaternion t2originalRotation;
void Start()
{
ResetTarget(t1);
}
void Update()
{
if (t1 != null)
{
//set the position of t2 to 1/10 of the position of t1
t2.position = t2originalPosition + 0.1f * (t1.position - t1originalPosition);
Quaternion t1Rotation = t1.rotation * Quaternion.Inverse(t1originalRotation);
//get the axis and angle of t1's rotation
t1Rotation.ToAngleAxis(out float angle, out Vector3 axis);
//t2 should be rotated in the same direction (axis), but with 1/10th of the angle
t2.rotation = Quaternion.AngleAxis(angle * 0.1f, axis) * t2originalRotation;
}
}
public void ResetTarget(Transform target = null)
{
t2originalPosition = t2.position;
t2originalRotation = t2.rotation;
t1 = target;
t1originalPosition = t1.position;
t1originalRotation = t1.rotation;
}
Use quaternions instead of the euler angles (xyz rotation angles). And simply give the global rotation value (quaternion) of one object to the other.
To add together quaternions, you just multiply them together.

How do I Instantiate Objects In Unity?

I am making a top down game in Unity, how do I instantiate objects based on the players current position from the top of the camera view in Unity?
So far I have tried:
Instantiate(prefab, player.transform.position * 2 * Space.World)
but this didn't work. This just spawned objects from the center of the camera.
Space is simply an enum and has nothing to do with what you are trying! (Its purpose is to define the relative transform space for e.g. Transform.Rotate or Transform.Translate)
in that enum afaik
Space.World simply has the int value 0
Space.Self has the int value 1
so what you actually do is
Instantiate(prefab, player.transform.position * 2 * 0);
which equals
Instantiate(prefab, Vector3.zero);
which means the object is instantiated at World position 0,0,0.
Also using
Instantiate(prefab, player.transform.position * 2);
looks a bit strange. Are you sure you want to duplicate the actual position of the player? This would mean the spawned object is always on a line with the player and the World 0,0,0 and always double as far from the center as the player.
To me it sounds more like you rather want to spawn something in front of the player ... depending on the player's view direction (in topdown games usually player.transform.up or player.transform.right) so I guess what you are trying to do instead is something like
Instantiate(prefab, player.transform.position + player.transform.forward * 2);
which would instead spawn the object 2 Unity units in front of the player object
After your comment it sounds like instead you want to spawn the object at the player position on the X-axis but "over it" on the Y-axis so
Instantiate(prefab, player.transform.position + Vector3.Up * 2);
maybe you'll have to tweak the 2 depending how your player can move and how far it has to be to be "off screen". Alternatively you could also use a bit more complex using Camera.ScreenToWorldPoint
// get players position
var playerPos = player.transform.position;
// get camera's position
var cameraPos = Camera.main.transform.position;
// get difference on Z-axis
var cameraDistance = cameraPos.z - playerPos.z;
// Get the world 3d point for the upper camera border
// don't care about the X value
// and as distance we use the z-distance to the player
var cameraTopPoint = Camera.main.ScreenToWorldPoint(new Vector3(0, Camera.main.pixelHeight, cameraDistance);
// use the players X-axis position
cameraTopPoint.x = player.transform.x;
// to spawn it exactly on the Screen border above the player
Instantiate(prefab, cameraTopPoint);
// or to spawn it a bit higher
Instantiate(prefab, cameraTopPoint + Vector3.Up * 1);
Update
you said you want the prefab to be spawned on the "opposide" of the player position on the X axis.
If your Camera is static on world x=0 than this is actually quite simple:
cameraTopPoint.x = -player.transform.x;
If your camera is moving or not on x=0 than we have to calculate it already on the screen position level:
// get players position
var playerPos = player.transform.position;
// additionally convert the player position to screen space
var playerScreenPos = Camera.main.WorldToScreenPoint(playerPos);
var centerX = Camera.main.pixelWidth / 2.0f;
// get the difference between camera an player X (in screen space)
var differenceX = Mathf.Abs(playerScreenPos.x - centerX);
// here calculate the opposide side
var targetX = centerX + differenceX * (playerScreenPos.x < centerX ? 1 : -1);
// or alternatively if you want e.g. that the prefab always
// spawn with a bit of distance to the player even though he is very close
// to the center of the screen you could do something like
//var targetX = centerX + centerX / 2 * (playerScreenPos.x < centerX ? 1 : -1);
// Get the world 3d point for the upper camera border
// with the calculated X value
// and as distance we use the z-distance to the player
var cameraTopPoint = Camera.main.ScreenToWorldPoint(new Vector3(targetX, Camera.main.pixelHeight, playerScreenPos.z);
// to spawn it exactly on the Screen border above the player
Instantiate(prefab, cameraTopPoint);
// or to spawn it a bit higher
Instantiate(prefab, cameraTopPoint + Vector3.Up * 1);
Other Update
Ok so you want the prefab always spawn next to the player in a certain distance. The side depends on which side of the screen the player is so you could actually use the first approach again but just add the desired distance:
// Adjust this is the Inspector (in Unity units)
public float spawnDistanceToPlayer;
...
// get players position
var playerPos = player.transform.position;
// additionally convert the player position to screen space
var playerScreenPos = Camera.main.WorldToScreenPoint(playerPos);
var centerX = Camera.main.pixelWidth / 2.0f;
// Get the world 3d point for the upper camera border
// with the calculated X value
// and as distance we use the z-distance to the player
var cameraTopPoint = Camera.main.ScreenToWorldPoint(new Vector3(playerScreenPos.x, Camera.main.pixelHeight, playerScreenPos.z);
// now add or reduce spawnDistanceToPlayer
// depending on which side of the screen he is
cameraTopPoint.x += spawnDistanceToPlayer * (playerScreenPos.x < centerX ? 1 : -1);
// OR if your camera sits static on X=0 anyway you also could compare
// the player position directly without calculating in screenspace:
cameraTopPoint.x += spawnDistanceToPlayer * (playerPos.x < 0 ? 1 : -1);
// to spawn it exactly on the Screen border above the player
Instantiate(prefab, cameraTopPoint);
// or to spawn it a bit higher
Instantiate(prefab, cameraTopPoint + Vector3.Up * 1);
Typed on smartphone so might not be "copy-paste-able" ;)

Why does Physics2D produce different results for AddForce() versus velocity?

Why does Gravity in Physics2D engine for Unity2D act different when these two following lines of codes are implemented alternately?
For example, I have attached to my player sprite a Player Controller C# script:
private float speed = 500f;
RigidBody2D playerChar = null;
And then to make my character walk:
Vector2 vec = new Vector2 (Input.GetAxis("Horizontal"), 0);
playerChar.AddForce(vec * speed);
And the Gravity is set to 50
Result 1: My character avatar falls down normally.
Meanwhile when I do:
Vector2 vec = new Vector2 (Input.GetAxis("Horizontal"), 0);
playerChar.velocity = (vec * speed);
And the Gravity is still set to 50
Result 2: My character now takes a long time to fall (it slowly "floats" down).
Why is that?
This is because you're forcing the y component of the rigidbody's velocity to zero.
When you add force, it adds, it doesn't replace.
When you set the velocity, you're specifically setting it to a Vector2 that has a y value of 0, gravity then kicks in on the fixed update cycle and adds a small amount of gravity, causing your player to fall slowly. Then Update happens again and you force the y value back to 0 once more.

Categories

Resources