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.
Related
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 am having a big struggle implementing a collision detect on a list of bricks.
I have a game which randomly drops bricks that are supposed to stack up at the bottom of the screen. I managed to make them stop at the bottom of the screen but they don't stack.
I have this in my update function:
if (r.Next(0, 50) == 8)
{
_bricks.Add(new NormalBrick(this, new Vector2(r.Next(0, 700), 100)));
}
foreach(Brick b in _bricks)
{
b.move(GraphicsDevice.Viewport);
}
My move() function has the following code:
public void move(Viewport viewport)
{
if (_position.Y == (viewport.Height - _texture.Height ))
{
_position = new Vector2(_position.X, _position.Y);
}
else
{
_position = new Vector2(_position.X, _position.Y + _speed);
}
}
How can I make sure that the bricks don't all stop at the bottom of the screen, instead the brick has to check if there is a brick beneath it?
I have checked other questions on here but I couldn't find my answer and I have tried several things to get it fixed. Any help would be appreciated.
I would create Rectangles for each of your Bricks (unless you have already done so). Then in the Update() method, use something like brick.Rectangle.Intersects(anotherBrick) after movement. If true, then position the current brick above the intersecting bottom brick.
Make sure to move the brick's rectangle each time the brick moves.
I hope this helps. Let me know if you require any further assistance.
This is a follow up question to a question that I posted last night. I have to write a game in a Windows Form for school, and I am creating a maze game in which a player must navigate through a maze before they are killed. As a maze game, some collision detection must be used to make sure that the player doesn't simply run through the walls (this would be quite a boring game). I've implemented a feature which prevents this based on the question that I asked last night, but I'm getting some weird results.
When the player touches a wall, the game stops them, and the player ends up getting stuck. The player CANNOT move unless they press a combination of keys to move through the wall (my game uses WASD, so if I touch a wall, I can press W + A and go through the wall to the other side where my player gets unstuck).
This is my collision code:
// This goes in the main class
foreach (Rectangle wall in mazeWalls)
{
if (playerRectangle.IntersectsWith(wall))
{
player.Stop();
}
}
This is the player's movement code:
public void Move(Direction dir)
{
// First, check & save the current position.
this.lastX = this.x;
this.lastY = this.y;
if (dir == Direction.NORTH)
{
if (!CheckCollision())
{
this.y -= moveSpeed;
}
else
{
this.y += 1;
}
}
else if (dir == Direction.SOUTH)
{
if (!CheckCollision())
{
this.y += moveSpeed;
}
else
{
this.y -= 1;
}
}
else if (dir == Direction.EAST)
{
if (!CheckCollision())
{
this.x += moveSpeed;
}
else
{
this.x -= 1;
}
}
else if (dir == Direction.WEST)
{
if (!CheckCollision())
{
this.x -= moveSpeed;
}
else
{
this.x += 1;
}
}
}
My CheckCollision() method:
private bool CheckCollision()
{
// First, check to see if the player is hitting any of the boundaries of the game.
if (this.x <= 0)
{
isColliding = true;
}
else if (this.x >= 748)
{
isColliding = true;
}
else if (this.y <= 0)
{
isColliding = true;
}
else if (this.y >= 405)
{
isColliding = true;
}
else if (isColliding)
{
isColliding = false;
}
// Second, check for wall collision.
return isColliding;
}
The Stop() method:
public void Stop()
{
this.x = lastX;
this.y = lastY;
}
Here is a gif that I have uploaded so that you can see the behavior of the player with the maze walls. Notice how he slides through the walls and repeatedly gets stuck.
My question is how do I get this player to stop sticking and actually be able to slide and move with the walls? I've tried multiple collision patterns, and used last night's (very helpful) answer, but he won't stop sticking to the walls! Let me know if you need any other details/information.
EDIT: The Input code, as requested by Dan-o: http://pastebin.com/bFpPrq7g
Game design is a complicated subject. There are some pretty well documented strategies for implementing 2D maze games. The most anyone can do here is to make general suggestions based on what you've done knowing that a commercial game is not what trying to make here. So anyhow, I'll throw in my two cents. I've only done anything like this in OpenGL, not with Windows Forms.
The first thing I see is that your sprite doesn't stay centered in it's lanes. Calculating that will ultimately make things easier because there are always only 4 possible directions that the player can be moving. North, South, East, and West don't mean as much when diagonal is also possible.
Design your gameboard so that your walls and your lanes are the same width. Use evenly spaces rows and columns. Since you're using a fixed board size, this should be as simple as dividing your width and height by the number of rows and columns you want.
Once you've done this, you will always know what lane you're in based on your x and y coordinates. If your columns are 80 pixels wide, for instance, and you're at x = 160 then you know you're in column 2 (1 in a 0-based array). This is the only way you are going to be able to put enemies on the board and be able to programmatically track where they are going. Also, you'll be able to size your players and enemies appropriately for the lanes.
Let's say your rows and columns are 80Wx60H pixels (they can be whatever you like best). Now let's say that your player is moving East starting from 0, 160. And, he can move North and South when he gets to column 3 (2 in a zero-based model), according to your map. Meaning, when he gets to 160, 160. Remember, if x = 0 starts column 1, then 80 starts column 2, and so on.
Forgetting collisions for a moment, you could use logic like this.
RectangleF playerRectangle = new RectangleF();
int COLUMN_WIDTH = 80;
int ROW_HEIGHT = 60;
if (playerRectangle.IntersectsWith(wall)){
int column = playerRectangle.X / COLUMN_WIDTH;
//----------------------------------------------
// This will return false if the player
// is not positioned right at the column. The
// result of % will contain decimal digits.
// playerRectangle.X has to be a float though.
//----------------------------------------------
if(column % 1 == 0){
//--------------------------------------------
// do this based on your keyboard logic. this
// is pseudo-code
//--------------------------------------------
if(keys == keys.UP){
// Move up
}
else if(keys == keys.DOWN){
// Move down
}
}
}
Another thing you'll want to do is to store an orientation property with your walls. That way when you collide with one, you'll know what your options are for movement. If the wall you collide with is WallOrientation.NorthSouth, you can only move North or South once you hit it. Or, you can go in reverse to the direction you hit it from. If no keys are pressed you sit still. Some thing like this will do.
public enum WallOrientation{
NorthSouth,
EastWest
}
None of this code is tested. I just wrote it on the fly so there could be mistakes. I'm just trying to give you some ideas. I hope this advice helps you out some.
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.
Okay, so, I am making a small tile-based digging game, now I want to do collision. How would I go about doing this correctly? I know how to check if the player collides with a tile, but I don't know how to actually make the player stop when it hits a wall.
This is the game, I got 20x20 tiles here.
This is the code I'm using atm:
foreach (Tile tiles in allTiles)
{
if (ply.rect.Intersects(tiles.rect))
{
if (tiles.ID != -1 && tiles.ID != 1)
{
if (ply.X > tiles.X)
{
Console.WriteLine("Right part.");
ply.X = tiles.pos.X + 30;
}
if (ply.X <= tiles.X)
{
Console.WriteLine("Left part.");
ply.X = tiles.pos.X - 30;
}
if (ply.Y > tiles.Y)
{
Console.WriteLine("Bottom part.");
ply.Y = tiles.pos.Y + 30;
}
if (ply.Y <= tiles.Y)
{
Console.WriteLine("Upper part.");
ply.Y = tiles.pos.Y - 30;
}
}
}
}
What type of collision detection are you using?
If your using Rectangles and the '.intersects' method you can always declare a bool to make sure your character is touching the floor. If he isn't you apply a Gravity Vector to make it fall to the next Tile with a different Rectangle so when he hits it he's going to stop falling.
If you want to block him from side to side just test to see which side of the rectangle he is touching and block him from moving on the 'X' axis.
E.g if he is going right and intersects with the left part of a rectangle, block is 'GoingRight' movement.
if(myCharacterRectangle.Intersects(tileRectangle)
{
if(myCharacterPosition.X > (tilePosition.X)
{
//You know the character hits the Right part of the tile.
}
if(mycharacterPosition.X <= tilePosition.X)
{
//You know the character hits the Left Part of the tile.
}
}
And same goes for the Position.Y if you want to test the Top or Bottom.
If you want to use Pixel by Pixel collision detection using Matrices I know a good tutorial here.
The detection will return a 'Vector2(-1,-1)' if there is no collision.
If there is a one the method will return the coordinates of the collisions which makes it even easier to determine what part of the tile your character is touching.
Hope this helps. Good Luck with your game.