Unity camera trembling on collision - unity3d

The script works perfectly, exactly as it should, except when zooming too much and the SphereCast collides with the mesh. When it hits a mesh, I want the camera to just stop, and not try to go through the mesh.
if(Input.touchCount == 2)
{
Touch first = Input.GetTouch(0);
Touch second = Input.GetTouch(1);
origin = mainCamera.transform.position;
direction = mainCamera.transform.forward;
RaycastHit hit;
if(Physics.SphereCast(origin, sphereRadius, direction, out hit, maxDistance))
{
inCollision = true;
}
else
{
inCollision = false;
}
if(first.phase == TouchPhase.Began || second.phase == TouchPhase.Began)
{
initialDistance = Vector3.Distance(first.position, second.position);
}
if(first.phase == TouchPhase.Moved || second.phase == TouchPhase.Moved)
{
movedDistance = Vector3.Distance(first.position, second.position) - initialDistance;
if(inCollision)
{
movedDistance = -Mathf.Abs(movedDistance);
}
mainCamera.transform.localPosition = new Vector3(0, 0, mainCamera.transform.localPosition.z + movedDistance * zoomSensitivity * Time.deltaTime);
initialDistance = Vector3.Distance(first.position, second.position);
}
}
I tried to:
Set movedDistance to 0 when SphereCast hits the mesh;
Clamp mainCamera.transform.localPosition.z to mainCamera.transform.localPosition.z, but only when SphereCast hits the mesh, so the z doesn't zoom more than it's current value.

As commented before you all the time move forward, collide, move backwards, don't collide anymore, move forward etc.
You could probably rather not move backwards but check if you would collide if you would move and do not move at all in this case:
if(first.phase == TouchPhase.Began || second.phase == TouchPhase.Began)
{
initialDistance = Vector3.Distance(first.position, second.position);
}
else if(first.phase == TouchPhase.Moved || second.phase == TouchPhase.Moved)
{
movedDistance = Vector3.Distance(first.position, second.position) - initialDistance;
// Only check until here if you WOULD collide
origin = mainCamera.transform.position;
var targetPosition = origin + mainCamera.transform.forward * movedDistance * Time.deltaTime * zoomSensitivity;
direction = mainCamera.transform.forward;
// only move if there WILL BE no collision
if(!Physics.SphereCast(targetPosition, sphereRadius, direction, out var hit, maxDistance))
{
mainCamera.position = targetPosition;
}
initialDistance = Vector3.Distance(first.position, second.position);
}
From the example for SphereCast you can see that you could even take the hit.distance into account in order to at least move the camera the closest possible.

Related

How to prevent camera from going through specific layers

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);
}

Using RayCastAll instead of RayCast

I have two gameobjects roller and marker. When I use physics raycast it works, but with raycastall it doesn't. I need to use raycastall, because i need to iterate through colliders. When marker is on roller normal raycast doesn't work and I need to use raycastall.
This code works:
if (Input.touchCount > 0)
{
// get mouse position in screen space
// (if touch, gets average of all touches)
Vector3 screenPos = Input.mousePosition;
// set a distance from the camera
screenPos.z = 10.0f;
// convert mouse position to world space
Ray cameraRay = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
Physics.Raycast(cameraRay, out hit);
if (hit.collider.tag == "Floor")
{
transform.position = hit.point;
}
/*
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
// get current position of this GameObject
Vector3 newPos = transform.position;
// set x position to mouse world-space x position
newPos.x = worldPos.x;
newPos.z = worldPos.z-3f;
// apply new position
transform.position = newPos;
*/
if (Input.GetTouch(0).phase == TouchPhase.Ended)
{
Destroy(gameObject);
touchhandler = GameObject.Find("Roller").GetComponent<TouchHandler>();
touchhandler.markerexists = false;
}
}
And this doesn't:
if (Input.touchCount > 0)
{
// get mouse position in screen space
// (if touch, gets average of all touches)
Vector3 screenPos = Input.mousePosition;
// set a distance from the camera
screenPos.z = 10.0f;
// convert mouse position to world space
Ray cameraRay = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit[] hits;
hits = Physics.RaycastAll(Camera.main.ScreenPointToRay(Input.mousePosition), 100.0F);
while (i < hits.Length)
{
RaycastHit hit = hits[i];
if (hit.collider.tag == "Floor")
{
transform.position = hit.point;
}
i++;
}
if (Input.GetTouch(0).phase == TouchPhase.Ended)
{
Destroy(gameObject);
touchhandler = GameObject.Find("Roller").GetComponent<TouchHandler>();
touchhandler.markerexists = false;
}
}

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...)

How to make Touch Controls like mini Golf King

Goal
Hello Guys! I am new with unity 3-D . I want to make controls exactly like Mini Golf king . Like when ball stops pointers becomes visible on the screen . On clicking that pointer and dragged its scale changes and points always towards ball.
This is what I want to achieve
MY Approach
Initially according to my knowledge I Have implemented something using ray-cast. In the pointer I placed 3-d pointer as child object of ball . when i touch on the screen I send a ray cast to world from screen that detects Ball if i am touching Ball the i show Pointer and on every point on the screen i am continuously sending ray-casts on the world and changing pointer direction accordingly but i think it is not the good way on performance point
RaycastHit GenerateRayCast(Vector2 position, Camera camera,LayerMask mask) { // GENERATE RAY CAST AT GIVEN DIRECTION IN 3D WORLD
RaycastHit hito;
Ray ray = camera.ScreenPointToRay(position);
Physics.Raycast(ray, out hito, rayLength, mask);
return hito;
}
#endregion
#region Updatemethod
void FixedUpdate () {
if (IsBallInStopPos) { // IF BALL IN STATIC POSITOIN THEN LISTEN FOR TOUCH INPUTS
//if (Input.GetMouseButton(0) && !EventSystem.current.IsPointerOverGameObject())
if (Input.touchCount > 0 && !EventSystem.current.IsPointerOverGameObject()) // CHECK FOR SREEN TOUCH
{
touchInput = Input.touches[0];
// touchInput = Input.mousePosition; // get touch information
hitObject = GenerateRayCast(touchInput.position, Camera.main, mask);
if (touchInput.phase == TouchPhase.Began)
{
if (hitObject.collider.name == "Pointer") { // IF TOUCHED ON POINTER :
if (touchPointSet == false) {
touchPointSet = true;
touchStartPoint = hitObject.point; // SETTING START TOUCH POINT
}
}
}
else if (touchInput.phase == TouchPhase.Moved) {
newScale= Mathf.Clamp (Vector3.Distance(pointerChild.transform.position , hitObject.point),1f,2f);
pointerPivot.transform.localScale = Vector3.one * newScale;// SETTING SCALE ACCORDING TO DRAG VALUE
if (touchPointSet == true) { // WHEN TOUCH DRAGGED AND PREVIOULY POINTER IS TOUCHED ROTATE THE POINTER AT GIVEN DIRECTION
newDirection = new Vector3(hitObject.point.x, transform.position.y, hitObject.point.z);
transform.LookAt(2 * transform.position - newDirection);
canShoot = true;
}
}
}
else if (touchInput.phase == TouchPhase.Ended)
{
if (newScale <= 1f)
return;
if (IsBallInStopPos && canShoot )
{
//shooted = true;
newScale = newScale -1;// AS SCALE VALUE IS BETWEEN 1 AND 2 SO SUBSTACT FROM TO MAKE IT BETWEEN
pointerPivot.gameObject.SetActive(false);
rigidBody.constraints = RigidbodyConstraints.None;
GetComponent<Rigidbody>().AddRelativeForce(new Vector3(0f, 0f, 10000f * newScale));
// Time.timeScale = 2f;
canShoot = false;
IsBallInStopPos = false; // AFTER SHOOT : after the ball is shooted now the ball is not in staticstatic condition
}
}
}

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