How can I make cam move in a perfect circular orbit, but without diving into colliders?
I am making a moving-sphere game from scratch. For the camera, I decided to control it through transform
instead of rigidbody
. I've tried several kind of approaches, but using a circle function around the target has offered me the most accurate rotation and following.
Why I move cam through Transform rather than Rigidbody
The motive I decided to implement cam moving through transform is that no matter how fast the ball is moving, or which direction the cam is looking: operating cam coords directly makes sure the cam is always over a perfect circle around the sphere. Also, due to my approach sets the camera position relative to the target's position (the sphere) in every frame, this way the relative positions of both the camera and the sphere remain the same, independent from the sphere's rotation.
I just couldn't figure out how to achieve the same without overriding cam's physics (ALERT: I am assuming that any translation or rotation through transform
overrides physics, but I am not sure if this assumption is completely true).
Other approaches I tried and failed (for the record)
transform
as well.My approach works well, but...
...it becomes a problem when avoiding that the cam goes through walls, though. I tried tracing rays out of the wall, but then the cam makes an annoying 'inside-out' alternative move.
What can I do to prevent the cam going inside the walls, but at the same time mantaing this "perfect orbit" system? (for the record, I'd throw away my cam's beautiful code if necessary) In other words, i would like that the orbit's arc that is inside the wall, becomes a straight line stuck to the wall's collider. Any tips for a better approach are welcome.
The code
Here is the script(in Javascript) I attached to the cam, for the sake of completeness:
#pragma strict
public var target : Transform;
public var height: float = 1.0f;
public var cam_distance: float = 4.0f;
public var orbit_speed: float = 40.0f;
private var trans: Transform;
function Start () {
trans = GetComponent.<Transform>();
}
function Update () {
//Initial positioning: later we change it depending on cam's angle
trans.position.x = target.position.x;
trans.position.y = target.position.y + height;
trans.position.z = target.position.z - cam_distance;
//Rotate the cam with A and D keys
if(Input.GetKey(KeyCode.A)) {
trans.Rotate(Vector3.up, -orbit_speed * Time.deltaTime);
}
if(Input.GetKey(KeyCode.D)) {
trans.Rotate(Vector3.up, orbit_speed * Time.deltaTime);
}
var base_angle : float;
// First, we get the cam's angle and turn it 180º
base_angle = (trans.rotation.eulerAngles.y + 180) % 360;
// Using trigonometry to translate the cam to the corresponding orbit position depending on angle
trans.position.x = cam_distance * Mathf.Sin(base_angle * Mathf.Deg2Rad) + target.position.x;
trans.position.z = cam_distance * Mathf.Cos(base_angle * Mathf.Deg2Rad) + target.position.z;
}
Answer by Glurth · Jul 31, 2016 at 04:48 PM
Once you have computed the "DESIRED" position of the camera (your existing code), you can use a physics.RAYCAST from the center of rotation to the desired camera position (sounds like you did the opposite), to determine if a wall is in the way. If so, simply decrease your (cam_distance) distance to the center, to the value provided by the raycast result. (let me know if you need more details on that last part which can be done with vector subtraction, Vector magnitude and Vector scaling).
https://docs.unity3d.com/ScriptReference/Physics.Raycast.html (scroll down to see version with hit-point as an out parameter)
Edit: Collider.Raycast is useful to detect collisions with a particular object, as opposed to the first collider hit by the ray (like Physics.Raycast). https://docs.unity3d.com/ScriptReference/Collider.Raycast.html
So Rays can interact with collisions... I didn't realize before
(I was using them just to move it towards the center, and depending on collision "cam vs wall"). Looks like your approach can avoid the "inside-out" issue if I implement it correctly. NOTE: I confused Ray with Raycast :P
I have to use the args origin
, direction
and maxDistance
on Physics.Raycast
.
camDistance
itself, perhaps with a little tweak.I will try to implement it this way and see what happens.
PD: @Glurth, about the last part, I am imagining looping while Raycast
returns true
, decreasing distance in every iteration. Looks like a good idea, but you seem to have something different in $$anonymous$$d. The part I don't get is "decrease to the value provided by Raycast result". Raycast returns a boolean
, so the only way I imagine doing it is with a while
loop. If my guess is wrong, would you edit your answer with further information? Thank you.
The direction will simply be camera's position $$anonymous$$us the center position, normalized. (Normilizing a vector keeps the direction the same, but sets it's length to 1.0). https://docs.unity3d.com/ScriptReference/Vector3.Normalize.html
The vector from the raycast hit point to the center (also a simple Vector subtraction), will have a length that you can get by using the Vector3.$$anonymous$$agnitude function. https://docs.unity3d.com/ScriptReference/Vector3-magnitude.html
Since the normalized vector has a length of 1.0, you can simply $$anonymous$$ultipy that vector by the length or magnitude of the center-to-hitpoint vector. That will keep the direction the same, but change the length of the vector. https://docs.unity3d.com/ScriptReference/Vector3-operator_multiply.html
Alternatively, you could just put the camera at the detected hit-point. ( Though this might need some adjustment (towards the center object) to make sure the camera does not see the wall.
you are quite right about the physics raycast function, I suggest the wrong one! (yes having two does get confusing,) Collider.Raycast is what I should have suggested, though this DOES assume the wall has a collider component. https://docs.unity3d.com/ScriptReference/Collider.Raycast.html This function outputs a "hit-point" (via an OUT parameter), which is what I keep talking about... sorry bout that!
It's allright. All this information will keep me in the right way. Now I just must develope the implementation until it works. Thank you.
BTW, the while
loop idea was a bad call. Trying it I just achieved to freeze Unity. It looks quite dangerous. Do not try this at home hehe
Answer by SebasSBM · Aug 01, 2016 at 05:53 AM
I implemented @Glurth 's approach by the following snippet, and it worked perfectly.
I made this fragment of code, and appended it to the script that I posted in the question:
function LateUpdate() {
// Correct radius if collider is in the way
var direction : Vector3 = trans.position - target.position;
direction.Normalize();
var radius : float = cam_distance;
var target_to_camera : Ray = new Ray(target.position, direction);
var hit : RaycastHit;
if (Physics.Raycast(target.position, direction, hit, cam_distance)) {
// the sphere radius is 0.5f <- actually, this wasn't relevant at all
// but you might need to tweak distance to avoid camera seeing the wall from behind
radius = hit.distance - 0.5f;
trans.position.x = radius * Mathf.Sin(base_angle * Mathf.Deg2Rad) + target.position.x;
trans.position.z = radius * Mathf.Cos(base_angle * Mathf.Deg2Rad) + target.position.z;
}
}
Your answer
Follow this Question
Related Questions
How to rotate a camera around a spaceship freely 0 Answers
Make camera follow and look at object at the same time 0 Answers
Need help with camera in top down shooter 0 Answers
Fixed Camera rotate to follow player 2 Answers
Collision Problems 1 Answer