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!
Related
I've been raking my brain for the last day on how to calculate a full rotation with how to count a full rotation of an object along the X axis thats using the base circular drive from SteamVR.
I thought a simple 3d cube, with the mesh turned off in the the path of the rotation with collision code on it would be a barebone way of doing it, but it doesn't even seem to be registering the detection when the object hits the placed cubes, and i know its not because of me being stupid, as its recycled code from a working part of the project.
Below i have a small piece of code that basically detects when the object has reached the end of the rotation, and then increments the Count by one.
My main problem is that sometimes it manages to clock more than once, and if you can find the right spot, you can just keep it there and it'll keep on adding the count up by. Im wondering how i can stop it and increment only by one, until another full rotation has been made?
EDIT: to be more clear in case there is any confusion, Once the angle is clocked in between 359 and 360, i want it to increment once, whereas currently if you get the angle to sit anywhere in between 359-360 it will carry on adding one to the rotation count, despite no full rotation having been made, so im trying to figure out how to make my code only increment once, and once it does increment once it resets the position to zero, so therefore no more Increments can happen. It's a crank mechanism in VR, along the X axis.
Any help is appreciated, Thanks!
float Test;
float RotationCount = 0;
// Start is called before the first frame update
void Start()
{
// Test = transform.localRotation.eulerAngles.x;
}
// Update is called once per frame
void Update()
{
if (Test > 359 && Test < 360)
{
Debug.Log("Clocked");
count();
}
else
{
// Debug.Log("Nope");
}
if (Test == 0)
{
Debug.Log("Yes");
}
Test = transform.localRotation.eulerAngles.x;
}
void count()
{
RotationCount++;
}
sometimes it manages to clock more than once, and if you can find the right spot, you can just keep it there and it'll keep on adding the count up by.
well in
if (Test > 359 && Test < 360)
{
Debug.Log("Clocked");
count();
}
what happens if your angle is e.g. 359.5?
It is very difficult to just take a current rottaion and know whether it was turned more or less than a certain angle.
I'ld rather store the last rotation, compare it to the current one and add the difference to a variable. Than if the variable exceeds 360 a full rotation was done. Since I also don't like to calculate anything with Quaternion and eulerAngles, way simplier is to use the methods provided by Vector3.
For the local rotation around X (== transform.right) I would use the angle between the current and the last transfrm.up vector.
Something like
public class RotationCheck : MonoBehaviour
{
public int RotationCount;
public float rotatedAroundX;
public Vector3 lastUp;
public UnityEvent On3TimesRotated;
private void Awake()
{
rotatedAroundX = 0;
// initialize
lastUp = transform.up;
}
private void Update()
{
var rotationDifference = Vector3.SignedAngle(transform.up, lastUp, transform.right);
rotatedAroundX += rotationDifference;
if (rotatedAroundX >= 360.0f)
{
Debug.Log("One positive rotation done", this);
RotationCount++;
rotatedAroundX -= 360.0f;
}
else if (rotatedAroundX <= -360.0f)
{
Debug.Log("One negative rotation done", this);
RotationCount--;
rotatedAroundX += 360.0f;
}
// update last rotation
lastUp = transform.up;
// check for fire the event
if (RotationCount >= 3)
{
On3TimesRotated?.Invoke();
RotationCount = 0;
}
}
}
You can use a UnityEvent to get the same thing the Button uses for onClick so you can reference callbacks there via the inspector.
BUT if you don't care about the single rotations but actually only wnat the final RotationCount >= 3 I would actually use
private void Update()
{
var rotationDifference = Vector3.SignedAngle(transform.up, lastUp, transform.right);
rotatedAroundX += rotationDifference;
RotationCount = Mathf.RoundToInt(rotatedAroundX / 360.0f);
// update last rotation
lastUp = transform.up;
// check for fire the event
if (RotationCount >= 3)
{
On3TimesRotated?.Invoke();
RotationCount = 0;
rotatedAroundX = 0;
}
}
which directly reduces the value by one if rotated under the 360 mark instead of waiting for a full negative rotation
*as you can see instead of reaching 3 the RotationCount is reset to 0. This is where the On3TimesRotated event is/would get fired.
I have a simple waypoint system. It uses a transform of arrays that act as the bucket what holds the waypoint values.
I use this waypoint system to move a camera throughout a scene by moving towards one point to another. The scene is not big - so everything is close to each other.
The camera moves from one position to another by button click/press. This works fine.
void Start()
{
//Sets the Camera to the first point
Camera = GameObject.Find("Main Camera");
Camera.transform.position = patrolPoints[0].position
currentPoint = 0;
}
//Fixed Update seems to work better for LookAt
void FixedUpdate()
{
//Looks at initial Target
Camera.transform.LookAt(TargetPoints[0]);
if (click == true)
{
Camera.transform.position = Vector3.MoveTowards(Camera.transform.position, patrolPoints[currentPoint].position, moveSpeed * Time.deltaTime);
//Camera.transform.rotation = Quaternion.Slerp(Camera.transform.rotation, patrolPoints[currentPoint].transform.rotation, Time.deltaTime);
Camera.transform.LookAt(TargetPoints[currentPoint]);
}
}
public void onNextClick()
{
if (currentPoint >= patrolPoints.Length)
{
currentPoint = 0;
}
if (Camera.transform.position == patrolPoints[currentPoint].position)
{
currentPoint++;
click = true;
}
}
I am having difficulty with one aspect of the transform that I haven't talked about yet. That is the rotation.
I have used lookAt for setting up the target of the first look at point and that works. However when it runs to the next target in the look at array - the change is sudden.
I have tried an Slerp in the shot as well - and this works when the waypoint has been placed in the appropriate rotation value - and the value Slerps from one position to the next. However, the timing isn't quite aligning up yet. Some position transitions get there quicker - meaning the rotation is trying to get caught up / while others are lagging behind.
I have tried getting the distance between the current waypoint and the next waypoint and treating that as an overall percentage in the update relative to the current position of the camera - I believe this could help work out how far the rotation should be orientated given the position update.
However, I am somewhat lost on it. Many examples suggest looking at iTween - and I wouldn't imagine this would work - however, I want to get into the math a bit.
Can anyone put me in the right direction?
Looks like the Lerp for Position and a Slerp for Rotation done the trick.
MoveTowards wasn't playing ball with a Slerp on rotation - so I believe the timings weren't aligning.
if (click == true)
{
Camera.transform.position = Vector3.MoveTowards(Camera.transform.position, patrolPoints[currentPoint].position, moveSpeed * Time.deltaTime);
Camera.transform.rotation = Quaternion.Lerp(Camera.transform.rotation, patrolPoints[currentPoint].rotation, moveSpeed * Time.deltaTime);
I've been led to believe the lerp values work like a percentage of such - so the same input value works for it.
Finally I used a range on the distance between current position and update on the click - this helped speed up the button click.
if (Vector3.Distance(Camera.transform.position, patrolPoints[currentPoint].position) < PositionThreshold)
{
currentPoint++;
click = true;
}
Thank you for your time.
I have stuck in this simple problem but unable to understand that why i am unable to control it.
I have these line of code which is displaying my canvas object in front of my player(camRotationToWatch object name in code) at certain rotation of the player.
if (camRotationToWatch.transform.localEulerAngles.x >= navigationCanvasXMinmumLimit && camRotationToWatch.transform.localEulerAngles.x <= navigationCanvasXMaximumLimit)
{
if (!navCanvasHasDisplay)
{
navigationCanvas.SetActive(true);
//Debug.Log(camRotationToWatch.transform.forward);
Vector3 navCanvas = camRotationToWatch.transform.position + camRotationToWatch.transform.forward * navCanvasDisplayDistanceFromCam;
navCanvas = new Vector3(navCanvas.x, 2f, navCanvas.z);
navigationCanvas.transform.position = new Vector3(navCanvas.x, navCanvas.y, navCanvas.z);
navigationCanvas.transform.rotation = camRotationToWatch.transform.rotation;
navCanvasHasDisplay = true;
}
}
else
{
//navigationCanvas.SetActive(false);
if (locationPanel.activeSelf == false && infoPanel.activeSelf == false) {
navigationCanvas.SetActive(false);
navCanvasHasDisplay = false;
}
}
This code is actually work fine when camRotationToWatch object rotate from down to up and Canvas show at correct position but as I try to to rotate camRotationToWatch from up to down it display(active) Canvas at very top position. How can I restrict canvas to show at same position (No matter player rotate from up to down or down to up) but display on front of the player object?
Kinda hard trying to figure out what exactly you want to do. But this did what I think you where trying to do
public GameObject follow; // The object you want to rotate around
public float distance = 2; // Distance to keep from object
private void Update() {
Vector3 forward = follow.transform.forward;
forward.y = 0; // This will result in Vector3.Zero if looking straight up or down. Carefull
transform.position = follow.transform.position + forward * distance;
transform.rotation = Quaternion.LookRotation(forward, Vector3.up);
}
I believe your "unexpected behavior" is due to the use of euler angles since they are not always entirely predictable. Try using Quaternions or Vector3.Angle() when possible.
If you want to limit the angle (say... if looking down or up more than 45° disable the object) you could do the following:
if (Vector3.Angle(forward, follow.transform.forward) > maxAngle) { ... }
This probably isn't a complete answer but something that might help. This line:
Vector3 navCanvas = camRotationToWatch.transform.position + camRotationToWatch.transform.forward * navCanvasDisplayDistanceFromCam;
You are creating a position at a fixed distance from camRotationToWatch. But if that object is looking up or down, that position is not horizontally at navCanvasDisplayDistanceFromCam. If it's looking straight up, then this position is in fact directly above.
So when you do this to set a fixed vertical height:
navCanvas = new Vector3(navCanvas.x, 2f, navCanvas.z);
you aren't getting the distance from camRotationToWatch that you think you are.
Instead of using camRotationToWatch.transform.forward, create a vector from it and zero out the Y component, and normalize before using it to offset the position. (You will need to watch out for zero length vectors with that though).
Whether that fixes your problem or not, it's too hard to guess but it should help improve your results some.
EDIT: Here is an example of how you can avoid the issue with the canvas being too close:
Vector3 camForward = camRotationToWatch.transform.forward;
camForward.y = 0;
if (camForward.magnitude == 0)
{
//TODO: you'll need to decide how to deal with a straight up or straight down vector
}
camForward.Normalize();
//Note: now you have a vector lying on the horizontal plane, pointing in
//the direction of camRotationToWatch
Vector3 navCanvas = camRotationToWatch.transform.position + camForward *
navCanvasDisplayDistanceFromCam;
//Note: if you want the canvas to be at the player's height (or some offset from),
//use the player's y
navCanvas = new Vector3(navCanvas.x, Player.transform.y, navCanvas.z);
navigationCanvas.transform.position = navCanvas;
Again, this might not fix all your issues but will help to ensure your canvas lies at a set distance horizontally from the Player and will also compensate for the player's up and down motion.
I've been working on a kind of spaceship game in which you take a lander and take off from the ground and fly up as high as you can get until you come crashing down. The problem I've been running into is that the camera that I've tried to implement doesn't keep the lander in the center, the lander eventually gets faster then the camera and goes off screen. I don't understand why and I've tried everything to make it work. I created a test case in which I used the camera on a simple program where a sprite moved in four directions. That one worked fine, but when I implemented the camera on my main game, it doesn't work correctly. Any help would be appreciated.
This is the camera class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace WindowsGame4.Main
{
class Camera
{
public Matrix transform;
Viewport view;
Vector2 centre;
public Camera(Viewport newView)
{
view = newView;
}
public void Update(GameTime gameTime, Game1 lander, Lander _lander)
{
centre = new Vector2(_lander.Position.X + (lander.lander.Width / 2) - 455, _lander.Position.Y + (lander.lander.Height/2)-910);
transform = Matrix.CreateTranslation(new Vector3(-centre.X - _lander.Velocity.X, -centre.Y - _lander.Velocity.Y, 0));
}
}
}
This is the main update method in Game1:
// Creates the handler to allow the use of the keyboard inputs
KeyboardState keyboard = Keyboard.GetState();
// Sets up the launch position to allow for scoring and height determining
startPos = 700f;
// Mainly for debug, allows for leaving the game whenever desired
if (keyboard.IsKeyDown(Keys.Escape))
{
Exit();
}
// The logic for the thruster, checks for if the space key is down and if
// the lander has any fuel remaining. If there is no fuel the thrusters
// will not work.
if (keyboard.IsKeyDown(Keys.Space) && !empty)
{
_lander.Accelerate(_thruster.GetAcceleration(), gameTime.ElapsedGameTime);
// Turns on the thruster
_thruster.Active = true;
// Tells the program that the lander has taken off, allowing the gravity to kick in
landed = false;
// Keeps track of the amount of fuel you have
if (_lander.Fuel > 0)
{
// Subtracts 1 from the fuel for every second that the 'SPACE' key is down
_lander.Fuel--;
}
else if (_lander.Fuel <= 0)
{
empty = true;
endPos = _lander.Position.Y;
}
}
if (keyboard.IsKeyUp(Keys.Space) || empty)
{
// Turns off the thruster as long as the player is not pressing 'SPACE'
_thruster.Active = false;
}
// This is the logic structure for the landing portion of the game, it sets up a
// level area in which the player can land on, which in turn then checks the Position.X value
// to see how many points to give based on their close proximity to the landing site.
// ** Could possibly add depth or different levels based on location, like landing on a mountain
// ** Or landing in the bottom of a crater
if (_lander.Position.Y >= 800)
{
landed = true;
// If you have died or Failed a mission 3 times then the game is over
if (_lander.Lives == 1)
{
currentGameState = GameState.GameOver;
}
// As long as you have a life left the game will continue
// This logic also is only applied if the player has landed on the ground
// ** Add a rotational fixer as to make the lander stand straight up as opposed to the way its oriented
// ** Upon landing
else if (_lander.Lives > 0)
{
// Sets the landers position to the current position that its landed at, thus stopping the gravity
// Resets the velocity, thus making sure it doesnt continue to fall
_lander.Position = new Vector2(_lander.Position.X, _lander.Position.Y);
_lander.Velocity = new Vector2();
// Sets up the first landing site, this particular one is the highset value landing site is almost right
// On top of the flag that is placed to indicate where to land.
// *** Will Not Use In Final Version, Will Substitue With Logic For Height And Cash Gained ***
if ((_lander.Position.X >= 600 && _lander.Position.X <= 650) || (_lander.Position.X <= 600 && _lander.Position.X >= 550))
{
// *** Will be implemented to display a "YOU HAVE FAILED THE MISSION" screen. ***
failure = false;
// This is for debugging purposes only, will change to be automatic once all functions are complete
if (keyboard.IsKeyDown(Keys.Enter))
{
// (1) -- Resets the position of the lander back to its original position and Velocity
// (2) -- Add 150 points to the score based on the location that the lander set down at
// (3) -- Continues on to the next level
_lander.Position = new Vector2(graphics.PreferredBackBufferWidth / 2, 100); // (1)
_lander.Velocity = new Vector2(); // (1)
_lander.Score += 150; // (2)
currentGameState = GameState.Level2; // (3)
}
}
// Sets up the second landing site, this particular one is the second highest value landing site that is
// A large circle surrounding the landing site above
else if ((_lander.Position.X >= 651 && _lander.Position.X <= 750 ) || (_lander.Position.X <= 549 && _lander.Position.X >= 450))
{
// *** Will be implemented to display a "YOU HAVE FAILED THE MISSION" screen. ***
failure = false;
// This is for debugging purposes only, will change to be automatic once all functions are complete
if (keyboard.IsKeyDown(Keys.Enter))
{
// (1) -- Resets the position of the lander back to its original position and Velocity
// (2) -- Add 50 points to the score based on the location that the lander set down at
// (3) -- Continues on to the next level
_lander.Position = new Vector2(graphics.PreferredBackBufferWidth / 2, 100); // (1)
_lander.Velocity = new Vector2(); // (1)
_lander.Score += 50; // (2)
currentGameState = GameState.Level2; // (3)
}
}
// Sets up the final landing site, this particular one is the failing portion of the map that is
// A large area that encompasses the rest of the map.
else if ((_lander.Position.X >= 751 && _lander.Position.X <= 850) || (_lander.Position.X <= 449 && _lander.Position.X >= 0))
{
// You get no points so it does not only need to done once, it can be done as many times as needed
// And it will not change the data
_lander.Score = 0;
// *** Will be implemented to display a "YOU HAVE FAILED THE MISSION" screen. ***
failure = true;
// This is for debugging purposes only, will change to be automatic once all functions are complete
if (keyboard.IsKeyDown(Keys.Enter))
{
// (1) -- Resets the position of the lander back to its original position and Velocity
// (2) -- Takes away one of your lives as a result of the players failure to land correctly
_lander.Position = new Vector2(graphics.PreferredBackBufferWidth / 2, 100); // (1)
_lander.Velocity = new Vector2(); // (1)
_lander.Lives--; // (2)
}
}
// This is just a loop that gives a visual representation of refueling before the next level begins.
for (double i = _lander.Fuel; i < 500; i++)
{
_lander.Fuel += .05;
}
}
}
// A very simple call to the Gravity and Lander classes that simulates gravity as long as the
// lander has not landed
else if (!landed)
{
_lander.Accelerate(_gravity.GetAcceleration(), gameTime.ElapsedGameTime);
}
// Moves the lander according to gravity calculated by the lander class
_lander.DoMovement(gameTime.ElapsedGameTime);
// Calculates the height achieved based off of starting height and ending height
height = startPos - endPos;
// This will rotate the lander when the keys are pressed down
// They will also check to make sure if it is landed or not
// If it's landed then it will not allow the lander to rotate.
if (keyboard.IsKeyDown(Keys.Left) && landed == false)
{
// (1) -- This will also change the angle of rotation for the thruster as to all for movement in
// That specific direction.
// Example: if you rotate to the left and turn on the thrusters you will starting moving
// to the left
rotation -= 0.1f;
_lander.Rotation = rotation;
_thruster.Rotation = rotation; // (1)
}
if (keyboard.IsKeyDown(Keys.Right) && landed == false)
{
// (1) -- This will also change the angle of rotation for the thruster as to all for movement in that specific direction.
// (2) -- This will also change the angle of rotation for the lander as to all for movement in that specific direction.
// Example: if you rotate to the right and turn on the thrusters you will starting moving
// to the right
rotation += 0.1f;
_lander.Rotation = rotation; // (2)
_thruster.Rotation = rotation; // (1)
}
// Calls the camera class to allow the screen to move with the player
camera.Update(gameTime, this, _lander);
If you guys need more code I can upload anything else you guys need
Why do you need to use a translation for a 2D camera? It's a very complicated way for something that can be done a lot simpler. A 2D camera in my opinion should just be a vector corresponding to the upper-left corner of the camera's viewport. Then just subtract the camera vector from any sprite position and it will move along with the camera.
So in your case, just set the camera vector to be at the center point minus half of the screen width and height:
Camera = SpriteCenter - new Vector2(GraphicsDevice.Viewport.Width / 2, GraphicsDevice.Viewport.Height / 2);
For every drawing call, subtract the Camera vector from the actual position of a sprite.
And there you go, your sprite will always be centered.
If you are doing something special that doesn't allow this to function, then I am unable to understand your actual question. I hope this helps.
I disagree with #Sarkilas, I think using a transformation will simplify your code, not needing to specify the camera each time.
You can use this code as a base for position, origin, zoom, and rotation.
return Matrix.CreateTranslation(new Vector3(-Position, 0.0f)) *
Matrix.CreateTranslation(new Vector3(-Origin, 0.0f)) *
Matrix.CreateRotationZ(Rotation) *
Matrix.CreateScale(Zoom, Zoom, 1) *
Matrix.CreateTranslation(new Vector3(Origin, 0.0f));
The issue is that the lander is not your origin, it should be your position, while the origin should be half of your viewport size like so
Position = lander.Position;
Origin = new Vector2(view.Width / 2, view.Height / 2);
You will probably have to add half your lander position back to the origin or position if it looks a bit off.
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.