I've been working on something that loads the geometry of a game and follows the players position and view but I've ran into a problem: I can't rotate the view without all the axis being messed up.
Here's a screenshot of the program with a pitch and yaw of 0 (just like the game's client):
http://i.stack.imgur.com/DXhIr.jpg
Here's my view matrix code:
public void UpdateViewMatrix()
{
Vector3 pos = Position;
pos.X *= -1;
pos.Y *= -1;
pos.Z *= -1;
this.ViewMatrix = Matrix.CreateScale(1) * Matrix.CreateLookAt(Vector3.Zero, new Vector3(1, 0, 0), new Vector3(0, 0, 1)) * Matrix.CreateTranslation(pos) * Matrix.CreateFromYawPitchRoll(-MathHelper.PiOver2, -MathHelper.PiOver2, MathHelper.Pi);
this.Frustum.Matrix = (this.ViewMatrix * this.ProjectionMatrix);
if (this.CameraUpdated != null) this.CameraUpdated(this, new EventArgs());
}
I'm unsure why I have to flip all the coordinates as well before I update the view matrix.
Thanks in advance!
I'm unsure why I have to flip all the coordinates as well before I
update the view matrix
You probably don't.
In an Xna first person shooter, an easy way to handle the camera's view matrix to follow a character is to create a copy of that character's world matrix, offset it behind and slightly above, then invert it. That way, all the rotation and position manipulation that is done for the player does not have to be duplicated for the camera. This is a simple example of this:
//player moved and rotated and player's world matrix is determined
Matrix cameraWorld = playerWorld;
cameraWorld.Translation += (cameraWorld.Backward * followingDistance) +
(cameraWorld.Up * verticalOffset);
ViewMatrix = Matrix.Invert(cameraWorld);
Now the camera view matrix is orientated identically as the player and will stick to him like glue.
Related
So I have trying to create a system that looks for a ledge to climb. I have a vertical field of view that looks for these ledges and I have, so far, got it to rotate with the analogue stick's direction, so I can find a ledge and move to it where my analogue is pointing. (Look up, find ledge, move up, etc). My problem is, while the the field of view moves where I need it to move, it stays fixed on one axis and does not rotate with the player.
In the picture the cone is facing where it should be, and rotates around the player as I want it to, but as you can see, the cone faces the X-Axis and does not move from there. I want it to rotate with the player.
In the below code, the returned Vector's y value makes the cone point upwards. I have tried all manner of functions in the x and z values, but nothing is sticking.
public Vector3 DirFromAngle(float angleInDegrees, bool angleIsGlobal)
{
if(!angleIsGlobal)
{
angleInDegrees += (viewDir + 90);
}
Vector3 newValue = new Vector3(0, Mathf.Sin(angleInDegrees * Mathf.Deg2Rad), Mathf.Cos(angleInDegrees * Mathf.Deg2Rad));
return newValue;
}
The viewDir value makes the cone rotate with my analogue stick, to make the cone rotate with my player, I assume that something needs to be done with either the x or z values, or both, but for the life of me, I can't figure it out.
Thank you for any help on this issue. Apologies if my explanation isn't very good.
void OnSceneGUI()
{
LedgePointSearch lps = (LedgePointSearch)target;
Handles.color = Color.white;
Handles.DrawWireArc(lps.transform.position, lps.transform.forward * 90, Vector3.up, 360, lps.viewRadius);
Vector3 viewAngleA = lps.DirFromAngle(-lps.viewAngle / 2, false);
Vector3 viewAngleB = lps.DirFromAngle(lps.viewAngle / 2, false);
Handles.DrawLine(lps.transform.position, lps.transform.position + viewAngleA * lps.viewRadius);
Handles.DrawLine(lps.transform.position, lps.transform.position + viewAngleB * lps.viewRadius);
}
This is the editor script that displays the field of view.
The circle simply represents the radius of the field of view, how far it goes out.
https://www.dropbox.com/s/k9siha2aog9vj8o/fov.mp4?dl=0
In the video, the circle rotates with the player, and the cone rotates with my left analogue movements. I want the cone to be like the circle, with it rotating as my player does, but still matching my analogue movements.
One way to approach this is to find the Y rotation of the player and apply it to newValue before returning it.
public Vector3 DirFromAngle(float angleInDegrees, bool angleIsGlobal)
{
if(!angleIsGlobal)
{
angleInDegrees += (viewDir + 90);
}
Vector3 newValue = new Vector3(0, Mathf.Sin(angleInDegrees * Mathf.Deg2Rad), Mathf.Cos(angleInDegrees * Mathf.Deg2Rad));
// get Y rotation of player
yRotationAngle = player.transform.rotation.eulerAngles.y;
// convert to Quaternion using only the Y rotation
Quaternion yRotation = Quaternion.Euler(0f, yRotationAngle + 90f, 0f);
// Apply yRotation
newValue = yRotation * newValue;
return newValue;
}
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.
I need to have a game object point north AND I want to combine this with gyro.attitude input. I have tried, unsuccessfully, to do this in one step. That is, I couldn't make any gyro script, which I found on the net, work with the additional requirement of always pointing north. Trust me, I have tried every script I could find on the subject. I deduced that it's impossible and probably was stupid to think it could be done; at least not this way (i.e. all-in-one). I guess you could say I surmised that you can't do two things at once. Then I thought possibly I could get the same effect by breaking-up the duties. That is, a game object that always points north via the Y axis. Great, got that done like this:
_parentDummyRotationObject.transform.rotation = Quaternion.Slerp(_parentDummyRotationObject.transform.rotation, Quaternion.Euler(0, 360 - Input.compass.trueHeading, 0), Time.deltaTime * 5f);
And with the game object pointing north on the Y, I wanted to add the second game-object, a camera in this case, with rotation using gyro input on the X and Z axis. The reason I have to eliminate the Y axes on the camera is because I get double rotation. With two things rotating at once (i.e. camera and game-object), a 180 degree rotation yielded 360 in the scene. Remember I need the game object to always point north (IRL) based on the device compass. If my device is pointing towards the East, then my game-object would be rotated 90 degrees in the unity scene as it points (rotation) towards the north.
I have read a lot about gyro camera controllers and one thing I see mentioned a lot is you shouldn't try to do this (limit it) on just 1 or 2 axis, when using Quaternions it's impossible when you don't know what you're doing, which I clearly do not.
I have tried all 3 solutions from this solved question: Unity - Gyroscope - Rotation Around One Axis Only and each has failed to rotate my camera on 1 axis to satisfy my rotational needs. Figured I'd try getting 1 axis working before muddying the waters with the 2nd axis. BTW, my requirements are simply that the camera should only rotate on 1 axis (in any orientation) based on the X axis of my device. If I could solve for X, then I thought it'd be great to get Z gyro input to control the camera as well. So far I cannot get the camera controlled on just 1 axis (X). Anyway, here are my findings...
The first solution, which used Input.gyro.rotationRateUnbiased, was totally inaccurate. That is, if I rotated my device around a few times and then put my phone/device down on my desk, the camera would be in a different rotation/location each time. There was no consistency. Here's my code for the first attempt/solution:
<code>
private void Update()
{
Vector3 previousEulerAngles = transform.eulerAngles;
Vector3 gyroInput = Input.gyro.rotationRateUnbiased;
Vector3 targetEulerAngles = previousEulerAngles + gyroInput * Time.deltaTime * Mathf.Rad2Deg;
targetEulerAngles.y = 0.0f;
targetEulerAngles.z = 0.0f;
transform.eulerAngles = targetEulerAngles;
}
</code>
The second solution was very consistent in that I could rotate my device around and then put it down on the desk and the unity camera always ended up in the same location/rotation/state so-to-speak. The problem I had was the camera would rotate on the one axis (X in this case), but it did so when I rotated my device on either the y or x axis. Either type of rotation/movement of my phone caused the unity camera to move on the X. I don't understand why the y rotation of my phone caused the camera to rotate on X. Here is my code for solution #2:
private void Start()
{
Input.gyro.enabled = true;
startEulerAngles = transform.eulerAngles;
startGyroAttitudeToEuler = Input.gyro.attitude.eulerAngles;
}
private void Update()
{
Vector3 deltaEulerAngles = Input.gyro.attitude.eulerAngles - startGyroAttitudeToEuler;
deltaEulerAngles.y = 0.0f;
deltaEulerAngles.z = 0.0f;
transform.eulerAngles = startEulerAngles - deltaEulerAngles;
}
The 3rd solution: I wasn't sure how to complete this last solution, so it never really worked. With the 2 axis zeroed-out, the camera just flipped from facing left to right and back, or top to bottom and back; depending on which axis were commented out. If none of the axis were commented-out (like the original solution) the camera would gyro around on all axis. Here's my code for attempt #3:
private void Start()
{
_upVec = Vector3.zero;
Input.gyro.enabled = true;
startEulerAngles = transform.eulerAngles;
}
private void Update()
{
Vector3 gyroEuler = Input.gyro.attitude.eulerAngles;
phoneDummy.transform.eulerAngles = new Vector3(-1.0f * gyroEuler.x, -1.0f * gyroEuler.y, gyroEuler.z);
_upVec = phoneDummy.transform.InverseTransformDirection(-1f * Vector3.forward);
_upVec.z = 0;
// _upVec.x = 0;
_upVec.y = 0;
transform.LookAt(_upVec);
// transform.eulerAngles = _upVec;
}
Originally I thought it was my skills, but after spending a month on this I'm beginning to think that this is impossible to do. But that just can't be. I know it's a lot to absorb, but it's such a simple concept.
Any ideas?
EDIT: Thought I'd add my hierarchy:
CameraRotator (parent with script) -> MainCamera (child)
CompassRotator (parent) -> Compass (child with script which rotates parent)
I'd do this in the following way:
Camara with default 0, 0, 0 rotation
Screenshot
Object placed at the center of the default position of the camera.
Script for the Camera:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
Camera m_MainCamera;
// Start is called before the first frame update
void Start()
{
// Disable the sleep timeout during gameplay.
// You can re-enable the timeout when menu screens are displayed as necessary.
Screen.sleepTimeout = SleepTimeout.NeverSleep;
// Enable the gyroscope.
if (SystemInfo.supportsGyroscope)
{
Input.gyro.enabled = true;
}
m_MainCamera = Camera.main;
m_MainCamera.enabled = true;
}
// Update is called once per frame
void Update()
{
if (m_MainCamera.enabled)
{
// First - Grab the Gyro's orientation.
Quaternion tAttitude = Input.gyro.attitude;
// The Device uses a 'left-hand' orientation, we need to transform it to 'right-hand'
Quaternion tGyro = new Quaternion(tAttitude.x, tAttitude.y, -tAttitude.z, -tAttitude.w);
// the gyro attitude is tilted towards the floor and upside-down reletive to what we want in unity.
// First Rotate the orientation up 90deg on the X Axis, then 180Deg on the Z to flip it right-side up.
Quaternion tRotation = Quaternion.Euler(-90f, 0, 0) * tGyro;
tRotation = Quaternion.Euler(0, 0, 180f) * tRotation;
// You can now apply this rotation to any unity camera!
m_MainCamera.transform.localRotation = tRotation;
}
}
}
With this script my Object always face SOUTH no matter what.
If you want the object to face NORTH you just have to turn the view 180ยบ on the Y axis as a last rotation:
Quaternion tRotation = Quaternion.Euler(-90f, 0, 0) * tGyro;
tRotation = Quaternion.Euler(0, 0, 180f) * tRotation;
//Face NORTH:
tRotation = Quaternion.Euler(0,180f, 0) * tRotation;
Hope this might help ;)
I have written this code for rotating and moving the camera.
Sadly, I am not very experianced with matrices and 3D programming, since I started only a few days ago:
plLookAt = new Vector3(plPos.X, plPos.Y, plPos.Z - 20);
if (kb.IsKeyDown(Keys.W))
{
plPos.Z++;
}
if (kb.IsKeyDown(Keys.A))
{
plPos.X++;
}
if (kb.IsKeyDown(Keys.S))
{
plPos.Z--;
}
if (kb.IsKeyDown(Keys.D))
{
plPos.X--;
}
view = Matrix.CreateLookAt(new Vector3(0, 0, 0), plLookAt, Vector3.UnitY);
view = view * Matrix.CreateRotationY(MathHelper.ToRadians(rotations.Y)) * Matrix.CreateRotationX(MathHelper.ToRadians(rotations.X));
view = view *Matrix.CreateTranslation(plPos);
if (PrMS.X < ms.X)
{
rotations.Y++;
}
else if (PrMS.X > ms.X)
{
rotations.Y--;
}
if (PrMS.Y < ms.Y)
{
rotations.X++;
}
else if (PrMS.Y > ms.Y)
{
rotations.X--;
}
plPos is the Player (camera) position
view is the view Matrix
rotations is where the rotation of the camera is saved (Vector3)
PrMS is the MouseState of the previous frame.
This code doesn't work and I think it is because of the order, which the multiplications are in, but I'm not sure. What is the best way of rotating the camera, so that it actually works and I can go in the direction the camera is facing.
Thank You in advance!
Your problem is not in the order of the matrix multiplication, it is that your rotations need to be around camera local axes and your are performing them around world axes.
I think that what you are expecting is that applying .CreateRotationX(rotations.X) and .CreateRotationY(rotationsY) will cause the camera to change pitch and yaw about it's own local axis. But these rotations always cause a rotation about the world axis. If your camera's local X axis just happens to be aligned with the world X axis and you performed a .CreateRotationX(), then it would work as expected. But in your case, you are rotating the camera about the Y axis first and this is throwing the camera's local X axis out of alignment with the world X axis so the next rotation (the X) is not going to go as expected. Even if you did the X first and the Y second, the X would throw the Y out of whack. Although the order of matrix multiplication does matter in general, in your particular case, it is the axis of rotation that is the problem.
It appears you are making a camera that is located at your player's position but can look left/right, up/down by mouse control. Here is a class that offers a different way to approach that criteria:
class Camera
{
public Matrix View { get; set; }
public Matrix Projection { get; set; }
Vector2 centerOfScreen;// the current camera's lookAt in screen space (since the mouse coords are also in screen space)
Vector3 lookAt;
float cameraRotationSpeed;
public Camera()
{
//initialize Projection matrix here
//initialize view matrix here
//initialize centerOfScreen here like this: new Vector2(screenWidth/2, screenHeihgt/2);
cameraRotationSpeed = 0.01f;//set to taste
}
public void Update(Vector3 playerPosition, MouseState currentMouse)
{
Matrix cameraWorld = Matrix.Invert(View);
Vector2 changeThisFrame = new Vector2(currentMouse.X, currentMouse.Y) - centerOfScreen;
Vector3 axis = (cameraWorld.Right * changeThisFrame.X) + (cameraWorld.Up * changeThisFrame.Y);//builds a rotation axis based on camera's local axis, not world axis
float angle = axis.Length() * cameraRotationSpeed;
axis.Normalize();
//rotate the lookAt around the camera's position
lookAt = Vector3.Transform(lookAt - playerPosition, Matrix.CreateFromAxisAngle(axis, angle)) + playerPosition;//this line does the typical "translate to origin, rotate, then translate back" routine
View = Matrix.CreateLookAt(playerPosition, lookAt, Vector3.Up);// your new view matrix that is rotated per the mouse input
}
}
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.