I'm trying to implement a smooth slow down scroll based on the velocity of the user gesture.
I'm using MR.Gestures to handle the pan gesture and obtain the velocity, then I'm running an animation loop to perform the deceleration but it's very jerky and I'm not sure what I can do to make it smooth.
_velocityX = (velocityX / (1000 / SCROLL_INTERVAL));
_velocityY = (velocityY / (1000 / SCROLL_INTERVAL));
I have SCROLL_INTERVAL set to 16 to scroll at roughly 60fps. My _friction variable is currently 0.98f.
In my animation loop I have the following:
var distance = new Point
{
X = 0,
Y = 0
};
if (Math.Abs(_velocityX) > 0)
{
distance.X += _velocityX;
_velocityX *= _friction;
}
if (Math.Abs(_velocityY) > 0)
{
distance.Y += _velocityY;
_velocityY *= _friction;
}
<snip>code here moves the camera by the values in distance.X and distance.Y</snip>
Now, this works but is very jerky and the issue appears to be that it may be moving by 10+ pixels at the start of the animation.
I believe based on this that my approach is incorrect but I can't find anything to help me change the approach.
Is there a better way to do this? Or a way to make this more smooth?
Related
I am trying to make a first-person game on Monogame and so far all I have come with or found on the internet didn't meet my standards.
this is how I am currently handling the mouse:
private void HandleMouse()
{
Vector2 mouseDifference;
mouseNow = Mouse.GetState();
if (mouseNow.X != mouseDefaultPos.X || mouseNow.Y != mouseDefaultPos.Y)
{
mouseDifference.X = mouseDefaultPos.X - mouseNow.X;
mouseDifference.Y = mouseDefaultPos.Y - mouseNow.Y;
leftrightRot += mouseSens * mouseDifference.X;
updownRot += mouseSens * mouseDifference.Y;
Mouse.SetPosition((int)mouseDefaultPos.X, (int)mouseDefaultPos.Y);
UpdateViewMatrix();
}
}
private void UpdateViewMatrix()
{
Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot);
Vector3 cameraOriginalTarget = new Vector3(0, 0, -1);
Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0);
Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation);
Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation);
viewMatrix = Matrix.CreateLookAt(new Vector3(0,0,0), cameraRotatedTarget, cameraRotatedUpVector);
}
My problem is that while this solution works, it is extremely inconsistent when it comes to how far the camera should rotate.
for example, happens when I make circles with the mouse and see that sometimes the mouse randomly jumps more than expected.
My main assumptions inconsistencies in fps that cause the time between frames to change, thus affecting the distance the mouse can move within that time.
I don't know if this is the reason or if my assumption can even cause this, but I would like to find a way to get consistent mouse movement.
I can provide a video of the problem if needed.
Thank you in advance.
The mouse in Windows is updated 240 times per second. The game loop runs at 60 frames per second. The discrepancy can lead to large mouse deltas. The solution is to limit the change in the mouse delta:
Vector2 mouseDifference;
const float MAXDELTA = 6; // Set to the appropriate value.
mouseNow = Mouse.GetState();
if (mouseNow.X != mouseDefaultPos.X || mouseNow.Y != mouseDefaultPos.Y)
{
mouseDifference.X = Math.Min(MAXDELTA, mouseDefaultPos.X - mouseNow.X);
mouseDifference.Y = Math.Min(MAXDELTA, mouseDefaultPos.Y - mouseNow.Y);
leftrightRot += mouseSens * mouseDifference.X;
updownRot += mouseSens * mouseDifference.Y;
Mouse.SetPosition((int)mouseDefaultPos.X, (int)mouseDefaultPos.Y);
UpdateViewMatrix();
}
This is an old thread, but I figured I would share a solution. The solution that's worked for me to use the GameTime object to relate how much the player should rotate by. In other words, rotate by (delta * rotationSpeed * GameTime.ElapsedTime.TotalSeconds) so that each rotation is relative to how much time has passed since the last frame. This protects it against frame drops, which I've found has been the problem for me.
So I'm working on my menu's background and I Draw() a texture there with a rectangle. How would I have the rectangle wait, move up and then down, wait and repeat? I tried the following:
// Update()
if (Rectangle.Y = -16) // My texture is positioned to -16 initially
Rectangle.Y++;
else if (Rectangle.Y = 0)
Rectangle.Y--;
So my game resolution is 1366x768. To have the background texture move up and down I had to make it have a height > 768. I made it 1366x800. Every time the above code is called it should wait 1-2seconds (not yet implemented), move 16 pixels down, wait again and go back 16 pixels up... But that code doesn't work... Could you guide me as to how this is done?
You can do it with Math.Sin, which will give you a smooth transition from -1 to 1. You will have to keep a copy of your rectangle's center Y position.
double time = gameTime.TotalGameTime.TotalSeconds;
Rectangle.Y = centerY + (int)(Math.Sin(time * transitionSpeed) * maxOffset);
You can play with double transitionSpeed to get the best visual effect. int maxOffset is the max amount of offset from centerY.
If you don't want smooth movements, then just do
int speed = 1; // speed of movement
Then in update
if (Rectangle.Y <= -16 || Rectangle.Y >= 0)
speed *= -1; // reverse move direction
Rectangle.Y += speed;
I'm working on a 2D game where the player can drag tiles around. It works in a way that the player clicks and hold a tile and depending in which direction the player moves the mouse from then on, the drag direction is decided.
The problem however is that this is overly sensitive. It might often be the case that the player starts dragging and wanted to drag vertically but due to the mouse sensitivity it turns out to drag horizontally (or vice versa).
Does anyone have an idea how to add a tolerance threshold to this dragging behavior? The relevant part in my code looks basically like this:
private void Update()
{
if (_isMouseDown && sourceTile != null)
{
_isDraggingTile = true;
/* Determine drag direction and drag target cell. */
Vector3 dragDistance = Input.mousePosition - _originMousePosition;
dragDistance.Normalize();
if (_dragDirection == DragDirection.None)
{
float f = Vector3.Dot(dragDistance, Vector3.up);
/* Mouse up drag. */
if (f >= 0.5f)
{
_dragDirection = DragDirection.Up;
_dragTargetCell = sourceTile.gridCell.upNeighbor;
}
/* Mouse down drag. */
else if (f <= -0.5f)
{
_dragDirection = DragDirection.Down;
_dragTargetCell = sourceTile.gridCell.downNeighbor;
}
else
{
/* Mouse right drag. */
f = Vector3.Dot(dragDistance, Vector3.right);
if (f >= 0.5f)
{
_dragDirection = DragDirection.Right;
_dragTargetCell = sourceTile.gridCell.rightNeighbor;
}
/* Mouse left drag. */
else if (f < -0.5f)
{
_dragDirection = DragDirection.Left;
_dragTargetCell = sourceTile.gridCell.leftNeighbor;
}
}
}
if (_dragTargetCell != null)
{
// Take care of moving the dragged tile!
}
}
}
Simply delaying the calculation of dragDistance by some frames doesn't turn out to work very well. I think what is needed is a solution to figure out the mouse movement and decide on which axes it moves farthest. Determining the drag direction as above will probably never work out well.
The problem with any collection of information is noise. In your case, the noise is defined by the wrong movement of the user. Nonetheless, it should be possible to minimize the effect of noise by averaging the values.
There are advanced algorithms used in DSP but I guess a basic averaging of the info should do in your case.
What you could try is that instead of moving in Update at once like you do, collect movement over several frames, then average all those frames and see if it goes better:
IEnumerator GetAverageMovement(Action<Vector3> result)
{
int frames = 0;
List<Vector3>list = new List<Vector3>();
while(frames < 30f) // half a second appr
{
list.Add(GetDirection());
frames++;
yield return null;
}
result(AverageAllValues());
}
GetDirection is just returning the delta between current and previous position, AverageAllValues simply adds all values in list and divides by list.Count (aka 30).
This should fix cases when the user move all the way right but a bit up at the end. The last bit should be canceled by the large right movement.
If that is still not enough, then you could add some logic within the method that if a value is too far gone from the average, discard it. I don't think you need this in there.
I think you should create a Queue of positions with limited size right after dragging .
by comparing final value of Queue and first value you can find the direction.
If you want to get better results you can get Variance of positions and get better results.
for my programm im programming a "clock like" behaviour. Meaning i
order some pictures in a circle (works)
if i click and drag on any item all items should rotate with the mouse (works)
But i get a weird bug. The first time i click and hold the mouse my images "jump" to different positions. if i hold the mouse down i can rotate my clock ust fine.
When i MouseUp and start dragin from the same image it works well. if i go to another image i get this "Jump" again.
When i only have a few images on my clock i see that it doesnt jump. but the start position seems to bee off.
When i only have one item, i can rotate it in a circle, but the moment i start to rotate it jumps away from my mouse and than i can rotate it as desired.
For me it seems to be a wrong "starting point" when first dragging an item. SInce it works fine when i than drag the same item again and again.
Unfortunately i cant find the damn bug, and im searching the whole day already.
#
public void SetLayoutHorizontal ()
{
Debug.Log ("LAYOUT");
for (var i =0; i < Rect.childCount; i++)
{
var PanelPrefab = Rect.GetChild (i) as RectTransform;
Transform ImageObject = PanelPrefab.GetComponentInChildren<Transform>().Find("Image");
if (PanelPrefab == null)
continue;
PanelPrefab.sizeDelta = CellSize;
PanelPrefab.anchoredPosition = new Vector2(radius * Mathf.Sin( CalculateCircleAngle(i) - deltaRadian),radius * Mathf.Cos(CalculateCircleAngle(i) - deltaRadian));
}
}
private float CalculateCircleAngle(int parts)
{
//parts == Number of parts the whole circle is to be cut into
return parts * (360/Rect.childCount) * (Mathf.PI/180);
}
public void OnDrag (PointerEventData eventData)
{
var diffX = eventData.position.x - _rect.rect.width/2; // MouseX - CenterScreenX
var diffY = eventData.position.y - _rect.rect.height/2; // MouseY - CenterScreenY
deltaRadian = Mathf.Atan2(diffY,diffX);
SetDirty();
}
Edit:
Ok i Edited the code but it still is not working.
I added the following method:
public void OnBeginDrag(PointerEventData eventData)
{
originalX = eventData.position.x;
originalY = eventData.position.y;
}
and i changed the drag method acordingly
public void OnDrag (PointerEventData eventData)
{
var diffX = eventData.position.x - originalX;
var diffY = eventData.position.y - originalY;
deltaRadian = Mathf.Atan2(diffY,diffX);
SetDirty();
}
The "Jumping" at the beginning of my drag event is gone, but the speed of the draggin is not on par with my mouse.
The closer i am to my starting point of the drag, the faster it moves, the further away i am the slower it gets.
I dont know if this brought me closer to a solution or further away :(
Edit2:
Ok i think the problem might be that my calculations were all done from the center point of view as 0,0 point.
Unity has the bottom left point as 0,0. So i somehow have to translate all those coordiantes first...
All that was needed was a transformation to kartesian coordinates
//Convert to kartesian coordinates with 0,0 in center of screen
var diffX = (eventData.position.x - _rect.rect.width / 2) % (_rect.rect.width / 2);
var diffY = (eventData.position.y - _rect.rect.height / 2) % (_rect.rect.height / 2);
and an addition of the delta instead of the subtraction
PanelPrefab.anchoredPosition = new Vector2(radius * Mathf.Sin(CalculateCircleAngle(i) + deltaRadian),radius * Mathf.Cos(CalculateCircleAngle(i) + deltaRadian));
Your mouse coordinates are relative to the control you click in. You could re-calculate them using the control's position within its container or you use absolute coordinates.
I don't think you are displaying all the relevant code, but your OnDrag function, specifically diffX, and diffY probably provide the answer for you.
I believe you're taking the absolute coordinates of the location you pick, and using them to generate your angle - which means that almost anywhere you click is a big jump from however the current angle is set. Your initial click handler should save off a starting coordinate, and your OnDrag should compare itself against that initial coordinate, based on how far you've dragged from that saved location.
I'm making a simple game where the player controls a tank. The rotation of the turret will be controlled by mouse movements. The code currently looks like this:
if (Game.MouseState.Y < yMovementBorder)
PossessedTurretPitchValue += dist;
if (Game.MouseState.Y > yMovementBorder)
PossessedTurretPitchValue -= dist;
if (Game.MouseState.X < xMovementBorder)
PossessedTurretYaw += rotationDist / 6;
if (Game.MouseState.X > xMovementBorder)
PossessedTurretYaw -= rotationDist / 6;
xMovementBorder and yMovementBorder are values representing the midpoint of the game screen. The problem is that any movement of the mouse will cause the turret to turn until its maximum pitch/yaw angle. How can I make it such that it will be able to read the mouse movement as well as its magnitude(ie: slight movement of the mouse will only cause a slight pitch/yaw movement of the turret)?
In your update method, you should store references to your previous mouse state and current mouse state. Then use these two variables to figure out how much the mouse moved since the last update. It will take some tweaking to get it just right, but try something like this:
//define your private variables
private MouseState prevMouseState = null;
private MouseState currMouseState = Game.MouseState;
public void Update(GameTime gt)
{
prevMouseState = currMouseState;
currMouseState = Game.MouseState;
//calculate how much the mouse has moved since the last update
var dX = currMouseState.X - prevMouseState.X;
var dY = currMouseState.Y - prevMouseState.Y;
//do your rotating depending on the values of dX and dY
}
I think what you want to do is set limits based on how far from center the mouse is.
Like:
if (Game.MouseState.Y < yMovementBorder)
if (PossessedTurretPitchValue < yMovementBorder*someFactor)
PossessedTurretPitchValue += dist;
And similarly for the other three cases.
Without setting a limit, any deviation from the center will cause the turret to move. It will be moving all the time, and eventually rail somewhere. This is what your code does now, if I understand correctly.