Top-down perspective aim tank turret with mouse - c++

I want to make tank's turret be aimed with mouse in a top-down perspective. I have written some code to animate rotation to given angle:
void Tank::rotateTurret(float angle) {
turretRotation += angle;
}
sf::Sprite turret;
void Tank::update(unsigned int time) {
if (turretRotation != 0.0f) {
float rotate;
if (turretRotation > 0.0f) {
rotate = turretRotationSpeed * time;
if (rotate > turretRotation) {
rotate = turretRotation;
turretRotation = 0;
}
else
turretRotation -= rotate;
}
else {
rotate = -turretRotationSpeed * time;
if (rotate < turretRotation) {
rotate = turretRotation;
turretRotation = 0;
}
else
turretRotation -= rotate;
}
turret.rotate(rotate);
}
}
And I can calculate mouse pointer angle relative to top left corner:
void TankPlayerController::update() {
sf::Vector2i mousePosition = sf::Mouse::getPosition(*relativeWindow);
sf::Vector2i mouseMovement = mousePosition - lastMousePosition;
if (mouseMovement.x != 0 || mouseMovement.y != 0) {
float mouseAngle = VectorAngleDeg(mousePosition.x, mousePosition.y);
tank->rotateTurret(???);
lastMousePosition = mousePosition;
}
}
But I have no idea how to combine it together. How should it be done?

You need to calculate the angle to the center of the turret (CoT) from the upper left corner (ULHC) and the angle to the mouse location from the ULHC. Next consider the triangle formed from the lines connecting the ULHC to the CoT, the line connecting the ULHC to the mouse pointer location and the line connecting the CoT to the mouse pointer location. Since you know the distance from the ULHC to the CoT and the distance from the ULHC to the mouse pointer location all you need to do is determine the difference between the angle to the CoT and the mouse pointer position you can use the Law of Cosines to get the angel between the ULHC and the mouse position at the turret and from there the angle to any arbitrary axis you choose.
It would be easier with a picture :|

Related

How to get reference to clicked sf::CircleShape?

In my SFML program I am storing drawn CircleShapes in a Vector. How to get reference to one on clicking mouse button?
There's no shape.makeClickable() function in SFML, all you have to do is:
sf::CircleShape* onClick(float mouseX, float mouseY) {//Called each time the players clicks
for (sf::CircleShape& circle : vec) {
float distance = hypot((mouseX - circle.getPosition().x), (mouseY - circle.getPosition().y)); //Checks the distance between the mouse and each circle's center
if (distance <= circle.getRadius())
return &circle;
}
return nullptr;
}
With this vector in your class:
std::vector<sf::CircleShape> vec;
EDIT
To get ALL circles that you have clicked-on, and not only the first one that it finds :
std::vector<sf::CircleShape*> onClick(float mouseX, float mouseY) {//Called each time the players clicks
std::vector<sf::CircleShape*> clicked;
for (sf::CircleShape& circle : vec) {
float distance = hypot((mouseX - circle.getPosition().x), (mouseY - circle.getPosition().y)); //Checks the distance between the mouse and each circle's center
if (distance <= circle.getRadius())
clicked.push_back(&circle);
}
return clicked;
}

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);
}

How to rotate points about a specific origin?

I'm working on rotating the vertices of my object around a point located on the object that's not necessarily its center. I followed this tutorial pretty closely and got the vertices to keep their same proportions, i.e. the shape that they create does rotate about the given point, however the amount by which it rotates seems arbitrary. I'll explain in the code and the screenshots. I'm using SFML, but I'll explain the sf:: namespaces in the comments where they're used for those who need it. Anyways, here's my main file that shows the problem:
int _tmain(int argc, _TCHAR* argv[]){
sf::RenderWindow window(sf::VideoMode(500, 500, 32), "Animation");
//sf::vertexarray is a list of POINTS on the screen, their position is determined with a sf::vector
sf::VertexArray vertices;
//arrange 6 points in a shape
vertices.setPrimitiveType(sf::PrimitiveType::Points);
//bottom middle
vertices.append(sf::Vector2f(200, 200));
//left bottom edge
vertices.append(sf::Vertex(sf::Vector2f(195, 195)));
//left top edge
vertices.append(sf::Vertex(sf::Vector2f(195, 175)));
//top middle
vertices.append(sf::Vertex(sf::Vector2f(200, 170)));
//top right corner
vertices.append(sf::Vertex(sf::Vector2f(205, 175)));
//bottom right corner
vertices.append(sf::Vertex(sf::Vector2f(205, 195)));
//rotation is the shape's rotation... 0 means it's straight up, and it rotates clockwise with positive rotation
float rotation = 0;
//used later to pause the program
bool keypressed = false;
while(window.isOpen()){
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Escape)){
window.close();
}
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)){
//this SHOULD rotate the shape by 10 degrees, however it rotates it by like 150-ish
//why does this not work as expected?
rotation = 10;
//this transformation part works fine, it simply moves the points to center them on the origin, rotates them using a rotation matrix, and moves
//them back by their offset
for(int i = 1; i < vertices.getVertexCount(); i++){
//translate current point so that the origin is centered
vertices[i].position -= vertices[0].position;
//rotate points
//I'm guessing this is the part where the rotation value is screwing up?
//because rotation is simply theta in a regular trig function, so the shape should only rotate 10 degrees
float newPosX = vertices[i].position.x * cosf(rotation) + vertices[i].position.y * sinf(rotation);
float newPosY = -vertices[i].position.x * sinf(rotation) + vertices[i].position.y * cosf(rotation);
//translate points back
vertices[i].position.x = newPosX + vertices[0].position.x;
vertices[i].position.y = newPosY + vertices[0].position.y;
}
keypressed = true;
}
//draw
window.clear();
window.draw(vertices);
window.display();
if(keypressed == true){
//breakpoint here so the points only rotate once
system("pause");
}
}
return 0;
}
Also, here are the screenshots showing what I mean. Sorry it's a bit small. The left side shows the shape created at the start of the program, with the green point being the origin. The right side shows the shape after the rotation for loop is called, with the red points showing where the shape actually rotates to (definitely not 10 degrees) versus the blue dots, which are roughly where I expected the shape to be at, around 10 degrees.
tl;dr: Using a rotation matrix, the points being rotated keep their proportions, but the amount by which they are rotating is totally arbitrary. Any suggestions/improvements?
Using the SFML, you first create a transformation :
sf::Transform rotation;
rotation.rotate(10, centerOfRotationX, centerOfRotationY);
Then you apply this transformation to the position of each vertex :
sf::Vector2f positionAfterRotation = rotation.transformPoint(positionBeforeRotation);
Sources : sf::Transform::rotate and sf::Transform::transformPoint.

Calculating ball deflection angle when colliding with paddle in brick slayer game

Here is my code:
void Draw()
{
int x = 59;
int y = 500;
int temp = x;
int colour;
for (int i = 0; i < 9; ++i)
{
for (int j = 0; j < 10; ++j)
{
if (i % 2 == 0)
colour = 2;
else
colour = 3;
DrawRectangle(x, y, 65, 25, colors[colour]);
x += 67;
}
x = temp;
y -= 39;
}
DrawRectangle(tempx, 0, 85, 12, colors[5]);
DrawCircle(templx, temply, 10, colors[7]);
}
// This function will be called automatically by this frequency: 1000.0 / FPS
void Animate()
{
templx +=5;
temply +=5;
/*if(templx>350)
templx-=300;
if(temply>350)
temply-=300;*/
glutPostRedisplay(); // Once again call the Draw member function
}
// This function is called whenever the arrow keys on the keyboard are pressed...
//
I am using OpenGL for this project. The function Draw() is used to print the bricks, slider, and the ball. The Animate() function is called automatically by the frequency given in the code. As it can be seen, I have incremented the values of templx and temply, but the ball goes out of screen as it crosses its limit. I have to deflect the ball if it collides with the paddle or the wall. What can I do to achieve this? All the conditions that I have used by now do not work properly.
So basically you would like to have a ball that is bouncing from the edges of your window. (For this answer I will ignore the slider, finding collision with the slider is very similar to finding collision with the walls).
templx and temply pair is position of your ball. I don't know what is the 3rd argument of DrawCircle function so I will assume that it is the radius. Let wwidth and wheight be width and height of a game window. Note that this magic constant 5 is, in fact, a velocity of the ball. Now ball is moving from upper left corner to lower right corner of your window. If you change 5 to -5 it will move from lower right corner to upper left corner.
Let's introduce two more variables vx and vy - velocity on x axis and velocity on y axis. The initial values will be 5 and 5. Now notice that when ball hits the right edge of the window it doesn't change its vertical velocity, it is still moving up/down but it changes its horizontal velocity from left->right to right->left. So if the vx was 5, after hitting the right edge of the window we should change it to -5.
The next problem is how to find out if we hit the edge of the window or not.
Note that the right-most point on the ball has the position templx + radius and the left-most point on the ball has the position templx - radius etc. Now to find out if we hit the wall or not we should just compare this values with window dimensions.
// check if we hit right or left edge
if (templx + radius >= wwidth || templx - radius <= 0) {
vx = -vx;
}
// check if we hit top or bottom edge
if (temply + radius >= wheight || temply - radius <= 0) {
vy = -vy;
}
// update position according to velocity
templx += vx;
temply += vy;

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);
}