TransformDirection not behaving as expected when creating RTS camera - c#

I'm trying to create a RTS game from scratch using Unity engine. I started with this Unity RTS game tutorial and I'm setting up the camera. Basically what I do is, that I generate [x,0,z] vector for movement. I then need to apply it on camera.
It works perfectly if the camera isn't rotated - but it usually IS rotated and must be rotatable.
According to the tutorial and the docs, I can transform vector to camera's point of view using Camera.main.transform.TransformDirection(MyVector). Which is what I do:
//Get mouse position
float xpos = Input.mousePosition.x;
float ypos = Input.mousePosition.y;
//Create vector for movement
Vector3 movement = new Vector3(0, 0, 0);
//horizontal camera movement
if (xpos >= 0 && xpos < ResourceManager.ScrollWidth)
{
movement.x -= ResourceManager.ScrollSpeed;
}
else if (xpos <= Screen.width && xpos > Screen.width - ResourceManager.ScrollWidth)
{
movement.x += ResourceManager.ScrollSpeed;
}
//vertical camera movement
if (ypos >= 0 && ypos < ResourceManager.ScrollWidth)
{
movement.z -= ResourceManager.ScrollSpeed;
}
else if (ypos <= Screen.height && ypos > (Screen.height - ResourceManager.ScrollWidth))
{
movement.z += ResourceManager.ScrollSpeed;
}
//make sure movement is in the direction the camera is pointing
//but ignore the vertical tilt of the camera to get sensible scrolling
movement = Camera.main.transform.TransformDirection(movement);
//Up/down movement will be calculated diferently
movement.y = 0;
However, if I do this, the vertical movement doesn't work for the inital camera rotation, and when I rotate camera, the movement happens at strange speeds.
How can I properly apply the movement vector on camera?

Well, the problem here is that TransformDirection will preserve the lenth of the vector. So if you tilt the camera downwards the overall length get distributed between the y, x and z axis. Since you simply eliminate the y part the vector will get shorter. The more the camera is tilted the more you will lose on the other two axes.
It's usually easier to just rotate a unit vector, modify it as you want and normalize it when done. This unit vector can then be scaled by your actual movement vector. Something like that:
// [...]
Vector3 forward = Camera.main.transform.forward;
Vector3 right = Camera.main.transform.right;
forward.y = 0f;
right.y = 0f; // not needed unless your camera is also rotated around z
movement = forward.normalized * movement.z + right.normalized * movement.x;
// [...]
Another approach is to seperate your two rotations. So by using an empty GameObject that is only rotated around y and have the camera a child of that gameobject. Now the camera will only be rotated around the local x axis to tilt it the way you want. To lat the camera look into a different direction you rotate the empty GameObject around y. That way you can use the transform of the empty GameObject to transform the direction just the way you did in your code since the tilt of the camera doesn't matter.

Related

How do I get a bullet prefab's sprite to use the same rotation as my weapon?

I'm working on a top down concept where the gun revolves around the player and flips according to the side your crosshair is on (similar to ZERO Sievert). I'm trying to have my bullet sprite have the correct rotation when firing in relation to my players weapon.
Below is how I'm instantiating the bullet in a shooting script which fires the correct way but the sprite itself is not rotated correctly.
void Shoot()
{
GameObject bullet = Instantiate(bulletPrefab, firingPoint.position, firingPoint.rotation);
Rigidbody2D rb = bullet.GetComponent<Rigidbody2D>();
rb.AddForce(firingPoint.right * bulletForce, ForceMode2D.Impulse);
}
and in my weapon handling script this is my implementation of the weapon rotation, I am flipping the weapons y scale to correct the sprite for now.
private void FixedUpdate()
{
RotateWeapon();
if (crosshair.transform.position.x < 0)
{
FlipWeapon();
}
}
void RotateWeapon()
{
float AngleRad = Mathf.Atan2(crosshair.transform.position.y - currentWeapon.transform.position.y, crosshair.transform.position.x - currentWeapon.transform.position.x);
float AngleDeg = (180 / Mathf.PI) * AngleRad;
currentWeapon.transform.rotation = Quaternion.Euler(0, 0, AngleDeg);
}
void FlipWeapon()
{
currentScale = transform.parent.localScale;
currentScale.y *= -1;
currentWeapon.transform.localScale = currentScale;
}
I'm currently at a standstill on how to achieve this as most resources I've come across for top down shooting have the player turning up to a full 360 degrees where my player only faces left or right and the weapon itself only has a range of motion of 180 degrees on either side before its set to flip.
Maybe you can try and add an offset so when you Instantiate the bullet it will look something like this:
GameObject bullet = Instantiate(bulletPrefab, firingPoint.position, firingPoint.rotation + offset);
here offset will be a rotational x y z coordinate so you would want to rotate it on the z coordinate (try all of them until you fin the right one) and rotate it either -90 degrees or 90 deegrees. You might have to rotate it 180 degress but dont use 270 instead use -90. Trust me it will help in your later days in coding. Also make sure to set offset as a variable:
private Vector3 offset = 0, 0, -90;
!Remember you can change the x y z coordinates!

Unity: How to limit the rotation of the z axis using Wheel Joint 2D?

I am creating a Jump and run game but you are driving with a car. I use the the Wheel Joint 2D collider and I am also able to jump. Here is my code for the movement:
void Update()
{
movement = Input.GetAxis("Horizontal");
if (Input.GetButtonDown("Jump") && IsGrounded())
{
carRb.velocity = new Vector2(carRb.velocity.x, jumpForce);
}
if (Input.GetButtonUp("Jump") && carRb.velocity.y > 0f)
{
carRb.velocity = new Vector2(carRb.velocity.x, carRb.velocity.y * 0.5f);
}
}
private void FixedUpdate()
{
backTire.AddTorque(-movement * speed * Time.fixedDeltaTime);
frontTire.AddTorque(-movement * speed * Time.fixedDeltaTime);
carRb.AddTorque(-movement * carTorque * Time.fixedDeltaTime);
}
It works just fine but when I am fast and jump I rotate completely around my own axis and I land on my head and can't move anymore. Therefore I want to limit the rotation of the z-axis to a certain degree so that it won't happen anymore. I looked up how to do it but it doesn't fit for my car-context. Do you have any idea?
I would be very grateful
You have multiple options to solve this:
Freeze the rotation axis on the rigibody (very limiting option)
Detect the faulty position as "upside-down + grounded" and respawn the vehicle after 2s
Forcefully rotate the vehicle back to normal rotation when it's grounded (not in air anymore, so you still allow flips in the air)
Limit the angle via script.
Last thing could be done like this:
Vector3 eulerRot = rb.rotation.eulerAngles; // read current rotation
eulerRot.z = Mathf.Clamp(eulerRot .y, minRotation, maxRotation); // clamp it only on z axis.
rb.rotation = Quaternion.Euler(eulerRot); // set clamped rotation

Finding the lowest y point of screen bounds

I am using c# in unity and I need to calculate the lowest y position of the camera (2D) however I only know how to find the height i.e.
private Vector2 screenBounds;
void Start()
{
screenBounds = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, Camera.main.transform.position.z));
}
void Update()
{
screenBounds = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, Camera.main.transform.position.z));
if (transform.position.y < screenBounds.y)
Destroy(this.gameObject);
}
}
I am trying to use this code to despawn old objects.
What you get is the top-right corner
Screenspace is defined in pixels. The bottom-left of the screen is (0,0); the right-top is (pixelWidth,pixelHeight).
Also I assume your camera position is e.g. z = -10 and you want a world point in front and not behind the camera
So if you want the bottom border you would rather use
screenBounds = Camera.main.ScreenToWorldPoint(new Vector3 (0, 0, -Camera.main.transform.position.z);
If your camera is orthogonal anyway then you don't have to care about the z at all and can just use
screenBounds = Camera.main.ScreenToWorldPoint(Vector2.zero);
In order to not have to calculate twice if you also want to check later if it is above the screen you could also go the other way round though which is often even easier:
// Other than the ScreenToWorldPoint here it doesn't matter whether
// the camera is orthogonal or perspective or how far in front of the camera you want a position
// or if your camera is moved or rotated
var screenPos = Camera.main.WorldToScreenPoint(transform.position);
if(screenPos.y < 0) Debug.Log("Below screen");
else if(screenPos.y > Screen.height) Debug.Log("Above screen");
if(screenPos.x < 0) Debug.Log("Left of screen");
else if(screenPos.x > Screen.width) Debug.Log("Right of screen");
However, using only the transform.position is a bit unreliable since you already would destroy objects that are still (half) visible.
Instead you could use GeometryUtility.CalculateFrustumPlanes to get the planes surrounding the actual camera frustum and then use GeometryUtility.TestPlanesAABB to check wether your objects' Renderer.bounds are actually visible to the camera like e.g.
Renderer _renderer;
Camera _camera;
void Update()
{
if(!_renderer) _renderer = GetComponent<Renderer>();
if(!_camera) _camera = Camera.main;
var planes = GeometryUtility.CalculateFrustumPlanes(_camera);
if (!GeometryUtility.TestPlanesAABB(planes, _renderer.bounds))
{
Debug.Log($"\"{name}\" is outside of the screen!", this);
// and e.g.
Destroy(gameObject);
}
}
If I understand correctly you want to save the initial box the camera sees and use that as a boundary?
If that's the case then it's easy to do in 2D, but gets much more complex in 3D. So below is the 2D solution.
The camera is in the center of the viewport. Meaning that the top boundry is the Camera position plus half the height of the viewport.
// Get the camera height
float height = Screen.height;
// Now we get the position of the camera
float camY = Camera.Main.transform.position.y;
// Now Calculate the bounds
float lowerBound = camY + height / 2f;
float upperBound = camY - height / 2f;

How to rotate an object along all 3 axes with finger swipe?

I'm working on a simple AR Vuforia application. How can I implement the rotation of an object along all three axes with one finger swipe?
The code I'm currently using has one bug: the rotation of the object depends on its local axes. For example, if I look at the object from the front, everything works as it should, but if I look at the object from the back side, the finger swipe upwards makes it rotate downwards and vice versa.
Here is this script:
public float rotSpeed = 30f;
void OnMouseDrag()
{
float rotX = Input.GetAxis("Mouse X")*rotSpeed*Mathf.Deg2Rad;
float rotY = Input.GetAxis("Mouse Y")*rotSpeed*Mathf.Deg2Rad;
transform.Rotate(Vector3.up, -rotX);
transform.Rotate(Vector3.right, -rotY);
}
This is not what I need, how can I rotate the object according to the finger swipe direction regardless of the angle from which I look at it?
Update
A simple non-AR example, which may help you understand what I need is an iOS game "Space Frontier 2". After a successful launch of the rocket, it lands on the planet and you can rotate the planet with your finger swipe.
Here is the video demo: https://youtu.be/OiNPP1WNIAI
This works nice regardless of your object's rotation, and regardless of your camera position relative to the object:
public float rotSpeed = 30f;
void OnMouseDrag()
{
float rotX = Input.GetAxis("Mouse X") * rotSpeed;
float rotY = Input.GetAxis("Mouse Y") * rotSpeed;
Camera camera = Camera.main;
Vector3 right = Vector3.Cross(camera.transform.up, transform.position - camera.transform.position);
Vector3 up = Vector3.Cross(transform.position - camera.transform.position, right);
transform.rotation = Quaternion.AngleAxis(-rotX, up) * transform.rotation;
transform.rotation = Quaternion.AngleAxis(rotY, right) * transform.rotation;
}
Make sure your camera has the "MainCamera" tag, or assign the camera externally if necessary.
I do not have the exact code with me right now.
But if you did not update your point of view coordinates, this should be the expected result.
Consider a real ball with 2 colors, blue and red, separated vertically.
When you are in front of it, seeing only the blue side, stroking it up will make the blue side go up and the red side appear from the bottom.
Now move behind it, seeing only the red side, and stroke it up again.
The blue face will go down and appear from the bottom.
Unity applies physics to virtual objects the same way we interact with real objects.
So you need to consider your camera position with the object orientation when you apply movements to it.
You need to apply a transformation matrix to your movement based on your camera location related to the object origin orientation.
I hope this is clear enough to put you on tracks to fix it.
I think you have to somehow clamp the rotation to have the desired behaviour. I wrote a script recently to do just that. I did a little modification though.
public float rotSpeed = 30f;
float ClampAngle(float _angle, float _min, float _max)
{
if (_angle < 0f) _angle = 360 + _angle;
if (_angle > 180f) Mathf.Max(_angle, 360 + _min);
return Mathf.Min(_angle, _max);
}
USAGE:
void RotateGameObject()
{
float h = Input.GetTouch(0).deltaPosition.x * Time.deltaTime * rotSpeed*Mathf.Deg2Rad;
float v = Input.GetTouch(0).deltaPosition.y * Time.deltaTime * rotSpeed*Mathf.Deg2Rad;
Vector3 rot = transform.rotation.eulerAngles + new Vector3(-v, h, 0f);
//Change the y & z values to match your expected behaviour.
rot.x = ClampAngle(rot.x, -5f, 20f);
//Clamp rotation on the y-axis
rot.y = ClampAngle(rot.y, -20f, 20f);
transform.eulerAngles = rot;
}
See if that works and of course, try to play with the values.

Unity - Sprite Rotation + Get Angle

Im trying to get a sprite rotation by key input (arrow down or up).
The point is lifting the arrow (sprite) to choose the angle. Its like a system of a golf game, actually.
So far i tried:
void Update () {
if (Input.GetKey(KeyCode.UpArrow)){
transform.Rotate (Vector3.forward * -2); }
if (Input.GetKey(KeyCode.DownArrow)){
transform.Rotate (Vector3.forward * +2); }
}
I will need the angle, since it will be related to a "shot" part i will be doing next. My point is setting the right angle with the keys up and down.
I can move the "arrow" sprite with my code, but i cannot set the max angle (90), minimum (0) and get the angle to use in the shot ^^
Hard question to answer without just simply giving you the code. This code works by assuming your character's forward vector is actually it's right vector (common in 2d sprite games) in order to shoot in the other direction, rotate your objects y axis 180.
float minRotation = 0f;
float maxRotation = 90f;
float rotationSpeed = 40f; //degrees per second
//get current rotation, seeing as you're making a sprite game
// i'm assuming camera facing forward along positive z axis
Vector3 currentEuler = transform.rotation.eulerAngles;
float rotation = currentEuler.z;
//increment rotation via inputs
if (Input.GetKey(KeyCode.UpArrow)){
rotation += rotationSpeed * Time.deltaTime;
}
else if (Input.GetKey(KeyCode.DownArrow)){
rotation -= rotationSpeed * Time.deltaTime;
}
//clamp rotation to your min/max
rotation = Mathf.Clamp(rotation, minRotation, maxRotation );
//set rotation back onto transform
transform.rotation = Quaternion.Euler( new Vector3(currentEuler.x, currentEuler.y, rotation));
If you were making a golf game, you'd set the ball's velocity to be transform.right * shotPower

Categories

Resources