How to prevent an object from exiting the camera in a 3d game, I need to prevent the object from leaving the camera on all four sides (the camera is on top of a type like in a 2D game)

This script is attached to the object that should not go beyond the edges of the camera:

using UnityEngine; using System.Collections; public class PlayerBehaviour : MonoBehaviour { void Start() { } void Update() { AntiExitPos(); } void AntiExitPos() { Vector3 cameraToObject = transform.position - Camera.main.transform.position; // ΠΎΡ‚Ρ€ΠΈΡ†Π°Π½ΠΈΠ΅ ΠΏΠΎΡ‚ΠΎΠΌΡƒ Ρ‡Ρ‚ΠΎ ΠΈΠ³Ρ€ΠΎΠ²Ρ‹Π΅ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Ρ‹ Π² Π΄Π°Π½Π½ΠΎΠΌ случаС находятся Π½ΠΈΠΆΠ΅ ΠΊΠ°ΠΌΠ΅Ρ€Ρ‹ ΠΏΠΎ оси y float distance = -Vector3.Project(cameraToObject, Camera.main.transform.forward).y; // Π²Π΅Ρ€ΡˆΠΈΠ½Ρ‹ "срСза" ΠΏΠΈΡ€Π°ΠΌΠΈΠ΄Ρ‹ видимости ΠΊΠ°ΠΌΠ΅Ρ€Ρ‹ Π½Π° Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎΠΌ расстоянии ΠΎΡ‚ ΠΊΠ°ΠΌΠ΅Ρ€Ρ‹ Vector3 leftBot = Camera.main.ViewportToWorldPoint(new Vector3(0, 0, distance)); Vector3 rightTop = Camera.main.ViewportToWorldPoint(new Vector3(1, 1, distance)); // Π³Ρ€Π°Π½ΠΈΡ†Ρ‹ Π² плоскости XZ, Ρ‚.ΠΊ. ΠΊΠ°ΠΌΠ΅Ρ€Π° стоит Π²Ρ‹ΡˆΠ΅ ΠΎΡΡ‚Π°Π»ΡŒΠ½Ρ‹Ρ… ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ² float x_left = leftBot.x; float x_right = rightTop.x; float z_top = rightTop.z; float z_bot = leftBot.z; // ΠΎΠ³Ρ€Π°Π½ΠΈΡ‡ΠΈΠ²Π°Π΅ΠΌ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ Π² плоскости XZ Vector3 clampedPos = transform.position; clampedPos.x = Mathf.Clamp(clampedPos.x, x_left, x_right); clampedPos.z = Mathf.Clamp(clampedPos.z, z_bot, z_top); transform.position = clampedPos; } } 
  • And how can it be that it was through a code without callouts - Aimon Z.

4 answers 4

It would seem that the simplest question on the topic of camera viewport ...

The coordinate values ​​on both axes inside the camera's viewport take values ​​from 0 to 1. If the point takes on other values, it lies outside the viewport of this camera.

Since the object should not go beyond the visibility of the camera, it means that its coordinates in the projection on the camera's viewport should be within [0, 1] .

This can be achieved in 2 ways:

  1. object world pos -> object viewport pos -> clamp object viewport pos to [0..1] -> object world pos

In this case, the transfers from world pos to [0..1] viewportΚ»a lead to a huge loss of accuracy and the output is nonsense, verified by experiment, does not work.

  1. Get a rectangular section of the camera's visibility pyramid in world coordinates, and check the coordinates of other objects relative to this rectangle β€” it works fine. This method will go further.

In this case, it will be about limiting the position of a single object, but rewriting it for use on an array of objects is not difficult.

To begin with, we will find the very "cut" of the camera frustum - a regular rectangle. As I wrote above, viewport is always limited, specific coordinates depend on the engine. In the case of Unity, this is [0..1] . Well, here we find the coordinates of the angles of the viewport in world coordinates, using the coordinates of its edges in the coordinate systems of the viewport itself. Although the viewport is a rectangle, we pass there the 3 coordinate - the distance from the camera to the object of interest to us, everything seems logical here, so I won’t chew.

 Camera cam = Camera.main; Vector3 cameraToObject = transform.position - cam.transform.position; float distance = -Vector3.Project(cameraToObject, Camera.main.transform.forward).y; Vector3 leftTop = cam.ViewportToWorldPoint(new Vector3(0, 1, distance)); Vector3 leftBot = cam.ViewportToWorldPoint(new Vector3(0, 0, distance)); Vector3 rightBot = cam.ViewportToWorldPoint(new Vector3(1, 0, distance)); Vector3 rightTop = cam.ViewportToWorldPoint(new Vector3(1, 1, distance)); 

If we throw the whole thing in gizmos - we get the following picture:

enter image description here

In fact, a rectangle can be defined exactly 2 diagonally opposite points - this is quite enough to get the restrictions on both coordinates.

Summary Code:

 Vector3 cameraToObject = transform.position - Camera.main.transform.position; // ΠΎΡ‚Ρ€ΠΈΡ†Π°Π½ΠΈΠ΅ ΠΏΠΎΡ‚ΠΎΠΌΡƒ Ρ‡Ρ‚ΠΎ ΠΈΠ³Ρ€ΠΎΠ²Ρ‹Π΅ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Ρ‹ Π² Π΄Π°Π½Π½ΠΎΠΌ случаС находятся Π½ΠΈΠΆΠ΅ ΠΊΠ°ΠΌΠ΅Ρ€Ρ‹ ΠΏΠΎ оси y float distance = -Vector3.Project(cameraToObject, Camera.main.transform.forward).y; // Π²Π΅Ρ€ΡˆΠΈΠ½Ρ‹ "срСза" ΠΏΠΈΡ€Π°ΠΌΠΈΠ΄Ρ‹ видимости ΠΊΠ°ΠΌΠ΅Ρ€Ρ‹ Π½Π° Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎΠΌ расстоянии ΠΎΡ‚ ΠΊΠ°ΠΌΠ΅Ρ€Ρ‹ Vector3 leftBot = Camera.main.ViewportToWorldPoint(new Vector3(0, 0, distance)); Vector3 rightTop = Camera.main.ViewportToWorldPoint(new Vector3(1, 1, distance)); // Π³Ρ€Π°Π½ΠΈΡ†Ρ‹ Π² плоскости XZ, Ρ‚.ΠΊ. ΠΊΠ°ΠΌΠ΅Ρ€Π° стоит Π²Ρ‹ΡˆΠ΅ ΠΎΡΡ‚Π°Π»ΡŒΠ½Ρ‹Ρ… ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ² float x_left = leftBot.x; float x_right = rightTop.x; float z_top = rightTop.z; float z_bot = leftBot.z; // ΠΎΠ³Ρ€Π°Π½ΠΈΡ‡ΠΈΠ²Π°Π΅ΠΌ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ Π² плоскости XZ Vector3 clampedPos = transform.position; clampedPos.x = Mathf.Clamp(clampedPos.x, x_left, x_right); clampedPos.z = Mathf.Clamp(clampedPos.z, z_bot, z_top); transform.position = clampedPos; 

Result of work:

enter image description here

PS

It is clear that this approach can be optimized and, for example, get viewport coordinates exactly 1 time, if the height of the camera and the depths of all elements in the game are constant and the same. Such optimizations depend on the specific case.

  • Thank. Yes, it’s not trivial to use the projection of the vector to get the distance. - M. Green
  • @ M.Green, I initially just wanted to simply clamp the position of the object in the coordinates of the viewport and then transfer those same coordinates back to world coordinates, but the accuracy of the float brings. - RiotBr3aker
  • My object is flickering right and left, what should I do? - Aimon Z. Nov.
  • Here is the gif: imgur.com/a/FCCKbet - Aimon Z. Nov.
  • @AimonZ. Attach the complete code to the question; my code should not trigger this behavior. - RiotBr3aker Nov.

Alternatively, you can consider using OnBecameInvisible , but the truth is that the method will work when the object goes beyond the visibility of the camera and then you can change the direction of movement of the object to return it. Note that the component with the OnBecameInvisible method must be on the object with the Renderer component.

You can make a crutch decision: place on all four sides of the object a single transparent Quad with a hung script that will trigger as a trigger and send the object in the opposite direction.

  • What is the variant with quad `s different from the colliders proposed earlier? - RiotBr3aker
  • @RiotBr3aker so Quad embed nested objects in the object that should not go out and do not need to align and position, depending on the screen size. Coding minimum, you only need to register how to behave if you go beyond the camera - KingPeas
  • @ RiotBr3aker Quad will work as a trigger for the object itself, which can no longer move. Adjusting the position of the quad, we adjust the border to the edge of the screen and do everything visually without gizmo and calculations. Moreover, such a thing will work when the camera turns, changing its size, changing the FOV and God knows what else affects the size of the picture). All the problems of tracking the visibility zone will be taken by the engine. - KingPeas
  • "No calculations"? Will "send the object in the opposite direction" by itself work? And here in general gizmos? I used it to show the boundaries of the object. OnBecameInvisible in principle is not suitable for this task - the object will first disappear and only then you will do something with it. I generally keep quiet about the decision through the Quad, the same colliders, only instead of the usual colliders there will be 4 MonoBehaviour `s that is absurd. - RiotBr3aker
  • @RiotBr3aker, dear, I'm not saying that this method is perfect. The approach you suggested for determining the position based on the ViewPort is more correct, although there are also difficulties with determining the area to remove an object from the edge if it has a complex shape and size. In the variant proposed by me, if the object does not rotate relative to the camera, placing triggers provides a visual opportunity to determine to what extent one can approach the edge of the screen (or enter it). Well, the behavior of the return to the area of ​​the camera will have to write in any case, here the logic is determined by the task. - KingPeas

Um ... Put the colliders in those places where the boundary of the camera.

  • Those. Connect physics for one feature? Such a solution) - RiotBr3aker
  • Not quite go. The camera depends on the screen resolution, which means that when launched on a different resolution, the boundary set by the colliders will remain in a completely different place - M. Green
  • @ M.Green what prevents these colliders from moving depending on the resolution? - RiotBr3aker
  • @RiotBr3aker, the fact that since we can already find out the "boundaries", then what's the point of putting colliders there at all? - M. Green
  • @ M.Green and to put the collider on the "ground" - much better? :) - RiotBr3aker

Maybe this will help you

  Vector3 screenPoint = Camera.main.WorldToViewportPoint("позиция вашСго ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π°"); bool onScreen = screenPoint.z > 0 && screenPoint.x > 0 && screenPoint.x < 1 && screenPoint.y > 0 && screenPoint.y < 1;