Restrict only camera viewport centre to ground plane
You can find the working Unity project containing this code here.
using UnityEngine;
using System.Collections;
public class CameraAnchorController : MonoBehaviour
{
public float xAcceleration = 0;
public float yAcceleration = 0;
public float accelerationRate; //set to 1 in inspector
public Vector2 acceleration;
public Vector2 velocity;
float friction = 0.98f;
public float xMin; //set to -5 in inspector
public float xMax; //set to 5 in inspector
public float yMin; //set to -5 in inspector
public float yMax; //set to 5 in inspector
//these -5/+d5 are because a Unity plane's origin is in the centre.
void Update()
{
xAcceleration = 0;
yAcceleration = 0;
//acceleration
xAcceleration += Input.GetKey(KeyCode.LeftArrow) ? -1 : 0;
xAcceleration += Input.GetKey(KeyCode.RightArrow) ? +1 : 0;
yAcceleration += Input.GetKey(KeyCode.UpArrow) ? +1 : 0;
yAcceleration += Input.GetKey(KeyCode.DownArrow) ? -1 : 0;
acceleration = new Vector2(xAcceleration, yAcceleration); //use Vector2 here or we incur extra, costly sqrt calls in magnitude.
acceleration = Vector2.ClampMagnitude(acceleration, 1.0f); //normalize it.
acceleration *= accelerationRate;
//speed
velocity += acceleration;
velocity *= friction;
//position
float xPos = transform.localPosition.x + velocity.x * Time.deltaTime;
float yPos = transform.localPosition.z + velocity.y * Time.deltaTime; //note the interchanged y/z here due to Unity's coord system.
//We'd usually just use 2 Mathf.Clamp(val, min, max) calls here, but we need to know
//the outcome of the clamping, so as to also restrict velocity if we hit a map side.
if (xPos < xMin)
{
xPos = xMin;
velocity = new Vector2(0, velocity.y);
}
if (xPos > xMax)
{
xPos = xMax;
velocity = new Vector2(0, velocity.y);
}
if (yPos < yMin)
{
yPos = yMin;
velocity = new Vector2(velocity.x, 0);
}
if (yPos > yMax)
{
yPos = yMax;
velocity = new Vector2(velocity.x, 0);
}
xPos = Mathf.Clamp(xPos, xMin, xMax); //limit to the plane's x extent in local space
yPos = Mathf.Clamp(yPos, yMin, yMax); //limit to the plane's y (z) extent in local space
//update transform
transform.localPosition = new Vector3(xPos, 0, yPos);
}
}
In Unity: Create a plane, create a sphere, then drag the sphere onto the plane so the plane is its parent. Now the sphere is moving in the plane's local space. You can name it "Camera anchor". Drag the Camera onto the anchor so that the anchor is its parent. Set your camera's x rotation to the required tilt in the inspector and its y rotation to 45; zero its position and then drag it out along local z axis to required viewing distance to see ground and anchor. Drag this script onto the anchor. Create a directional light. Using the arrow keys, you will see how the anchor restricts the camera's look vector (in centre of its viewport) so that it never leaves the ground plane. Notice that your controls are a bit funny, and treat up/down and left/right as diagonal planes. Fix is to replace:
xAcceleration += Input.GetKey(KeyCode.LeftArrow) ? -1 : 0;
xAcceleration += Input.GetKey(KeyCode.RightArrow) ? +1 : 0;
yAcceleration += Input.GetKey(KeyCode.UpArrow) ? +1 : 0;
yAcceleration += Input.GetKey(KeyCode.DownArrow) ? -1 : 0;
with
if (Input.GetKey(KeyCode.UpArrow))
{
xAcceleration += +1;
yAcceleration += +1;
}
if (Input.GetKey(KeyCode.DownArrow))
{
xAcceleration += -1;
yAcceleration += -1;
}
if (Input.GetKey(KeyCode.LeftArrow))
{
xAcceleration += -1;
yAcceleration += +1;
}
if (Input.GetKey(KeyCode.RightArrow))
{
xAcceleration += +1;
yAcceleration += -1;
}
Note that you can effectively zoom in and out by changing ortho camera's size
in the inspector.
This eliminates any need to perform matrix transforms, and solves other problems (below).
Restrict to camera edges to near ground plane edges
Let's say things are exactly as in your diagram - tiny world relative to large camera viewport. In that case, I can see exactly how you expect this to work. We'd pan the red rectangle around in a screen-axis aligned rectangle that is a bit larger, and exactly contains the isometric ground plane. We'll call this Implementation Style A. This is what Bálint has very reasonably suggested, given your diagram. But what if you have a large map with smaller viewport? I doubt then that this will be the ideal solution for you: even with a map just 4x the size of that in your diagram, you're going to be able to scroll into large, dark spaces in the top left, top right, bottom left and bottom right of the map, because you're only restricting by a screen-space rectangle. Not good?
I imagine you'd then want a different mechanism, one which locks the camera so its centre can't leave the bounds of the ground plane; you could then never run the camera into large, void spaces. I've supplied this above, call it Implementation Style B.
In either implementation, you'll need sufficient dummy tiles on the outside of the ground plane to ensure that at maximum zoom, you will never see black space. I've seen this done in some of the XCom / UFO games. Calculating how many rows / columns of extra tiles you would need exactly, is going to depend on the size
value on your camera (which links directly to viewport height in unity), and some calculation of your tile width as against your tile height, which can be accomplished with Mathf.Sin()
. You may also need a size
value that relates to horizontal vs vertical - that is as simple as size * Screen.width / Screen.height
. Still, all of this is probably unnecessary as you can manually test how many tiles worth of buffering works for you. Test against a 2:1 screen ratio to be very sure you've got it covered.
For the above solution, to create a buffer space inside your existing tile map, replace this:
//position
float xPos = transform.localPosition.x + velocity.x * Time.deltaTime;
float yPos = transform.localPosition.z + velocity.y * Time.deltaTime; //note the interchanged y/z here due to Unity's coord system.
with:
//position
float xPos = transform.localPosition.x + velocity.x * Time.deltaTime;
float yPos = transform.localPosition.z + velocity.y * Time.deltaTime; //note the interchanged y/z here due to Unity's coord system.
float buffer = 2f; //2 units in world space which might be 2 tiles across in your code.
float xMinLimited = xMin + buffer;
float xMaxLimited = xMax - buffer;
float yMinLimited = yMin + buffer;
float yMaxLimited = yMax - buffer;
//We'd usually just use 2 Mathf.Clamp(val, min, max) calls here, but we need to know
//the outcome of the clamping, so as to also restrict velocity if we hit a map side.
if (xPos < xMinLimited)
{
xPos = xMinLimited;
velocity = new Vector2(0, velocity.y);
}
if (xPos > xMaxLimited)
{
xPos = xMaxLimited;
velocity = new Vector2(0, velocity.y);
}
if (yPos < yMinLimited)
{
yPos = yMinLimited;
velocity = new Vector2(velocity.x, 0);
}
if (yPos > yMaxLimited)
{
yPos = yMaxLimited;
velocity = new Vector2(velocity.x, 0);
}