How to prevent camera from going through specific layers - unity3d

I have a camera controller that can move, zoom and rotate the camera. It's pretty much the exact camera controller from Cities Skyline. i.e top-down with some freedom of movement.
I am using CheckSphere to detect when the camera collides with wall/ground layers, and this works perfectly. However my issue is I do not know how to reset it's position to the last valid one, and to keep it there until the camera chooses a valid direction. At the moment as soon as my CheckSphere hits a wall, the camera is stuck and no longer responds to input.
I tried to store the last valid position, rotation and zoom and simply force this when CheckSphere hits something, but it's not working.
Is there some way I can can use this method with CheckSphere in a way that the camera won't get stuck every time it hits something it's not allowed to hit?
void HandleInput()
{
if (Physics.CheckSphere(cameraTransform.position, 7, collideWith))
{
newPosition = oldPosition;
newRotation = oldRotation;
newZoom = oldZoom;
Debug.Log("Did Hit");
}
else
{
//Speed controls
if (Input.GetKey(KeyCode.LeftShift))
{
movementSpeed = fastSpeed;
}
else
{
movementSpeed = normalSpeed;
}
// Adjust movement speed based on camera zoom
movementSpeed *= (cameraTransform.localPosition.y / zoomSpeedFactor);
Vector3 adjustedForward = transform.forward;
adjustedForward.y = 0;
//Movement controls
if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow))
{
newPosition += (adjustedForward * movementSpeed);
}
if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow))
{
newPosition += (adjustedForward * -movementSpeed);
}
if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow))
{
newPosition += (transform.right * movementSpeed);
}
if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow))
{
newPosition += (transform.right * -movementSpeed);
}
//Rotation controls
if (Input.GetMouseButtonDown(2))
{
rotateStartPosition = Input.mousePosition;
}
if (Input.GetMouseButton(2))
{
rotateCurrentPosition = Input.mousePosition;
Vector3 difference = rotateStartPosition - rotateCurrentPosition;
rotateStartPosition = rotateCurrentPosition;
newRotation *= Quaternion.Euler(Vector3.up * (-difference.x / 5f));
newRotation *= Quaternion.Euler(Vector3.right * (difference.y / 5f));
Vector3 euler = newRotation.eulerAngles;
euler.x = ClampAngle(euler.x, verticalRotationClamp.x, verticalRotationClamp.y);
euler.z = 0;
newRotation.eulerAngles = euler;
}
//Zoom controls
if (Input.mouseScrollDelta.y != 0 && !EventSystem.current.IsPointerOverGameObject())
{
newZoom -= Input.mouseScrollDelta.y * zoomAmount;
newZoom.y = ClampValue(newZoom.y, zoomClamp.x, zoomClamp.y);
newZoom.z = ClampValue(newZoom.z, -zoomClamp.y, -zoomClamp.x);
}
oldPosition = new Vector3(newPosition.x, newPosition.y, newPosition.z);
oldRotation = new Quaternion(newRotation.x, newRotation.y, newRotation.z, newRotation.w);
oldZoom = new Vector3(newZoom.x, newZoom.y, newZoom.z);
Debug.Log("Did not Hit");
}
transform.position = Vector3.Lerp(transform.position, newPosition, Time.unscaledDeltaTime * acceleration);
transform.rotation = Quaternion.Lerp(transform.rotation, newRotation, Time.unscaledDeltaTime * acceleration);
cameraTransform.localPosition = Vector3.Lerp(cameraTransform.localPosition, newZoom, Time.unscaledDeltaTime * acceleration);
}

Related

How to add collisions in Unity with character controller?

I am currently working on a Unity game and I was having some trouble with it.
I created the movement of my character watching a tutorial and implementing the new input system. I now need to implement collisions so my character can shoot at enemies but I can not add a rigid body or a box collider because it uses character controller for the movement. Any ideas on how to resolve this? I tried watching some tutorials but I couldnt solve my problem and as I am new to this, I do not know what else to do.
This is the code I have at the moment for the movement. Controller.Move() is commented because I was having trouble with it but it is what makes my character move:
void playerMovement()
{
Vector2 inputVector = move.ReadValue<Vector2>();
finalVector.x = inputVector.x;
finalVector.y = inputVector.y;
//Vector3 currentPos = transform.position;
if (finalVector != Vector3.zero)
{
if (move.ReadValue<Vector2>().y == -1)
{
Quaternion target = Quaternion.Euler(180, 0, 0);
transform.rotation = target;
//controller.Move(finalVector * Time.deltaTime * speed);
}
else
{
if (move.ReadValue<Vector2>().y == 1)
{
Quaternion target = Quaternion.Euler(0, 0, 0);
transform.rotation = target;
//controller.Move(finalVector * Time.deltaTime * speed);
}
else
{
if (move.ReadValue<Vector2>().x == 1)
{
Quaternion target = Quaternion.Euler(0, 0, -90);
transform.rotation = target;
//controller.Move(finalVector * Time.deltaTime * speed);
}
else
{
if (move.ReadValue<Vector2>().x == -1)
{
Quaternion target = Quaternion.Euler(0, 0, 90);
transform.rotation = target;
//controller.Move(finalVector * Time.deltaTime * speed);
}
}
}
}
}
}
This is how I shoot:
void playerShoot() {
if (!canShoot) return;
GameObject b = Instantiate(bullet, bulletDirection.position, bulletDirection.rotation);
bullet.SetActive(true);
StartCoroutine(canPlayerShoot());
}
You should add a child game object, and then add the box collider to that. When using OnCollisionEnter(), it uses all the collider in child game objects too.

Frame-rate independent pushForce?

I'm working with a CharacterController and added the ability to push Rigidbodies. The problem is however that the pushing is frame-rate dependent. How would I be able to make it frame-rate independent? I have tried adding Time.deltatime, but this makes pushing not possible, I might be adding it wrong though.
Here's the code that adds force to rigidbodies;
void OnControllerColliderHit(ControllerColliderHit hit)
{
Rigidbody body = hit.collider.attachedRigidbody;
if (body == null || body.isKinematic)
return;
if (hit.moveDirection.y < -.3f)
return;
Vector3 pushDirection = new Vector3(hit.moveDirection.x, 0, hit.moveDirection.z);
body.velocity = pushForce * pushDirection;
}
As far as I know it has something to do with the last 2 lines of code.
Edit(The code for pushing):
public void PushStates() {
// Creating the raycast origin Vector3's
Vector3 forward = transform.TransformDirection(Vector3.forward) * distanceForPush;
Vector3 middle = controller.transform.position - new Vector3(0, -controller.height / 2, 0);
// Inspector bool
if (pushRay)
{
Debug.DrawRay(middle, forward, Color.cyan);
}
// Force the pushForce and movementSpeed to normal when the player is not pushing
pushForce = 0f;
movementSpeed = walkSpeed;
// Draws a raycast in front of the player to check if the object in front of the player is a pushable object
if (Physics.Raycast(middle, forward, out hit, distanceForPush))
{
if (InputManager.BButton() && playerIsInPushingTrigger)
{
PushableInfo();
playerIsPushing = true;
anim.SetBool("isPushing", true);
if (hit.collider.tag == "PushableLight")
{
pushForce = playerPushForceLight;
movementSpeed = pushSpeedLight;
}
else if (hit.collider.tag == "PushableHeavy")
{
pushForce = playerPushForceHeavy;
movementSpeed = pushSpeedHeavy;
}
// Checks the players speed now instead off movement. This is neccesary when the player is pushing a pushable into a collider.
// The player and pushable never stop moving because of force.
if (currentSpeed < 0.15f)
{
//Removes all remaining velocity, when the player stops pushing
pushableObjectRB.velocity = Vector3.zero;
pushableObjectRB.angularVelocity = Vector3.zero;
anim.SetFloat("pushSpeedAnim", 0f);
}
else
{
// Calls a rotation method
PushingRot();
if (hit.collider.tag == "PushableLight")
{
anim.SetFloat("pushSpeedAnim", pushSpeedAnimLight);
}
else if (hit.collider.tag == "PushableHeavy")
{
anim.SetFloat("pushSpeedAnim", pushSpeedAnimHeavy);
}
}
}
else
{
anim.SetBool("isPushing", false);
pushForce = 0f;
movementSpeed = walkSpeed;
playerIsPushing = false;
}
}
else
{
anim.SetBool("isPushing", false);
playerIsPushing = false;
}
// Setting the time it takes to rotate when pushing
AnimatorStateInfo stateInfo = anim.GetCurrentAnimatorStateInfo(0);
if (stateInfo.fullPathHash == pushStateHash)
{
turnSmoothTime = maxTurnSmoothTimePushing;
}
else
{
turnSmoothTime = 0.1f;
}
}
You were right, you need to multiply by Time.deltaTime which is the time elapsed since last rendered frame.
As this number is really small (has you have 60+ fps) you'll also need to increase the value (multiply it by 100, 1000...)

Unity: How to limit the zooming out of the camera

Using Unity 2018.3
This camera script allows the user to zoom in and out and move the camera with touch around.
On Start() the script takes the cameras current position and saves it as the out of bounds limit. So if the player is zoomed in and moves the camera to the side, the camera will stop when it reaches the bounds limit.
My problem is that currently the user can zoom out all they want, causing the camera to get shaky as the script is trying to relocate the camera but it doesn't stop the user from increasing the orthographicSize.
How do I limit the user from zooming out passed the out of bound limit it got on Start()?
public class ControlCamera : MonoBehaviour
{
private Vector2?[] oldTouchPositions = {
null,
null
};
private Vector2 oldTouchVector;
private float oldTouchDistance;
private Vector3 bottomLeft;
private Vector3 topRight;
private float cameraMaxY;
private float cameraMinY;
private float cameraMaxX;
private float cameraMinX;
public float maxZoom;
public float minZoom;
private void Start()
{
// Set max camera bounds
topRight = GetComponent<Camera>().ScreenToWorldPoint(new Vector3(GetComponent<Camera>().pixelWidth, GetComponent<Camera>().pixelHeight, -transform.position.z));
bottomLeft = GetComponent<Camera>().ScreenToWorldPoint(new Vector3(0, 0, -transform.position.z));
cameraMaxX = topRight.x;
cameraMaxY = topRight.y;
cameraMinX = bottomLeft.x;
cameraMinY = bottomLeft.y;
}
private void Update()
{
// No touches
if (Input.touchCount == 0)
{
oldTouchPositions[0] = null;
oldTouchPositions[1] = null;
}
// 1 Touch
else if (Input.touchCount == 1)
{
if (oldTouchPositions[0] == null || oldTouchPositions[1] != null)
{
oldTouchPositions[0] = Input.GetTouch(0).position;
oldTouchPositions[1] = null;
}
else
{
Vector2 newTouchPosition = Input.GetTouch(0).position;
transform.position += transform.TransformDirection((Vector3)((oldTouchPositions[0] - newTouchPosition) * GetComponent<Camera>().orthographicSize / GetComponent<Camera>().pixelHeight * 2f));
oldTouchPositions[0] = newTouchPosition;
}
// 2 touches or more
} else {
if (oldTouchPositions[1] == null)
{
oldTouchPositions[0] = Input.GetTouch(0).position;
oldTouchPositions[1] = Input.GetTouch(1).position;
oldTouchVector = (Vector2)(oldTouchPositions[0] - oldTouchPositions[1]);
oldTouchDistance = oldTouchVector.magnitude;
} else {
Vector2 screen = new Vector2(GetComponent<Camera>().pixelWidth, GetComponent<Camera>().pixelHeight);
Vector2[] newTouchPositions = {
Input.GetTouch(0).position,
Input.GetTouch(1).position
};
Vector2 newTouchVector = newTouchPositions[0] - newTouchPositions[1];
float newTouchDistance = newTouchVector.magnitude;
transform.position += transform.TransformDirection((Vector3)((oldTouchPositions[0] + oldTouchPositions[1] - screen) * GetComponent<Camera>().orthographicSize / screen.y));
GetComponent<Camera>().orthographicSize *= oldTouchDistance / newTouchDistance;
transform.position -= transform.TransformDirection((newTouchPositions[0] + newTouchPositions[1] - screen) * GetComponent<Camera>().orthographicSize / screen.y);
oldTouchPositions[0] = newTouchPositions[0];
oldTouchPositions[1] = newTouchPositions[1];
oldTouchVector = newTouchVector;
oldTouchDistance = newTouchDistance;
// --------------------------------------------------------------->>
// Control Zoom In
if (GetComponent<Camera>().orthographicSize > minZoom)
{
//
}
// Control Zoom Out
if (GetComponent<Camera>().orthographicSize < maxZoom)
{
//
}
// --------------------------------------------------------------->>
}
}
// Check if camera is out-of-bounds, if so, move back in-bounds
topRight = GetComponent<Camera>().ScreenToWorldPoint(new Vector3(GetComponent<Camera>().pixelWidth, GetComponent<Camera>().pixelHeight, -transform.position.z));
bottomLeft = GetComponent<Camera>().ScreenToWorldPoint(new Vector3(0, 0, -transform.position.z));
if (topRight.x > cameraMaxX)
{
transform.position = new Vector3(transform.position.x - (topRight.x - cameraMaxX), transform.position.y, transform.position.z);
}
if (topRight.y > cameraMaxY)
{
transform.position = new Vector3(transform.position.x, transform.position.y - (topRight.y - cameraMaxY), transform.position.z);
}
if (bottomLeft.x < cameraMinX)
{
transform.position = new Vector3(transform.position.x + (cameraMinX - bottomLeft.x), transform.position.y, transform.position.z);
}
if (bottomLeft.y < cameraMinY)
{
transform.position = new Vector3(transform.position.x, transform.position.y + (cameraMinY - bottomLeft.y), transform.position.z);
}
}
}
You should not use GetComponent repeatedly especially not in Update or any repeatedly called method as this creates a lot unnecessary overhead. Instead get it once
private Camera camera;
private void Awake()
{
camera = GetComponent<Camera>();
}
and reuse that reference -> Everywhere replace GetComponent<Camera>() width camera
If my understanding is correct, orthographicSize is half of the height of the camera in world units.
Since you already get topRight and bottomLeft in world coordinates you should be able to calculate an orthographicSize
maxZoom = (CameraMaxY - CameraMinY) / 2.0f;
however, if anyway you want the original zoom to be the max zoom you could also simply store the original zoom like
maxZoom = camera.orthographicSize;
And later you can simply clamp the value like
camera.orthographicSize = Mathf.Clamp(
camera.orthographicSize * oldTouchDistance / newTouchDistance,
minZoom,
maxZoom);
-> no need for extra if checks

How to check rotation of an object in Unity3d?

I'm a unity3d learner. I have a problem with rotation of an object. I want to rotate objects about 40 degrees along the z axis. If the objects rotation has reached 40 degrees, I want something to happen. Here is my code.
foreach(Touch touch in Input.touches) {
if(touch.phase != TouchPhase.Ended && touch.phase != TouchPhase.Canceled) {
var target = Quaternion.Euler (0, 0,-40);
transform.rotation = Quaternion.Slerp(transform.rotation, target, Time.deltaTime * smooth);
if (transform.rotation.eulerAngles.z == -40) {
toggle = true;
speech = "blah blah blah";
snake = man;
}
}
}
The if(transform.rotation.eulerAngles.z == -40) line of code is not working. So I don't know if the rotation degree has reached 40 degree or not. How do I check if the rotation degree has reached 40 degrees?
I don't understand your code intent.
EulerAngles is not set negative only positive value(Vector3)
-if(eulerAngles.z == -40) is not work you try change value -40 -> 320
If you want scenario ontouch -> object rotation -> event
try this code.
float rotTime = 1f; // rotation duration
Vector3 rotValue = new Vector3(0, 0, -40f); // rotation value
void Update () {
foreach (Touch touch in Input.touches)
if (touch.phase == TouchPhase.Began) OnTouchEvent();
}
void OnTouchEvent()
{
StopCoroutine("rotationCoroutine");
StartCoroutine("rotationCoroutine");
}
IEnumerator rotationCoroutine()
{
float startTime = Time.time;
Vector3 startRot = transform.eulerAngles;
Vector3 endRot = startRot;
endRot += rotValue;
while (Time.time - startTime <= rotTime)
{
transform.eulerAngles = Vector3.Slerp(startRot, endRot,(Time.time - startTime) / rotTime);
yield return null; // wait 1 frame
}
//rotation end
MyAction();
}
void MyAction()
{
Debug.Log("rotation end");
//toggle = true;
//speech = "blah blah";
//snake = man;
}
Good Luck :D

Orthographic camera zoom with focus on a specific point

I have an orthographic camera and would like to implement a zoom feature to a specific point. I.e., imagine you have a picture and want to zoom to a specific part of the picture.
I know how to zoom in, the problem is to move the camera to a position that has the desired zone in focus.
How can I do so?
The camera's orthographicSize is the number of world space units in the top half of the viewport. If it's 0.5, then a 1 unit cube will exactly fill the viewport (vertically).
So to zoom in on your target region, center your camera on it (by setting (x,y) to the target's center) and set orthographicSize to half the region's height.
Here is an example to center and zoom to the extents of an object. (Zoom with LMB; 'R' to reset.)
public class OrthographicZoom : MonoBehaviour
{
private Vector3 defaultCenter;
private float defaultHeight; // height of orthographic viewport in world units
private void Start()
{
defaultCenter = camera.transform.position;
defaultHeight = 2f*camera.orthographicSize;
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Collider target = GetTarget();
if(target != null)
OrthoZoom(target.bounds.center, target.bounds.size.y); // Could directly set orthographicSize = bounds.extents.y
}
if (Input.GetKeyDown(KeyCode.R))
OrthoZoom(defaultCenter, defaultHeight);
}
private void OrthoZoom(Vector2 center, float regionHeight)
{
camera.transform.position = new Vector3(center.x, center.y, defaultCenter.z);
camera.orthographicSize = regionHeight/2f;
}
private Collider GetTarget()
{
var hit = new RaycastHit();
Physics.Raycast(camera.ScreenPointToRay(Input.mousePosition), out hit);
return hit.collider;
}
}
hope you find this code example useful, should be a copy / paste.
Note: this script assume it's attached to the camera object, otherwise you should adjust the transform to the camera object reference.
private float lastZoomDistance = float.PositiveInfinity; // remember that this should be reset to infinite on touch end
private float maxZoomOut = 200;
private float maxZoomIn = 50;
private float zoomSpeed = 2;
void Update() {
if (Input.touchCount >= 2) {
Vector2 touch0, touch1;
float distance;
Vector2 pos = new Vector2(transform.position.x, transform.position.y);
touch0 = Input.GetTouch(0).position - pos;
touch1 = Input.GetTouch(1).position - pos;
zoomCenter = (touch0 + touch1) / 2;
distance = Vector2.Distance(touch0, touch1);
if(lastZoomDistance == float.PositiveInfinity) {
lastZoomDistance = distance;
} else {
if(distance > lastZoomDistance && camera.orthographicSize + zoomSpeed <= maxZoomOut) {
this.camera.orthographicSize = this.camera.orthographicSize + zoomSpeed;
// Assuming script is attached to camera - otherwise, change the transform.position to the camera object
transform.position = Vector3.Lerp(transform.position, zoomCenter, Time.deltaTime);
} else if(distance < lastZoomDistance && camera.orthographicSize - zoomSpeed >= maxZoomIn) {
this.camera.orthographicSize = this.camera.orthographicSize - zoomSpeed;
transform.position = Vector3.Lerp(transform.position, zoomCenter, Time.deltaTime);
}
}
lastZoomDistance = distance;
}
}