- Home /
Clamping tilemaps to screen (without moving camera)?
I really hope someone can help, because we've been stuck on this for 4 days straight...
Our 2D text adventure project is composed almost entirely out of UI elements, and so there hasn't been any need to move our 2D orthographic camera for anything at all. But after adding a map system into the game, we decided on using Tilemaps to visually represent it, and then add the ability to pan/zoom the tilemap itself inside the UI Canvas, over the top of the rest of the UI.
Here is a simplified version of our hierarchy:
After adding this functionality, we also decided it would be best to keep the map within the boundaries of the screen, even when zooming in or out.
Here's how we're handling input:
InputUtils.cs
(a singleton class attached to EventSystem):
void HandlePan(float panSpeed) {
Vector3 newPanPos = Input.mousePosition;
Vector3 offset = Camera.main.ScreenToViewportPoint(prevPanPos - newPanPos);
Vector3 panDelta = new Vector3(offset.x * panSpeed, offset.y * panSpeed);
if(move.magnitude > 0.01f) {
if(onPan != null && onPan.Target != null)
onPan.Invoke(panDelta); // Broadcast this event to listeners
}
prevPanPos = newPanPos; // Store this most recent input to check against it later
}
void HandleZoom(float zoomSpeed) {
float offset = Input.GetAxis("Mouse ScrollWheel");
if (offset == 0 || onZoom == null || onZoom.Target == null)
return;
onZoom.Invoke(offset * zoomSpeed); // Broadcast this event to listeners
}
And here's how we're handling panning & zooming:
Areamap.cs
(attached to MapPanel):
private void Start()
{
mapPanel = transform; // This is only here for the sake of clarity
// Called from the InputUtils singleton when the user pans the tilemap
InputUtils.onPan += (Vector3 delta) => {
// Here, we would need to calculate panBoundsMin & panBoundsMax
// to keep the map within screen bounds
// This works, but how to calculate panBoundsMin & panBoundsMax,
// while also taking mapPanel.localScale into account?
delta.x = Mathf.Clamp(
floorMap.transform.localPosition.x + delta.x,
panBoundsMin.x, panBoundsMax.x
);
delta.y = Mathf.Clamp(
floorMap.transform.localPosition.y + delta.y,
panBoundsMin.y, panBoundsMax.y
);
floorMap.transform.localPosition = delta; // >> These perform the actual tilemap pan <<
overlayMap.transform.localPosition = delta; // >> These perform the actual tilemap pan <<
};
// Called from the InputUtils singleton when the user zooms in/out on the tilemap
InputUtils.onZoom += (float delta) => {
zoomValue += delta * zoomScalar; // zoomValue and zoomScalar are private variables
zoomValue = Mathf.Clamp(delta, minZoom, maxZoom); // min & maxZoom are defined in editor
if(mapPanel != null) {
var newScale = new Vector3(zoomValue, zoomValue, 1);
mapPanel.localScale = newScale; // >> This performs the actual tilemap zoom <<
// Here, we would need to pan the map back onto the screen,
// or un-zoom the map if it shows any part outside of map bounds,
// or simply prevent zooming if we can pre-calculate such an event
}
};
Generate(); // This creates or loads the map, which I think is out of scope of this question
}
How can we calculate panBoundsMin
and panBoundsMax
taking mapPanel.localScale
into account to clamp the map onto the screen while panning/zooming? or how else can we go about this?