- Home /
Constrain a GameObject's movement to an orthographic camera's viewport bounds
(I'm new here -- and to Unity itself -- please be gentle.)
I've got a simple orthographic camera projecting at a quad (the "ground") and a cube (my character). I'd like to have the character move randomly, but only within the bounds of the camera's projection.
Here's how I'm doing it now:
void Start()
{
Ray topRightRay = Camera.main.ViewportPointToRay(Vector3.one);
Ray bottomLeftRay = Camera.main.ViewportPointToRay(Vector3.zero);
int groundLayerMask = 1 << LayerMask.NameToLayer("Ground");
RaycastHit topRightRayHit;
RaycastHit bottomLeftRayHit;
if(Physics.Raycast(topRightRay, out topRightRayHit, Mathf.Infinity, groundLayerMask)
&& Physics.Raycast(bottomLeftRay, out bottomLeftRayHit, Mathf.Infinity, groundLayerMask))
{
cameraRect = Rect.MinMaxRect(bottomLeftRayHit.point.x, topRightRayHit.point.z, topRightRayHit.point.x, bottomLeftRayHit.point.z);
}
}
void Update()
{
Debug.DrawLine (new Vector3(cameraRect.xMin, 0.5f, cameraRect.yMin), new Vector3(cameraRect.xMin, 0.5f, cameraRect.yMax), Color.red);
Debug.DrawLine (new Vector3(cameraRect.xMax, 0.5f, cameraRect.yMin), new Vector3(cameraRect.xMax, 0.5f, cameraRect.yMax), Color.yellow);
Debug.DrawLine (new Vector3(cameraRect.xMin, 0.5f, cameraRect.yMin), new Vector3(cameraRect.xMax, 0.5f, cameraRect.yMin), Color.blue);
Debug.DrawLine (new Vector3(cameraRect.xMin, 0.5f, cameraRect.yMax), new Vector3(cameraRect.xMax, 0.5f, cameraRect.yMax), Color.green);
}
This seems to work just fine when the camera is not rotated around the Y axis (e.g., if its rotation is set at (30, 0, 0)), creating colorful lines that represent the camera (and, hence, game world) boundaries for my character, projected against the "Ground" quad. It looks like this:
However, once I change the camera's Y axis rotation to (30, 45, 0), my bounds get very small. It looks like this:
(ugh, apologies for the green-on-green, but it's a very thin rectangle, in case it's hard to tell)
I tried to do a rotation calculation (multiplied against each line) against a 45-degree rotation of the Y axis, but that seemed to just rotate the very small box:
mCameraRotation = Quaternion.AngleAxis(45, Vector3.up);
mBottomLeft = mCameraRotation * (new Vector3(cameraRect.xMin, 0.5f, cameraRect.yMin));
// ...
Is there any hope for me?
Answer by adamt · Apr 25, 2014 at 12:23 AM
I'm going to answer my own question, in case it helps other people.
It turned out that I was on the right track, but just didn't go far enough. I needed to raycast to the other two screen edges (i.e., the top left and bottom right) to get the full viewport dimensions. Then I created a new class called GameBounds
that simply encapsulates 4 Vector
objects representing these corners (I tried doing this with both a Rect
and a Bounds
object, but found it difficult since a Rect
doesn't allow you to define verticies and there was a bit less math involved with defining these verticies statically versus deriving them from a Bounds
object).
Anyhow... here's my code (```VisibleArea``` is a C# property of my GameBounds
class):
Ray bottomLeftRay = Camera.main.ViewportPointToRay(Vector3.zero);
Ray topLeftRay = Camera.main.ViewportPointToRay(new Vector3(0, 1, 0));
Ray bottomRightRay = Camera.main.ViewportPointToRay(new Vector3(1, 0, 0));
Ray topRightRay = Camera.main.ViewportPointToRay(Vector3.one);
int groundLayerMask = 1 << LayerMask.NameToLayer(GROUND_LAYER);
RaycastHit bottomLeftRayHit;
RaycastHit topLeftRayHit;
RaycastHit bottomRightRayHit;
RaycastHit topRightRayHit;
if(Physics.Raycast(bottomLeftRay, out bottomLeftRayHit, Mathf.Infinity, groundLayerMask)
&& Physics.Raycast(topLeftRay, out topLeftRayHit, Mathf.Infinity, groundLayerMask)
&& Physics.Raycast(bottomRightRay, out bottomRightRayHit, Mathf.Infinity, groundLayerMask)
&& Physics.Raycast(topRightRay, out topRightRayHit, Mathf.Infinity, groundLayerMask))
{
VisibleArea = new GameBounds(bottomLeftRayHit.point, topLeftRayHit.point, bottomRightRayHit.point,
topRightRayHit.point);
}
To get my cube wandering around, randomly, within the bounds of the game world, I just take half the bounds in the X and Z axes, and keep my Y axis as-is (so my cube always sits on the ground):
float xPos = Random.Range(VisibleArea.BottomLeft.x / 2.0f, VisibleArea.TopRight.x / 2.0f);
float zPos = Random.Range(VisibleArea.BottomRight.z / 2.0f, VisibleArea.TopLeft.z / 2.0f);
It's probably not the most efficient way to go about this, but it's working for me!
Answer by robertbu · Apr 22, 2014 at 03:09 AM
I see you are using Viewport points to construct the ray for your rect. To make your code work, you'd need to re-initialize your cameraRect with a new Raycast each time the camera is moved or rotated. You don't really need to do the Raycast. You can just do something like this in Update():
Vector3 pos = Camera.main.WorldToViewportPoint(transform.position);
pos.x = Mathf.Clamp01(pos.x);
pos.y = Mathf.Clamp01(pos.y);
transform.position = Camera.main.ViewportToWorldPoint(pos);
Note this clamps the pivot point, so 1/2 of your cube can leave the screen. If you want the entire cube on the screen, you'll have to figure a more restrictive clamping:
Vector3 pos = Camera.main.WorldToViewportPoint(transform.position);
pos.x = Mathf.Clamp(pos.x, 0.07f, 0.93f);
pos.y = Mathf.Clamp01(pos.y, , 0.07f, 0.93f);
transform.position = Camera.main.ViewportToWorldPoint(pos);
Thanks for the response. I should have been more clear in that I'm not actually moving nor rotating my camera -- its transform should be considered fixed at a rotation of (30, 45, 0). I've just extended the scale of the ground quad past the bounds of the camera so as to not get any background (i.e., non-ground) color in the game view.
Your solution led me towards what I feel like is the answer to my question, but I'm not exactly sure why. If I draw a line from (0, 0, -13) to (1, 1, 32.5) using ViewportToWorldPoint, I get a diagonal line across my viewport. For example:
Debug.DrawLine(Camera.main.ViewportToWorldPoint(0, 0, -13), Camera.main.ViewportToWorldPoint(0, 0, 32.5));
The z value of the "from" line (-13) is set to the value of my camera's near clip plane, but I don't know why setting the z value of my "to" line to 32.5 works the way it does. The camera's transform is positioned at -10 in the z axis, has a size of 13 (I realize this could also be related to the z value of my "from" line) and a far clip plane of 50. Any ideas why 32.5 for the z value seems to be the magic number to get the full dimensions of the viewport?
Answer by JacobHockey13 · Apr 22, 2014 at 03:09 AM
I had a very similar problem recently. I would recommend setting up colliders (on empty objects) outside of the camera view and adding a force (with a z and x components) when the level loads(this can be done with the awake function. Of course, you would put a material with a bounciness of 1 on the colliders and eliminate drag and friction
Answer by rakkarage · Apr 05, 2015 at 04:14 PM
static public Vector2 ConstrainRect(Bounds screen, Bounds map)
{
return ConstrainRect(screen.min, screen.max, map.min, map.max);
}
static public Vector2 ConstrainRect(Vector2 minScreen, Vector2 maxScreen, Vector2 minMap, Vector2 maxMap)
{
var offset = Vector2.zero;
var screenWidth = maxScreen.x - minScreen.x;
var screenHeight = maxScreen.y - minScreen.y;
var mapWidth = maxMap.x - minMap.x;
var mapHeight = maxMap.y - minMap.y;
if (screenWidth > mapWidth)
{
var diff = screenWidth - mapWidth;
minMap.x -= diff;
maxMap.x += diff;
}
if (screenHeight > mapHeight)
{
var diff = screenHeight - mapHeight;
minMap.y -= diff;
maxMap.y += diff;
}
if (minScreen.x < minMap.x) offset.x += minMap.x - minScreen.x;
if (maxScreen.x > maxMap.x) offset.x -= maxScreen.x - maxMap.x;
if (minScreen.y < minMap.y) offset.y += minMap.y - minScreen.y;
if (maxScreen.y > maxMap.y) offset.y -= maxScreen.y - maxMap.y;
return offset;
}
Your answer
Follow this Question
Related Questions
Moving a Rotated Camera 0 Answers
Creating a trimetric camera? 0 Answers
Ortho-Isometric camera not playing nice with culling 1 Answer
Isometric + Orthographic Player Movement 0 Answers
Constrain axis of isometric camera ("vertical" only) 0 Answers