Creating a Google Earth like navigation for a sphere - c#

This question is about Unity3D.
I want to create a navigation similar to Google Earth where you click and drag on a sphere and let the camera orbit accordingly. It is important that the point that was grabbed is always under the mouse position while dragging. The navigation should also work if I zoom close to the sphere. I do not want to rotate the sphere itself. Just exactly like Google Earth does it.
My attempt is to project the mouse position to the sphere if I start to drag. On the next frame I do the same and calculate the angle between the start drag and end drag position.
private void RotateCamera(Vector3 dragStart, Vector3 dragEnd)
{
// calc the rotation of the drag
float angle = Vector3.Angle(dragStart, dragEnd);
// rotate the camera around the sphere
Camera.main.transform.RotateAround(sphere), Vector3.up, angle);
}
I thought of using Unitys RotateAround method to rotate the camera with the calculated angle. Unfortunately I do not have the rotation vector (using Vector3.up in the example is obviously wrong).
Does somebody know how I can calculate this vector to apply it for the method? Am I on the right direction to implement the Google Earth navigation?
Thank You!
UPDATE
I am very close with a new solution. I project the drag vectors to a down and a right plane to get the angles. Afterwards I rotate the camera around up and left. This works well until I reach the poles of the sphere. The camera rotates a lot around itself if I reach a pole.
private void RotateCamera(Vector3 dragStart, Vector3 dragEnd)
{
Vector3 plane = Vector3.down;
var a = Vector3.ProjectOnPlane(dragStart, plane);
var b = Vector3.ProjectOnPlane(dragEnd, plane);
float up = Vector3.SignedAngle(a, b, plane);
plane = Vector3.right;
a = Vector3.ProjectOnPlane(dragStart, plane);
b = Vector3.ProjectOnPlane(dragEnd, plane);
float left = Vector3.SignedAngle(a, b, plane);
Camera.main.transform.RotateAround(_sphere, Vector3.up, up);
Camera.main.transform.RotateAround(_sphere, Vector3.left, left);
}

Turns out that is was easier than I expected. I thought about calculating the rotation axis and came to the conclusion that is must be the cross product of the start and end vector. Take a look at the solution. The RotateCamera method is where the math magic happens :)
public class GoogleEarthControls : MonoBehaviour
{
private const int SpehreRadius = 1;
private Vector3? _mouseStartPos;
private Vector3? _currentMousePos;
void Start () {
// init the camera to look at this object
Vector3 cameraPos = new Vector3(
transform.position.x,
transform.position.y,
transform.position.z - 2);
Camera.main.transform.position = cameraPos;
Camera.main.transform.LookAt(transform.position);
}
private void Update()
{
if (Input.GetMouseButtonDown(0)) _mouseStartPos = GetMouseHit();
if (_mouseStartPos != null) HandleDrag();
if (Input.GetMouseButtonUp(0)) HandleDrop();
}
private void HandleDrag()
{
_currentMousePos = GetMouseHit();
RotateCamera((Vector3) _mouseStartPos, (Vector3)_currentMousePos);
}
private void HandleDrop()
{
_mouseStartPos = null;
_currentMousePos = null;
}
private void RotateCamera(Vector3 dragStartPosition, Vector3 dragEndPosition)
{
// in case the spehre model is not a perfect sphere..
dragEndPosition = dragEndPosition.normalized * SpehreRadius;
dragStartPosition = dragStartPosition.normalized * SpehreRadius;
// calc a vertical vector to rotate around..
var cross = Vector3.Cross(dragEndPosition, dragStartPosition);
// calc the angle for the rotation..
var angle = Vector3.SignedAngle(dragEndPosition, dragStartPosition, cross);
// roatate around the vector..
Camera.main.transform.RotateAround(transform.position, cross, angle);
}
/**
* Projects the mouse position to the sphere and returns the intersection point.
*/
private static Vector3? GetMouseHit()
{
// make sure there is a shepre mesh with a colider centered at this game object
// with a radius of SpehreRadius
RaycastHit hit;
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit))
{
return hit.point;
}
return null;
}
}

Basic rotation based on mouse drag, based on what you have:
Transform camTransform = Camera.main.transform;
if (Input.GetMouseButton(0))
{
camTransform.RotateAround(currentLookTargetTransform.position, -camTransform.right * Input.GetAxis("Mouse Y") + camTransform.up * Input.GetAxis("Mouse X"), 120 * Time.deltaTime);
}
You can multiply the relative direction by the mouse change value to get the axis. Then you can supplant your clamp points in; but the point was to rotate it relatively.

Related

How to rotate a Globe, So that when selecting a position on globe using mouse click on it don't tilt the Globe sideways?

I have achieved Globe rotating on mouse click on any particular panel on the Globe and the panel position gets in the Center of the Camera on click but the problem is Globe travels the shortest distance between the two points (Camera Center and Next Position on Globe).
As per the image currently Globe jumps to next position and tilts like this
As per this Image I need Globe to result in this rotation where NorthPole and SouthPole remains at their side looking through the camera.
public List<GameObject> characters = new List<GameObject>();
private Quaternion targetRotation = Quaternion.identity;
public void CharacterPosition(int charNum)
{
Quaternion diff = Quaternion.FromToRotation(characters[charNum].transform.up, camera.transform.position - transform.position);
targetRotation = diff * transform.rotation;
transform.DORotateQuaternion(targetRotation, 1f);
hitAgainNumber = charNum;
}
"characters" List conists of the panels on the globe on multiple positions.
DoTween Plugin is used for the Rotation.
I will be very thankful if someone helps me with that.
Explanations are in the comments:
public List<GameObject> characters = new List<GameObject>();
private Quaternion targetRotation = Quaternion.identity;
public void CharacterPosition(int charNum)
{
// Use cross products to find the closest vector to Vector3.up which is orthogonal
// to the direction from the sphere to the camera.
// This direction is to be the sprite's forward after we rotate the sphere
Vector3 charGoalUp = camera.transform.position - transform.position;
Vector3 charGoalRight = Vector3.Cross(charGoalUp, Vector3.up);
Vector3 charGoalForward = Vector3.Cross(charGoalRight, charGoalUp);
// Get the goal rotation for the character using `LookRotation`:
Quaternion charGoalRotation = Quaternion.LookRotation(charGoalForward, charGoalUp);
// Determine the world rotation that needs to apply to the character:
// delta * char_start = char_end -> delta = char_end * inv(char_start)
Quaternion worldCharDelta = charGoalRotation
* Quaternion.Inverse(characters[charNum].transform.rotation);
// Apply that same world rotation to the globe:
targetRotation = worldCharDelta * transform.rotation;
transform.DORotateQuaternion(targetRotation, 1f);
hitAgainNumber = charNum;
}

2 rotations with transform.Rotate() and transform.localEulerAngles() doesn't work

I'm trying to make a simple car simulator, and when I use the transform.Rotate() to rotate the wheels foreword (x axis), and the transform.localEulerAngles() to rotate in the turning direction (y axis), only the localEulerAngles() works. when I use only one method, the wheel foreword rotation (x axis) works, but I can't manage to make them both work. Do you have any ideas how to make them work together?
float ro = 20f; // 20 degrees turn
//to preserve the x and z values of rotation
Vector3 rot = wheel.gameObject.transform.rotation.eulerAngles;
//rotates the wheels angle
wheel.gameObject.transform.localEulerAngles = new Vector3(rot.x, ro, rot.z);
float vel = wheel.rpm * 2 * Mathf.PI / 60 * Time.deltaTime * Mathf.Rad2Deg;
//rotates the wheels forward
wheel.gameObject.transform.Rotate(vel, 0, 0);
You should not be using localEulerAngles when you are having two different rotations, because when you have 2 movements if you use localEulerAngles you change the local angles with the movements you are providing themselves so you loose the reference of where the angular position of your axis is at.
You can indicate to the rotate method to rotate respect to the world, transform.rotate rotates the transform of your object relative to the world so you can handle simultaneus rotations together because world axis do not change.
Find below the code snippet that I tried, and the picture of my axis orientation which is not exactly the same as yours.
public class WheelTurning : MonoBehaviour
{
float rpm=10f;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
turn(direction.left);
}
if (Input.GetKeyDown(KeyCode.D))
{
turn(direction.right);
}
float vel = this.rpm * 2 * Mathf.PI / 60 * Time.deltaTime * Mathf.Rad2Deg;
rotate(vel);
}
enum direction
{
left,
right
}
void turn(direction dir)
{
Vector3 rot = this.gameObject.transform.rotation.eulerAngles;
//rotates the wheels angle
float rotation = dir == direction.left ? 20f : -20f;
gameObject.transform.Rotate(new Vector3(0, rotation, 0), Space.World);
}
void rotate (float speed)
{
gameObject.transform.Rotate(0, speed, 0);
}
}
However it is much more operative to use quaternions, try to check that out.
Hope that helps!
I do not get your point. If you want to turn a wheel it will alwiys be respect to its previous rotational postion. With that reference you can choose the value you like in the rotation vector Vector3(0, rotation, 0). If you want an absolute reference you can make a parent object and rotate this. Objects have the same world axis and when rotated or transformed, the children "suffer" the same transformation. You have to meke sure that the posistion in the childre is 0,0,0 as this is the distance respect to the parent position that will be the rotation pivot.
void setRotation()
{
Vector3 rot = this.gameObject.transform.rotation.eulerAngles;
GameObject wheelParent = gameObject.transform.parent.gameObject;
Vector3 rotationVector = new Vector3(0, 30, 0);//absolute turn set
Quaternion rotation = Quaternion.Euler(rotationVector);
wheelParent.transform.localRotation = rotation;
}
Hope that helps.

PlayerMovement And CameraFollow

I am making a thirdperson Unity game. I think that I'm almost done but I just can't figure out how to make so the WASD control is relative to the camera?
Right now when I'm moving with WASD it works, but if i start to rotate the camera by using mouse, suddenly my (W) button makes the player walk backwards and left button (A) goes right. All controls are just bugging.
This is my PlayerMovement.cs and CameraFollow.cs:
PlayerMovement:
public float speed = 6f; // The speed that the player will move at.
Vector3 movement; // The vector to store the direction of the player's movement.
Animator anim; // Reference to the animator component.
Rigidbody playerRigidbody; // Reference to the player's rigidbody.
int floorMask; // A layer mask so that a ray can be cast just at gameobjects on the floor layer.
float camRayLength = 100f; // The length of the ray from the camera into the scene.
void Awake ()
{
// Create a layer mask for the floor layer.
floorMask = LayerMask.GetMask ("Floor");
// Set up references.
anim = GetComponent <Animator> ();
playerRigidbody = GetComponent <Rigidbody> ();
}
void FixedUpdate ()
{
// Store the input axes.
float h = CrossPlatformInputManager.GetAxisRaw("Horizontal");
float v = CrossPlatformInputManager.GetAxisRaw("Vertical");
// Move the player around the scene.
Move (h, v);
// Turn the player to face the mouse cursor.
Turning ();
// Animate the player.
Animating (h, v);
Shooting ();
}
void Move (float h, float v)
{
// Set the movement vector based on the axis input.
movement.Set (h, 0f, v);
// Normalise the movement vector and make it proportional to the speed per second.
movement = movement.normalized * speed * Time.deltaTime;
// Move the player to it's current position plus the movement.
playerRigidbody.MovePosition (transform.position + movement);
}
void Turning ()
{
// Create a ray from the mouse cursor on screen in the direction of the camera.
Ray camRay = Camera.main.ScreenPointToRay (Input.mousePosition);
// Create a RaycastHit variable to store information about what was hit by the ray.
RaycastHit floorHit;
// Perform the raycast and if it hits something on the floor layer...
if(Physics.Raycast (camRay, out floorHit, camRayLength, floorMask))
{
// Create a vector from the player to the point on the floor the raycast from the mouse hit.
Vector3 playerToMouse = floorHit.point - transform.position;
// Ensure the vector is entirely along the floor plane.
playerToMouse.y = 0f;
// Create a quaternion (rotation) based on looking down the vector from the player to the mouse.
Quaternion newRotatation = Quaternion.LookRotation (playerToMouse);
// Set the player's rotation to this new rotation.
playerRigidbody.MoveRotation (newRotatation);
}
}
void Animating (float h, float v)
{
// Create a boolean that is true if either of the input axes is non-zero.
bool walking = h != 0f || v != 0f;
// Tell the animator whether or not the player is walking.
anim.SetBool ("IsWalking", walking);
}
void Shooting ()
{
// Create a boolean that is true if either of the input axes is non-zero.
bool shooting = (Input.GetMouseButtonDown(0));
// Tell the animator whether or not the player is walking.
anim.SetBool ("IsShooting", shooting);
}
CameraFollow:
// Camera target to look at.
public Transform target;
// Exposed vars for the camera position from the target.
public float height = 20f;
public float distance = 20f;
// Camera limits.
public float min = 10f;
public float max = 60;
// Rotation.
public float rotateSpeed = 1f;
// Options.
public bool doRotate;
public bool doZoom;
// The movement amount when zooming.
public float zoomStep = 30f;
public float zoomSpeed = 5f;
private float heightWanted;
private float distanceWanted;
// Result vectors.
private Vector3 zoomResult;
private Quaternion rotationResult;
private Vector3 targetAdjustedPosition;
void Start(){
// Initialise default zoom vals.
heightWanted = height;
distanceWanted = distance;
// Setup our default camera. We set the zoom result to be our default position.
zoomResult = new Vector3(0f, height, -distance);
}
void LateUpdate(){
// Check target.
if( !target ){
Debug.LogError("This camera has no target, you need to assign a target in the inspector.");
return;
}
if( doZoom ){
// Record our mouse input. If we zoom add this to our height and distance.
float mouseInput = Input.GetAxis("Mouse ScrollWheel");
heightWanted -= zoomStep * mouseInput;
distanceWanted -= zoomStep * mouseInput;
// Make sure they meet our min/max values.
heightWanted = Mathf.Clamp(heightWanted, min, max);
distanceWanted = Mathf.Clamp(distanceWanted, min, max);
height = Mathf.Lerp(height, heightWanted, Time.deltaTime * zoomSpeed);
distance = Mathf.Lerp(distance, distanceWanted, Time.deltaTime * zoomSpeed);
// Post our result.
zoomResult = new Vector3(0f, height, -distance);
}
if( doRotate ){
// Work out the current and wanted rots.
float currentRotationAngle = transform.eulerAngles.y;
float wantedRotationAngle = target.eulerAngles.y;
// Smooth the rotation.
currentRotationAngle = Mathf.LerpAngle(currentRotationAngle, wantedRotationAngle, rotateSpeed * Time.deltaTime);
// Convert the angle into a rotation.
rotationResult = Quaternion.Euler(0f, currentRotationAngle, 0f);
}
// Set the camera position reference.
targetAdjustedPosition = rotationResult * zoomResult;
transform.position = target.position + targetAdjustedPosition;
// Face the desired position.
transform.LookAt(target);
}
Your 'movement' vector should not be independent of camera forward vector, if you want a normal 3-rd person movement. Instead of moving in the 'h' or 'v' directions, move in camera.right and camera.forward directions, multiplied by 'h' and 'v' magnitudes respectively.
To be more accurate, if your camera is slightly inclined (so that its forward will go downward), you should first project camera.forward onto [player.forward, player.right] plane. Unity has a helper function just for that (Vector3.ProjectOnPlane()). If your camera also rotates around its forward direction (e.g. camera shake), you should also project camera.right on the same plane.
// Set the movement vector based on the axis input.
movement.Set (camera.right * h, 0f, camera.forward * v);

Shooting a projectile but goes in wrong direction - 2D game

I am trying to make a simple game where I am shooting a projectile when the user touches the screen, I first spawn 8 projectiles (sprites) and when the user touches the screen I would like the top projectile to fly in the touch direction. I was able to do this; However, every time I shoot, the projectile goes in the wrong direction, here is an image which will illustrate the issue.
Obviously the image is still here but the object will continue flying until it goes out of the screen and then gets destroyed.
Here is the code snippet that handles this
GameplayController.cs
if (Input.GetMouseButtonDown(0))
{
Vector3 position = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0);
position = Camera.main.ScreenToWorldPoint(position);
GameObject target;
target = new GameObject();
target.transform.position = position;
Arrow comp = currentArrows[0].GetComponent<Arrow>();
comp.setTarget(target.transform);
comp.GetComponent<Arrow>().arrowSpeed = 12;
comp.GetComponent<Arrow>().shoot = true;
currentArrows.RemoveAt(0);
Destroy(target);
}
I know I am getting the mouse input here and not the phone touch and that's fine for me, later I will convert it to the touch input.
Arrow.cs
public bool shoot = false;
public float arrowSpeed = 0.0f;
public Vector3 myDir;
public float speed = 30.0f;
private Transform target;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if(shoot)
{
transform.position += transform.right * arrowSpeed * Time.deltaTime;
}
}
public void setTarget(Transform targetTransform)
{
this.target = targetTransform;
Vector3 vectorToTarget = target.position - transform.position;
float angle = Mathf.Atan2(vectorToTarget.y, vectorToTarget.x) * Mathf.Rad2Deg;
Quaternion q = Quaternion.AngleAxis(angle, Vector3.forward);
transform.rotation = Quaternion.Slerp(transform.rotation, q, Time.deltaTime * speed);
}
private void OnBecameInvisible()
{
print("Disappeared");
Destroy(gameObject);
Gameplay.instance.isShooting = false;
}
Vector3 position = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0);
I think that your problem is that you're getting the screen coordinates by click, not the world coordinates, which is actually two different things. In order to direct your projectile correctly you need to convert your screen coordinates to world like it's done here.
The next thing is how you move the projectile:
transform.position += transform.right * arrowSpeed * Time.deltaTime;
You're moving the projectile to the right and then rotating it somehow. Maybe you should try to move it with Vector3.Lerp, which will be easier and rotate it with Transform.LookAt.

Instantiate Object on Raycast2D hit and rotate Instance

I want to move an instance of a gameObject along the outline of another gameobject. Its a 2D Project in Unity.
My current Code:
Vector3 mousePosition = m_camera.ScreenToWorldPoint(Input.mousePosition);
RaycastHit2D hit = Physics2D.Raycast(new Vector2(mousePosition.x, mousePosition.y), new Vector2(player.transform.position.x, player.transform.position.y));
if (hit.collider != null && hit.collider.gameObject.tag == "Player") {
if (!pointerInstance) {
pointerInstance = Instantiate(ghostPointer, new Vector3(hit.point.x, hit.point.y, -1.1f), Quaternion.identity);
} else if(pointerInstance) {
pointerInstance.gameObject.transform.position = new Vector3(hit.point.x, hit.point.y, -1.1f);
pointerInstance.gameObject.transform.eulerAngles = new Vector3(0f, 0f, hit.normal.x);
}
}
Unfortunately, the gameObject doesn't rotate towards the mouse and the position on the left side of the playerObject is also sometimes off. I tried to use Instantiate() with Quaternion.LookRotation(hit.normal), but no luck either.
Here a rough sketch of what I want to achieve:
Any help is appreciated. Thanks!
it's better to use Mathematical way instead of physical way(Raycasting),because in raycasting you have to throw ray several time for checking hit point and rotate your object,it makes lag in your game.
Attach this script to your instantiated object:
using UnityEngine;
using System.Collections;
public class Example : MonoBehaviour
{
public Transform Player;
void Update()
{
//Rotating Around Circle(circular movement depend on mouse position)
Vector3 targetScreenPos = Camera.main.WorldToScreenPoint(Player.position);
targetScreenPos.z = 0;//filtering target axis
Vector3 targetToMouseDir = Input.mousePosition - targetScreenPos;
Vector3 targetToMe = transform.position - Player.position;
targetToMe.z = 0;//filtering targetToMe axis
Vector3 newTargetToMe = Vector3.RotateTowards(targetToMe, targetToMouseDir, /* max radians to turn this frame */ 2, 0);
transform.position = Player.position + /*distance from target center to stay at*/newTargetToMe.normalized;
//Look At Mouse position
var objectPos = Camera.main.WorldToScreenPoint(transform.position);
var dir = Input.mousePosition - objectPos;
transform.rotation = Quaternion.Euler(0, 0, Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg);
}
}
Useful explanations
Atan2:
atan2(y,x) gives you the angle between the x-axis and the vector (x,y), usually in radians and signed such that for positive y you get an angle between 0 and π, and for negative y the result is between −π and 0.
https://math.stackexchange.com/questions/67026/how-to-use-atan2
Returns the angle in radians whose Tan is y/x.
Return value is the angle between the x-axis and a 2D vector starting at zero and terminating at (x,y).
https://docs.unity3d.com/ScriptReference/Mathf.Atan2.html
Mathf.Rad2Deg:
Radians-to-degrees conversion constant (Read Only).
This is equal to 360 / (PI * 2).

Categories

Resources