Follow touch point by sprite with rotation - c++

I've successfully implemented dragging a sprite on the screen, by simply remembering touch position on touchbegan and translating sprite by currentTouchPoint - startingTouchPoint, which gives me nice dragging. But now I want to sprite track my finger instead so it also should rotate. This would give me an advantage of making user able to rotate sprite as well as drag it around with just one finger.
I was getting unexpected results, but I've ran into this code:
http://www.freeactionscript.com/2011/01/mouse-follow-with-easing-smooth-rotation-v2/
There's a demo so you could try it on the browser.
It's pretty much the same I need except sprite should be a lot faster (near the finger all the time) and stop after reaching it's destination instead of orbiting around (while holding touch). I've tried to port it to cocos2d-x, but it doesn't work the same. Well, updatePosition() looks good, sprite is going where it should be pretty much, but updateRotation() is a total mess. It's rotating in strange directions and sometimes even makes a full very fast circle in opposite direction.
onTouchBegan:
//check if any sprite is touched and then assign it to draggedItem
onTouchMove:
_destinationX = touchPoint.x;
_destinationY = touchPoint.y;
onTouchEnd:
//make draggedItem nullptr
void CreatorScene::update(float delta){
if(draggedItem != nullptr){
updateItemPosition();
updateItemRotation();
}
}
void CreatorScene::updateItemRotation()
{
// calculate rotation
_dx = draggedItem->getPositionX() - _destinationX;
_dy = draggedItem->getPositionY() - _destinationY;
// which way to rotate
float rad = atan2(_dy, _dx);
if(_dy < 0) rad += 2 * M_PI;
float rotateTo = CC_RADIANS_TO_DEGREES(rad);
// keep rotation positive, between 0 and 360 degrees
if (rotateTo > draggedItem->getRotation() + 180) rotateTo -= 360;
if (rotateTo < draggedItem->getRotation() - 180) rotateTo += 360;
// ease rotation
_trueRotation = (rotateTo - draggedItem->getRotation()) / ROTATE_SPEED_MAX;
// update rotation
draggedItem->setRotation(draggedItem->getRotation() + _trueRotation);
}
void CreatorScene::updateItemPosition()
{
// check if mouse is down
// if (_isActive)
// {
// update destination
// _destinationX = stage.mouseX;
// _destinationY = stage.mouseY;
// update velocity
_vx += (_destinationX - draggedItem->getPositionX()) / MOVE_SPEED_MAX;
_vy += (_destinationY - draggedItem->getPositionY()) / MOVE_SPEED_MAX;
// }
// else
// {
// // when mouse is not down, update velocity half of normal speed
// _vx += (_destinationX - _player.x) / _moveSpeedMax * .25;
// _vy += (_destinationY - _player.y) / _moveSpeedMax * .25;
// }
// apply decay (drag)
_vx *= DECAY;
_vy *= DECAY;
// if close to target, slow down turn speed
if (sqrtf((_dx*_dx)+(_dy*_dy)) < 50)
{
_trueRotation *= .5;
}
// update position
draggedItem->setPosition(draggedItem->getPosition() + Point(_vx, _vy));
}
What's wrong here? How can I achieve such a smooth movement?
Edit:
Functionality that I want to exactly achieve is here:
https://www.youtube.com/watch?v=RZouMyyNGG8
You can see it on 2:10.

Related

Why is my collision response not stopping my player from going through walls (SDL2, C++)?

While I found many problems that are similar too mine, none of the solutions solved my problem.
I've been experimenting with SDL2 in C++ (Visual C++) and the entity-component-system (ECS). But I just can't figure out the bug in my collision response.
So here it is: My player sometimes gets set back to its origin when it encounters something like a rock (a simple gray tile). But sometimes it goes right through and gets stuck or ends up on the other side.
I can only assume it has something to do with the data changed in between frames, so it isn't always caught. But for the life of me I can't figure it out.
Here is my rectangular detection method:
bool Collision::RectIntersect(const SDL_Rect& a, const SDL_Rect& b, SDL_Rect& intersect)
{
intersect = { 0, 0, 0, 0 };
int leftX = std::max(a.x, b.x);
int rightX = std::min(a.x + a.w, b.x + b.w);
int topY = std::max(a.y, b.y);
int bottomY = std::min(a.y + a.h, b.y + b.h);
if (leftX < rightX && topY < bottomY)
{
intersect = { leftX, topY, rightX - leftX, bottomY - topY };
return true;
}
return false;
}
Here is my snippet where my inputs are handled and subsequently any collision detections are resolved before the code actually moves anything:
void InputComponent::handleEvents(SDL_Event* e)
{
const Uint8 *keyboardState = SDL_GetKeyboardState(NULL);
if (e != nullptr)
{
/*
keyHeld: array of 4 for each direction (+/- x, +/- y (WASD))
hold value true, if pressed down, otherwise false
*/
if (keyboardState[SDL_SCANCODE_A])
{
keyHeld[0] = true;
}
else
{
keyHeld[0] = false;
}
if (keyboardState[SDL_SCANCODE_D])
{
keyHeld[1] = true;
}
else
{
keyHeld[1] = false;
}
if (keyboardState[SDL_SCANCODE_W])
{
keyHeld[2] = true;
}
else
{
keyHeld[2] = false;
}
if (keyboardState[SDL_SCANCODE_S])
{
keyHeld[3] = true;
}
else
{
keyHeld[3] = false;
}
}
/*
tmpVel: Vector to store the assumed velocity in x- and y-direction
*/
Vector2D tmpVel(0.0f, 0.0f);
// left and right (A and D)
if (keyHeld[0] && !keyHeld[1]) // left
{
tmpVel.x = -1.0f;
}
else if (!keyHeld[0] && keyHeld[1]) // right
{
tmpVel.x = 1.0f;
}
else
{
tmpVel.x = 0.0f; // left and right cancel each other out
}
// up and down (W and S)
if (keyHeld[2] && !keyHeld[3]) // up
{
tmpVel.y = -1.0f;
}
else if (!keyHeld[2] && keyHeld[3]) // down
{
tmpVel.y = 1.0f;
}
else
{
tmpVel.y = 0.0f; // up and down cancel each other out
}
/*
check for collision with presumed direction according to tmpVel
*/
SDL_Rect intersection;
// get current player position
SDL_Rect movedPlayer = entity->getComponent<CollisionComponent>().getCollider();
// add trajectory of theoretical movement
movedPlayer.x += static_cast<int>(tmpVel.x * vel_->getSpeed());
movedPlayer.y += static_cast<int>(tmpVel.y * vel_->getSpeed());
bool hasCollided = false;
// collect all collidable objects
for (auto& c : manager_->getGroup(GroupLabel::GR_COLLIDERS))
{
// check player against each collidable tile
//if (SDL_IntersectRect(&movedPlayer, &c->getComponent<CollisionComponent>().getCollider(), &intersection))
if (Collision::RectIntersect(movedPlayer, c->getComponent<CollisionComponent>().getCollider(), intersection))
{
// collision on x-axis
if (intersection.w > 0)
{
// set velocity on x-axis to 0
vel_->setVelocityX(0.0f);
// reset player position back according to width of intersected rectangle
pos_->setPosX(pos_->getPos().x + (static_cast<float>(intersection.w) * (-tmpVel.x)));
}
// collision on y-axis
if (intersection.h > 0)
{
// set velocity on y-axis to 0
vel_->setVelocityY(0.0f);
// reset player position back according to height of intersected rectangle
pos_->setPosY(pos_->getPos().y + (static_cast<float>(intersection.h) * (-tmpVel.y)));
}
hasCollided = true;
}
}
if (!hasCollided)
{
vel_->setVelocity(tmpVel);
}
}
Can anybody put me in the right direction?
What happens when the right edge of the player exactly equals the left edge of the rock? It looks like the collision is not detected, since the test is for (leftX < rightX). So the velocity is updated and the player is moved by the velocity. (It's odd that you simply update the velocity and later move the player instead of just moving them to the new already calculated position.) If you change the check to (leftX <= rightX), does the problem persist?
As far as I can see there are two things wrong with your collision detection. The first is that you're testing (leftX < rightX && topY < bottomY) when you should be testing (leftX <= rightX && topY <= bottomY). If you fix this your code will work in most situations.
The second problem you've got, which may not become apparent straight away, is that your are performing collision detection for discreet points in time. If your player has a large enough velocity vector you may end up with this situation:
Update 1: Player is in front of wall travelling towards it. AABB test shows no collision.
Update 2: Player is behind wall travelling away from it. AABB test shows no collision.
Your AABB test is correct and yet the player has passed through the wall. The naive approach to fixing this is to test more often (update 1.5 may have shown a collision), or to limit player velocity. Both approaches will require a lot of fine tuning especially if you're dealing with objects that can move at different speeds and walls with differing thickness.
A more robust approach is to take account of velocity in your test. Since you know the velocity of your AABB you can project this shape along its velocity vector. If you do this for both AABBs you'll end up with two elongated shapes which you can test against each other. If they overlap then you know that their paths cross and that there may be a collision.
Of course, knowing that there might be a collision is not hugely helpful. The problem is one AABB may be moving very slowly and the other very quickly so even though they both pass through the same space (their elongated shapes intersect) they don't pass through it at the same time.
Figuring out whether they both pass through the same space at the same time is hard, so instead we cheat. If you subtract the velocity of B from the velocity of A and then use this modified velocity to project the elongated shape of A, you can effectively treat B as a stationary object and still get the correct result. Knowing this, your test is now "does B overlap the elongated shape of A?". This is just a simple AABB vs Ngon problem.
While the above will give you a boolean as to whether two moving AABBs collide it will not tell you when they collide which is also useful for calculating things like rebounds.
I would very much recommend the book Real Time Collision Detection by Christer Ericson which is pretty much the go to book on collision detection for any aspiring game developer.
The following is a code snippet from the CD-ROM which accompanies the book. It tests a moving AABB against another moving AABB and also provides a time of first contact.
// Intersect AABBs ‘a’ and ‘b’ moving with constant velocities va and vb.
// On intersection, return time of first and last contact in tfirst and tlast
int IntersectMovingAABBAABB(AABB a, AABB b, Vector va, Vector vb, float &tfirst, float &tlast)
{
// Exit early if ‘a’ and ‘b’ initially overlapping
if (TestAABBAABB(a, b)) {
tfirst = tlast = 0.0f;
return 1;
}
// Use relative velocity; effectively treating ’a’ as stationary
Vector v = vb - va;
// Initialize times of first and last contact
tfirst = 0.0f;
tlast = 1.0f;
// For each axis, determine times of first and last contact, if any
for (int i = 0; i < 3; i++) {
if (v[i] < 0.0f) {
if (b.max[i] < a.min[i]) return 0;
// Nonintersecting and moving apart
if (a.max[i] < b.min[i]) tfirst = Max((a.max[i] - b.min[i]) / v[i], tfirst);
if (b.max[i] > a.min[i]) tlast = Min((a.min[i] - b.max[i]) / v[i], tlast);
}
if (v[i] > 0.0f) {
if (b.min[i] > a.max[i]) return 0;
// Nonintersecting and moving apart
if (b.max[i] < a.min[i]) tfirst = Max((a.min[i] - b.max[i]) / v[i], tfirst);
if (a.max[i] > b.min[i]) tlast = Min((a.max[i] - b.min[i]) / v[i], tlast);
}
// No overlap possible if time of first contact occurs after time of last contact
if (tfirst > tlast) return 0;
}
return 1;
}
The following attribution is required by Elsevier's Software License Agreement:
“from Real-Time Collision Detection by Christer Ericson, published by Morgan Kaufmann Publishers, © 2005 Elsevier Inc”

Applying gravity to objects

I'm having trouble with implementing gravity on objects.
I have a bunch of circle objects that I'm drawing using OpenGL. I'm using delta-x and delta-y to move the circles (balls). I'm trying to add a gravitational constant to the y coordinate each frame so it simulates being pulled downward, but I'm not sure exactly how to do that.
Here's the relevant code:
class Ball
{
public:
double x;
double y;
double radius;
double deltaX;
double deltaY;
};
std::vector<Ball> gAllBalls; // a vector of balls with random positions and delta-values
double gGravity = ?; // gravitational constant - I know it's 92 m/s^s, but I don't know how to apply that to deltaX and deltaY
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
for (auto &thisBall : gAllBalls)
{
// draw
glColor3d(thisBall.red, thisBall.green, thisBall.blue);
DrawCircle(thisBall.x, thisBall.y, thisBall.radius);
// gravity - not working
if (thisBall.y + thisBall.radius < gScreenHeight - gGravity)
{
thisBall.y += gGravity;
}
// wall bouncing
if (thisBall.y + thisBall.radius + thisBall.deltaY >= gScreenHeight) // up
{
thisBall.deltaY = -thisBall.deltaY;
}
if (thisBall.y + thisBall.deltaY - thisBall.radius < 0) // down
{
thisBall.deltaY = -thisBall.deltaY;
}
if (thisBall.x + thisBall.deltaX - thisBall.radius < 0) // left
{
thisBall.deltaX = -thisBall.deltaX;
}
if (thisBall.x + thisBall.radius + thisBall.deltaX >= gScreenWidth) // right
{
thisBall.deltaX = -thisBall.deltaX;
}
// move
thisBall.x += thisBall.deltaX;
thisBall.y += thisBall.deltaY;
}
glutSwapBuffers();
glutPostRedisplay();
}
A big problem I'm having is that I don't know how to calculate the gravity, since I'm using deltaX and deltaY instead of having separate speed and distance variables to calculate 92 m/s^2. However, no matter what I set the gravity to, the balls don't behave like they should - regardless of gravity strength, so there has to be something else wrong, but I don't know what.
I think the problem here is the physics, rather than the programming technique.
In your case, I would change the 'delta' members of your Ball class to 'speed', since they are a unit of distance that change the position of your object per cycle (time), however this is just a suggestion to make it easier to visualize...
class Ball
{
public:
double x;
double y;
double radius;
double speedX;
double speedY;
};
In second place, I in your code, you are changing the 'y' member, rather than the speed, and since gravity changes speed, hence the problem.
Try doing that, and for debugging purposes, I would try with punctual objects (no radius, just plain (x,y) coordinates).
So, to conclude, I would simply change your gravity code to the following:
// gravity - 'fixed'
if (thisBall.y + thisBall.radius < gScreenHeight - gGravity)
{
thisBall.speedY -= gGravity; //notice the '-'
}
The value of gravity should be absolute and positive, so as to keep things as simple as posible. If you try this, you should have an ideal simple physics simulator, with a ball of constant speedX (only changing its direction, not magnitude).
Please try this, and let me know how it went, good luck, keep it up =)

Issue with angle limiting

I am creating a game using SFML 2.2 and Box2D. The game is similar to Peggle in that you have a cannon attached to the top of the screen. I have the movement of the cannon mostly working but I would like to limit the rotation so it cannot rotate and point straight up (off screen). I have implemented the code below and it works to an extent. The cannon can move fine within the bounds, but if it hits one of the two edges it becomes stuck there. If I press the opposite direction, instead of rotating back around it quickly jumps to the opposite edge, causing the cannon to be either all the way to the right or all the way to the left.
In my game, when the cannon is pointing straight down the angle is 0/360 degrees, all the way left is 90 degrees and all the way right is 270 degrees.
//******************************/
// Aiming Controls
//******************************/
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Left))
{
float sin = sinf(-m_fRotationSpeed*elapsedTime);
float cos = cosf(-m_fRotationSpeed*elapsedTime);
sf::Vector2f newDirection;
newDirection.x = (cos*m_vAimDirection.x) - (sin * m_vAimDirection.y);
newDirection.y = (sin*m_vAimDirection.x) + (cos*m_vAimDirection.y);
//Update sprite rotation
// Find angle of rotation by acos(v1dotv2)
float rotationAngle = acosf(1 * m_vAimDirection.y);
// Check to make sure we get the right direction!
if (m_vAimDirection.x < 0)
rotationAngle = (b2_pi*2.0f) - rotationAngle;
// convert to degrees
rotationAngle *= 180.0f / b2_pi;
// HERE IS WHERE I CHECK THE BOUNDS
if (rotationAngle > 88.0f && rotationAngle < 272.0f)
rotationAngle = 88.0f;
else
m_vAimDirection = newDirection;
//apply new rotation
m_sCannon.setRotation(rotationAngle);
}
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Right))
{
float sin = sinf(m_fRotationSpeed*elapsedTime);
float cos = cosf(m_fRotationSpeed*elapsedTime);
sf::Vector2f newDirection;
newDirection.x = (cos*m_vAimDirection.x) - (sin * m_vAimDirection.y);
newDirection.y = (sin*m_vAimDirection.x) + (cos*m_vAimDirection.y);
//Update sprite rotation
// Find angle of rotation by acos(v1dotv2)
float rotationAngle = acosf(1 * m_vAimDirection.y);
// Check to make sure we get the right direction!
if (m_vAimDirection.x < 0)
rotationAngle = (b2_pi*2.0f) - rotationAngle;
// convert to degrees
rotationAngle *= 180.0f / b2_pi;
// HERE IS WHERE I CHECK THE BOUNDS
if (rotationAngle < 272.0f && rotationAngle > 88.0f)
rotationAngle = 272.0f;
else
m_vAimDirection = newDirection;
//apply new rotation
m_sCannon.setRotation(rotationAngle);
}

DirectX C++ Xbox Controller Input Issue

So I've started using DirectX C++ and have a base game with is a bunch of asteroids which you can shoot using the mouse. It also has a drawn crosshair sprite which follows the mouses location.
I have decided that I would like to change input to support Xbox 360 controllers. I have managed to program the shoot button to the A button on the gamepad however I am having some issues changing to movement of the crosshair from mouse to the analog sticks on the controller.
The following is the code which is used for determining the mouses location and getting its position for use:
virtual void Update()
{
POINT mousePosition;
D3DXVECTOR3 position, currentPosition;
DirectInput::Get().ProcessMouse();
mousePosition = DirectInput::Get().GetCurrentMousePosition();
position.x = (float) mousePosition.x;
position.y = (float) mousePosition.y;
position.z = 0.0f;
currentPosition = m_pCrossHairs->GetSpritePosition();
position.x -= currentPosition.x;
position.y -= currentPosition.y;
position.x = position.x/2.0f;
position.y = position.y/2.0f;
m_pCrossHairs->SetTranslationMatrix(position);
m_pCrossHairs->CheckBoundary();
m_pCrossHairs->Update();
}
And here is what I've changed it to for use with an Xbox Controller Instead:
virtual void Update()
{
POINT crosshairPosition;
D3DXVECTOR3 position, currentPosition;
crosshairPosition.x = XBox360Controller::Get().RightJoyStickX();
crosshairPosition.y =- XBox360Controller::Get().RightJoyStickY();
position.x = crosshairPosition.x;
position.y = crosshairPosition.y;
position.z = 0.0f;
currentPosition = m_pCrossHairs->GetSpritePosition();
position.x -= currentPosition.x;
position.y -= currentPosition.y;
position.x = position.x/2.0f;
position.y = position.y/2.0f;
m_pCrossHairs->SetTranslationMatrix(position);
m_pCrossHairs->CheckBoundary();
m_pCrossHairs->Update();
}
On a positive note what I have done kind of works. The Crosshair does move when I move the analog stick, however it is stuck in the top right hand corner and can only move within a range of about 1 square inch on the screen. I'm a bit stuck as to why this is and therefore would appreciate any kind of input. Sorry if I'm lacking any important info that I may have missed, a bit of a late post however would really love to get it working!
On a side note - I'm getting an error on the following giving me a warning about conversion from long to float or something (should I be worried about this)?
crosshairPosition.x = XBox360Controller::Get().RightJoyStickX();
crosshairPosition.y =- XBox360Controller::Get().RightJoyStickY();
Once again thank you in advance.
Billy
Edit:
Code for XBox360Controller::Get().
XBox360Controller& XBox360Controller::Get()
{
if (s_pXBox360Controller == NULL)
s_pXBox360Controller = new XBox360Controller();
return *s_pXBox360Controller;
}
Code for Right analog stick X-Axis
float XBox360Controller::RightJoyStickX()
{
float joyStickX = GetState().Gamepad.sThumbRX;
// Make sure there has been movement, as joystick does return different values even if no movement.
if ( joyStickX< XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE &&
joyStickX > -XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE)
return 0;
else
return joyStickX/DynamicRange;
}
Code for Right analog stick Y-Axis
float XBox360Controller::RightJoyStickY()
{
float joyStickY = GetState().Gamepad.sThumbRY;
// Make sure there has been movement, as joystick does return different values even if no movement.
if ( joyStickY< XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE &&
joyStickY > -XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE)
return 0;
else
return joyStickY/DynamicRange;
}
EDIT SOLVED:
Added a new translation matrix and it's fixed the issue, thanks for the help.

How do I make projectiles?

I am totally stumped on this one. I'm using C++ and SFML 1.6 for a game I'm developing, and I have no bloody idea. How do I make projectiles (like bullets)? I just don't understand it. It could be my lack of sleep but I don't know.
So my question is how do I create a Sprite that moves in a definite direction based on where the mouse is? (Think of a top down shooter with mouse aiming)
Easiest solution:
If the mouse is at Mx,My and the ship is at Sx,Sy then calculate the direction from the ship to the mouse:
Dx=Sx-Mx
Dy=Sy-My
Now normalise D (this means scale it so that it's length is one):
DLen=sqrt(Dx*Dx + Dy*Dy)
Dx/=DLen;
Dy/=DLen;
Now Dx is the distance you want to move the bullet on the x axis in order to get bullet speed of 1.
Thus each frame you move the bullet like so (position of bullet: Bx,By Speed of bullet: Bs [in pixels per millisec] Frame time Ft[in millisec])
Bx=Bx+Dx*Bs*Ft
By=By+Dy*Bs*Ft
This give you a bullet that moves towards the mouse position at a speed independent of the direction of the mouse or framerate of the game.
EDIT: As #MSalters says you need to check for the DLen==0 case when the mouse is directly above the ship to avoid division by zero errors on the normalise
One way to do it is to make the bullet face the mouse and then move it across the x and y axis by using trigonometry to find the hypotinuse from the angle. I don't think i explained this very well, so here the code to make a sprite move from its rotation:
void sMove(sf::Sprite& input,float toMove, int rotation){
bool negY = false;
bool negX = false;
int newRot = rotation;
if (rotation <= 90){
negY = true;
}
else if (rotation <= 180){
//newRot -= 90;
negY = true;
}
else if (rotation <= 360){
newRot -= 270;
negY = true;
negX = true;
}
float y = toMove*cos(newRot*PI/180);
float x = toMove*sin(newRot*PI/180);
if (negY){
y = y*-1;
}
if (negX){
x = x*-1
}
input.move(x, y);
}