I have these functions:
void apply_gravity(double delta){
velocity.y+=9.8*100*delta;
}
void move_body(double delta){
location.x+=velocity.x*delta;
location.y+=velocity.y*delta;
}
void processPhysics(double delta){
apply_gravity(delta);
move_body(delta);
if(location.y>=SCREENY){
velocity.y=-coefficient_of_restitution*velocity.y;
}
}
delta is the time elapsed between two calls to the function.
velocity contains two parts, x and y which represent the increment per second to the location's x and y.
coefficient of restitution represents how much of the original velocity the body retains after collision.
Basically, here is what I want this code to do:
Accelerate downwards by 9.8*100px per second. When the body goes below a limit (SCREENY px), it should bounce back, just like if it is hitting a floor. The collision should be perfectly elastic, and for now, SCREENY does not vary at all.
The code perfectly works for most of the times. BUT, sometimes, instead of "bouncing", the body just passes through the "floor". Basically, it seems that velocity.y does NOT negate even when the body crosses SCREENY. The comparision (location.y>=SCREENY) just fails at random times. Sometimes it works, sometimes it doesn't.
That should not happen.
What is going wrong here?
Your object is still allowed to pass through the floor, only updating the velocity. However, in the next frame, the object may still be in the floor, and have its velocity negated a second time (and third, and fourth). I imagine it would look like it gets stuck and vibrates a bit of a distance within the wall. If the object is within the wall, you should instead calculate where it should be if it had bounced, and update the position AND velocity accordingly. Not just the velocity.
A possible solution could look like:
void apply_gravity(double delta){
velocity.y += 9.8 * 100 * delta;
}
void move_body(double delta){
location.x += velocity.x * delta;
location.y += velocity.y * delta;
}
void processPhysics(double delta){
apply_gravity(delta);
move_body(delta);
if(location.y > SCREENY) // change to > from >= (eliminates edge case)
{
location.y = SCREENY - coefficient_of_restitution * (location.y - SCREENY); // update position as well, accounting for loss of velocity due to bounce
velocity.y = -coefficient_of_restitution * velocity.y;
}
}
The above solution will yield an error if the object is moving greater than one screen length in a single physics tic, so be careful about high speeds. You'll need a slightly more complicated algorithm to handle those situations.
Related
I am trying to write simple loop with fixed delta time used for physics and interpolation before rendering the state. I am using Gaffer on games tutorial on fixed timesteps and I tried to understand it and make it work.
float timeStep = 0.01;
float alpha = 1.0;
while (isOpen()) {
processInput();
deltaTime = clock.restart(); // get elapsed time
if (deltaTime > 0.25) { deltaTime = 0.25; } // drop frame guard
accumulator += deltaTime;
while (accumulator >= timeStep) {
// spritePosBefore = sprite.getPosition();
accumulator -= timeStep;
// sprite.move(velocity * timeStep, 0);
// spritePosAfter = sprite.getPosition();
}
if (accumulator > timeStep) { alpha = accumulator / timeStep; } else { alpha = 1.0; }
// sprite.setPosition(Vector2f(spritePosBefore * (1 - alpha) + spritePosAfter * alpha));
clear();
draw(sprite);
display();
}
Now, everything looks good. I have fixed timestep for physics, draw whenever I can after physics are updated and interpolate between two positions. It should work flawless but I can still see sprite stuttering or even going back by one pixel once in a while. Why does it happen? Is there any problem with my code? I spent last two days trying to understand game loop which would ensure me flawless motions but it seems like it doesn't work as I thought it will. Any idea what could be improved?
You should remove the if statement and always calculate alpha; the if statement will never be executed as the condition is always false after the while loop is exited!
After the loop the accumulator will be between 0 and timeStep so you just end up drawing the latest position instead of interpolating.
I don't think the way you do it is necessarily wrong but it looks a bit overcomplicated. I don't understand exactly what you're trying to do so I'm just going to share the way I implement a "fixed time step" in my SFML applications.
The following is the simplest way and will be "good enough" for most applications. It's not the most precise though (the can be a little error between the measured time and the real time) :
sf::Clock clock;
sf::Event event;
while (window_.isOpen()) {
while (window_.pollEvent(event)) {}
if (clock.getElapsedTime().asSeconds() > FLT_FIXED_TIME_STEP) {
clock.restart();
update(FLT_FIXED_TIME_STEP);
}
render();
}
And if you really need precision, you can add a float variable that will act as a "buffer" :
sf::Clock clock;
sf::Event event;
float timeBeforeNextStep = 0.f; // "buffer"
float timeDilation = 1.f; // Useful if you want to slow or speed up time ( <1 for slowmo, >1 for speedup)
while (window_.isOpen()) {
while (window_.pollEvent(event)) {}
timeBeforeNextStep -= clock.restart().asSeconds() * timeDilation;
if (timeBeforeNextStep < FLT_FIXED_TIME_STEP) {
timeBeforeNextStep += FLT_FIXED_TIME_STEP; // '+=', not '=' to make sure we don't lose any time.
update(FLT_FIXED_TIME_STEP);
// Rendering every time you update is not always the best solution, especially if you have a very small time step.
render();
}
}
You might want to use another buffer for rendering (if you want to run at exactly 60 FPS for example).
I'm trying to make my asteroid keep moving once I press a button.
void Ship::applyThrust()
{
_v.setX(_v.getX() - sin(2 * M_PI * (_angle / 360)) * 2.5);
_v.setY(_v.getY() + cos(2 * M_PI * (_angle / 360)) * 2.5);
}
This is the formula I have to help my ship move.
The _v.setX and _vsetY updates the X and Y position
The M_PI is just 3.14.
The _angle is how much of a rotation I set with my left and right arrow keys and
The 2.5 is how many frames per I want it to move
My ship is moving just fine, the problem is that I'm trying to simulate space inertia and have my ship continue moving.
Any ideas on how would be the logic for this?
Thanks,
Inside of your game loop, you'll need a function that updates the position of the ship based on its x and y velocity. You're close in getting the new x and y coordinates of your ship, but you don't account for the velocity of the ship. When you apply thrust, get the x and y components of your velocity, not your new position. You'll have an additional function to update position which should be called within the game loop at a timed interval - e.g. every time you update the frame. So your applyThrust function should actually update your ship's velocity. That means you'll need to add variables to your Ship class for your ship's position and your ship's velocity if you don't already have them. I'm breaking out the components of position and velocity for simplicity, but you'll probably want to store them in a vector for clarity:
class Ship
{
private float xpos, ypos; // current position of your ship
private float velocityX, velocityY; // current velocity of your ship
}
Then, when you apply thrust, you change the velocity, and remember that applyThrust is only called when the thrust button is pushed, not every frame as the position update is:
void Ship::applyThrust()
{
/*
Assume 1 total unit of thrust is applied each time you hit the thrust button
so you break down the velocity into x and y components depending on the direction
your ship is facing.
*/
// thrustUnit is how much thrust each thrust button press gets you, it's arbitrary
// and can be the 2.5 multiplier you're using in your question
float thrustUnit = 1.0;
// now get the x and y thrust components of velocity based on your ship's direction
// assuming 0 degrees is pointing to the right
float newVX, newVY = 0.0;
newVX = thrustUnit * cos(_angle);
newVY = thrustUnit * sin(_angle); // radian conversion left out to save space
// Update your ship's velocity
updateVelocity(newVX, newVY);
}
updateVelocity will look something like: (note that the velocity is additive so it continues to drift unless a thrust is applied in the opposite direction - the same as if it were in a frictionless medium such as space)
void Ship::updateVelocity(newVX, newVY)
{
// update the x component
velocityX += newVX;
velocityY += newVY;
}
So now you'll also need an updatePosition function that takes into account your ship's velocity. This gets called with each frame update:
void Ship::updatePosition()
{
// add the x component of velocity to the ship's x position
// assuming xpos and ypos are variables in the Ship class for tracking position
xpos += velocityX;
ypos += velocityY;
}
Now the position of the ship changes incrementally according to each velocity component at each frame update. You can also make thrustUnit a member variable to allow for power-ups that can either boost or decrease your thrust component for your ship's speed and being able to control it better with a smaller thrustUnit or giving it hyperspeed with a large thrustUnit.
Good luck with your game!
So I have two separate detections for collision for both the player paddle and the enemy paddle and in the player paddle I have commented out what seems to be the math calculation needed to bounce a ball based on where it hit. The code that isn't commented simply bounces the ball around the screen as you would expect when you reverse the velocity (it's velocity is a float value) on each bounce. So for starters, is the commented out formula actually the correct formula, and if so does it need to be applied to both xSpeed and ySpeed when it collides with a paddle?
When I saw this formula it took BallAngle or PaddleAngle as an argument (I can't remember to be honest). Now if this IS the correct formula, how do I even get that angle in the first place? I'm just using integer's in there (for testing) because I don't know how to get the angle.
if (ball.getPosition().x < (player.getPosition().x) //PLAYER COLLISION DETECTION
&& (ball.getPosition().y + (ball.getRadius() * 2)) >= player.getPosition().y
&& ball.getPosition().y <= (player.getPosition().y + player.getSize().y))
{
xSpeed = -xSpeed;
//xSpeed = xSpeed*cos(91);
//ySpeed = ySpeed*sin(90);
ball.move(xSpeed, ySpeed);
}
if (ball.getPosition().x > enemy.getPosition().x - 30 //ENEMY COLLISION DETECTION
&& (ball.getPosition().y + (ball.getRadius() * 2)) >= enemy.getPosition().y
&& ball.getPosition().y <= (enemy.getPosition().y + enemy.getSize().y))
{
xSpeed = -xSpeed;
ball.move(xSpeed, ySpeed);
}
cos(91) is just under -1, presumably it is meant to reverse the X direction and slow it down due to the impact. sin(90) is almost one so again is presumably meant to slow it down a bit.
Of course these functions are actually quite expensive to calculate so you may as well just hard code the values.
const float xBounce = -0.99436746092
const float yBounce = 0.8939966636
...
xSpeed *= xBounce;
ySpeed *= yBounce;
ball.move(xSpeed, ySpeed);
I feel this is a difficult question to articulate, so I have illustrated on this graph (I am using SDL in C++).
Each square represents a pixel on the screen, I want the red pixel to move at the same speed regardless of direction.
If the speed is 8 pixels/sec then after 1 second:
If the user input is right OR down the pixel will arrive at the position marked in blue
If the user input is right AND down it will arrive at the position marked green.
In both cases the pixel has been displaced by 8 pixels, however.. The euclidean distance between red and blue = 8.00 and red and green = 11.31. I want the pixel to arrive at yellow instead.
So I have tried to correct this by declaring a constant speed, then I divide this by the actual displacement, giving me a number I use to multiple the X and Y coordinates and travel back along the trajectory, limiting my speed.
The code looks sorta like this (I have commented the area of interest):
float velX = 0, velY = 0, currentX, currentY;
int time = 0, speed = 300;
//Events
void handleInput(){
if( event.type == SDL_KEYDOWN ){
switch( event.key.keysym.sym ){
case SDLK_UP: {velY -= speed;} break;
case SDLK_DOWN: {velY += speed;} break;
case SDLK_LEFT: {velX -= speed;} break;
case SDLK_RIGHT: {velX += speed;} break;
}
}
else if( event.type == SDL_KEYUP ){
//do the opposite
}
}
//Logic
void move(){
//float dist = sqrt( (velX*velX) + (velY*velY) );
//
//if(dist > 0){
// velX *= speed / dist;
// velY *= speed / dist;
//}
currentX += velX * (get_delta_ticks(&time) / 1000.f);
currentY += velY * (get_delta_ticks(&time) / 1000.f);
set_delta_ticks(&time);
}
//Render
void Player::render(){
apply_surface(currentX, currentY, spriteSheet, screen, ¤tClip);
}
So here is my question, I am new to programming games and I'm unsure if this is the CORRECT way to be doing movement.. It seems a bit inefficient in ways, should I be trying to deduce the position based on an angle and the length of the hypotenuse instead? I don't know very much about trigonometry but of course I am keen to learn.
Separate the logical position from the display position.
The logical position will probably need to use floating-point coordinates, and you'll round them to integer pixel coordinates for the display position. You can even do anti-aliasing with this if you want to smooth the movement.
So:
right would have logical unit vector (x,y)=(1.0,0.0)
down would have logical unit vector (x,y)=(0.0,-1.0)
down+right would have logical unit vector (x,y)=(1/sqrt(2),-1/sqrt(2))
every 1/8th of a second, you add the unit vector to your current logical location, and select which pixel to draw. Obviously you can choose different units and update frequencies, but this will give the numbers you asked for.
You need to get the speed in a 2D Space. To get it you have to do a sqrt with both speeds.
curSpeed = sqrt( ( velX * velX ) + (velY * velY ) );
The point is: You counted 8-x and 8-y key press events, which lead to a shortest distance from the origin of v=sqrt(8*8+8*8)=11.31, exactly as you observed.
You should be aware, that, within the time you are measuring, either 8 (only x OR y) or 16 (x plus y) key press events might be sampled, resulting in different "speeds", where speed=number_of_key_events/period_of_time
If you want to travel to the "yellow" spot, there should be only 6 X key press events plus 6 Y key press events in the same period of time in which you sampled the 8 key presses in one of the basic directions.
So there is nothing wrong with your code, and, as the other posters pointed out, your euclidian speed can be calculated using the euclidian distance divided by the sampling period, resulting in v=8 or v=11.31, respectively.
I would start with different user controls: namely absolute speed and direction.
Given speed velAbs and the angle theta, you have
velX = velAbs * cos(theta);
velY = velAbs * sin(theta);
When updating the position, it is typically most convenient to decompose the absolute speed in its X and Y components, update the X and Y positions for the given time interval dt
currentX = velX * dt;
currentY = velY * dt;
whereas for collision impact computations the absolute speed is more relevant.
This will avoid your yellow/green problem because maximum throttle in both the X and Y directions will get you to green. Just let the user set the throttle from 0 to 8 and also set a direction, then you will get to yellow or blue.
Well, it looks like most people forgot about analog input...
Anyway, It should work like this:
velX, velY are floats within [-1.0..1.0] range.
In case of digital input (keyboard, dpad), pressing "left" sets velX to -1, pressing "right" sets velX to 1, etc.
However, if you use analog stick, you put floating point values, where velX == 1.0 corresponds to rightmost position of analog stick, velX == -1.0 corresponds to leftmost position, and so on.
maxSpeed is maximum game movement speed, also float.
With all this in mind, you could calculate next position of object like this:
void move(){
float curVelX = velX, curVelY = velY;
float moveSquared = (curVelX*curVelX + curVelY*curVelY);
if (moveSquared > 1.0f){
float d = sqrtf(moveSquared);
curVelX /= d;
curVelY /= d;
}
currentX += curVelX * maxSpeed * (get_delta_ticks(&time) / 1000.f);
currentY += curVelY * maxSpeed * (get_delta_ticks(&time) / 1000.f);
set_delta_ticks(&time);
}
It seems a bit inefficient in ways,
Look, you have ONE object. When you'll have few hundreds of thousands of them, then you can start worrying about efficiency.
should I be trying to deduce the position based on an angle and the length of the hypotenuse instead?
If your object is torpedo-like and can slowly turn left/right and accelerate/decelerate (you can steer it a bit and make it go faster/slower), then you probably need to store movement direction and linear movement speed.
If your object is some kind of flying orb or rolling ball that can go in any direction it wants, then you should use method similar to the one I described. Have separate velocity for x/y and limit maximum linear velocity using sqrtf.
My particle system's physics update function seems to be incorrect. I'm aiming for all the particles to be attracted towards the mouse.
The particles move towards the mouse pointer just as expected, until they go very near. When they are near, they speed up so much, that they fly far away from the pointer and never return.
Here's the update function:
void updateParticle(particle& p,double time){
const double G=0.000000000066726;
const double POINTERMASS=1000000000000;
double squareDistance=pow(p.coords.x-pointerDevice.x,2)+pow(p.coords.y-pointerDevice.y,2)+pow(p.coords.z-pointerDevice.z,2);
if(squareDistance<0.001)
squareDistance=0.001;//to fix the possible division by zero
coords_3d_f accelerationVector={p.coords.x-pointerDevice.x,p.coords.y-pointerDevice.y,p.coords.z-pointerDevice.z};
accelerationVector=vector_scalar_multiplication(vector_unit(accelerationVector),((G*POINTERMASS)/squareDistance));
accelerationVector=vector_scalar_multiplication(accelerationVector,time);
p.velocity=vector_addition(p.velocity,accelerationVector);
p.coords.x-=p.velocity.x*time;
p.coords.y-=p.velocity.y*time;
p.coords.z-=p.velocity.z*time;
}
When the squareDistance is constant, the program looks OK, but I know it's false.
So, what am I doing wrong?
Force is inversely proportional to the square of the distance, so as the distance approaches 0, force (and acceleration) approach infinity. In other words, if the particles get very close, they also get very fast.
If you want to be physically accurate, make your pointer-object have a finite size, so that particles bounce off of it.
If you don't need to be accurate, you can make the force decrease when the particles are very close.
It's very simple: when particles get in touch with the mouse pointer squareDistance becomes 0 and produces undefined behavior for your particles by ((G*POINTERMASS)/squareDistance) because dividing by zero is illegal.
This might work better for you:
if (squareDistance >= 1.0) // 1.0 is the zero tolerance for your context of pixel distances
{
// proceed normally
accelerationVector=vector_scalar_multiplication(vector_unit(accelerationVector),((G*POINTERMASS)/squareDistance));
accelerationVector=vector_scalar_multiplication(accelerationVector,time);
}
else
{
// no acceleration
accelerationVector=/*{0, 0}*/;
}
When your particle gets very close to the mouse pointer the particle is going to have a very high velocity. When this velocity is multiplied by the time this is when the particle will jump very far away.
You can try to fix this by setting a maximum velocity.
Simulating the equation of motion involve the integration of a function at finite intervals, and so one can only approximate the function. This give rise to instability in the system. An easy and fast solution is to use a fixed-step verlet integration:
void integrate(particle& p, double t2, const particle& mouse)
{
// universal gravitational constant
const double G = 0.000000000066726;
// artificial drag
// set it to 1.0 to not have any drag
// set it to 0.0 to not have any momentum
const double drag = 0.99;
// get direction and distance between the particle and the mouse
dvec3 dir = p.pos - mouse.pos;
double dist2 = dot(dir, dir);
double dist = sqrt(dist2);
dir /= dist;
// calculate relative acceleration vector
dvec3 a = -dir * G * (p.mass + mouse.mass) / dist2;
// verlet integration
dvec3 tmp = p.pos;
p.pos += (p.pos - p.prev_pos) * drag + a * t2;
p.prev_pos = tmp;
}
void update(particle& p, double elapsed, const particle& mouse, double& accumulator)
{
// fixed timestep (arbitrary)
const double timestep = 1.0 / 120.0;
const double timestep2 = timestep * timestep;
// "accumulate" time
accumulator += elapsed;
// "consume" time
while(accumulator > timestep)
{
// perform integration
integrate(p, timestep2, mouse);
accumulator -= timestep;
}
}
Note: It use the GLM math library for clarity.