Draging camera to a limit (x, y axis) - UNITY - unity3d

i did a tutorial about doing a camera drag on a 2D or 3D worldmap.
The code is working. Looks like the following:
void Update_CameraDrag ()
{
if( Input.GetMouseButtonUp(0) )
{
Debug.Log("Cancelling camera drag.");
CancelUpdateFunc();
return;
}
// Right now, all we need are camera controls
Vector3 hitPos = MouseToGroundPlane(Input.mousePosition);
//float maxWidth = 10;
//if (hitPos.x > maxWidth)
//{
// hitPos.x = maxWidth;
//}
Vector3 diff = lastMouseGroundPlanePosition - hitPos;
Debug.Log(diff.x+ Space.World);
Camera.main.transform.Translate (diff, Space.World);
lastMouseGroundPlanePosition = hitPos = MouseToGroundPlane(Input.mousePosition);
}
Now my problem is, that you can drag unlimiited into any directions.
I would rather like to difine something like borders on the x and y axis.
Basically maximum values for the camera. If the camera would surpass these values, their position value would be set to the given maximum value.
Unfortunately, i am not sure how it works, especially, since the tutorial set everything into relation to Space.World - and i am not even sure what that is. I mean i understand that "diff" is the change between the current position and the new positon in relation to Space.World and then the camera gets moved accordingly. I would just like to define a max value which the camera can not surpass. Any ideas how to do that. I am unfortunately still learning - so kinda hard for me and i was hoping for help.

If you were to record the X and Y position of the camera as it goes in a variable and use the MathF function. I.e
if you have a map that is 100(x)x150(y)units you could use
xPositionOfCamera = Mathf.Clamp(xPositionOfCamera, -50, 50);
yPositionOfCamera = Mathf.Clamp(YPositionOfCamera, -75, 75);
I'm not 100% sure if that's what you want it to do, but it's how I would limit it.

I write a simple and reliable script for my game to handle camera drag and swipe for any camera aspect ratio. Everyone can use this code easily :) Just adjust the xBoundWorld and yBoundWorld values
using UnityEngine;
public class CameraDragController : MonoBehaviour
{
[SerializeField] private Vector2 xBoundWorld;
[SerializeField] private Vector2 yBoundWorld;
[SerializeField] public bool HorizentalDrag = true;
[SerializeField] public bool VerticalDrag = true;
[SerializeField] public float speedFactor = 10;
private float leftLimit;
private float rightLimit;
private float topLimit;
private float downLimit;
public bool allowDrag = true;
private void Start()
{
CalculateLimitsBasedOnAspectRatio();
}
public void UpdateBounds(Vector2 xBoundNew, Vector2 yBoundNew)
{
xBoundWorld = xBoundNew;
yBoundWorld = yBoundNew;
CalculateLimitsBasedOnAspectRatio();
}
private void CalculateLimitsBasedOnAspectRatio()
{
leftLimit = xBoundWorld.x - Camera.main.ViewportToWorldPoint(new Vector3(0, 0, 0)).x;
rightLimit = xBoundWorld.y - Camera.main.ViewportToWorldPoint(new Vector3(1, 0, 0)).x;
downLimit = yBoundWorld.x - Camera.main.ViewportToWorldPoint(new Vector3(0, 0, 0)).y;
topLimit = yBoundWorld.y - Camera.main.ViewportToWorldPoint(new Vector3(0, 1, 0)).y;
}
Vector3 lastPosView; // we use viewport because we don't want devices pixel density affect our swipe speed
private void LateUpdate()
{
if (allowDrag)
{
if (Input.GetMouseButtonDown(0))
{
lastPosView = Camera.main.ScreenToViewportPoint(Input.mousePosition);
}
else if (Input.GetMouseButton(0))
{
var newPosView = Camera.main.ScreenToViewportPoint(Input.mousePosition);
var cameraMovment = (lastPosView - newPosView) * speedFactor;
lastPosView = newPosView;
cameraMovment = Limit2Bound(cameraMovment);
if (HorizentalDrag)
Camera.main.transform.Translate(new Vector3(cameraMovment.x, 0, 0));
if (VerticalDrag)
Camera.main.transform.Translate(new Vector3(0, cameraMovment.y, 0));
}
}
}
private Vector3 Limit2Bound(Vector3 distanceView)
{
if (distanceView.x < 0) // Check left limit
{
if (Camera.main.transform.position.x + distanceView.x < leftLimit)
{
distanceView.x = leftLimit - Camera.main.transform.position.x;
}
}
else // Check right limit
{
if (Camera.main.transform.position.x + distanceView.x > rightLimit)
{
distanceView.x = rightLimit - Camera.main.transform.position.x;
}
}
if (distanceView.y < 0) // Check down limit
{
if (Camera.main.transform.position.y + distanceView.y < downLimit)
{
distanceView.y = downLimit - Camera.main.transform.position.y;
}
}
else // Check top limit
{
if (Camera.main.transform.position.y + distanceView.y > topLimit)
{
distanceView.y = topLimit - Camera.main.transform.position.y;
}
}
return distanceView;
}
}

Related

Unity RotateAround Clamp Dead Zone Issue

For the past 3 days, I have tried numerous methods and read dozens of questions on forums in order to achieve the desired effect of rotating a camera on the Y axis using mouse rotations, and clamping the rotation within a certain range. The amount of rotation is dependent on how close the mouse is to the edge of the screen.
I was unable to find a solution, but I was able to learn enough to have my own honest attempt at it, and I came quite close. The prominent issue I faced was being able to properly clamp the RotateAround() inside of the desired range. This is because of eulerAngles being 0 - 360, and having the min and max of the clamp transition to the other side of the spectrum.
By clamping the rotation degrees before calling RotateAround, I was able to get the clamping to work, though I had to use Booleans to get them to work properly.
My current issue is that when the initial Y rotation(anchor) is slightly below 360, the camera fails to clamp on the left and will clamp back around to the right side of the range.
In all other situations, the clamping works just fine:
Initial Y rotation slightly above 0
Not within the dead zone
While debugging, I find that this is because rightOverFlag is true. When I set this bool to true manually in situations clamping normally works fine, the clamping will no longer work on the left.
I can't seem to find how this bool being true causes something like this to happen, so I am hoping some fresh eyes and seasoned advice can help me learn from this. Thank you.
public gameState gameState;
public openCams openCams;
private GameObject GameStateManager;
[SerializeField]
private GameObject Player;
[SerializeField]
private Camera cam;
// Rotation Variables
private Quaternion initialRotation;
private float initialYRotation = 0f;
[SerializeField]
private float angleRange;
private float anchorY = 0f;
public bool leftOverFlag = false;
public bool rightOverFlag = false;
void Start()
{
cam = Camera.main;
Cursor.lockState = CursorLockMode.None;
}
public void OrientControls()
{
initialRotation = Player.transform.rotation;
initialYRotation = Player.transform.eulerAngles.y;
anchorY = initialYRotation;
if (anchorY > 360)
{
anchorY -= 360;
}
rightOverFlag = false;
leftOverFlag = false;
if ((anchorY + angleRange) > 360)
{
rightOverFlag = true;
}
else if ((anchorY - angleRange) <= 0)
{
leftOverFlag = true;
}
}
void Update()
{
if (openCams.GetMonitorState() == false)
{
mousePos = Input.mousePosition;
float rotateDegrees = 0f;
//Debug.Log(Player.transform.eulerAngles.y);
// THERE IS CODE HERE THAT INCREASES ROTATE DEGREES BASED ON MOUSE POSITION. I removed it for the sake of readability, because it was quite long and unrelated to my issue.
float angleFromInitial = Quaternion.Angle(initialRotation, Player.transform.rotation);
float currentPlayerYRot = Player.transform.eulerAngles.y;
if (currentPlayerYRot > 360)
{
currentPlayerYRot -= 360;
}
bool currentRightOverageFlag = false;
bool currentLeftOverageFlag = false;
if (rightOverFlag)
{
if ((currentPlayerYRot) < (anchorY - (angleRange - 5))) //
{
currentRightOverageFlag = true;
}
}
if (leftOverFlag)
{
if ((currentPlayerYRot) > (anchorY + (angleRange + 5)))
{
currentLeftOverageFlag = true;
}
}
// !!! - For some reason, when rightOverFlag is enabled, the clamp does not work on the left side. In all other situations, the clamp works perfectly.- !!!
if (!currentLeftOverageFlag && !currentRightOverageFlag)
{
if (currentPlayerYRot < anchorY) // Regular
{
angleFromInitial *= -1;
}
}
else if (currentLeftOverageFlag && !currentRightOverageFlag)
{
if (currentPlayerYRot > anchorY) // If over the left line
{
angleFromInitial *= -1;
}
}
else if (!currentLeftOverageFlag && currentRightOverageFlag)
{
if (currentPlayerYRot > anchorY) // If over the right line
{
angleFromInitial *= -1;
}
}
else
{
Debug.Log("staticPersonController: ERROR => Cannot have current left and right overage flags enabled at the same time.");
}
currentLeftOverageFlag = false;
currentRightOverageFlag = false;
float newAngle = Mathf.Clamp(angleFromInitial + rotateDegrees, -angleRange, angleRange);
rotateDegrees = newAngle - angleFromInitial;
Player.transform.RotateAround(Player.transform.position, Vector3.up, rotateDegrees);
}
}
}
Assume you have these two variables set.
// This is the initial value of the player's Y rotation. Set only once.
float initialRotY = Player.transform.eulerAngles.y;
// This is the offset of the player's Y rotation based on the cursor position
// e.g. -90 when the cursor is on the left edge and 90 on the right
float mouseBasedRotY;
In the Update method, just do this.
Player.transform.rotation = Quaternion.Euler(0f, initialRotY + mouseBasedRotY, 0f);
You don't have to clamp the value, because the degree is always limited in [initialRotY-90, initialRotY+90]

Circular Slider for Rotation in Unity

For this setup in Unity, there is a circular dial. The values of the rotation are being used to scale the model. The actual scaling code is removed because there is a bug in the way zAngle value is calculated. Ideally it should be in steady increments in all directions, but here, the values jump across horizontal and vertical axes but they grow slowly when the dial is dragged in other directions.
Is there a recommended practice for such cases when using the screen drag position to calculate the rotation value?
zAngle is the final angle value used to rotate the dial and would be also used after some clamping to scale the model, but that is not the requirement here.
public class CircularScaleDial : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
RectTransform rectTransform;
public GameObject model;
[SerializeField] Vector2 delta = Vector2.one;
[SerializeField] CanvasScaler canvasScaler;
private Vector3 startPosition;
[SerializeField] int zAngle;
Vector3 gd1 = Vector3.zero;
private void Awake()
{
rectTransform = GetComponent<RectTransform>();
}
public void OnBeginDrag(PointerEventData eventData)
{
startPosition = GetDelta_3(eventData);
}
int GetAngleFromPosition(Vector2 pos) {
var angleRadians = Mathf.Atan2(pos.y, pos.x);
var angleDegrees = angleRadians * Mathf.Rad2Deg;
return (int)angleDegrees;
}
Vector3 GetDelta_3(PointerEventData data)
{
Vector3 pos = new Vector3(data.position.x, data.position.y, 0);
Vector3 s2vp = Camera.main.ScreenToViewportPoint(pos);
return s2vp;
}
public void OnDrag(PointerEventData data)
{
if (data.dragging)
{
gd1 = GetDelta_3(data);
delta.x = gd1.x;
delta.y = gd1.y;
zAngle = GetAngleFromPosition(delta);
rectTransform.localRotation = Quaternion.Euler(new Vector3(0, 0, zAngle));
}
}
}
It was sort of fixed with a simpler code. Made other tweaks but this was the main change. The essence here is to resolve the delta along y. For this case I just take the y component but ideally we would take a dot product of two values to resolve one along the other.
public void OnBeginDrag(PointerEventData eventData)
{
startV2 = eventData.position;
}
public void OnDrag(PointerEventData data)
{
if (data.dragging)
{
endV2 = data.position;
Vector2 deltaV2 = endV2 - startV2;
deltaV2 = deltaV2.normalized;
rectTransform.Rotate(new Vector3(0, 0, -deltaV2.y), Space.Self);
startV2 = endV2;
}
}

How to get width difference between the canvas and a scaled image and do an efficient PingPong effect in Unity3D

I am trying to get the width difference between the canvas and a image which is scaled by CanvasScaler in order to create translation between the image and his border.
Illustration:
How get the size of the red arrow?
[EDIT 1]
The code snippet bellow give me a possible correct result
var dist = (canvasRectTransform.rect.width - image.sprite.rect.width) / 2;
But It seems to be incorrect:
public class Background : Monobehaviour
{
private float dist;
private float _percentage;
private float _currentLerpTime;`
private readonly Dictionary<LerpDirection, Func<Vector3>> _lerpDirectionActions;
public float lerpTime;
void Awake()
{
var image = GetComponent<Image>();
var canvasRectTransform = GetComponentInParent<RectTransform>();
dist = (canvasRectTransform.rect.width - image.sprite.rect.width) / 2;
_lerpDirectionActions = new Dictionary<LerpDirection, Func<Vector3>>
{
[LerpDirection.Left] = LerpToLeft,
[LerpDirection.Right] = LerpToRight
};
}
private Vector3 Lerp()
{
return Vector3.Lerp(
transform.position,
_lerpDirectionActions[lerpDirection].Invoke(), // will call LerpToRight or LerpToLeft
_percentage
);
}
private float LerpX => Lerp().x;
private Vector3 LerpToRight()
{
return new Vector3(transform.position.x - dist, transform.position.y);
}
private Vector3 LerpToLeft()
{
return new Vector3(transform.position.x + dist , transform.position.y);
}
void Update()
{
_currentLerpTime += Time.deltaTime;
_percentage = _currentLerpTime / lerpTime;
var localPositionX = tranform.position.x;
var mustGoRight = localPositionX <= 0 && lerpDirection == LerpDirection.Right;
var mustGoLeft = localPositionX >= dist && lerpDirection == LerpDirection.Left;
if (mustGoLeft || mustGoRight)
{
direction = direction.Invert(); // invert direction
Reset();
}
tranform.position = new Vector3(LerpX, tranform.position.y)
}
}
The Background script is applied to the Background GameObject.
_lerpDirectionActions[lerpDirection].Invoke()
This code above will invoke the right function for lerping on left or on right.
Illustration:
The translation change his direction but not when the canvas is on the border on the image.
The value you are searching for would be
var difference = (canvas.GetComponent<RectTransform>().rect.width - image.GetComponent<RectTransform>().rect.width) / 2f;
Your script looks quite complicated to be honest.
Why not simply put it all into one single Coroutine using Mathf.PingPong which does exactly what you are currently controlling with your direction flags and actions
PingPongs the value t, so that it is never larger than length and never smaller than 0.
The returned value will move back and forth between 0 and length.
public Canvas canvas;
public Image image;
// Time in seconds to finish the movement from one extreme to the other
public float duration = 1;
private void Start()
{
StartCoroutine (LerpForthAndBack());
}
private IEnumerator LerpForthAndBack()
{
var difference = (canvas.GetComponent<RectTransform>().rect.width - image.GetComponent<RectTransform>().rect.width) / 2f;
var maxPosition = Vector3.right * difference;
var minPosition = Vector3.left * difference;
// Hugh? :O
// -> This is actually totally fine in a Coroutine
// as long as you yield somewhere within it
while(true)
{
image.transform.position = Vector3.Lerp(minPosition, maxPosition, Mathf.PingPong(Time.time / duration, 1));
// "Pause" the routine, render the frame and
// and continue from here in the next frame
yield return null;
}
}
ofcourse you could also do the same still in Update
private Vector3 minPosition;
private Vector3 maxPosition;
private void Start()
{
var difference = (canvas.GetComponent<RectTransform>().rect.width - image.GetComponent<RectTransform>().rect.width) / 2f;
maxPosition = Vector3.right * difference;
minPosition = Vector3.left * difference;
}
private void Update()
{
image.transform.position = Vector3.Lerp(minPosition, maxPosition, Mathf.PingPong(Time.time / duration, 1));
}

Level Selection menu, camera boundary unity 2d

My level selection menu scene will be a graphic map. Think level selection similar to candy crush. I have different sprites for different maps that will be instantiated when the user moves the camera to that map. In order for me to achieve this, I scroll/move the camera using touch on it's Y-axis. But the problem is, I'm having a trouble getting the boundary of the map sprite, I already read a lot of solution about this one. Here is my current script. I don't know what I'm doing wrong. My camera got stuck when it is near the boundary. Thank you for your help
public float scrollSpeed = 10.0f;
public GameObject[] maps;
Camera mainCamera;
private float topBound;
private float bottomBound;
private Vector3 pos;
private SpriteRenderer currentSprite;
private int mapIndex;
void Awake ()
{
mapIndex = 0;
mainCamera = Camera.main;
currentSprite = maps [mapIndex].GetComponent<SpriteRenderer> ();
bottomBound = currentSprite.sprite.bounds.size.y * -1;
topBound = currentSprite.sprite.bounds.size.y + currentSprite.gameObject.transform.position.y;
}
void Update ()
{
if (Input.touchCount > 0 && Input.GetTouch (0).phase == TouchPhase.Moved)
{
Vector2 touchDeltaPosition = Input.GetTouch(0).deltaPosition;
mainCamera.transform.Translate(0f,-touchDeltaPosition.y * scrollSpeed * Time.deltaTime,0f);
}
}
private float getAxisDelta(float axisDelta,float camMin,float camMax,float bottom,float top)
{
//get where edge of camera will have moved if full translate is done
float boundaryMinPixelsDestination = camMin - Mathf.Abs(axisDelta);
float boundaryMaxPixelsDestination = camMax + Mathf.Abs(axisDelta);
Debug.Log("MaxPixels:"+boundaryMaxPixelsDestination+"="+camMax+"+"+Mathf.Abs(axisDelta));
//check to see if you're within the border
if ((boundaryMinPixelsDestination <= bottom && axisDelta > 0) ||
(boundaryMaxPixelsDestination >= top && axisDelta < 0))
{
axisDelta = 0;
}
return axisDelta;
}
private float camBoundPos(string pos)
{
float bounds = 0f;
if (pos == "top")
bounds = mainCamera.transform.position.y + mainCamera.orthographicSize;
if(pos == "bottom")
bounds = mainCamera.transform.position.y - mainCamera.orthographicSize;
return bounds;
}

Lifting platforms is not working as it should

I would like to make some lifting platforms in my game, so if the platform went down, the characters can't go over it. I have written a script for it, but for some reason the "lifting up" is not working as intended. It won't go back to its starting place, but it will go a bit below. And for some reason it won't go smoothly to the place where it should, just "teleport" there and done. I thougt multiplying Time.deltaTime with a const will help, but it is the same.
Here is my code, any help would be appreciated:
public class LiftingPlatform : MonoBehaviour {
private Transform lift;
private bool isCanBeLifted;
private float timeToLift;
public float timeNeededToLift = 5f;
private Vector3 startPos;
private Vector3 downPos;
private Vector3 shouldPos;
private bool isDown;
public GameObject[] collidingWalls;
// Use this for initialization
void Start () {
lift = transform;
isCanBeLifted = true;
timeToLift = 0f;
isDown = false;
startPos = transform.position;
downPos = new Vector3(startPos.x, startPos.y - 5f, startPos.z);
}
// Update is called once per frame
void Update () {
timeToLift += Time.deltaTime;
if (timeToLift >= timeNeededToLift) {
if (isCanBeLifted) {
if (isDown) {
shouldPos = Vector3.Lerp(startPos, downPos, Time.deltaTime * 10);
lift.position = new Vector3(shouldPos.x, shouldPos.y, shouldPos.z);
isDown = true;
}
else if (!isDown) {
shouldPos = Vector3.Lerp(downPos, new Vector3(startPos.x, startPos.y, startPos.z), Time.deltaTime * 10);
lift.position = new Vector3(shouldPos.x, shouldPos.y, shouldPos.z);
isDown = false;
}
}
timeToLift = 0;
}
if (!isDown) {
for (int i = 0; i < collidingWalls.Length; i++) {
collidingWalls[i].SetActive(true);
}
}
else if (isDown) {
for (int i = 0; i < collidingWalls.Length; i++) {
collidingWalls[i].SetActive(false);
}
}
}
void OnTriggerEnter(Collider collider) {
if (collider.tag == "Player" || collider.tag == "Enemy") {
isCanBeLifted = false;
}
}
void OnTriggerExit(Collider collider) {
if (collider.tag == "Player" || collider.tag == "Enemy") {
isCanBeLifted = true;
}
}
}
These lifting platforms are a child of another Platforms object.
It doesn't look like you are updating the object's position every frame. You are only checking if the total time passed is greater than the time needed to lift, and then updating the position to a value that is dependent on the delta time (using the Vector3.Lerp function).
What I would do is in the update step, if timeToLift is greater then timeNeededToLift, subtract the latter from the former and invert the value of isDown. Then, in your Vector3.Lerp, make the third argument (timeToLift / timeNeededToLift) instead of (Time.deltaTime * 10). Can you try that and see if it works?
The third argument for Vector3.Lerp is the "blending factor" between the two vectors, 0 is the first vector, 1 is the second, and 0.5 is in between. If the total time is greater than the time needed to lift, but the delta time is not greater than 1, it will get the position of the platform using a blending factor of less than 1, resulting in a platform that didn't move fully.