Here I updated my code with your help.
Anyway it still does not do what is supposed to do, following the mouse pointer with a delay.
The balloon (the sprite) is flying diagonally and does not stop once the mouse pointer is met, only slows down then keeps moving and then speeds up.
I added an if condition once the balloonPosition is equal to mouse pointer, to have the velocity = 0, but that does not stop the balloon.
I added a portion of code for keeping the balloon (the sprite) in the screen.
protected override void Update(GameTime gameTime)
{
MouseState currentMouseState = Mouse.GetState();
//balloonPosition = new Vector2(currentMouseState.X, currentMouseState.Y);
//System.Windows.Input.MouseState currentMouseState = System.Windows.Input.Mouse.GetState();
// Get the current mouse position
Vector2 mousePosition = new Vector2(currentMouseState.X, currentMouseState.Y);
// Get the distance between the balloon and the mouse.
float distance = Vector2.Distance(mousePosition, balloonPosition);
// You can change the standard velocity / or the max distance to make the sprite move faster or slower.
// Currently it may move to fast or to slow for you to know a difference.
balloonVelocity = StandardVelocity * (distance/MaxDistance);
// Set the balloons position to the new velocity.
balloonPosition += balloonVelocity;
if (balloonPosition == mousePosition)
{
balloonVelocity = new Vector2(0);
}
//Keep the balloon in the screen
if (balloonPosition.X < balloon.Width / 2)
balloonPosition.X = balloon.Width / 2;
if (balloonPosition.Y < balloon.Height / 2)
balloonPosition.Y = balloon.Height / 2;
if (balloonPosition.X > Window.ClientBounds.Width - balloon.Width / 2)
balloonPosition.X = Window.ClientBounds.Width - balloon.Width / 2;
if (balloonPosition.Y > Window.ClientBounds.Height - balloon.Height / 2)
balloonPosition.Y = Window.ClientBounds.Height;
}
NewPosition = Vector2.Lerp(CurrentPosition, DesiredPosition, Speed);
Here's what I'd go with off the top of my head:
public List<Tuple<TimeSpan, Vector2>> Points { get; set; }
public Vector2 CurrentPoint { get; set; }
public void Update(GameTime gameTime)
{
Points.Add(new Tuple<TimeSpan, Vector2>(TimeSpan.FromSeconds(1), mouseCoords));
foreach(var point in Points)
{
point.Item1 -= gameTime.ElapsedTimeSpan;
if (point.Item1 < TimeSpan.Zero)point
CurrentPoint = point.Item2;
}
}
I'm pretty sure I wouldn't need to explain the code, right?
In the draw loop, you'd simply always draw the CurrentPoint
You can use a list or an array to keep the delay. Store the mouse position every 0.03125 seconds in a list. Then set timer for the delay (integer counter should work). When the delay is up start reading from the list at the same pace it was out in the list. (0.03125 in this case) and move your sprite towards that point.
Since you are looking for how to change the speed this is one way to do it.
Vector2 balloonOrigin, balloonPosition;
Vector2 balloonVelocity;
private float MaxDistance = 1920; // Reduce this value to slow down the balloon.
public static float AngleBetweenVectors(Vector2 v1, Vector2 v2) { return (float)Math.Atan2(v2.Y - v1.Y, v2.X - v1.X); }
public void test()
{
//System.Windows.Input.MouseState currentMouseState = System.Windows.Input.Mouse.GetState();
// Get the current mouse position
Vector2 mousePosition = new Vector2(currentMouseState.X, currentMouseState.Y);
// Get the distance between the balloon and the mouse.
float distance = Vector2.Distance(mousePosition, balloonPosition);
// You can change the max distance to make the sprite move faster or slower.
// Currently it may move to fast or to slow for you to know a difference.
balloonVelocity = AngleBetweenVectors(balloonPosition, mousePosition) * (distance / MaxDistance);
// Set the balloons position to the new velocity.
balloonPosition += balloonVelocity;
}
Related
I want a c# script so when I hold right click it zooms in a little and when you stop holding right click it goes back to its original position.
You would first want to store the original camera z distance. (As I assume you want it to go forward in the z direction). Then you would want to have a float for the amount of tome zoom was held down. (Increase it when zooming, and decrease it when you dont click it).
Also have a target z, so it doesn't go too far
public float startZ;
public float targetZ;
public float zoomInSpeed;
public float zoomOutSpeed;
float heldDownTime;
float originalZ;
void Start()
{
startZ = transform.position.z;
}
void Update()
{
if (Input.GetKey(KeyCode.Mouse0))
{
heldDownTime += Time.deltaTime * zoomInSpeed;
}
else
{
heldDownTime -= Time.deltaTime * zoomOutSpeed;
heldDownTime = Mathf.Clamp(heldDownTime, 0, Mathf.Infinity);
}
float smoothed = System.Math.Tanh(heldDownTime);
Vector3 pos = transform.position;
transform.position = new Vector3(pos.x, pos.y, startZ + smoothed * (targetZ - startZ));
}
I basically get the time held down, then I smooth it out, so it doesn't zoom to far forward.
Let me know in the comments if you get any errors!
I've been trying to realistically reflect a 3d sphere on the walls of a box for a while now in Unity. For some reason, the reflection is generally correct, but when the ball hits a wall in certain directions, the reflection is incorrect.
To illustrate what happens to the ball upon hitting a wall: T = top wall, R = right wall, L = left wall, and B = bottom wall. Let r = the ball comes/goes to the right, l = for the left, and s = the ball stops/slows down significantly. The instructions below take this format: Xyz, where X = the wall the ball is about to hit, y = the ball's initial direction, z = the reflection. The game has a top-down perspective, and the instructions are based on the wall's perspective. I'm also new to C#, so the code is potentially eye burning.
Instructions: Tll, Trl; Bll, Brl; Rls or after hitting another wall Rlr, Rrl; Lls or after hitting another wall Llr, Lrl
Generally, when the ball stops, it jumps in the air. I wonder if this is because the angle reflects along the wrong axis, but why would this only sometimes happen? Also, when only one key is held, the ball bounces back and forth until it leaves the arena. I know about discrete and continuous hit detection, and the setting is on discrete, but the walls generally contain the ball well enough, with this case being the exception.
What I tried:
Figuring out how to use Vector3.Reflect. I do not understand what variables this function should contain and how to apply this to my code. I did look at the Unity Documentation page for help, but it did not answer my question.
Changing the negative signs, as the angle has to be a reflection on the y-axis, and this does change how the reflections work, but does not solve the problem. The current way the negatives are ordered are the most ideal I found.
Giving the ball a physics material for bounciness.
Adding a small number to the denominator in the arctan equation to help prevent a division by zero. This did not help at all.
Creating different equations (basically changing the negatives) for varying combinations of positive and negative accelerations. Since there is a certain positive or negative acceleration associated with each button press (see Movement script), and there seems to be an issue with said signs, I wondered if associating each acceleration with its own set of equations could solve the problem. It did not work.
Checked if the walls were at different angles.
Deleting the variables xA and yA, or placing them in different spots.
Experimented with finding the speed of the object, but had no idea how to implement it.
The code for the Movement script for the player named Controller:
public class Movement : MonoBehaviour
{
public static float xAcceleration = 0.0f;
public static float yAcceleration = 0.0f;
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.W)) //If the key W is pressed:
{
Vector3 position = this.transform.position; //Variable position is set to transform the players placement in the game.
if (yAcceleration >= -5 && yAcceleration <= 5) //If the y vector of the acceleration is >= -5 and <= 5:
{
yAcceleration = yAcceleration + 0.01f; //The y vector of the acceleration increases by 0.01 as long as the key W is pressed.
}
position.z = position.z + (0.1f * yAcceleration); //The position of the object on the z-axis (pretend it is the y-axis in the game world) is transformed by its original position plus its speed times its yAcceleration.
this.transform.position = position; //The gameObject is now transformed to a position equal to the variable position by the z-axis.
}
else //If the key W is let go of:
{
Vector3 position = this.transform.position;
position.z = position.z + (0.1f * yAcceleration);
this.transform.position = position; //The position of the gameObject continues to update, but its acceleration does not change. Basically, it continues to move forward.
}
//The rest of the code is very similar to the above, but I included it just in case there was something wrong.
if (Input.GetKey(KeyCode.S))
{
Vector3 position = this.transform.position;
if (yAcceleration >= -5 && yAcceleration <= 5)
{
yAcceleration = (yAcceleration) - 0.01f;
}
position.z = position.z + (0.1f * yAcceleration);
this.transform.position = position;
}
else
{
Vector3 position = this.transform.position;
position.z = position.z + (0.1f * yAcceleration);
this.transform.position = position;
}
if (Input.GetKey(KeyCode.A))
{
Vector3 position = this.transform.position;
if (xAcceleration >= -5 && xAcceleration <= 5)
{
xAcceleration = (xAcceleration) - 0.01f;
}
position.x = position.x + (0.1f * xAcceleration);
this.transform.position = position;
}
else
{
Vector3 position = this.transform.position;
position.x = position.x + (0.1f * xAcceleration);
this.transform.position = position;
}
if (Input.GetKey(KeyCode.D))
{
Vector3 position = this.transform.position;
if (xAcceleration >= -5 && xAcceleration <= 5)
{
xAcceleration = (xAcceleration) + 0.01f;
}
position.x = position.x + (0.1f * xAcceleration);
this.transform.position = position;
}
else
{
Vector3 position = this.transform.position;
position.x = position.x + (0.1f * xAcceleration);
this.transform.position = position;
}
}
}
This is the code for the collider and reflection:
public class Collider : MonoBehaviour
{
public float xA;
public float yA;
void OnCollisionEnter(Collision collision) //If a gameObject enters the collision of another object, this immediately happens once.
{
if (gameObject.tag == "Boundary") //If the gameObject has a tag named Boundary:
{
yA = -Movement.yAcceleration; //yA stores the value of yAcceleration after being called from script Movement as a negative. Its a reflection.
Movement.xAcceleration = (Movement.xAcceleration * -Mathf.Atan(yA / Movement.xAcceleration)); //xAcceleration is changed based on this equation: A * artan(A_y / A_x). The 0.000001 was here, adding to A_x to help prevent a 0 as the denominator.
xA = Movement.xAcceleration; //This is declared now...
Movement.yAcceleration = (Movement.yAcceleration * Mathf.Atan(Movement.yAcceleration / xA)); //This uses xA because Movement.xAcceleration is changed, and the yAcceleration calculation is based on the xAcceleration prior the collision.
}
}
void OnCollisionStay(Collision collision)
{
if (gameObject.tag == "Boundary")
{
yA = Movement.yAcceleration; //The same thing happens as before.
Movement.xAcceleration = (Movement.xAcceleration * -Mathf.Atan(yA / Movement.xAcceleration));
xA = Movement.xAcceleration;
Movement.yAcceleration = (Movement.yAcceleration * Mathf.Atan(Movement.yAcceleration / xA));
Movement.xAcceleration = -Movement.xAcceleration / 2; //On collision, the ball is reflected across the x-axis at half its speed.
Movement.yAcceleration = Movement.yAcceleration / 2; //yAcceleration is half its original value.
}
}
}
The picture below is the game setup. I apologize that it is a link; I do not have enough Reputation to merit a loaded image on this page. Also, if anything is unclear, please message me.
https://i.stack.imgur.com/VREV4.png
I would really appreciate the help. Thanks!
One very important note here: As soon as there is any Rigidbody involved you do not want to set any values through the .transform - This breaks the physics and collision detection!
Your Movement should rather alter the behavior of the Rigidbody e.g. by simply changing its Rigibody.velocity
I would then also place the collision check directly into the balls's component and check whether you hit a wall ("Boundary")
Then another note: Your code is currently frame-rate dependent. It means that if your target device runs with only 30 frames per second you will add 0.3 per second to the acceleration. If you run however on a more powerful device that manages to run with 200 frames per second then you add 2 per second.
You should rather define the de/increase per second and multiply it by Time.deltaTime
All together maybe something like this
public class Movement : MonoBehaviour
{
// Adjust these settings via the Inspector
[SerializeField] private float _maxMoveSpeed = 5f;
[SerializeField] private float _speedIncreasePerSecond = 1f;
// Already reference this via the Inspector
[SerializeField] private Rigidbody _rigidbody;
private void Awake()
{
if(!_rigidbody) _rigidbody = GetComponent<Rigidbody>();
}
// Get User Input in Update
private void Update()
{
var velocity = _rigidbody.velocity;
velocity.y = 0;
if (Input.GetKey(KeyCode.W) && velocity.z < _maxMoveSpeed)
{
velocity.z += _speedIncreasePerSecond * Time.deltaTime;
}
if (Input.GetKey(KeyCode.S) && velocity.z > -_maxMoveSpeed)
{
velocity.z -= _speedIncreasePerSecond * Time.deltaTime;
}
if (Input.GetKey(KeyCode.A) && velocity.x > -_maxMoveSpeed)
{
velocity.x -= _speedIncreasePerSecond * Time.deltaTime;
}
if (Input.GetKey(KeyCode.D) && velocity.x < _maxMoveSpeed)
{
velocity.x += _speedIncreasePerSecond * Time.deltaTime;
}
// clamp to the max speed in case you move diagonal
if(velocity.magnitude > _maxMoveSpeed)
{
velocity = velocity.normalized * _maxMoveSpeed;
}
_rigidbody.velocity = velocity;
}
}
And then finally simply add a PhysicsMaterial with desired settings to the walls and ball.
I used Friction = 0f and Bounciness = 0.7f for ball and walls. For slow movements you also might want/have to adjust the Bounce Threshold in the Project's Physics Settings otherwise there will be no bouncing if the velocity is smaller then 2 by default.
This depends a bit on your definition of "realistic". I disabled gravity so the ball also has no rotation and angular friction:
using (unity 2019.3.7f1) 2d.
I have a player that moves around using a pullback mechanic and has a max power(like in Angry Birds).
I'm trying to draw a line(using a line renderer) that shows the exact path the player will go. I'm trying to make the line curve just like the player's path will. so far I've only managed to make a straight line in a pretty scuffed way.
The known variables are the Jump Power and the player's position, there is no friction. and I believe gravity is a constant(-9.81). Also, I would like to have a variable that allows me to control the line's length. And, if possible, the line will not go through objects and would act as if it has a collider.
// Edit
This is my current code. I changed The function so it would return the list's points because I wanted to be able to access it in Update() so it would only draw while I hold my mouse button.
My problem is that the trajectory line doesn't seem to curve, it goes in the right angle but it's straight. the line draws in the right direction and angle, but my initial issue of the line not curving remains unchanged. If you could please come back to me with an answer I would appreciate it.
enter code here
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TrajectoryShower : MonoBehaviour
{
LineRenderer lr;
public int Points;
public GameObject Player;
private float collisionCheckRadius = 0.1f;
public float TimeOfSimulation;
private void Awake()
{
lr = GetComponent<LineRenderer>();
lr.startColor = Color.white;
}
// Update is called once per frame
void Update()
{
if (Input.GetButton("Fire1"))
{
lr.positionCount = SimulateArc().Count;
for (int a = 0; a < lr.positionCount;a++)
{
lr.SetPosition(a, SimulateArc()[a]);
}
}
if (Input.GetButtonUp("Fire1"))
{
lr.positionCount = 0;
}
}
private List<Vector2> SimulateArc()
{
float simulateForDuration = TimeOfSimulation;
float simulationStep = 0.1f;//Will add a point every 0.1 secs.
int steps = (int)(simulateForDuration / simulationStep);
List<Vector2> lineRendererPoints = new List<Vector2>();
Vector2 calculatedPosition;
Vector2 directionVector = Player.GetComponent<DragAndShoot>().Direction;// The direction it should go
Vector2 launchPosition = transform.position;//Position where you launch from
float launchSpeed = 5f;//The initial power applied on the player
for (int i = 0; i < steps; ++i)
{
calculatedPosition = launchPosition + (directionVector * ( launchSpeed * i * simulationStep));
//Calculate gravity
calculatedPosition.y += Physics2D.gravity.y * (i * simulationStep);
lineRendererPoints.Add(calculatedPosition);
if (CheckForCollision(calculatedPosition))//if you hit something
{
break;//stop adding positions
}
}
return lineRendererPoints;
}
private bool CheckForCollision(Vector2 position)
{
Collider2D[] hits = Physics2D.OverlapCircleAll(position, collisionCheckRadius);
if (hits.Length > 0)
{
for (int x = 0;x < hits.Length;x++)
{
if (hits[x].tag != "Player" && hits[x].tag != "Floor")
{
return true;
}
}
}
return false;
}
}
Here's a simple way to visualize this.
To create your line you want a bunch of points.
The points represents the player's positions after being fired after X amount of time.
The position of each point is going to be : DirectionVector * (launch speed * time elapse) + (GravityDirection * time elapse^2)
You can decide in advance how far you pre calculate the points by simulating X duration and choosing the simulation step(calculate a point every X amount of time)
To detect collision each time you calculate a point you can do a small circle cast at that location. If it hits something you can stop add new points.
private float collisionCheckRadius = 0.1f;
private void SimulateArc()
{
float simulateForDuration = 5f;//simulate for 5 secs in the furture
float simulationStep = 0.1f;//Will add a point every 0.1 secs.
int steps = (int)(simulateForDuration/simulationStep);//50 in this example
List<Vector2> lineRendererPoints = new List<Vector2>();
Vector2 calculatedPosition;
Vector2 directionVector = new Vector2(0.5f,0.5f);//You plug you own direction here this is just an example
Vector2 launchPosition = Vector2.zero;//Position where you launch from
float launchSpeed = 10f;//Example speed per secs.
for(int i = 0; i < steps; ++i)
{
calculatedPosition = launchPosition + ( directionVector * (launchSpeed * i * simulationStep));
//Calculate gravity
calculatedPosition.y += Physics2D.gravity.y * ( i * simulationStep) * ( i * simulationStep);
lineRendererPoints.Add(calculatedPosition);
if(CheckForCollision(calculatedPosition))//if you hit something
{
break;//stop adding positions
}
}
//Assign all the positions to the line renderer.
}
private bool CheckForCollision(Vector2 position)
{
Collider2D[] hits = Physics2D.OverlapCircleAll(position, collisionCheckRadius);
if(hits.Length > 0)
{
//We hit something
//check if its a wall or seomthing
//if its a valid hit then return true
return true;
}
return false;
}
This is basically a sum of 2 vectors along the time.
You have your initial position (x0, y0), initial speed vector (x, y) and gravity vector (0, -9.81) being added along the time. You can build a function that gives you the position over time:
f(t) = (x0 + x*t, y0 + y*t - 9.81t²/2)
translating to Unity:
Vector2 positionInTime(float time, Vector2 initialPosition, Vector2 initialSpeed){
return initialPosition +
new Vector2(initialSpeed.x * t, initialSpeed.y * time - 4.905 * (time * time);
}
Now, choose a little delta time, say dt = 0.25.
Time | Position
0) 0.00 | f(0.00) = (x0, y0)
1) 0.25 | f(0.25) = (x1, y1)
2) 0.50 | f(0.50) = (x2, y2)
3) 0.75 | f(0.75) = (x3, y3)
4) 1.00 | f(1.00) = (x4, y4)
... | ...
Over time, you have a lot of points where the line will cross. Choose a time interval (say 3 seconds), evaluate all the points between 0 and 3 seconds (using f) and put your line renderer to cover one by one.
The line renderer have properties like width, width over time, color, etc. This is up to you.
I am making a 3D game. The player is the "camera". I want it not to go through walls which is achieved. But now I want it to be able to "glide" along the wall as in any other fps. Here is the code: and thanks in advance:
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;
keyState = Keyboard.GetState();
camera.Update(gameTime);
if (keyState.IsKeyDown(Keys.W)) camera.moveVector.Z = 1;
if (keyState.IsKeyDown(Keys.S)) camera.moveVector.Z = -1;
if (keyState.IsKeyDown(Keys.A)) camera.moveVector.X = 1;
if (keyState.IsKeyDown(Keys.D)) camera.moveVector.X = -1;
if (keyState.IsKeyDown(Keys.Space)&&camera.Position.Y>=0.5f) camera.moveVector.Y = 0.5f;
if (camera.moveVector != Vector3.Zero)
{
//We don't want to make the player move faster when it is going diagonally.
camera.moveVector.Normalize();
//Now we add the smoothing factor and speed factor
camera.moveVector *= (dt * camera.cameraSpeed);
Vector3 newPosition = camera.PreviewMove(camera.moveVector);
bool moveTrue = true;
if (newPosition.X < 0 || newPosition.X > Map.mazeWidth) moveTrue = false;
if (newPosition.Z < 0 || newPosition.Z > Map.mazeHeight) moveTrue = false;
foreach (BoundingBox boxes in map.GetBoundsForCell((int)newPosition.X, (int)newPosition.Z))
{
if (boxes.Contains(newPosition) == ContainmentType.Contains)
{
moveTrue = false;
}
}
if (moveTrue) camera.Move(camera.moveVector);
base.Update(gameTime);
And here is the code for excecuting the movement:
//Updating the look at vector
public void UpdateLookAt()
{
//Built a rotation matrix to rotate the direction we are looking
Matrix rotationMatrix = Matrix.CreateRotationX(cameraRotation.X) * Matrix.CreateRotationY(cameraRotation.Y);
// Build a look at offset vector
Vector3 lookAtOffset = Vector3.Transform(Vector3.UnitZ, rotationMatrix);
//Update our camera's look at the vector
cameraLookAt = (cameraPosition + lookAtOffset);
}
//Method to create movement and to check if it can move:)
public Vector3 PreviewMove(Vector3 amount)
{
//Create a rotation matrix to move the camera
Matrix rotate = Matrix.CreateRotationY(cameraRotation.Y);
//Create the vector for movement
Vector3 movement = new Vector3(amount.X, amount.Y, amount.Z);
movement = Vector3.Transform(movement, rotate);
// Give the value of the camera position +ze movement
return (cameraPosition+movement);
}
//Method that moves the camera when it hasnt'collided with anything
public void Move(Vector3 scale)
{
//Moveto the location
MoveTo(PreviewMove(scale), Rotation);
}
Already thought of using the invert method given by xna. But I can't seem to find the normal. And I have tried to move the camera parallel to the wall. But I was unable to achieve that. Any help is appreicated.
If you have found that a point intersects a bounding box, you have to check which of the six faces the entry point lies in. This can be done as follows: Construct a line segment between the old camera position and the new one:
p = (1 - t) * oldPos + t * newPos
, where you use only the dimension of oldPos and newPos, which is interesting for the face (e.g. for the left/right face, take the x-coordinate). p is the according coordinate for the face. Calculate t for every face and find the face for which t is maximal, ignoring faces behind which the point already lies (i.e. the dot product of the face normal and the direction from the face to the point is negative). This will be the face, in which your entry point lies. All you then need to do is adapt the relevant coordinate (again the x-coordinate for left/right face etc.), such that it does not lie within the bounds (e.g. set newPos.x = boundingBox.MaxX for the right face). This is equal to a projection of the point onto the bounding box surface and equivalent to using only the component of the movement vector that is parallel to the box if an intersection would occur).
Btw, the solution of the above formula is:
t = (p - oldPos) / (newPos - oldPos)
I'm working on a script for Android tablet devices in Unity3d, where the user drags to move the camera. I want the "ground" at the touch position to stay underneath the users finger while he is panning. Here is my simplyfied working code so far:
using UnityEngine;
public class CameraMovement : MonoBehaviour
{
Plane plane = new Plane(Vector3.forward, Vector3.zero);
Vector2 worldStartPoint;
void Update()
{
if (Input.touchCount == 1)
{
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began)
{
this.worldStartPoint = GetWorldPoint(touch.position);
}
if (touch.phase == TouchPhase.Moved)
{
Vector2 worldDelta = GetWorldPoint(touch.position) - worldStartPoint;
transform.Translate(-worldDelta.x, -worldDelta.y, 0);
}
}
}
Vector2 GetWorldPoint(Vector2 screenPoint)
{
Ray ray = Camera.main.ScreenPointToRay(screenPoint);
float rayDistance;
if (plane.Raycast(ray, out rayDistance))
return ray.GetPoint(rayDistance);
return Vector2.zero;
}
}
Now the problematic part: I would like the camera to move like a physics object once the user lifts up his finger. I'm trying to calculate the current velocity while dragging and then applying it as a dampened/inertia-like effect while not currently dragging. Theoretically I would do this:
Vector2 worldStartPoint;
Vector3 velocity;
void Update()
{
if (Input.touchCount == 1)
{
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began)
{
this.worldStartPoint = GetWorldPoint(touch.position);
}
if (touch.phase == TouchPhase.Moved)
{
Vector2 worldDelta = GetWorldPoint(touch.position) - worldStartPoint;
transform.Translate(-worldDelta.x, -worldDelta.y, 0);
velocity = worldDelta / Time.deltaTime;
}
}
else
{
transform.Translate(-velocity * Time.deltaTime);
velocity = Vector3.MoveTowards(velocity, Vector3.zero, damping * Time.deltaTime);
}
}
So, I always calculate velocity while moving, and once I stop input, it should remain at the last known velocity, apply it and reduce from there on until stop. However, usually the last velocity is zero, because it appears that when dragging/swiping across the screen the finger actually stops shortly and reports zero velocity before TouchPhase.Moved is over.
Now my solution/workaround would be to keep an array of velocities of the last few frames (maybe 30) and once the finger is lifted up, I would calculate the average velocity.
Vector3[] velocityBuffer = new Vector3[bufferSize];
int nextBufferIndex;
Vector3 GetAverage()
{
Vector3 sum = Vector3.zero;
for (int i = 0; i < bufferSize; i++)
sum += velocityBuffer[i];
return sum / bufferSize;
}
This works a little better, since more often than not it reports at least some velocity, but in total it's not much better and also feels very hacky. Depending on the speed of the touch, I might end up with 20 entries of zero velocity, making the damping much too strong, and sometimes the velocity randomly becomes so big that the camera just flicks a few hundred units.
Is there anything wrong with my calculations or am I overseeing something simple to fix this? Does anybody have a working solution of a smooth camera drag? I looked at a few mobile games and a lot of them actually were feeling a little clumsy, like snapping the camera to the finger position, after a few pixels of deltaMovement and then suddenly releasing it without any damping.
I'm not feeling as if I've found the perfect solution, but at least by now I have something which works better and also is more "physically correct". First, I am now storing the camera position and deltaTime in my buffer instead of the velocity. This way I can calculate a rolling average every 10 frames with the correct time factor.
/// <summary>
/// Stores Vector3 samples such as velocity or position and returns the average.
/// </summary>
[Serializable]
public class Vector3Buffer
{
public readonly int size;
Sample[] sampleData;
int nextIndex;
public Vector3Buffer(int size)
{
if (size < minSize)
{
size = minSize;
Debug.LogWarning("Sample count must be at least one. Using default.");
}
this.size = size;
sampleData = new Sample[size];
}
public void AddSample(Vector3 position, float deltaTime)
{
sampleData[nextIndex] = new Sample(position, deltaTime);
nextIndex = ++nextIndex % size;
}
public void Clear()
{
for (int i = 0; i < size; i++)
sampleData[i] = new Sample();
}
public Vector3 GetAverageVelocity(Vector3 currentPosition, float currentDeltaTime)
{
// The recorded sample furthest back in time.
Sample previous = sampleData[nextIndex % size];
Vector3 positionDelta = currentPosition - previous.position;
float totalTime = currentDeltaTime;
for (int i = 0; i < size; i++)
totalTime += sampleData[i].deltaTime;
return positionDelta / totalTime;
}
[Serializable]
struct Sample
{
public Vector3 position;
public float deltaTime;
public Sample(Vector3 position, float deltaTime)
{
this.position = position;
this.deltaTime = deltaTime;
}
}
public const int minSize = 1;
}
Also, I noticed, that I was recording a lot of zero velocity values, which is now mitigated because I'm tracking the position, which I also want to update when not dragging, but holding:
if (input.phase == TouchPhase.Moved || input.phase == TouchPhase.Stationary)
{
velocityBuffer.AddSample(transform.position, Time.deltaTime);
}
if (input.phase == TouchPhase.Ended || input.phase == TouchPhase.Canceled)
{
velocity = -velocityBuffer.GetAverageVelocity(transform.position, Time.deltaTime);
}
Lastly, instead of setting the camera to the fingers world position every frame, I use a tiny bit of Lerp/MoveTowards interpolation to smooth out any jitter. It's hard to get best value between crisp control and smooth look, but I assume that's the way to go with user input, which can vary rapidly.
Of course, I'd still be interested in other approaches, better solutions or opinions about my current implementation.