• Create Account

# Shoot bullet from rotating canon(s)

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

12 replies to this topic

### #1JohnnyBlomgren  Members   -  Reputation: 102

Like
0Likes
Like

Posted 10 October 2013 - 04:09 AM

Hello,

I've always been afraid to work with everything that has with circles to do when programming and now I'm here! I've tried to spawn 2 bullets from a ship who can rotate 360 degrees. When the ships rotation is equal to 0 it works as it should but as soon as I rotate the ship things get ugly.

1. The ships rotation

My ship is rotating (looking towards) the direction you pull a joystick (this works perfectly)

            theta = Math.atan2(

theta += Math.PI;

angle = (float) Math.toDegrees(theta);
rotation = -angle;


The ship points towards the direction it should.

2. Spawning the bullet (let's try one of the bullets)

When I pull the joystick I also want the bullets to spawn with a small delay between each shot. The method to shoot looks like this:

   private void shoot(){
double angle = rotation;
float x2 = x;
float y2 = y;
gunRightTipX = getCenterX() - 36;
gunRightTipY = getCenterY() - 48;

float bulletX = x2 + (gunRightTipX * (float)Math.toRadians(Math.cos(angle)) - gunRightTipY * (float)Math.toRadians(Math.sin(angle)));
float bulletY = y2 + (gunRightTipX * (float)Math.toRadians(Math.sin(angle)) + gunRightTipY * (float)Math.toRadians(Math.cos(angle)));

Bullet b = new Bullet(new TextureRegion(Assets.bullet), bulletX, bulletY, (float)angle, 5f);
}


Where gunRightTipX and gunRightTipY is the end of the barrell where the shot is supposed to spawn.

This works fine when the rotation equals 0. But when I rotate the ship it seems that the spawn position's always the same or atleast very close to it. It doesn't follow the barrell point of the rotated ship..

3. The Bullet class

The bullets then travel in the direction they should (the direction the ship is facing)

(Render method for the class "Bullet")

   public void render(SpriteBatch batch, float delta) {
super.render(batch, delta);

x += (float)(speed * Math.cos(Math.toRadians(angle - 90))) * delta * Assets.FPS;
y += (float)(speed * Math.sin(Math.toRadians(angle - 90))) * delta * Assets.FPS;

batch.draw(texture, x, y, texture.getRegionWidth() / 2,
texture.getRegionHeight() / 2, texture.getRegionWidth(),
texture.getRegionHeight(), 1, 1, (float)rotation);
}


I really need help with this...

Edited by JohnnyBlomgren, 10 October 2013 - 04:21 AM.

### #2haegarr  Crossbones+   -  Reputation: 7042

Like
0Likes
Like

Posted 10 October 2013 - 06:01 AM

In general it is better to not work with angles in an extent you do. Using orientation matrices and difference vectors (means a direction vector with a non-unit length, e.g. for velocity) is often simpler and more integrative. It also would reduce the amount of on-the-fly trigonometric calculations.

What are the reference frames of (x,y) and (getCenterX(),getCenterY()) as are used in shoot(), and those expected by new TextureRegion(...)? I assume that co-ordinate systems get confused.

The system can be modeled so: The placement of the ship in the world is described by transformation matrix S, describing its position and orientation. The placement of the turret relative to the ship (i.e. relative to S) is T. The placement of the barrel relative to the turret (i.e. relative to T) is B. The placement of the spawn point of a projectile relative to the barrel (i.e. relative to B) is P. Then the point (written for row vector matrices)

P * B * TS =: W

gives the spawn point in world co-ordinates. It is likely that the bullet lives in world co-ordinate system. Notice how the computation shifts the reference frame from the most local placement (front of barrel) stepwise to the most global placement (world) to finally yield in exactly that, the world placement for the new object.

The direction of motion of the new bullet is given as the forward vector of the orientation of the barrel, of course to be interpreted in world co-ordinates, too. With P don't changing the orientation (in this case), this is the same as a vector extracted from W:

Wf

With the parameter given you can steer the heading of the ship, the heading of the (displaced) turret, a pitching of the (displaced) barrel, and a displacement of the spawn point distant from the origin of the barrel. There is more freedom inside the parameters, but that can be neglected for the given use case, IMHO. You can join neighbored placements if your needs for freedom is less than those, of course.

Computing the placement matrices itself is another aspect. It is often good to store position and orientation as distinct parts of the placement, because it makes local changes easier.

Lets assume that the ship drives forward. This means to shift the position by an amount along the forward direction of the orientation matrix, e.g. so:

SP := SP + SOh * speed * timestep

where the index h denotes the dimension in which heading takes place, so that SOh means a vector extracted from the ship's orientation matrix in the world.

Steering the ship means to compute the delta angle like you already do, and rotate to the new orientation, e.g. so:

SO := SO * Rh( angle_speed * timestep )

where the angular speed can be computed as fraction of the maximum angle speed, based on the current angle:

angle_speed := angle * max_angle_speed / max_angle;

The other parts can be handled similarly.

Then the joined placement of e.g. the ship is to be computed as (still demonstrated using row vectors)

S := SO * SP

Going this way lets you implement an arbitrarily complex forward kinematic (here spawn point to barrel to turret to ship) ever the same way.

### #3JohnnyBlomgren  Members   -  Reputation: 102

Like
0Likes
Like

Posted 10 October 2013 - 06:13 AM

In general it is better to not work with angles in an extent you do. Using orientation matrices and difference vectors (means a direction vector with a non-unit length, e.g. for velocity) is often simpler and more integrative. It also would reduce the amount of on-the-fly trigonometric calculations.

What are the reference frames of (x,y) and (getCenterX(),getCenterY()) as are used in shoot(), and those expected by new TextureRegion(...)? I assume that co-ordinate systems get confused.

The system can be modeled so: The placement of the ship in the world is described by transformation matrix S, describing its position and orientation. The placement of the turret relative to the ship (i.e. relative to S) is T. The placement of the barrel relative to the turret (i.e. relative to T) is B. The placement of the spawn point of a projectile relative to the barrel (i.e. relative to B) is P. Then the point (written for row vector matrices)

P * B * TS =: W

gives the spawn point in world co-ordinates. It is likely that the bullet lives in world co-ordinate system. Notice how the computation shifts the reference frame from the most local placement (front of barrel) stepwise to the most global placement (world) to finally yield in exactly that, the world placement for the new object.

The direction of motion of the new bullet is given as the forward vector of the orientation of the barrel, of course to be interpreted in world co-ordinates, too. With P don't changing the orientation (in this case), this is the same as a vector extracted from W:

Wf

With the parameter given you can steer the heading of the ship, the heading of the (displaced) turret, a pitching of the (displaced) barrel, and a displacement of the spawn point distant from the origin of the barrel. There is more freedom inside the parameters, but that can be neglected for the given use case, IMHO. You can join neighbored placements if your needs for freedom is less than those, of course.

Computing the placement matrices itself is another aspect. It is often good to store position and orientation as distinct parts of the placement, because it makes local changes easier.

Lets assume that the ship drives forward. This means to shift the position by an amount along the forward direction of the orientation matrix, e.g. so:

SP := SP + SOh * speed * timestep

where the index h denotes the dimension in which heading takes place, so that SOh means a vector extracted from the ship's orientation matrix in the world.

Steering the ship means to compute the delta angle like you already do, and rotate to the new orientation, e.g. so:

SO := SO * Rh( angle_speed * timestep )

where the angular speed can be computed as fraction of the maximum angle speed, based on the current angle:

angle_speed := angle * max_angle_speed / max_angle;

The other parts can be handled similarly.

Then the joined placement of e.g. the ship is to be computed as (still demonstrated using row vectors)

S := SO * SP

Going this way lets you implement an arbitrarily complex forward kinematic (here spawn point to barrel to turret to ship) ever the same way.

I really dont understand anything about Matrices. The x and y are the lower left corner of the ship. CenterX and CenterY is the center of the ship. It feels like I'm getting close to what I want and I would really love it if I could achieve this using angles, cos, sin etc...

I do thank you for your reply and I would reallly like to learn Matrices and such but I'd need a very good tutorial for beginners though.

Thank you.

### #4Álvaro  Crossbones+   -  Reputation: 19083

Like
0Likes
Like

Posted 10 October 2013 - 07:40 AM

float bulletX = x2 + (gunRightTipX * (float)Math.toRadians(Math.cos(angle)) - gunRightTipY * (float)Math.toRadians(Math.sin(angle)));

Remove the calls to Math.toRadians' and Math.toDegrees', and you can probably do away with the castings to float': Make angle' a float', then Math.cos(angle)' will be a float' as well, and it's not an angle, so it doesn't need to be converted to radians. If you never use anything other than radians for angles, you won't make this kind of mistake.

Better yet, follow haegarr's advice and don't use angles. You can search Khan Academy for an introduction to matrices.

For yet another alternative (very close to haegarr's proposal, actually), you can use complex numbers to represent points on the plane, vectors and rotations (multiplying a complex number by a unit-length complex number results in a rotation). The resulting code is often extremely elegant. I particularly like the approach because using unit-length complex numbers to represent rotations is a good introduction to using quaternions for 3D rotations (which requires some mental gymnastics). Feel free to ask me about how individual operations would be performed using complex numbers.

Edited by Álvaro, 10 October 2013 - 07:41 AM.

### #5Paradigm Shifter  Crossbones+   -  Reputation: 5832

Like
0Likes
Like

Posted 10 October 2013 - 07:48 AM

Yep, you don't convert the result of sin/cos calls to radians (since they represent lengths), you convert the argument (angle) to radians, if it isn't already.

So you want Math.cos(Math.toRadians(angle)) assuming angle is in degrees, otherwise get rid of the toRadians call.

I agree about using radians throughout. The only time you would normally use degrees is in a user interface so people can adjust Euler angles.

I don't think trig is that bad to represent a turret rotation, normally I am all for using vectors and matrices, but that is usually because I see an awful lot of people doing stuff like calling atan2 on a vector to get an angle and then use sin/cos to turn it back into a vector again, madness!

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

### #6Álvaro  Crossbones+   -  Reputation: 19083

Like
0Likes
Like

Posted 10 October 2013 - 08:41 AM

I don't think trig is that bad to represent a turret rotation, normally I am all for using vectors and matrices, but that is usually because I see an awful lot of people doing stuff like calling atan2 on a vector to get an angle and then use sin/cos to turn it back into a vector again, madness!

I am not sure if you are being facetious or if you didn't read the code in the first post, but that's precisely what he is doing.

In a couple of days we'll have the OP back in the forum asking how to reliably move from the turret's current position to a target position through the shortest path, because around the 0/360 mark things get messy.

### #7Paradigm Shifter  Crossbones+   -  Reputation: 5832

Like
0Likes
Like

Posted 10 October 2013 - 08:54 AM

I missed that bit, I guess I only looked at the code in the second codebox ;)

I also didn't notice the call atan2 with the arguments in the wrong order, adding PI, then negating it. Then later subtracting 90 from the degrees representation. Things are so much simpler if you stick to mathematical conventions (angles measured anticlockise from the x axis, atan2 takes the y offset as first argument, x offset as 2nd argument). EDIT: It looks like the code has been written and then angles bodged to make things work. I bet the OP didn't draw a diagram on paper before writing the code...

Well, don't do that then, use a vector.

Edited by Paradigm Shifter, 10 October 2013 - 08:56 AM.

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

### #8JohnnyBlomgren  Members   -  Reputation: 102

Like
0Likes
Like

Posted 10 October 2013 - 09:38 AM

Wow, I feel bullied here. You two pounding at me without me even replying haha... Thanks for the code snippets, I've been reading through the posts and I'll try to implement this and play around with it.

I bet the OP didn't draw a diagram on paper before writing the code...

As I said in the very beginning. Circles are a nightmare for me. Radians, cos, sin, angles, rotation.. Everything. I do appreciate the help but we all can't be equally good at math

Thanks!

### #9Paradigm Shifter  Crossbones+   -  Reputation: 5832

Like
0Likes
Like

Posted 10 October 2013 - 09:53 AM

Don't worry about it, it's just me and Alvaro having a math-off again ;). Trig is extremely important to understand though, as are vectors. Drawing diagrams is good, sitting down and coding (and adjusting numbers until it kind of works) is bad.

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

### #10Álvaro  Crossbones+   -  Reputation: 19083

Like
0Likes
Like

Posted 10 October 2013 - 11:42 AM

Wow, I feel bullied here. You two pounding at me without me even replying haha...

Yeah, sorry about that. We should have used softer gloves.

I second Paradigm Shifter's opinion about drawing diagrams and fixing your understanding when there are unexpected results, instead of just tweaking the code until it seems to work.  I feel very strongly about this, because I used to just tweak the code until it seemed to work, and that got me in trouble later on.

### #11JohnnyBlomgren  Members   -  Reputation: 102

Like
0Likes
Like

Posted 10 October 2013 - 01:32 PM

Wow, I feel bullied here. You two pounding at me without me even replying haha...

Yeah, sorry about that. We should have used softer gloves.

I second Paradigm Shifter's opinion about drawing diagrams and fixing your understanding when there are unexpected results, instead of just tweaking the code until it seems to work.  I feel very strongly about this, because I used to just tweak the code until it seemed to work, and that got me in trouble later on.

Yeah I get that. I really do want to know whats happening but in this particular case I just need this to work, and I'm happy. I'm pretty sure I can learn every row of logic when it's up and working... And I really wanna do this one without Matrices and such...

I have tweaked the code a bit now after reading previous posts but I still get very shady results. The spawn of the bullets seem to follow a much larger radius than the ship.

Code for rotating player towards direction (now with Math.Atan2(Y,X) instead of XY and no alter to the rotation with Math.PI:

				theta = Math.atan2(

rotation = (float)Math.toDegrees(theta);


But it seems like it's still here the spawn position gets really screwed (still):

	private void shoot(){
double angle = rotation;
float x2 = x;
float y2 = y;
gunRightTipX = getCenterX() + 48;
gunRightTipY = getCenterY() + 36;

float bulletX = x2 + gunRightTipX * (float)Math.cos(Math.toRadians(angle)) - gunRightTipY * (float)Math.sin(Math.toRadians(angle));
float bulletY = y2 + gunRightTipX * (float)Math.sin(Math.toRadians(angle)) + gunRightTipY * (float)Math.cos(Math.toRadians(angle));

Bullet b = new Bullet(new TextureRegion(Assets.bullet), bulletX, bulletY, (float)angle, 5f);
}


Am I referring to something crazy here?

Edited by JohnnyBlomgren, 10 October 2013 - 01:32 PM.

### #12Álvaro  Crossbones+   -  Reputation: 19083

Like
0Likes
Like

Posted 10 October 2013 - 02:09 PM

The short description of what we are proposing (removing all the confusing math as much as I can) is that instead of saving the rotation as angle', you keep it as rotationX' and rotationY'. rotationX' plays the roll of (float)Math.cos(Math.toRadians(angle))' in your code, and similarly for rotationY' and sin'.

float directionX = shootingPad.getKnobX() - shootingPad.getWidth() / 2;
float length = Math.sqrt(directionX*directionX + directionY*directionY);
rotationX = directionX / length;
rotationY = directionY / length;

// ...

float bulletX = towerCenterX + gunRightTipX * rotationX - gunRightTipY * rotationY;
float bulletY = towerCenterY + gunRightTipX * rotationY + gunRightTipY * rotationX;


I am not sure what (x,y) and (getCenterX(),getCenterY()) are, but gunRightTipX should probably be just 48 and gunRightTipY should probably be just 36.

[EDIT: Oh, when you create the bullet, instead of passing an angle and a speed, you should pass it a velocity (that is, velocityX and velocityY, which are simply rotationX*speed and rotationY*speed).]

Edited by Álvaro, 10 October 2013 - 02:12 PM.

### #13JohnnyBlomgren  Members   -  Reputation: 102

Like
0Likes
Like

Posted 11 October 2013 - 12:39 AM

The short description of what we are proposing (removing all the confusing math as much as I can) is that instead of saving the rotation as angle', you keep it as rotationX' and rotationY'. rotationX' plays the roll of (float)Math.cos(Math.toRadians(angle))' in your code, and similarly for rotationY' and sin'.

float directionX = shootingPad.getKnobX() - shootingPad.getWidth() / 2;
float length = Math.sqrt(directionX*directionX + directionY*directionY);
rotationX = directionX / length;
rotationY = directionY / length;

// ...

float bulletX = towerCenterX + gunRightTipX * rotationX - gunRightTipY * rotationY;
float bulletY = towerCenterY + gunRightTipX * rotationY + gunRightTipY * rotationX;
`

I am not sure what (x,y) and (getCenterX(),getCenterY()) are, but gunRightTipX should probably be just 48 and gunRightTipY should probably be just 36.

[EDIT: Oh, when you create the bullet, instead of passing an angle and a speed, you should pass it a velocity (that is, velocityX and velocityY, which are simply rotationX*speed and rotationY*speed).]

And as soon as you said it like this, it worked!

I sincerely thank you for this!

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

PARTNERS