I'm trying to implement jumping in my OpenGL game. My left and right 'velocity' and 'friction' is working well, but I'm not able to get my jump working in a nice arc. The player gets stuck in the air:
void keyboard() {
//Move Left
if (GetAsyncKeyState(keyA)) {
playerVelX -= 3;
}
//Move Right
if (GetAsyncKeyState(keyD)) {
playerVelX += 3;
}
//Jump
if (GetAsyncKeyState(keySpace)) {
playerVelY += 3;
}
}
void position() {
//Move on x and y axis
playerX += playerVelX;
playerY += playerVelY;
//Slow down respective axes
playerVelX *= friction;
playerVelY *= gravity;
}
while(gameRunning) {
keyboard();
position();
}
I think I've been looking at it too long. Does anyone have experience implementing gravity like this? Thanks
Your formula for applying gravity is wrong. Remember that F=m.a, v=v_0+ a*t, x=x_0 +v_0*t + 0.5*a*t^2. But the usual approach is to use Euler's method for numerical integration:
gravity=9.81;
velocity+=gravity*deltaT;
position+=velocity*deltaT;
In your code it would look like:
playerVelX *= friction;
playerVelY -= gravity;//If Y point up, so gravity acts against that.
playerX += playerVelX;
playerY += playerVelY;
If you want to have deterministic and stable simulation (which you should) then you need to have deltaTime - how much time passed since last update. See this excellent article Fix Your Timstep! . Also the code above applies the gravity constantly,even when not jumping, so be aware of that.
Summarizing all inaccuracies:
Obvious mistake m * dv/dt = -m * g so dv = -g * dt so playerVelY -= gravity * dt
In the jump, further repulsion is impossible, so
if (GetAsyncKeyState(keySpace) && (playerY <= groundLevel)) {
playerVelY += 3;
}
Gravity should not act if the player is on the surface.
if (playerY < groundLevel)
{
playerY = groundLevel;
playerVelY = 0;
}
More realistic to take into account the reaction force.
Your model playerVelX *= friction; corresponds to viscous friction. The player will never stop. Are you sure you need viscous, not dry friction?
dt should be defined as real time between updates otherwise, the result will depend on the current refresh rate. You need something like
void position() {
static double lastTime = getCurrTime();
double currTime = getCurrTime();
double dt = currTime - lastTime;
playerX += playerVelX * dt;
playerY += playerVelY * dt;
if (playerY < groundLevel)
{
playerY = groundLevel;
playerVelY = 0;
}
//Slow down respective axes
playerVelX *= exp(-friction * dt); // playerVelX *= (1 - friction * dt) for small dt
playerVelY -= gravity * dt;
lastTime = currTime;
}
Related
void update(RenderWindow& window)
{
if (Keyboard::isKeyPressed(Keyboard::W))
{
dy = -0.3;
}
if (Keyboard::isKeyPressed(Keyboard::A))
{
dx = -0.3;
}
if (Keyboard::isKeyPressed(Keyboard::S))
{
dy = 0.3;
}
if (Keyboard::isKeyPressed(Keyboard::D))
{
dx = 0.3;
}
x += dx;
y += dy;
dx = dy = 0;
EntitySprite.setPosition(x, y);
window.draw(EntitySprite);
}
When the movement is described in such a code, the player moves angularly: either to the left, or up, or to the right, or down, or diagonally at an angle of 45 degrees by pressing two buttons together, for example S and D. Can this angle be made smoother so that the movement itself was carried out not only to the left, right, diagonally, etc.? My knowledge of geometry is not enough here, so I ask for your help).
In your code, the user input directly modifies the player position. This is probably the reason why the player movement looks so abrupt to you. Technically, in your code, the user input is what is determining the player velocity at any given moment.
A more realistic approach would be to let the player have a velocity property instead –
that represents the player position's rate of change – and then have the player position updated only through this velocity, not directly from the user input. Instead, the velocity would be what is directly modified by the input, but not entirely determined by the current input as it will also depend on its previous value.
Following this approach, the user input is used to calculate the player acceleration in each call to update(). This acceleration – the rate of change of the velocity – is used to update the player velocity directly. Finally, the player velocity is, in turn, used to update the player position.
The following code implements this approach by introducing the velocity_ data member and the acceleration local variable:
void update(RenderWindow& window) {
sf::Vector2f acceleration;
// adjust this at will
const float dAcc = 0.3f;
// set acceleration
if (Keyboard::isKeyPressed(Keyboard::W))
acceleration.y -= dAcc;
if (Keyboard::isKeyPressed(Keyboard::A))
acceleration.x -= dAcc;
if (Keyboard::isKeyPressed(Keyboard::S))
acceleration.y += dAcc;
if (Keyboard::isKeyPressed(Keyboard::D))
acceleration.x += dAcc;
// update velocity through acceleration
velocity_ += acceleration;
// update position through velocity
x += velocity_.x;
y += velocity_.y;
// apply damping to the velocity
velocity_ = 0.99f * velocity_;
EntitySprite.setPosition(x, y);
window.draw(EntitySprite);
};
This way, the player possesses some kind of inertia, and its movement looks smoother.
Note that you may want to have some damping for the velocity as in:
velocity_ = 0.99f * velocity_;
This will resemble the effect of drag forces.
with help of that man I did it like this, but some better)
void update(RenderWindow& window)
{
float decceleration = 0.3;
if (Keyboard::isKeyPressed(Keyboard::W))
{
accelerationY -= decceleration;
}
if (Keyboard::isKeyPressed(Keyboard::S))
{
accelerationY += decceleration;
}
if (Keyboard::isKeyPressed(Keyboard::A))
{
accelerationX -= decceleration;
}
if (Keyboard::isKeyPressed(Keyboard::D))
{
accelerationX += decceleration;
}
dx += accelerationX;
dy += accelerationY;
speed = sqrt(dx * dx + dy * dy);
if (speed > maxSpeed)
{
dx *= maxSpeed / speed;
dy *= maxSpeed / speed;
}
x += dx;
y += dy;
dx *= 0.9;
dy *= 0.9;
accelerationX = 0;
accelerationY = 0;
EntitySprite.setPosition(x, y);
window.draw(EntitySprite);
}
I am trying to do the equivalent of multiplying the velocity by the time between frames. I would imagine that doing this for quaternions would be done by raising them to a power. I have code to rotate an object based on my mouse movements. It has a main loop running at one frame rate and a physics loop running at a fixed frame rate. Here is the relevant part of the main loop:
glfwPollEvents();
Input::update();
window.clear(0,0,0,1);
rigidBody.angularVelocity *= glm::angleAxis(0.001f * Input::deltaMouse().x, glm::vec3(0,1,0));
rigidBody.angularVelocity *= glm::angleAxis(0.001f * Input::deltaMouse().y, glm::vec3(1,0,0));
if(Input::getKey(Input::KEY_A))
{
rigidBody.velocity -= float(Time::getDelta()) * glm::vec3(1,0,0);
}
if(Input::getKey(Input::KEY_D))
{
rigidBody.velocity += float(Time::getDelta()) * glm::vec3(1,0,0);
}
if(Input::getKey(Input::KEY_W))
{
rigidBody.velocity -= float(Time::getDelta()) * glm::vec3(0,0,1);
}
if(Input::getKey(Input::KEY_S))
{
rigidBody.velocity += float(Time::getDelta()) * glm::vec3(0,0,1);
}
if(Input::getKey(Input::KEY_LCONTROL))
{
rigidBody.velocity -= float(Time::getDelta()) * glm::vec3(0,1,0);
}
if(Input::getKey(Input::KEY_LSHIFT))
{
rigidBody.velocity += float(Time::getDelta()) * glm::vec3(0,1,0);
}
Here is the relevant part of the physics loop:
for(int i = 0; i < *numRigidBodies; i++)
{
rigidBodies[i].transform->getPos() += rigidBodies[i].velocity;
rigidBodies[i].transform->getRot() *= rigidBodies[i].angularVelocity;
}
rigidBodies[0].angularVelocity = glm::quat();
rigidBodies[0].velocity = glm::vec3();
This works fine, but when I try raising angular velocity to a power with glm::pow, the object rotates randomly and does not follow my mouse. I realize I could do this with a line of code like
rigidBodies[i].transform->getRot() *= glm::angleAxis((float)Time::getFixedDelta() * glm::angle(rigidBodies[i].angularVelocity), glm::axis(rigidBodies[i].angularVelocity));
but this seems needlessly complicated for the task. What is causing this issue, and how can I fix it?
Not sure exactly how to do it with the API you're using, but basically, you would use Quaternion::Slerp(). Slerp means "spherical linear interpolation".
Something like this(pseudocode) should work:
auto& rot = rigidBodies[i].transform->getRot();
auto goal = rigidBodies[i].angularVelocity * rot;
rot = rot.slerp(rot, goal, Time::deltaTime);
Edit:
I should note that this is not how I would approach this problem. I would just store the rotation around the X and Y axis as scalars and construct a new quaternion from them each frame.
Please excuse the sloppy pseudo code:
// previous x and y positions, could probably be set in MouseDown event
float lastX = ...;
float lastY = ...;
float xRotation = 0;
float yRotation = 0;
float rotationSpeed = 1.0;
void OnMouseMove(float x, float y) {
float dx = x - lastX;
float dy = y - lastY;
lastX = x;
lastY = y;
xRotation += dy * rotationSpeed * Time::deltaTime;
yRotation += dx * rotationSpeed * Time::deltaTime;
rigidBodies[i].transform->getRot() = eulerQuat(xRotation, yRotation, 0);
}
Turns out angular velocity is usually represented as a 3d vector where the direction is the axis and the magnitude is the angular speed. Replace this line of code:
rigidBodies[i].transform->getRot() *= rigidBodies[i].angularVelocity;
with this:
if(rigidBodies[i].angularVelocity != glm::vec3())
rigidBodies[i].transform->getRot() *= glm::quat(rigidBodies[i].angularVelocity * float(Time::getFixedDelta()));
and the physics system works as expected. The if check makes sure that angular speed is not 0.
The following is my timestep:
void step(float dt){
static double UPDATE_INTERVAL = 1.0f/60.0f;
static double MAX_CYCLES_PER_FRAME = 5;
static double timeAccumulator = 0;
timeAccumulator += dt;
if (timeAccumulator > (MAX_CYCLES_PER_FRAME * UPDATE_INTERVAL))
{
timeAccumulator = UPDATE_INTERVAL;
}
int32 velocityIterations = 3;
int32 positionIterations = 2;
while (timeAccumulator >= UPDATE_INTERVAL)
{
timeAccumulator -= UPDATE_INTERVAL;
this->world->Step(UPDATE_INTERVAL,
velocityIterations, positionIterations);
this->world->ClearForces();
}
}
While the game as such works great regardless of the framerate, it causes sprites to "tremble", or "stutter" even when the framerate is 60fps!
I think this is because each frame the sprite moves by a different amount, because each frame the number of times the while loop executes is different.
What is a better way to fix the time step ? I have read a number of articles on fixing the time step, and am very confused. The above "works" except for the stutter.
EDIT: Just to clarify, the tremble is very small! People with poor eyesight don't notice it, but it makes a game look very low budget if you look carefully. It makes the game look not smooth.
you don't clamp your timeAccumulator correctly:
timeAccumulator += dt;
if (timeAccumulator > (MAX_CYCLES_PER_FRAME * UPDATE_INTERVAL))
{
timeAccumulator = UPDATE_INTERVAL;
}
should be
timeAccumulator = std::min(dt + timeAccumulator, MAX_CYCLES_PER_FRAME * UPDATE_INTERVAL);
Concerning the stepping: you'll need to interpolate the last full step with the next full step according to the left-over in timeAccumulator. There was a tutorial on this by Allan Bishop though the original site is no longer reachable (blog.allanbishop.com). Here's how his smooth step (only that last one with the fractional step) looks like:
// for the remaining timeAccumulator < UPDATE_INTERVAL
fixedTimestepAccumulatorRatio = timeAccumulator / UPDATE_INTERVAL;
private function smoothStates():void
{
const oneMinusRatio:Number = 1.0 - fixedTimestepAccumulatorRatio;
//Makes it easier to wrap up the Box2D body in another object so we can track other variables.
for each (var body:Body in bodies)
{
var box2Dbody = body.box2DBody;
texture.position.x = fixedTimestepAccumulatorRatio * box2Dbody.GetPosition().x + oneMinusRatio * body.previousPosition.x;
texture.position.y = fixedTimestepAccumulatorRatio * box2Dbody.GetPosition().y + oneMinusRatio * body.previousPosition.y;
texture.rotation = box2Dbody.GetAngle() * fixedTimestepAccumulatorRatio + oneMinusRatio * body.previousAngle;
}
}
I am working on a Direct3D9 space simulator game in which I need to create a camera that holds the position and point of view of the player's spaceship. For the moment I limited my code just for moving backward and forward, up and down as well as strafing. Below is my code and it has a problem. Everything is wrapped in a class and all vectors are initialized to D3DXVECTOR3(0.0f, 0.0f, 0.0f) in the constructor, excepting the LocalUp( D3DXVECTOR3(0.0f, 1.0f, 0.0f) ) and LocalAhead( D3DXVECTOR3(0.0f, 0.0f, 1.0f) ) and the floats are set to 0.0f;
D3DXVECTOR3 Position, LookAt ,PosDelta, PosDeltaWorld, WorldAhead, WorldUp, LocalUp,
LocalAhead, Velocity;
D3DXMATRIX View, CameraRotation;
float SpeedX, SpeedY, SpeedZ;
void Update(float ElapsedTime)
{
SpeedX = 0.0f;
SpeedY = 0.0f;
if(IsKeyDown('A'))
{
SpeedX = -0.02f;
Velocity.x -= SpeedX;
}
if(IsKeyDown('D'))
{
SpeedX = 0.02f;
Velocity.x += SpeedX;
}
if(IsKeyDown('X'))
{
SpeedZ += 0.01f;
Velocity.z += SpeedZ;
}
if(IsKeyDown('Z'))
{
SpeedZ -= 0.01f;
Velocity.z -= SpeedZ;
}
if(IsKeyDown('W'))
{
SpeedY = 0.02f;
Velocity.y += SpeedY;
}
if(IsKeyDown('S'))
{
SpeedY = -0.02f;
Velocity.y -= SpeedY;
}
D3DXVec3Normalize(&Velocity, &Velocity);
PosDelta.x = Velocity.x * SpeedX;
PosDelta.y = Velocity.y * SpeedY;
PosDelta.z = Velocity.z * SpeedZ;
D3DXMatrixRotationYawPitchRoll(&CameraRotation, 0, 0, 0);
D3DXVec3TransformCoord(&WorldUp, &LocalUp, &CameraRotation);
D3DXVec3TransformCoord(&WorldAhead, &LocalAhead, &CameraRotation);
D3DXVec3TransformCoord(&PosDeltaWorld, &PosDelta, &CameraRotation);
Position += PosDeltaWorld;
LookAt = Position + WorldAhead;
D3DXMatrixLookAtLH(&View, &Position, &LookAt, &WorldUp);
}
The "D3DXMatrixPerspectiveFovLH" and "IDirect3DDevice9::SetTransform" function are called in another part of the application. As they are working fine I will no longer talk about them.
The problem is that whenever the Z-axis speed is quite big and I strafe and move laterally, separately or at the same time, the camera's Z-axis speed will decrease. Moreover, after the speed is almost 0 and then I press the key that increased the speed, the sense of the vector inverts, then comes back to normal. This also happens when changing the vector's sense at quite high speeds(e.g. Pressing X then immediately pressing 'Z'). Can anybody explain me why is this happening and how can I solve this problem?
I will also ask another question: how can I slowly decrease the strafe and Y-axis speed if no key is pressed? I want to have the inertia effect implemented in the game.
If there is anyone able to help me, please respond!
EDIT: NEW CODE:
void NewFrontiers3DEntityPlayer::OnFrameUpdate(float ElapsedTime)
{
State.SpeedX = 0.0f;
State.SpeedY = 0.0f;
if(IsKeyDown(State.Keys[CAM_STRAFE_LEFT]))
State.SpeedX = -0.02f;
if(IsKeyDown(State.Keys[CAM_STRAFE_RIGHT]))
State.SpeedX = 0.02f;
if(IsKeyDown(State.Keys[CAM_MOVE_FORWARD]))
{
State.SpeedZ += 0.01f;
}
if(IsKeyDown(State.Keys[CAM_MOVE_BACKWARD]))
{
State.SpeedZ -= 0.01f;
}
if(IsKeyDown(State.Keys[CAM_MOVE_UP]))
State.SpeedY = 0.02f;
if(IsKeyDown(State.Keys[CAM_MOVE_DOWN]))
State.SpeedY = -0.02f;
State.Velocity.x = State.SpeedX;
State.Velocity.y = State.SpeedY;
State.Velocity.z = State.SpeedZ;
D3DXVec3Normalize(&State.Velocity, &State.Velocity);
State.PosDelta.x = State.Velocity.x * ElapsedTime;
State.PosDelta.y = State.Velocity.y * ElapsedTime;
State.PosDelta.z = State.Velocity.z * ElapsedTime;
D3DXMatrixRotationYawPitchRoll(&State.CameraRotation, 0, 0, 0);
D3DXVec3TransformCoord(&State.WorldUp, &State.LocalUp, &State.CameraRotation);
D3DXVec3TransformCoord(&State.WorldAhead, &State.LocalAhead, &State.CameraRotation);
D3DXVec3TransformCoord(&State.PosDeltaWorld, &State.PosDelta, &State.CameraRotation);
State.Position += State.PosDeltaWorld;
State.LookAt = State.Position + State.WorldAhead;
D3DXMatrixLookAtLH(&State.View, &State.Position, &State.LookAt, &State.WorldUp);
return;
}
"State" is a structure that holds all the information about the camera.
I would guess your speed changes when you move in more than one direction at once because you normalize your velocity.
For example, moving in Z:
Velocity = (0, 0, 0.01)
Speed = (0,0, 0.01)
Normalized Velocity = (0, 0, 1)
PosDelta = (0, 0, 0.01)
and moving in X+Z:
Velocity = (0.02, 0, 0.01)
Speed = (0.02, 0, 0.01)
Normalized Velocity = (0.897, 0, 0.435)
PosDelta = (0.018, 0, 0.0044)
Regarding your inversion of direction I'm guessing it may be related in part to your relatively strange method of using velocity/speed (see more below) and possibly due to the in-precision of floats. Regarding the last point what do you think the following code outputs (disregard potential compiler optimizations):
float test1 = 0f;
test1 += 0.1f;
test1 += 0.1f;
test1 += 0.1f;
test1 += 0.1f;
test1 += 0.1f;
test1 -= 0.1f;
test1 -= 0.1f;
test1 -= 0.1f;
test1 -= 0.1f;
test1 -= 0.1f;
printf("%g\n", test1);
It (likely?) won't output the obvious answer of 0 since 0.1 cannot be exactly represented in base-2. It prints 1.49012e-008 on my system. What could be happening is you may be close to 0 but not exactly which may cause the coordinate inversion to appear. You can get rid of this by rounding speeds to a certain accuracy.
Your overall method of handling the velocity/speed/position is a little strange and may be the source of your difficulties. For example, I would expect Velocity to be a vector representing the velocity of the player and not a normalized vector as you have. I would do something like:
SpeedX = 0.0f;
SpeedY = 0.0f;
SpeedZ = 0.0f
if(IsKeyDown('A')) SpeedX += -0.02f;
if(IsKeyDown('D')) SpeedX += 0.02f;
...
Velocity.x += SpeedX;
Velocity.y += SpeedY;
Velocity.z += SpeedZ;
D3DXVECTOR3 NormVelocity;
D3DXVec3Normalize(&NormVelocity, &Velocity);
//Round velocity here if you need to
Velocity.x = floor(Velocity.x * 10000) / 10000.0f;
...
float FrameTime = 1; //Use last frame time here
PosDelta.x = Velocity.x * FrameTime;
PosDelta.y = Velocity.y * FrameTime;
PosDelta.z = Velocity.z * FrameTime;
That gets rid of your velocity changing when moving in more than one direction. It also lets you properly compensate for changing frame rates if you set the FrameTime to be the time of the last frame (or a value derived from it). It also correctly stops the player from moving when they try to move in two opposite directions at once.
As for your last question regarding the decay of the Y-velocity there are a few ways to do it. You could simply do something like:
Velocity.y *= 0.7f;
every frame (adjust the constant to suit your needs). A more accurate model would be to do something like:
if (Velocity.y > 0) {
Velocity.y -= 0.001f; //Pick constant to suit
if (Velocity.y < 0) Velocity.y = 0;
}
An even better way would be to use the last frame time to account for varying frame rates like:
Velocity.y -= FrameTime * 0.2f; //Pick constant to suit
Here is what I'm trying to do. I'm trying to make a bullet out of the center of the screen. I have an x and y rotation angle. The problem is the Y (which is modified by rotation on the x) is really not working as intended. Here is what I have.
float yrotrad, xrotrad;
yrotrad = (Camera.roty / 180.0f * 3.141592654f);
xrotrad = (Camera.rotx / 180.0f * 3.141592654f);
Vertex3f Pos;
// get camera position
pls.x = Camera.x;
pls.y = Camera.y;
pls.z = Camera.z;
for(float i = 0; i < 60; i++)
{
//add the rotation vector
pls.x += float(sin(yrotrad)) ;
pls.z -= float(cos(yrotrad)) ;
pls.y += float(sin(twopi - xrotrad));
//translate camera coords to cube coords
Pos.x = ceil(pls.x / 3);
Pos.y = ceil((pls.y) / 3);
Pos.z = ceil(pls.z / 3);
if(!CubeIsEmpty(Pos.x,Pos.y,Pos.z)) //remove first cube that made contact
{
delete GetCube(Pos.x,Pos.y,Pos.z);
SetCube(0,Pos.x,Pos.y,Pos.z);
return;
}
}
This is almost identical to how I move the player, I add the directional vector to the camera then find which cube the player is on. If I remove the pls.y += float(sin(twopi - xrotrad)); then I clearly see that on the X and Z, everything is pointing as it should. When I add pls.y += float(sin(twopi - xrotrad)); then it almost works, but not quite, what I observed from rendering out spheres of the trajector is that the furthur up or down I look, the more offset it becomes rather than stay alligned to the camera's center. What am I doing wrong?
Thanks
What basically happens is very difficult to explain, I'd expect the bullet at time 0 to always be at the center of the screen, but it behaves oddly. If i'm looking straight at the horizon to +- 20 degrees upward its fine but then it starts not following any more.
I set up my matrix like this:
void CCubeGame::SetCameraMatrix()
{
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotatef(Camera.rotx,1,0,0);
glRotatef(Camera.roty,0,1,0);
glRotatef(Camera.rotz,0,0,1);
glTranslatef(-Camera.x , -Camera.y,-Camera.z );
}
and change the angle like this:
void CCubeGame::MouseMove(int x, int y)
{
if(!isTrapped)
return;
int diffx = x-lastMouse.x;
int diffy = y-lastMouse.y;
lastMouse.x = x;
lastMouse.y = y;
Camera.rotx += (float) diffy * 0.2;
Camera.roty += (float) diffx * 0.2;
if(Camera.rotx > 90)
{
Camera.rotx = 90;
}
if(Camera.rotx < -90)
{
Camera.rotx = -90;
}
if(isTrapped)
if (fabs(ScreenDimensions.x/2 - x) > 1 || fabs(ScreenDimensions.y/2 - y) > 1) {
resetPointer();
}
}
You need to scale X and Z by cos(xradrot). (In other words, multiply by cos(xradrot)).
Imagine you're pointing straight down the Z axis but looking straight up. You don't want the bullet to shoot down the Z axis at all, this is why you need to scale it. (It's basically the same thing that you're doing between X and Z, but now doing it on the XZ vector and Y.)
pls.x += float(sin(yrotrad)*cos(xrotrad)) ;
pls.z -= float(cos(yrotrad)*cos(xrotrad)) ;
pls.y += float(sin(twopi - xrotrad));