Jump to content
  • Advertisement
Sign in to follow this  
JohnnyBlomgren

Shoot bullet from rotating canon(s)

This topic is 2112 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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(
                  shootingPad.getKnobX() - shootingPad.getWidth() / 2,
                  shootingPad.getKnobY() - shootingPad.getHeight() / 2);

            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);
      bulletList.add(b);
   } 

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...

 

Thanks in advance!

Edited by JohnnyBlomgren

Share this post


Link to post
Share on other sites
Advertisement

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.

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

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!

Share this post


Link to post
Share on other sites


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.

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

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!

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites


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.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!