I have a simple rectangle-tile collision scheme set up, and it works beautifully.
The only problem is when you start falling off of a ledge. Your speed reaches the point where the change in Y/X each frame is large enough for you to clip into solid objects and glitch about.
Basically my setup is as follows:
To start with, the player's position has its velocity added to it, so the player is now at the place he would be next frame if no collisions happen.
The list below is just a single function, checkIntersectTiles(Vector2 maskPos);
Calculate tiles around the character to check.
Loop through tiles, including those inside the bounding tiles.
Check the player's collision rectangle against each of these tiles.
If there's an intersection, move the largest offending axis out of the tile, then set that axis velocity to 0.
Continue checks.
When you clip into the ground, you jitter around inside, as my algorithm attempts to move you outside the tile that is most colliding with you.
My solution: Check each position from the player's pos, to the player's pos + velocity.
I'm stuck on that bit.
Can anyone give me a hand?
I assume that your code to move the player out of the colliding tile does so in a single step. So if the player collides with a tile you determine that the penetration depth is 5 in the Y direction you immediately adjust the player Y position by -5.
As you suggest, check the player position at each step. So if the Y velocity is 5 then you can adjust the players Y position by 1, check for collision and then repeat 4 more times. See later for calculations handling time stepping. The following is just some basic pseudo code and just in the Y direction.
player.Y += vel;
if (player.CheckCollisions())
{
// handle collision
}
Becomes
for (int i = 0; i < vel; ++i)
{
player.Y += 1;
if (player.CheckCollisions())
{
// handle collision
break;
}
}
That is the simple version if you are not adjusting for ellaped time. If you are, then you rather perform smaller time steps
So
player.Y += vel * elapsedTime;
if (player.CheckCollisions())
{
// handle collision
}
Becomes
float currentTimeStep = 0;
Position startY = player.Y;
while (currentTimeStep < elapsedTime)
{
currentTimeStep = Math.Min(currentTimeStep + stepDelta, elapsedTime); // You need to tune the stepDelta
player.Y = startY + vel * currentTimeStep;
if (player.CheckCollisions())
{
// handle collision
break;
}
}
In the above you will need to tune the time step delta to ensure that you balance performance with accuracy. You might event consider calculating the delta dynamically each frame to ensure that the adjustment is close to 1 pixel.
Related
I have a LineRenderer with many positions and am try to animate the line from point a -> b, b ->c, etc...
First, I save all the positions of the line, then I reset the positionCount so that it is not visible at the start. But when I draw the lineRenderer in a loop, increasing positionCount over each iteration, and when drawing the next line starts, the previous line shakes a little and the width changes momentarily.
Here is code:
public float LineDrawSpeed;
void Start()
{
lineRenderer = GetComponent<LineRenderer>();
int lineCountInLineRenderer = lineRenderer.positionCount - 1;
var startPositions = new Vector3[lineRenderer.positionCount];
lineRenderer.GetPositions(startPositions);
lineRenderer.positionCount = 1;
StartCoroutine(LineDrawCoroutine(startPositions));
}
Here is coroutine:
IEnumerator LineDrawCoroutine(Vector3[] positions)
{
for (int i = 0; i < positions.Length - 1; i++)
{
if (lineRenderer.positionCount <= i + 1)
lineRenderer.positionCount++;
lineRenderer.SetPosition(i, positions[i]);
float timePass = 0f;
while (timePass < LineDrawSpeed)
{
var factor = timePass / LineDrawSpeed;
factor = Mathf.SmoothStep(0, 1, factor);
lineRenderer.SetPosition(i + 1, Vector3.Lerp(positions[i], positions[i + 1], factor));
timePass += Mathf.Min(Time.deltaTime, LineDrawSpeed - timePass);
yield return null;
}
}
}
The mechanic works well, but something is wrong with animation.
I found this width variation topic quite interesting.
As far as I checked, there two important points to take into account.
1.- LineRenderer render billboard lines.
https://docs.huihoo.com/unity/5.3/Documentation/en/Manual/class-LineRenderer.html
Billboards are 2D elements incrusted in a 3D world, whose orientation is automatically computed so that it always faces the camera. This explains width variations along with camera movement.
2.- If the camera is not moving, take into account that: As defined in the documentation, width attribute defines: "a width value and a curve value to control the width of your trail along its length.
The curve is sampled at each vertex, so its accuracy is limited by the number of vertices in your trail. The overall width of the trail is controlled by the width value."
https://docs.unity3d.com/Manual/class-LineRenderer.html
So as you are dinamically changing the vertexes of your line, the overall width of your trail might suffer changes.
So I think that your algorithim works fine, and that the width variations comes along with the line renderer component.
I'm pretty confident your issue lies in the fact that you are adding to the position count
over and over again. Not 100% sure though.
Regardless, below is working code for how to incrementally increase the length of the line. It's just an IEnumerator, but you can just call it in StartCoroutine and it should work
This will work for a straight line, but will take some adjustments for a curved line (but I think you could make it work if you wanted to).
To explain this a bit extra, instead of incrementing and increasing the positionCount, and then adding the vector, I simply set the positionCount to the desired amount of vectors and then incrementally fill each vector in with the desired position. When setting them all right off the bat, they default to 0,0,0, so just be sure this doesn't cause any issues for you.
Below is the code I use:
// Using IEnumerator to ensure that the line is drawn over a specific amount of time
private IEnumerator DrawLine()
{
float renderTime = 2f; // total time for full line render
int renderSteps = 75; // desired resolution of render (higher amt of steps creates smoother render)
float timeBetweenSteps = renderTime / renderSteps; // time between each render step
// Grab the LineRenderer component that is attached to the gameObject and defined in Inspector
LineRenderer lineRenderer = yourGameObject.GetComponent<LineRenderer>();
// Declare the endpoint
Vector3 endPoint = new Vector3(0, 5, 0);
// Take the end point and break into the amount of steps we need in order to create
// fully rendered line
// Create an additiveVector for the forLoop that will step through the renderSteps
// >> Start at zero becuase zero is the localOrigin
Vector3 stepVector = endPoint / renderSteps;
Vector3 additiveVector = Vector3.zero;
// Setting the line's position element count to the render resolution because we will
// have to step through
lineRenderer.positionCount = renderSteps;
// For Loop that steps through each position element in the Line
for (int i = 0; i != lineRenderer.positionCount; i++)
{
lineRenderer.SetPosition(i, additiveVector); // set the position element to the additiveVEctor
additiveVector += stepVector; // add one step to the additiveVector
yield return new WaitForSeconds(timeBetweenSteps); // Wait the appropriate step time before repeating the loop
}
}
Let me know if you have any questions. I hope this helps!
I'm having a glitch with this simple collision algorithm. I'm doing the rpg game tutorial from codingmadeeasy, and the collision is much alike any algorithm collision I've done in previous platformer style games. I've had this problem with previous games as well, just don't remember the fix. I have a map, it has lots of tiles, some are flagged as solid. their Update method checks for collision with the player.
If the tile is solid, it should stop the player and move him to a valid location.
Problem is when I go up or down, stick to the tile, and press a left or right movement key.
For example:
I'm pressing the Down key, I'm on top of the solid tile, and I press to go Left. at that moment, the player is relocated to to the Right of the tile.
This wouldn't happen if I go left or right and press up or down because of the if order in the collision method.
public void Update(GameTime gameTime, ref Player player)
{
if (State != TileState.Solid) return;
Rectangle tileRect =
new Rectangle((int)Position.X, (int)Position.Y,
SourceRect.Width, SourceRect.Height);
Rectangle playerRect =
new Rectangle((int)player.Sprite.position.X, (int)player.Sprite.position.Y,
player.Sprite.SourceRect.Width, player.Sprite.SourceRect.Height);
HandleCollisionWithTile(player, playerRect, tileRect);
}
/// <summary>
/// Repositions the player at the current position after collision with tile
/// </summary>
/// <param name="player">the player intersecting with the tile</param>
/// <param name="playerRect">the rectangle of the player</param>
/// <param name="tileRect">the rectangle of the tile</param>
private static void HandleCollisionWithTile(Player player, Rectangle playerRect, Rectangle tileRect)
{
// Make sure there's even collosion
if (!playerRect.Intersects(tileRect)) return;
// Left
if (player.Velocity.X < 0)
player.Sprite.position.X = tileRect.Right;
// Right
else if (player.Velocity.X > 0)
player.Sprite.position.X = tileRect.Left - player.Sprite.SourceRect.Width;
// Up
else if (player.Velocity.Y < 0)
player.Sprite.position.Y = tileRect.Bottom;
// Down
else if (player.Velocity.Y > 0)
player.Sprite.position.Y = tileRect.Top - player.Sprite.SourceRect.Height;
// Reset velocity
player.Velocity = Vector2.Zero;
}
Obviously, If I switch the two first ifs with the last, that would happen if I come from the side and press up or down.
What exactly am I missing?
As you've noted, this issue occurs because of the order in which you check the condition for collision. In particular, while visually your player sprite appears to be above a tile, your code is detecting the sprite as intersecting, and since the first check is for horizontal collisions, the player sprite's position is moved to the right of the tile's sprite (above and to the right, actually, so you only adjust the coordinate in the axis of the detected collision).
It seems to me that this exposes a fundamental flaw in the code: while one might argue that having resolved the collision that occurred in the vertical direction, by placing the player sprite at the top of the tile sprite, now the player sprite should not be in a collision state with the tile sprite, instead your code detects this condition as a collision.
Without a better code example, it's hard to know what the best fix might be. But an obvious quick-and-dirty solution would be to ensure that when you resolve a collision, you are not putting the player sprite in a position that intersects with the tile sprite. For example:
private static void HandleCollisionWithTile(Player player, Rectangle playerRect, Rectangle tileRect)
{
// Make sure there's even collosion
if (!playerRect.Intersects(tileRect)) return;
// Left
if (player.Velocity.X < 0)
player.Sprite.position.X = tileRect.Right + 1;
// Right
else if (player.Velocity.X > 0)
player.Sprite.position.X = tileRect.Left - player.Sprite.SourceRect.Width - 1;
// Up
else if (player.Velocity.Y < 0)
player.Sprite.position.Y = tileRect.Bottom + 1;
// Down
else if (player.Velocity.Y > 0)
player.Sprite.position.Y = tileRect.Top - player.Sprite.SourceRect.Height - 1;
// Reset velocity
player.Velocity = Vector2.Zero;
}
In other words, give yourself a 1 pixel margin around the tile into which the player sprite may not enter.
An alternative would be to "deflate" the tile rectangle (i.e. call Inflate() with negative values) by 1 pixel before checking for the intersection that defines a collision. Then when a collision does occur, the placement will be as before (i.e. without the +1 and -1 adjustments shown above), but that placement will not itself be detected as a collision.
There are many other ways to handle collision detection, but a) without more specifics about your scenario it's hard to know whether and which of those might be better choices, and b) given the apparently simple nature of your current implementation, a relatively simple fix as I've proposed above seems more likely to be warranted and useful.
Recently I've been trying to make a 2D game in XNA, but I seem to have hit a dead end. No mater where I look on the internet, I can't seem to find any examples of how to make two objects collide in XNA. I know perfectly well about how to detect if something is colliding, just not what to put between the if statement.
In my case it's trying to make a player not pass through any blocks on the screen. I've tried several ways, but none of them seems to work. :(
Thanks in advance!
So, this is sort of a dirty solution, but you could save the position of where you are, move, and then if you collide, revert back to the original position.
Here's a code example, providing your method to check for collision is called doesCollide() and your method to do the game logic and move the player accordingly is called update().
Also, getX(), getY(), setX() and setY() are just methods to get/set the coordinates of the player.
int lastX = player.getX();
int lastY = player.getY();
player.update();
if(player.doesCollide()){
player.setX(lastX);
player.setY(lastY);
}
This method especially faces problems, when the player is moving at a high speed, because then the player might glitch through obstacles or not be able to get close to walls.
Here's a solution that fixes that, but it is even dirtier:
int lastX = player.getX();
int lastY = player.getY();
player.update();
int moveX = player.getX() - lastX;
int moveY = player.getY() - lastY;
player.setX(lastX);
player.setY(lastY); //we're basically figuring out where the player would move
int ratio = moveY / moveX;
for(int i = 0; i < moveX; i++){ //we move the player pixel by pixel
player.setX(lastX + i);
player.setY(lastY + (int) (i * ratio));
if(player.doesCollide()){ //we revert the last pixel move
player.setX(lastX + i - 1);
player.setY(lastY + (int) ((i - 1) * ratio));
}
}
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.
I am trying to simulate gravity in my first xna 2d game. I have the following
//Used for Jumping
double elapsedAirTime = 0.0;
double maxAirTime = 8.35;
//End Jumping
So I am trying to move the sprite up by a certain amount while the elapsedAirTime < maxAirTime
However, there is some issue where my code only seems to move the sprite up once and not multiple times during this segment of time. here is the code in my "player.cs" class, or the update method of the class.
if (newState.IsKeyDown(Keys.Space))
{
if(oldState.IsKeyUp(Keys.Space))
{
//if we are standing or jumping then change the velocity
if (playerState == PlayerStates.Standing)
{
playerState = PlayerStates.Jumping;
this.position.Y -= (float)(30.0 + ((1.2)*elapsedAirTime)*elapsedAirTime);
}
}
}
//if we are jumping give it some time
if (playerState == PlayerStates.Jumping)
{
if ((elapsedAirTime < maxAirTime) && position.Y < 3)
{
this.position.Y -= (float)(30.0 + ((1.2) * elapsedAirTime)*elapsedAirTime);
elapsedAirTime += gameTime.ElapsedGameTime.TotalSeconds;
}
//otherwise time to fall
else
{
playerState = PlayerStates.Falling;
}
}
//add gravity to falling objects
if (playerState == PlayerStates.Falling || playerState == PlayerStates.Standing)
{
//if we are above the ground
if (this.position.Y < windowBot - 110)
{
//chnage state to falling
playerState = PlayerStates.Falling;
this.position.Y += 3.0f + ((float)(gameTime.ElapsedGameTime.TotalSeconds));
}
else
{
playerState = PlayerStates.Standing;
elapsedAirTime = 0.0f;
}
}
Any help is much appreciated, please and thank you!
To give your sprite the feel of gravity, you should add velocity and acceleration to your Sprite class. Then, create an Update method for the Sprite, and have acceleration be added to your velocity every update, and velocity added to position every update. Position should not be based on the amount of elapsed air time. You can set the acceleration to a constant gravitational value, and then add to the velocity of the Sprite whenever you jump. This will create a flowing parabolic jump that looks nice. If you want to include timing, you can pass the GameTime into the Sprite's Update method, and use it as a modifier on the velocity. Here is an example Update method:
void Update(GameTime gt)
{
int updateTime = gt.ElapsedGameTime.TotalMilliseconds - oldgt.ElapsedGameTime.TotalMilliseconds;
float timeScalar = updateTime / AVG_FRAME_TIME;
this.velocity += this.acceleration * timeScalar;
this.position += this.velocity;
oldgt = gt;
}
If you use timing, this method is a little complicated. You have to keep track of how much time the update took, then divide it by the average amount of time an update or frame should take to get the amount you should adjust your velocity by. Without timing, the method is very simple:
void Update()
{
this.velocity += this.acceleration;
this.position += this.velocity;
}
I would suggest using the simpler method until you understand exactly how timing works and why you need to implement it.
It looks like this line is at fault:
this.position.Y -= (float)(30.0 + ((1.2) * elapsedAirTime)*elapsedAirTime);
I think you will find that this updates the sprites position quicker than you imagine, the sprite will move 330 pixels up the screen in 10 updates (assuming Game.IsFixedTimeStep == true) that is 1 tenth of a second realtime
It is likely that this is just updating so quickly that you don't get a change to see it rise before the && position.Y < 3 condition kicks in and changes the playerState.
It looks like you are trying to say - jump at a rate of x pixels per second for upto 8.5 seconds so long as space is held.
What you need for that is to change the calculation to this.position.y -= (float) (30 * gameTime.ElapsedGameTime.TotalSeconds), this will give a very liner movement to the jump action but it will mean that the sprite jumps at exactly 30 pixels per second.
If Game.IsFixedTimeStep == true - which is the default - the update gets called 60 times per second so gameTime.ElapsedGameTime.TotalSeconds is going to be about 0.1 every update. If something happens to cause an update to skip (rendering issues for example) then update will get delayed and gameTime.ElapsedGameTime.TotalSeconds may be 0.3 (the 2 updates skipped) but the formular still works out the correct jump rate.