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);
}
Related
I have to move a ball in an angle in an open SFML and keep it within the window size (Like the DVD thing), but my current function makes it to the bottom and doesn't "bounce". It slides across the bottom and stops once it reaches the other corner. The initial position is (1,1)
void Bubble::updatePosition() {
if( isTopBottom() ){
do{
_x += .1;
_y += -.2;
}while( !isTopBottom() );
}
else if( isLeftRight() ){
do{
_x += -.1;
_y += .2;
}while( !isLeftRight() );
}
else{
_x += .1;
_y += .2;
}
_bubble.setPosition(_x, _y);
}
the isLeftRight, isTopBottom are bools that check if they have reached the edges
Simple Solution
Use velocities and manipulate those on collision; then, use the velocity to update the position.
Check each edge separately and decide on one relevant velocity component based on that.
e.g. (following your values)
// Positions:
float x = 1.f;
float y = 1.f;
// Velocities:
float vx = 0.1f;
float vy = 0.2f;
// ... then, inside loop:
// Check collisions (and adjust velocity):
if (x < 0.f)
vx = 0.1f;
else if (x > 640.f)
vx = -0.1f;
if (y < 0.f)
vy = 0.2f;
else if (y > 640.f)
vy = -0.2f;
// update position (still inside loop):
x += vx;
y += vy;
Cleaner Solution
This is the same as the simple solution above but, since you tagged SFML, you can use SFML vectors to keep the two components together. Also modified variable names to be more clear. Pulled out the size of the window and the velocity amounts from being hard-coded into the logic as well:
const sf::Vector2f windowSize(640.f, 640.f);
const sf::Vector2f velocityAmount(0.1f, 0.2f);
sf::Vector2f position(1.f, 1.f);
sf::Vector2f velocity = velocityAmount;
// ... then, inside loop:
// Check collisions (and adjust velocity):
if (position.x < 0.f)
velocity.x = velocityAmount.x;
else if (position.x > windowSize.x)
velocity.x = -velocityAmount.x;
if (position.y < 0.f)
velocity.y = velocityAmount.y;
else if (position.y > windowSize.y)
velocity.y = -velocityAmount.y;
// update position (still inside loop):
position += velocity;
You should notice that the velocity is the values that are added to the position on each iteration of the loop and that velocity does not change when it is not considered colliding with an edge.
Initial Problem
The initial problem you had is it always moves in the same direction (towards the bottom-right) if it is not hitting an edge. This means that it'll never be allowed to rise above the bottom edge (or away from right edge).
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;
}
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.
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));
I've got some jerky movement of my sprite.
Basically, when the user touches a point on the screen, the sprite should move to that point. This is working mostly fine... it's even taking into account a delta - because frame rate may not be consistant.
However, I notice that the y movement usually finishes before the x movement (even when the distances to travel are the same), so it appears like the sprite is moving in an 'L' shape rather than a smooth diagonal line.
Vertical and horizontal velocity (vx, vy) are both set to 300. Any ideas what's wrong? How can I go about getting my sprite to move in a smooth diagonal line?
- (void)update:(ccTime)dt
{
int x = self.position.x;
int y = self.position.y;
//if ball is to the left of target point
if (x<targetx)
{
//if movement of the ball won't take it to it's target position
if (x+(vx *dt) < targetx)
{
x += vx * dt;
}
else {
x = targetx;
}
} else if (x>targetx) //same with x being too far to the right
{
if (x-(vx *dt) > targetx)
{
x -= vx * dt;
}
else {
x = targetx;
}
}
if (y<targety)
{
if (y+(vy*dt)<targety)
{
y += vy * dt;
}
else {
y = targety;
}
} else if (y>targety)
{
if (y-(vy*dt)>targety)
{
y -= vy * dt;
}
else {
y = targety;
}
}
self.position = ccp(x,y);
}
You want to move to (targetx, targety) from any (x,y) and arrive at both coordinates at the same time (to avoid the "dogleg"). So, suppose the x velocity is vx and you get there in t seconds. That means vx = (targetx - x)/t. t must be the same for the y coordinate if you want smooth movement to the same point at the same time, so that means t = (targetx - x)/vx and vy must actually be (targety - y)*vx/(targetx - x).
In other words, you can't set vx and vy separately and get the result you want.