Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

enter image description here

From the image you can see that the ball fired on the left that fire behind it, does not match the calculated trajectory. Im drawing the ball trajectory using an equation from a SO question, this is modified to take into consideration the box2d steps of 30 frames per second. This does calculate a valid trajectory but it does not match the actual trajectory of the ball, the ball has a smaller trajectory. I am applying a box2d force to the ball, this also has a density set and a shape. The shape radius varies depending on the type of ball. Im setting the start velocity in the touchdown event.

public class ProjectileEquation {  

    public float gravity;  
    public Vector2 startVelocity = new Vector2();  
    public Vector2 startPoint = new Vector2();  
    public Vector2 gravityVec = new Vector2(0,-10f);

    public float getX(float n) {  
        return startVelocity.x * (n * 1/30f) + startPoint.x;  
    }  

    public float getY(float n) {
        float t = 1/30f * n;
        return 0.5f * gravity * t * t + startVelocity.y * t + startPoint.y;  
    }  



} 

@Override  
    public void draw(SpriteBatch batch, float parentAlpha) {  
        float t = 0f;  
        float width = this.getWidth();  
        float height = this.getHeight();  

        float timeSeparation = this.timeSeparation;  

        for (int i = 0; i < trajectoryPointCount; i+=timeSeparation) {  
            //projectileEquation.getTrajectoryPoint(this.getX(), this.getY(), i);
            float x = this.getX() + projectileEquation.getX(i);  
            float y = this.getY() + projectileEquation.getY(i);  

            batch.setColor(this.getColor());  
            if(trajectorySprite != null) batch.draw(trajectorySprite, x, y, width, height);  

           // t += timeSeparation;  
        }  
    } 



public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
                if(button==1 || world.showingDialog)return false;
                touchPos.set(x, y);
                float angle = touchPos.sub(playerCannon.position).angle();
                if(angle > 270 ) {
                    angle = 0;
                }
                else if(angle >70) {
                    angle = 70;
                }
                playerCannon.setAngle(angle);
                world.trajPath.controller.angle = angle;
                float radians =  (float) angle * MathUtils.degreesToRadians;
                float ballSpeed = touchPos.sub(playerCannon.position).len()*12;
                world.trajPath.projectileEquation.startVelocity.x = (float) (Math.cos(radians) * ballSpeed);
                world.trajPath.projectileEquation.startVelocity.y = (float) (Math.sin(radians) * ballSpeed);
                return true;
            }



public CannonBall(float x, float y, float width, float height, float damage, World world,  Cannon cannonOwner) {
        super(x, y, width, height, damage, world);
        active = false;
        shape = new CircleShape();
        shape.setRadius(width/2);

        FixtureDef fd = new FixtureDef();
        fd.shape = shape;
        fd.density = 4.5f;
        if(cannonOwner.isEnemy) { //Enemy cannon balls cannot hit other enemy cannons just the player
            fd.filter.groupIndex = -16;
        }
        bodyDef.type = BodyType.DynamicBody;
        bodyDef.position.set(this.position);

        body = world.createBody(bodyDef);
        body.createFixture(fd);
        body.setUserData(this);
        body.setBullet(true);
        this.cannonOwner = cannonOwner; 
        this.hitByBall = null;
        this.particleEffect = null;
    }

  private CannonBall createCannonBall(float radians, float ballSpeed, float radius, float damage)
    {
        CannonBall cannonBall =  new CannonBall(CannonEnd().x, CannonEnd().y, radius * ballSizeMultiplier, radius * ballSizeMultiplier, damage, this.world, this);
        cannonBall.velocity.x = (float) (Math.cos(radians) * ballSpeed);
        //cannonBall.velocity.x = (float) ((Math.sqrt(10) * Math.sqrt(29) *
            //  Math.sqrt((Math.tan(cannon.angle)*Math.tan(cannon.angle))+1)) / Math.sqrt(2 * Math.tan(cannon.angle) - (2 * 10 * 2)/29))* -1f;
        cannonBall.velocity.y = (float) (Math.sin(radians) * ballSpeed);
        cannonBall.active = true;
        //cannonBall.body.applyLinearImpulse(cannonBall.velocity, cannonBall.position);
        cannonBall.body.applyForce(cannonBall.velocity, cannonBall.position );
        return cannonBall;
    }


trajPath = new TrajectoryActor(-10f);
        trajPath.setX(playerCannon.CannonEnd().x);
        trajPath.setY(playerCannon.CannonEnd().y);
        trajPath.setWidth(10f);
        trajPath.setHeight(10f);
        stage.addActor(trajPath);
share|improve this question
1  
It's probably better to use Box2D itself to form the trajectory, rather than attempt to replicate its calculations. –  congusbongus Sep 12 '13 at 4:16
    
wouldn't that be slow, how would that be done? –  tsukimi Sep 12 '13 at 4:19
    
I don't want to know how the ball interacts with other objects just draw a trajectory.Stepping the world i would have to store the state of all the world bodies , there are alot of them, particles etc.Then restore them after stepping the world ahead.Ive read some articles on it and they normally suggest doing the trajectory calculation when you dont need to take other world objects into account. –  tsukimi Sep 12 '13 at 6:49
    
@tsukimi: create a new box2d instance world, use it just for trajectory calculations of the ball, which you duplicate in the new instance. One object, no constraints, no interactions. Reset to origin, apply force, sample trajectory. –  Sean Middleditch Sep 12 '13 at 7:04
    
Yep, if you don't want interactions, don't put any of the other objects in the duplicate instance. The problem you're running into is Box2D is not simulating the motion exactly as you are. So the best way to simulate Box2D movement is with Box2D. –  Byte56 Sep 12 '13 at 16:16
show 4 more comments

1 Answer

up vote 3 down vote accepted

The problem is that your trajectory is built from an analytical form, while your physics engine is using an erroneous numerical approximation. Here are 3 solutions:

  1. Make a kinematic projectile: I don't know Box2D, but I'd guess there's a way to make a rigid body "kinematic," which would allow you to specify its position/velocity as a function of time. You would then just use the same logic you're using to draw the trajectory to place the projectile.
  2. Draw the trajectory using the same numerical integrator Box2D does: A simpler solution is the inverse of that, where you'd recreate the integration scheme Box2D is using to numerically solve for the drawn trajectory. Assuming Box2D uses symplectic Euler integration (I think all the physics engines do), it'd look something like this, in pseudo-code:

    while(ball.y > 0)
        ball.velocity += g * dt
        ball.position += ball.velocity * dt
        // draw ball
  3. Find a closed form solution: The tidiest answer is to find a closed form solution to the Euler integration. I just ran through all the math and come up with:

    def get_pos_at_nth_step(n)
        return x0 + n * ((0.5 * g * dt^2) * (n + 1) + v0 * dt)

    where x0 and v0 are the initial position and velocity, respectively. This allows you to keep your code as it is, but end up with a trajectory equal to what your physics engine will find. Note that the solution won't work if Box2D doesn't use symplectic Euler, but you can take the same approach.

share|improve this answer
    
An accepted answer does not indicate the question was not a duplicate. –  Byte56 Sep 18 '13 at 13:16
    
It certainly suggests it if the selected answer is different than the linked duplicate. My answer does not apply to the supposed duplicate. –  Drew Cummins Sep 18 '13 at 21:42
    
Perhaps. I asked the OP to update the question about it. They have not done so. –  Byte56 Sep 18 '13 at 21:44
    
Yeah, I found OP over on SO with the same question; is definitely not a "tie up loose ends" user, so was just trying to get the duplicate tag removed since it's a decidedly different question. –  Drew Cummins Sep 18 '13 at 22:52
1  
Fair enough. Though, this is the duplicate question to the one posted on SO (since this was posted after), but you did answer before the other person. This is exactly why cross posting sucks. –  Byte56 Sep 18 '13 at 22:57
show 1 more comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.