Change location of pivot based on Input.GetTouch.position to zoom in - c#

So i am trying so to make a script for zooming in and out on a ui element(image), and so far i am doing it by scaling the image based on differences in magnitudes between touches from frame to frame, the only problem is that is zooms from where the pivot is.
The solution would be to move the pivot to the point the is the middle of the line that connects the touches. I tried but it puts my pivot way out of the screen due to the fact that i get values greater than 1 from the formula i used.
Basically, i don't know how to move the pivot so it matches the point the would be the middle of the line that connects the touches.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Zoom : MonoBehaviour
{
public GameObject image1, image2;
public RectTransform tran1, tran2;
public float zoomSpeed = 0.0090f;
public bool startZooming = false;
void Start()
{
tran1 = image1.GetComponent<RectTransform>();
tran2 = image2.GetComponent<RectTransform>();
}
void Update()
{
if(Input.touchCount == 0)
{
//Remember if the player is zooming to be able to change pivot point
startZooming = false;
}
// If there are two touches on the device...
if (Input.touchCount == 2)
{
// Store both touches.
Touch touchZero = Input.GetTouch(0);
Touch touchOne = Input.GetTouch(1);
// Find the position in the previous frame of each touch.
Vector2 touchZeroPrevPos = touchZero.position - touchZero.deltaPosition;
Vector2 touchOnePrevPos = touchOne.position - touchOne.deltaPosition;
// Find the magnitude of the vector (the distance) between the touches in each frame.
float prevTouchDeltaMag = (touchZeroPrevPos - touchOnePrevPos).magnitude;
float touchDeltaMag = (touchZero.position - touchOne.position).magnitude;
// Find the difference in the distances between each frame.
float deltaMagnitudeDiff = prevTouchDeltaMag - touchDeltaMag;
//Find pivot point, the middle of the line that connects the touch points
if (deltaMagnitudeDiff < 0 && startZooming == false)
{
float xpivot, ypivot;
xpivot = (touchZero.position.x + touchOne.position.x) / 2;
ypivot = (touchOne.position.y + touchZero.position.y) / 2;
tran1.pivot = new Vector2(xpivot, ypivot);
tran2.pivot = new Vector2(xpivot, ypivot);
startZooming = true; // player is currently zooming, don't change the pivot point
}
float x, y;
x = tran1.localScale.x - deltaMagnitudeDiff * zoomSpeed;
y = tran1.localScale.y - deltaMagnitudeDiff * zoomSpeed;
// Make sure the localScale size never goes below 1 or above 5
x = Mathf.Clamp(x, 1.0f, 5.0f);
y = Mathf.Clamp(y, 1.0f, 5.0f);
// ... change the localScale size based on the change in distance between the touches.
tran1.localScale = new Vector3(x, y, tran1.localScale.z);
tran2.localScale = new Vector3(x, y, tran2.localScale.z);
}
}
}

The RectTrasnform.pivot you are trying to set is
The normalized position in this RectTransform that it rotates around.
this would be a vector between 0,0 and 1,1,!
You can use ScaleAroundRelative from this post
public static void ScaleAround(Transform target, Vector3 pivotInWorldSpace, Vector3 newScale)
{
// pivot
var localPivot = target.InverseTransformPoint(pivotInWorldSpace);
// diff from object pivot to desired pivot/origin
Vector3 pivotDelta = target.transform.localPosition - pivot;
Vector3 scaleFactor = new Vector3(
newScale.x / target.transform.localScale.x,
newScale.y / target.transform.localScale.y,
newScale.z / target.transform.localScale.z );
pivotDelta.Scale(scaleFactor);
target.transform.localPosition = pivot + pivotDelta;
//scale
target.transform.localScale = newScale;
}
then I would implement it using something like
private float initialTouchDistance;
private Vector2 pivot1;
private Vector2 pivot2;
private Vector2 initialScale1;
private Vector2 initialScale2;
void Update()
{
// If there are two touches on the device...
if (Input.touchCount == 2)
{
// Store both touches.
var touchZero = Input.GetTouch(0);
var touchOne = Input.GetTouch(1);
var touchZeroPosition = touchZero.position;
var touchOnePosition = touchOne.position;
var currentDistance = Vector2.Distance(touchZeroPosition, touchOnePosition);
// Is this a new zoom process? => One of the two touches was added this frame
if(touchZero.phase == TouchPhase.Began || touchOne.phase == TouchPhase.Began)
{
// initialize values
initialTouchDistance = currentDistance;
initialScale1 = tran1.localScale;
initialScale2 = tran2.localScale;
// get center between the touches
// THIS IS STILL IN PIXEL SPACE
var zoomPivot = (touchZeroPosition + touchOnePosition) / 2f;
// Get the position on the RectTransforms planes in WORLD SPACE
RectTransformUtility.ScreenPointToWorldPointInRectangle(tran1, zoomPivot, Camera.main, out pivot1));
RectTransformUtility.ScreenPointToWorldPointInRectangle(tran2, zoomPivot, Camera.main, out pivot2));
}
// Is this an already running zoom process and at least one of the touches was moved?
else if(touchZero.phase == TouchPhase.Moved || touchOne.phase == TouchPhase.Moved)
{
// Scale factor
var factor = (currentDistance / initialTouchDistance) * zoomSpeed;
factor = Mathf.Clamp(factor, 1, 5);
var scaleVector1 = Vector3.Scale(new Vector3(factor, factor, 1), initialScale1);
var scaleVector2 = Vector3.Scale(new Vector3(factor, factor, 1), initialScale2);
// apply scaled around pivot
ScaleAround(tran1, pivot1, scaleVector1);
ScaleAround(tran2, pivot2, scaleVector2);
}
}
}
Note: Typed on smartphone but I hope the idea gets clear

Related

Making Field of view using Raycasts With for loops (Using Unity C#)

Code:
void IdleState ()
{
RaycastHit hit;
for (float i = -ViewWidth; i < ViewWidth; i++)
{
float Iterater = i/20;
if (Physics.Raycast(transform.position, transform.forward + new Vector3(Iterater,0,0), out hit, ViewRange))
{
Debug.DrawRay(transform.position,(transform.forward + new Vector3(Iterater,0,0)).normalized * ViewRange, Color.red);
if (hit.transform.gameObject.layer == LayerMask.NameToLayer("Player"))
{
FoundPlayer(hit.transform);
}
}
}
}
Problem: In Unity I am trying to create a Field of view for an enemy AI by drawing multiple raycasts with for loops. For some reason the raycasts do this:
GIF
I've been trying to fix this for days Please help!
Also the FoundPlayer() function in case you need it:
void FoundPlayer (Transform DetectedObject)
{
float step = TurnSpeed * Time.deltaTime;
Vector3 Direc = DetectedObject.position - transform.position;
Vector3 RotMath = Vector3.RotateTowards(transform.forward, Direc,step,0.0f);
transform.rotation = Quaternion.LookRotation(RotMath);
Vector3 CurrentRot = transform.eulerAngles;
transform.rotation = Quaternion.Euler(0,CurrentRot.y,0);
}
The FoundPlayer() function just rotates the enemy towards the player when one of the raycasts hit it.
Regarding Raycast:
transform.Forward is in world-space coordinates, to which you add some fraction of world-space X-axis offset. What you wanted is to add some fraction of 'local-space' X-axis. transform.Right is the world-space conversion of local-space X-axis. Try:
var rayDirection = (transform.Forward + (Vector3.Scale(transform.Right, new Vector3(Iterater, 0, 0))).normalized;
Regarding Fields of View:
If all you want to do is check what objects are within a FOV, start finding everything within a sphere, then filter those objects to what's within appropriate angles from transform.forward:
float ViewRange = 10;
float hHalfFov = 45; // Horizontal Half-Field of View (Degrees)
float vHalfFov = 45; // Vertical Half-Field of View (Degrees)
void IdleState() {
// Find all colliders within ViewRange
var hits = Physics.OverlapSphere(transform.position, ViewRange);
foreach (var hit in hits) {
if ( hit.gameObject == this.gameObject ) continue; // don't hit self
// Check FOV
var direction = (transform.position - hit.transform.position);
var hDirn = Vector3.ProjectOnPlane(direction, transform.up).normalized; // Project onto transform-relative XY plane to check hFov
var vDirn = Vector3.ProjectOnPlane(direction, transform.right).normalized; // Project onto transform-relative YZ plane to check vFov
var hOffset = Vector3.Dot(hDirn, transform.forward) * Mathf.Rad2Deg; // Calculate horizontal angular offset in Degrees
var vOffset = Vector3.Dot(vDirn, transform.forward) * Mathf.Rad2Deg; // Calculate vertical angular offset in Degrees
if (hOffset > hHalfFov || vOffset > vHalfFov) continue; // Outside of FOV
Debug.DrawLine(transform.position, hit.transform.position, Color.red);
if (hit.transform.gameObject.layer == LayerMask.NameToLayer("Player")) {
FoundPlayer(hit.transform);
}
}
}

UNITY - Camera Orbit, move camera to some fixed positions

i have a scene in which i have a camera that is orbiting around a pivot in a 180° semisphere. All is working like intended but now i would like to point the camera to some fixed coordinates of the semisphere (possibily in a smooth way). Can someone give me some hints? I'll post the code i'm using right now.
public class CameraOrbitTouch : MonoBehaviour {
protected Transform _XForm_Camera;
protected Transform _XForm_Parent;
protected Vector3 _LocalRotation;
protected float _CameraDistance = 10f;
// The rate of change of the field of view in perspective mode.
public float perspectiveZoomSpeed = 0.2f;
public float OrbitDampening = 30f;
public float ScrollDampening = 18f;
public bool CameraDisabled = false;
public bool moving = false;
// Use this for initialization
void Start() {
this._XForm_Camera = this.transform;
this._XForm_Parent = this.transform.parent;
}
void LateUpdate() {
if (Input.GetKeyDown(KeyCode.LeftShift))
CameraDisabled = !CameraDisabled;
if (!CameraDisabled)
{
//Rotation of the Camera based on Mouse Coordinates
if (Input.touchCount == 1 && Input.GetTouch(0).phase == TouchPhase.Moved)
{
moving = true;
_LocalRotation.x += Input.touches[0].deltaPosition.x ;
_LocalRotation.y += Input.touches[0].deltaPosition.y;
//Clamp the y Rotation to horizon and not flipping over at the top
if (_LocalRotation.y < 0f)
_LocalRotation.y = 0f;
else if (_LocalRotation.y > 90f)
_LocalRotation.y = 90f;
}
//Zooming Input from our Mouse Scroll Wheel
if ((Input.touchCount == 2)&& (Input.GetTouch(0).phase == TouchPhase.Moved) && (Input.GetTouch(1).phase == TouchPhase.Moved))
{
moving = true;
// Store both touches.
Touch touchZero = Input.GetTouch(0);
Touch touchOne = Input.GetTouch(1);
// Find the position in the previous frame of each touch.
Vector2 touchZeroPrevPos = touchZero.position - touchZero.deltaPosition;
Vector2 touchOnePrevPos = touchOne.position - touchOne.deltaPosition;
// Find the magnitude of the vector (the distance) between the touches in each frame.
float prevTouchDeltaMag = (touchZeroPrevPos - touchOnePrevPos).magnitude;
float touchDeltaMag = (touchZero.position - touchOne.position).magnitude;
// Find the difference in the distances between each frame.
float deltaMagnitudeDiff = (prevTouchDeltaMag - touchDeltaMag);
//this._CameraDistance += deltaMagnitudeDiff * -1f;
this._CameraDistance += deltaMagnitudeDiff * perspectiveZoomSpeed;
this._CameraDistance = Mathf.Clamp(this._CameraDistance, 1.5f, 100f);
}
}
if (moving)
{
//Actual Camera Rig Transformations
Quaternion QT = Quaternion.Euler(_LocalRotation.y, _LocalRotation.x, 0);
this._XForm_Parent.rotation = Quaternion.Lerp(this._XForm_Parent.rotation, QT, Time.deltaTime * OrbitDampening);
if (this._XForm_Camera.localPosition.z != this._CameraDistance * -1f)
{
this._XForm_Camera.localPosition = new Vector3(0f, 0f,Mathf.Lerp(this._XForm_Camera.localPosition.z, this._CameraDistance * -1f, Time.deltaTime * ScrollDampening));
}
moving = false;
}
}
}
I tought i could use somethig like this
string selectedObj = "CameraPivot";
camera = GameObject.Find(selectedObj);
camera.transform.rotation = Quaternion.Euler(90.0f, 0.0f, 100.0f);
But i think is not the right waty to make it work...

Pinch-To-Zoom with Unity 5 UI

I'm trying to re-implement a pinch-to-zoom system in a Unity UI-based app. About six months ago I was able to hack one together by making the UI canvas a child of a regular GameObject, and manipulating that object's transform, but since updating to Unity 5.5+ I find this doesn't work. The closest I can get allows the pinch gesture to change the canvas' scaleFactor, which a) can make images, panels, etc resize improperly depending on their alignments, and b) won't allow me to pan once zoomed.
What I have so far is this:
public class PinchToZoomScaler : MonoBehaviour {
public Canvas canvas; // The canvas
public float zoomSpeed = 0.5f; // The rate of change of the canvas scale factor
public float _resetDuration = 3.0f;
float _durationTimer = 0.0f;
float _startScale = 0.0f;
void Start() {
_startScale = canvas.scaleFactor;
}
void Update()
{
// If there are two touches on the device...
if (Input.touchCount == 2) {
// Store both touches.
Touch touchZero = Input.GetTouch (0);
Touch touchOne = Input.GetTouch (1);
// Find the position in the previous frame of each touch.
Vector2 touchZeroPrevPos = touchZero.position - touchZero.deltaPosition;
Vector2 touchOnePrevPos = touchOne.position - touchOne.deltaPosition;
// Find the magnitude of the vector (the distance) between the touches in each frame.
float prevTouchDeltaMag = (touchZeroPrevPos - touchOnePrevPos).magnitude;
float touchDeltaMag = (touchZero.position - touchOne.position).magnitude;
// Find the difference in the distances between each frame.
float deltaMagnitudeDiff = prevTouchDeltaMag - touchDeltaMag;
// ... change the canvas size based on the change in distance between the touches.
canvas.scaleFactor -= deltaMagnitudeDiff * zoomSpeed;
// Make sure the canvas size never drops below 0.1
canvas.scaleFactor = Mathf.Max (canvas.scaleFactor, _startScale);
canvas.scaleFactor = Mathf.Min (canvas.scaleFactor, _startScale * 3.0f);
_durationTimer = 0.0f;
} else {
_durationTimer += Time.deltaTime;
if (_durationTimer >= _resetDuration) {
canvas.scaleFactor = _startScale;
}
}
}
}
As I said, this works to a degree, but doesn't give me a nice uniform zooming, not does it allow me to pan the canvas. Thanks in advance for any help.
Attach this script in canvas object which you want to zoom in and zoom out by pinch
using UnityEngine;
using UnityEngine.EventSystems;
public class ObjectScalling : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
private bool _isDragging;
private float _currentScale;
public float minScale, maxScale;
private float _temp = 0;
private float _scalingRate = 2;
private void Start()
{
_currentScale = transform.localScale.x;
}
public void OnPointerDown(PointerEventData eventData)
{
if (Input.touchCount == 1)
{
_isDragging = true;
}
}
public void OnPointerUp(PointerEventData eventData)
{
_isDragging = false;
}
private void Update()
{
if (_isDragging)
if (Input.touchCount == 2)
{
transform.localScale = new Vector2(_currentScale, _currentScale);
float distance = Vector3.Distance(Input.GetTouch(0).position, Input.GetTouch(1).position);
if (_temp > distance)
{
if (_currentScale < minScale)
return;
_currentScale -= (Time.deltaTime) * _scalingRate;
}
else if (_temp < distance)
{
if (_currentScale > maxScale)
return;
_currentScale += (Time.deltaTime) * _scalingRate;
}
_temp = distance;
}
}
}
Reminder: This script only works in canvas objects
You can use this function ( just pass to it negative deltaMagnitudeDiff )
Also it is good to multiplay deltaMagnitudeDiff with a ratio like ( 0.05 )
float currentScale = 1f;
void Zoom (float increment)
{
currentScale += increment;
if (currentScale >= maxScale)
{
currentScale = maxScale;
}
else if (currentScale <= minScale)
{
currentScale = minScale;
}
rectTransform.localScale = new Vector3 (currentScale, currentScale, 1);
pan.ValidatePosition ();
}
For Panning,
you can use something like this :
public class Pan : MonoBehaviour
{
public float Speed;
Vector3 startDragPosition;
public void BeginDrag ()
{
startDragPosition = Input.mousePosition;
}
public void Drag ()
{
transform.localPosition += (Input.mousePosition - startDragPosition) * Speed;
startDragPosition = Input.mousePosition;
ValidatePosition ();
}
public void ValidatePosition ()
{
var temp = transform.localPosition;
var width = ((RectTransform)transform).sizeDelta.x;
var height = ((RectTransform)transform).sizeDelta.y;
var MaxX = 0.5f * width * Mathf.Max (0, transform.localScale.x - 1);
var MaxY = 0.5f * height * Mathf.Max (0, transform.localScale.y - 1);
var offsetX = transform.localScale.x * width * (((RectTransform)transform).pivot.x - 0.5f);
var offsetY = transform.localScale.y * width * (((RectTransform)transform).pivot.y - 0.5f);
if (temp.x < -MaxX + offsetX)
temp.x = -MaxX + offsetX;
else if (temp.x > MaxX + offsetX)
temp.x = MaxX + offsetX;
if (temp.y < -MaxY + offsetY)
temp.y = -MaxY + offsetY;
else if (temp.y > MaxY + offsetY)
temp.y = MaxY + offsetY;
transform.localPosition = temp;
}
Just call the functions ( BeginDrag & Drag ) from the Events Trigger component.
what i did to scale an object using pinch was this, it works on any touch screen when the object is in the middle of the screen:
if (Input.touchCount == 2)
{
//The distance between the 2 touches is checked and subsequently used to scale the
//object by moving the 2 fingers further, or closer form eachother.
Touch touch0 = Input.GetTouch(0);
Touch touch1 = Input.GetTouch(1);
if (isScaling)//this will only be done if scaling is true
{
float currentTouchDistance = getTouchDistance();
float deltaTouchDistance = currentTouchDistance - touchDistanceOrigin;
float scalePercentage = (deltaTouchDistance / 1200f) + 1f;
Vector3 scaleTemp = transform.localScale;
scaleTemp.x = scalePercentage * originalScale.x;
scaleTemp.y = scalePercentage * originalScale.y;
scaleTemp.z = scalePercentage * originalScale.z;
//to make the object snap to 100% a check is being done to see if the object scale is close to 100%,
//if it is the scale will be put back to 100% so it snaps to the normal scale.
//this is a quality of life feature, so its easy to get the original size of the object.
if (scaleTemp.x * 100 < 102 && scaleTemp.x * 100 > 98)
{
scaleTemp.x = 1;
scaleTemp.y = 1;
scaleTemp.z = 1;
}
//here we apply the calculation done above to actually make the object bigger/smaller.
transform.localScale = scaleTemp;
}
else
{
//if 2 fingers are touching the screen but isScaling is not true we are going to see if
//the middle of the screen is looking at the object and if it is set isScalinf to true;
Ray ray;
RaycastHit hitTouch;
ray = cam.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
if (Physics.Raycast(ray, out hitTouch, 100f))
{
if (hitTouch.transform == transform)
{
isScaling = true;
//make sure that the distance between the fingers on initial contact is used as the original distance
touchDistanceOrigin = getTouchDistance();
originalScale = transform.localScale;
}
}
}
}

Unity 2D - zoom in =ok, zoom out = borked

I hope someone can help. Im trying to create a little script that zooms in to my player and back out - toggling.
The zoom in works fine, but when I try to zoom back out it doesn't work, it gets stuck. I've created a bool to ensure it only runs the code when it needs to and I'm wondering if that is what's causing the error.
using UnityEngine;
using System.Collections;
public class CameraZoom : MonoBehaviour
{
public float zoom = 10f;
public float normal = 3.471398f;
public float smooth = 5f;
private bool isZoomed = false;
public Camera cam;
public GameObject player;
// lock the camera settings
public float LockedX = 0f;
public float LockedY = 0f;
public float LockedZ = 0f;
private bool hasBeenZoomed = false;
Vector3 targetPos;
private Transform playerTransform;
// Use this for initialization
void Start()
{
targetPos = transform.position;
playerTransform = GameObject.FindGameObjectWithTag("Player").transform;
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown("z")) { isZoomed = !isZoomed; }
if (isZoomed == true)
{
ZoomInToPlayer();
hasBeenZoomed = true;
}
else
{
if (hasBeenZoomed)
{
ZoomOutFromPlayer();
hasBeenZoomed = false;
}
}
}
void ZoomInToPlayer()
{
// By default the target x and y coordinates of the camera are it's current x and y coordinates.
float targetX = transform.position.x;
float targetY = transform.position.y;
// ... the target x coordinate should be a Lerp between the camera's current x position and the player's current x position.
targetX = Mathf.Lerp(transform.position.x, playerTransform.position.x, smooth * Time.deltaTime);
//Debug.Log("player x is " + playerTransform.position.x + " and TargetX is " + targetX);
// ... the target y coordinate should be a Lerp between the camera's current y position and the player's current y position.
targetY = Mathf.Lerp(transform.position.y, playerTransform.position.y, smooth * Time.deltaTime);
//Debug.Log("player y is " + playerTransform.position.y+ " and TargetY is " + targetY);
// Set the camera's position to the target position with the same z component.
cam.transform.position = new Vector3(targetX, targetY, transform.position.z);
// Change the size of the camera viewport
cam.orthographicSize = Mathf.Lerp(cam.orthographicSize, zoom, Time.deltaTime * smooth);
}
void ZoomOutFromPlayer()
{
// By default the target x and y coordinates of the camera are it's current x and y coordinates.
float targetX;
float targetY;
// Change the size of the camera viewport
cam.orthographicSize = Mathf.Lerp(cam.orthographicSize, normal, Time.deltaTime * smooth);
// ... the target x coordinate should be a Lerp between the camera's current x position and the original x position.
targetX = Mathf.Lerp(transform.position.x, LockedX, smooth * Time.deltaTime);
// ... the target y coordinate should be a Lerp between the camera's current y position and the original y position.
targetY = Mathf.Lerp(transform.position.y, LockedY, smooth * Time.deltaTime);
// Set the camera's position to the target position with the same z component.
cam.transform.position = new Vector3(targetX, targetY, transform.position.z);
}
}
Your methods ZoomInToPlayer and ZoomOutFromPlayer are written in a way that suggests that they should be called once per frame for the duration of the zoom in/out animation. However, ZoomOutFromPlayer will only be called once, because Update, when ZoomOutFromPlayer is called, the hasBeenZoomed is immediately set to false.
What you're trying to do here, essentially, is a simple Finite State Machine. I suggest researching this design pattern a little more — it will help you noticing the sources of such problems and structuring your code in a better way.
In this particular case, a good way to prevent this problem when designing your code would be to write something akin to "API documentation" for yourself, when writing your methods. For ZoomOutFromPlayer, it would read something like this:
Call every frame when you want to perform zoom-out animation, until the animation is complete.
After you written (and read) such a description, you should immediately notice a red flag — "until the animation is complete"? So, the code that calls this method should somehow take track of whether the animation is complete or not, during a separate mechanism? Wouldn't it make it really easy to use this method incorrectly? Well, that's exactly what happened here.
Instead, what you could've done, is to create two different methods, ZoomInUpdate and ZoomOutUpdate, with descriptions that would read something like this:
Call every frame when the camera should be zoomed out/zoomed in.
This way, using this methods is a lot easier, and you can safely throw out additional logic with hasBeenZoomed out. Just call these methods every frame, and ensure (inside these methods) that they change the camera settings with a certain speed, if these settings need to be changed, or otherwise do nothing.
Try this
using UnityEngine;
using System.Collections;
public class CameraZoom : MonoBehaviour
{
public float zoom = 10f;
public float normal = 3.471398f;
public float smooth = 5f;
private bool isZoomed = false;
private bool isZoomFinished = true; // the animation zoom is over ?
public Camera cam;
public GameObject player;
public float LockedX = 0f;
public float LockedY = 0f;
public float LockedZ = 0f;
private bool hasBeenZoomed = false;
Vector3 targetPos;
private Transform playerTransform;
void Start()
{
targetPos = transform.position;
playerTransform = GameObject.FindGameObjectWithTag("Player").transform;
}
void Update()
{
if (Input.GetKeyDown("z") && isZoomFinished) {
isZoomed = !isZoomed;
isZoomFinished = false;
}
if (isZoomed && !isZoomFinished)
{
ZoomInToPlayer();
}
else if (!isZoomed && !isZoomFinished)
{
ZoomOutFromPlayer()
}
}
float delta = 0;
void ZoomInToPlayer()
{
delta += smooth * Time.deltaTime;
//Cam size
cam.orthographicSize = Mathf.Lerp(cam.orthographicSize, zoom, delta);
//Cam pos
float targetX = transform.position.x;
float targetY = transform.position.y;
targetX = Mathf.Lerp(transform.position.x, playerTransform.position.x, delta);
targetY = Mathf.Lerp(transform.position.y, playerTransform.position.y, delta);
cam.transform.position = new Vector3(targetX, targetY, transform.position.z);
// is animation over ?
if(delta >= 1) {
isZoomFinished = true;
delta = 0;
}
}
void ZoomOutFromPlayer()
{
delta += smooth * Time.deltaTime;
//Cam size
cam.orthographicSize = Mathf.Lerp(cam.orthographicSize, normal, delta);
//Cam pos
float targetX;
float targetY;
targetX = Mathf.Lerp(transform.position.x, LockedX, delta);
targetY = Mathf.Lerp(transform.position.y, LockedY, delta);
cam.transform.position = new Vector3(targetX, targetY, transform.position.z);
// is animation over ?
if(delta >= 1) {
isZoomFinished = true;
delta = 0;
}
}
}

Smooth Collision (Wall sliding)

I recently started learning XNA and C# because of a school project I am doing. I followed this tutorial on YouTube to learn how XNA works with 3D:
http://www.youtube.com/watch?v=XkpZLzT5OV4
It works pretty well actually, and I already made some modification/added some features to my project. I recently started to implement the collision detection, which works well using BoundingBoxes, but the camera literally stops when it collides. I would like it to kinda slide on the wall, as every other first person games do. Basically, the camera stops if the movement is directed to the wall. I would like the camera to "slide" on the wall, removing the movement directed to the wall, and making it only going parallel to the wall. I hope that makes sense. This way, the player will be able to still move around while he's touching the wall.
I looked at a lot of posts on google, and I saw that I needed to play with the player's velocity. However, I don't have any velocity implemented in my code, and I don't know how to add it.
Here are my main classes:
Here is my Camera class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
namespace Deimos
{
class Camera : GameComponent
{
// ...
// Constructor
public Camera(Game game, Vector3 position, Vector3 rotation, float speed)
: base(game)
{
CameraSpeed = speed;
// Setup projection matrix
Projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.PiOver4,
Game.GraphicsDevice.Viewport.AspectRatio,
0.05f,
1000.0f // Draw distance
);
// Set the camera position and rotation
moveTo(position, rotation);
PreviousMouseState = Mouse.GetState();
}
// Set camera position and rotation
private void moveTo(Vector3 position, Vector3 rotation)
{
// Thanks to the properties set at the beginning, setting up these values will execute
// the code inside the property (i.e update our vectors)
Position = position;
Rotation = rotation;
}
// Update the look at vector
private void updateLookAt()
{
// Build a rotation matrix
Matrix rotationMatrix = Matrix.CreateRotationX(CameraRotation.X) * Matrix.CreateRotationY(CameraRotation.Y);
// Build look at offset vector
Vector3 lookAtOffset = Vector3.Transform(Vector3.UnitZ, rotationMatrix);
// Update our camera's look at vector
CameraLookAt = CameraPosition + lookAtOffset;
}
// Methods that simulate movement
private Vector3 previewMove(Vector3 amount)
{
// Create a rotate matrix
Matrix rotate = Matrix.CreateRotationY(CameraRotation.Y);
// Create a movement vector
Vector3 movement = new Vector3(amount.X, amount.Y, amount.Z);
movement = Vector3.Transform(movement, rotate);
// Return the value of camera position + movement vector
if (Collision.CheckCollision(CameraPosition + movement)) // Testing for the UPCOMING position
{
return CameraPosition;
}
else
{
return CameraPosition + movement;
}
}
// Method that actually moves the camera
private void move(Vector3 scale)
{
moveTo(previewMove(scale), Rotation);
}
// Update method, overriding the original one
public override void Update(GameTime gameTime)
{
float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;
CurrentMouseState = Mouse.GetState();
// Let's get user inputs
KeyboardState ks = Keyboard.GetState();
// Handle basic key movement
Vector3 moveVector = Vector3.Zero;
if (ks.IsKeyDown(ForwardKey))
{
moveVector.Z = 1;
}
if (ks.IsKeyDown(BackKey))
{
moveVector.Z = -1;
}
if (ks.IsKeyDown(LeftKey))
{
moveVector.X = 1;
}
if (ks.IsKeyDown(RightKey))
{
moveVector.X = -1;
}
if (ks.IsKeyDown(Keys.Up))
{
moveVector.Y = 1;
}
if (ks.IsKeyDown(Keys.Down))
{
moveVector.Y = -1;
}
if (moveVector != Vector3.Zero) // If we are actually moving (if the vector changed depending on the ifs)
{
// Normalize that vector so that we don't move faster diagonally
moveVector.Normalize();
// Now we add in move factor and speed
moveVector *= dt * CameraSpeed;
DebugScreen.Log(moveVector.ToString());
// Move camera!
move(moveVector);
}
// Handle mouse movement
float deltaX;
float deltaY;
if (CurrentMouseState != PreviousMouseState)
{
// Cache mouse location
deltaX = CurrentMouseState.X - (Game.GraphicsDevice.Viewport.Width / 2); // We devide by 2 because mouse will be in the center
deltaY = CurrentMouseState.Y - (Game.GraphicsDevice.Viewport.Height / 2);
MouseRotationBuffer.X -= MouseSpeed * deltaX * dt;
MouseRotationBuffer.Y -= MouseSpeed * deltaY * dt;
// Limit the user so he can't do an unlimited movement with his mouse (like a 7683°)
if(MouseRotationBuffer.Y < MathHelper.ToRadians(-75.0f))
MouseRotationBuffer.Y = MouseRotationBuffer.Y - (MouseRotationBuffer.Y - MathHelper.ToRadians(-75.0f));
if(MouseRotationBuffer.Y > MathHelper.ToRadians(75.0f))
MouseRotationBuffer.Y = MouseRotationBuffer.Y - (MouseRotationBuffer.Y - MathHelper.ToRadians(75.0f));
float mouseInverted = (MouseInverted == true) ? 1 : -1;
Rotation = new Vector3(
mouseInverted * MathHelper.Clamp(
MouseRotationBuffer.Y,
MathHelper.ToRadians(-75.0f),
MathHelper.ToRadians(75.0f)
),
MathHelper.WrapAngle(MouseRotationBuffer.X),
// This is so the camera isn't going really fast after some time
// (as we are increasing the speed with time)
0
);
// Resetting them
deltaX = 0;
deltaY = 0;
}
// Putting the cursor in the middle of the screen
Mouse.SetPosition(Game.GraphicsDevice.Viewport.Width / 2, Game.GraphicsDevice.Viewport.Height / 2);
PreviousMouseState = CurrentMouseState;
base.Update(gameTime);
}
}
}
And here is my Collision class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Deimos
{
class Collision
{
// ...
public Boolean CheckCollision(Vector3 cameraPosition)
{
// Creating the sphere of the camera for later collisions checks
BoundingBox cameraBox = new BoundingBox(
new Vector3(
cameraPosition.X - (PlayerDimention.X / 2),
cameraPosition.Y - (PlayerDimention.Y),
cameraPosition.Z - (PlayerDimention.Z / 2)
),
new Vector3(
cameraPosition.X + (PlayerDimention.X / 2),
cameraPosition.Y,
cameraPosition.Z + (PlayerDimention.Z / 2)
)
);
// Let's check for collision with our boxes
if (CollisionBoxesArray != null)
{
for (int i = 0; i < CollisionBoxesArray.Length; i++)
{
if (CollisionBoxesArray[i].Contains(cameraBox) != ContainmentType.Disjoint) // If our player is inside the collision region
return true;
}
}
if (CollisionSpheresArray != null)
{
// And with our spheres
for (int i = 0; i < CollisionSpheresArray.Length; i++)
{
if (CollisionSpheresArray[i].Contains(cameraBox) != ContainmentType.Disjoint)
return true;
}
}
return false;
}
}
}
I really don't know what to do. Thanks a lot.
EDIT: I updated my post removing some unnecessary code so it's more readable for you.
I also explained better my problem.
I think something like this might work. This is basically the same thing you did, except the previewMove returns a Vector3 that should allow you to slide along the wall. What the function does now is returns a value that checks to see if the camera can move in any of the X, Y, or Z directions of the movement vector, and if the camera can move in the X, Y, and/or Z, it adds that(those) value(s) to the CameraPosition.
private Vector3 previewMove(Vector3 amount)
{
// Create a rotate matrix
Matrix rotate = Matrix.CreateRotationY(CameraRotation.Y);
// Create a movement vector
Vector3 movement = new Vector3(amount.X, amount.Y, amount.Z);
movement = Vector3.Transform(movement, rotate);
// Return the value of camera position + movement vector
return CameraPosition + new Vector3(
Collision.CheckCollision(CameraPosition + new Vector3(movement.X, 0, 0)) ? 0 : movement.X,
Collision.CheckCollision(CameraPosition + new Vector3(0, movement.Y, 0)) ? 0 : movement.Y,
Collision.CheckCollision(CameraPosition + new Vector3(0, 0, movement.Z)) ? 0 : movement.Z);
}
I haven't tried the code, so I don't know how well this will work. But hopefully it will help.

Categories