Search code examples
javavectorlibgdx

When adding a velocity vector to position vector, the velocity vector tends towards infinity


I am working on some code, using LibGdx. I have entity code that draws on update and an input adapter that adds to the velocity vector on key down. During debugging I've noticed that when adding the velocity vector to the position vector the velocity goes to infinity after a moment. The code can be found below:

InputAdapter:

@Override
        public boolean keyDown(int keycode) {
            switch (keycode) {
                case Input.Keys.UP:
                    e.getVelocity().add(0,1);
                    break;
                case Input.Keys.DOWN:
                    e.getVelocity().add(0,-1);
                    break;
                case Input.Keys.LEFT:
                    e.getVelocity().add(-1,0);
                    break;
                case Input.Keys.RIGHT:
                    e.getVelocity().add(1,0);
                    break;
            }
            return true;
        }

        @Override
        public boolean keyUp(int keycode) {
            switch (keycode) {
                case Input.Keys.UP:
                    e.getVelocity().sub(0,1);
                    break;
                case Input.Keys.DOWN:
                    e.getVelocity().sub(0,-1);
                    break;
                case Input.Keys.LEFT:
                    e.getVelocity().sub(-1,0);
                    break;
                case Input.Keys.RIGHT:
                    e.getVelocity().sub(1,0);
                    break;
            }
            return true;
        }

Game Class:

SpriteBatch batch;
Map<UUID,Entity> entities = new HashMap<>();
@Override
public void create () {
    batch = new SpriteBatch();
    Entity e = new Entity(UUID.randomUUID(),new Texture("avatar.png"),Vector2.Zero,Vector2.Zero);
    entities.put(e.getId(),e);
    Gdx.input.setInputProcessor(new InputAdapter(){...});
}

@Override
public void render () {
    entities.values().forEach(Entity::update);
    Gdx.gl.glClearColor(1, 0, 0, 1);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    batch.begin();
    entities.values().forEach(e->e.draw(batch));
    batch.end();
}

@Override
public void dispose () {
    batch.dispose();
    entities.values().forEach(Entity::dispose);
    entities.clear();
}

Entity Class:

 private UUID id;
private Texture texture;
private Vector2 position;
private Vector2 velocity;

public Entity(UUID id, Texture texture, Vector2 position, Vector2 velocity) {
    this.id = id;
    this.texture = texture;
    this.position = position;
    this.velocity = velocity;
}

public UUID getId() {
    return id;
}

public Texture getTexture() {
    return texture;
}

public Vector2 getPosition() {
    return position;
}

public void setPosition(Vector2 position) {
    this.position = position;
}

public Vector2 getVelocity() {
    return velocity;
}

public void setVelocity(Vector2 velocity) {
    this.velocity = velocity;
}

public void draw(SpriteBatch batch) {
    batch.draw(texture,getPosition().x,getPosition().y);
}

public void dispose() {
    texture.dispose();
}

public void update() {
    System.out.println(getVelocity());
    getPosition().add(getVelocity());
}

Output (Once down key is pressed):

(0.0,0.0)
(0.0,-1.0)
(0.0,-2.0)
(0.0,-4.0)
(0.0,-8.0)
(0.0,-15.0)
(0.0,-30.0)
(0.0,-60.0)
(0.0,-120.0)
(0.0,-240.0)
(0.0,-480.0)
(0.0,-960.0)
(0.0,-1920.0)
(0.0,-3840.0)
(0.0,-7680.0)
(0.0,-15360.0)
(0.0,-30720.0)
(0.0,-61440.0)
(0.0,-122880.0)
(0.0,-245760.0)
(0.0,-491520.0)
(0.0,-983040.0)
(0.0,-1966080.0)
(0.0,-3932160.0)
(0.0,-7864320.0)
(0.0,-1.572864E7)
(0.0,-3.145728E7)
(0.0,-6.291456E7)
(0.0,-1.2582912E8)
(0.0,-2.5165824E8)
(0.0,-5.0331648E8)
(0.0,-1.00663296E9)
(0.0,-2.01326592E9)
(0.0,-4.02653184E9)
(0.0,-8.0530637E9)
(0.0,-1.61061274E10)
(0.0,-3.22122547E10)
(0.0,-6.4424509E10)
(0.0,-1.28849019E11)
(0.0,-2.57698038E11)
(0.0,-5.15396076E11)
(0.0,-1.03079215E12)
(0.0,-2.0615843E12)
(0.0,-4.1231686E12)
(0.0,-8.2463372E12)
(0.0,-1.64926744E13)
(0.0,-3.29853488E13)
(0.0,-6.5970698E13)
(0.0,-1.31941395E14)
(0.0,-2.63882791E14)
(0.0,-5.27765581E14)
(0.0,-1.05553116E15)
(0.0,-2.11106233E15)
(0.0,-4.22212465E15)
(0.0,-8.4442493E15)
(0.0,-1.68884986E16)
(0.0,-3.37769972E16)
(0.0,-6.7553994E16)
(0.0,-1.35107989E17)
(0.0,-2.70215978E17)
(0.0,-5.40431955E17)
(0.0,-1.08086391E18)
(0.0,-2.16172782E18)
(0.0,-4.32345564E18)
(0.0,-8.6469113E18)
(0.0,-1.7293823E19)
(0.0,-3.4587645E19)
(0.0,-6.917529E19)
(0.0,-1.3835058E20)
(0.0,-2.7670116E20)
(0.0,-5.5340232E20)
(0.0,-1.10680464E21)
(0.0,-2.2136093E21)
(0.0,-4.4272186E21)
(0.0,-8.854437E21)
(0.0,-1.7708874E22)
(0.0,-3.5417749E22)
(0.0,-7.0835497E22)
(0.0,-1.4167099E23)
(0.0,-2.8334199E23)
(0.0,-5.6668398E23)
(0.0,-1.13336796E24)
(0.0,-2.2667359E24)
(0.0,-4.5334718E24)
(0.0,-9.0669436E24)
(0.0,-1.8133887E25)
(0.0,-3.6267775E25)
(0.0,-7.253555E25)
(0.0,-1.450711E26)
(0.0,-2.901422E26)
(0.0,-5.802844E26)
(0.0,-1.1605688E27)
(0.0,-2.3211376E27)
(0.0,-4.642275E27)
(0.0,-9.28455E27)
(0.0,-1.85691E28)
(0.0,-3.71382E28)
(0.0,-7.42764E28)
(0.0,-1.485528E29)
(0.0,-2.971056E29)
(0.0,-5.942112E29)
(0.0,-1.1884224E30)
(0.0,-2.3768449E30)
(0.0,-4.7536898E30)
(0.0,-9.5073795E30)
(0.0,-1.9014759E31)
(0.0,-3.8029518E31)
(0.0,-7.6059036E31)
(0.0,-1.5211807E32)
(0.0,-3.0423614E32)
(0.0,-6.084723E32)
(0.0,-1.2169446E33)
(0.0,-2.4338892E33)
(0.0,-4.8677783E33)
(0.0,-9.7355566E33)
(0.0,-1.9471113E34)
(0.0,-3.8942226E34)
(0.0,-7.7884453E34)
(0.0,-1.5576891E35)
(0.0,-3.1153781E35)
(0.0,-6.2307562E35)
(0.0,-1.24615125E36)
(0.0,-2.4923025E36)
(0.0,-4.984605E36)
(0.0,-9.96921E36)
(0.0,-1.993842E37)
(0.0,-3.987684E37)
(0.0,-7.975368E37)
(0.0,-1.5950736E38)
(0.0,-3.1901472E38)
(0.0,-Infinity)
(0.0,-Infinity)
(0.0,-Infinity)
(0.0,-Infinity)
(0.0,-Infinity)
(0.0,-Infinity)
(0.0,-Infinity)

I'm unsure why the velocity vector is increasing, since the only time that it is modified is during the keydown event. If anyone could explain why this is happening, it would be much appriciated!


Solution

  • In Java, there is no such thing as an immutable object. You have passed in two copies of the static object instance Vector2.Zero to every new Entity. So whenever you press a key, you are adding a value to Vector2.Zero, and whenever you update an entity, you are adding Vector2.Zero's new value to itself, multiple times because the same instance is referenced by every Entity object.

    A safe way to create your Entity class is to have it instantiate its own final Vector2 instances:

    public class Entity {
    
        private UUID id;
        private Texture texture;
        private final Vector2 position = new Vector2();
        private final Vector2 velocity = new Vector2();
    
        public Entity(UUID id, Texture texture) {
            this.id = id;
            this.texture = texture;
        }
    
        //...
    }
    

    Also, it is not a good idea to instantiate a new Texture object for every instance of Entity, when they all use the same image. You are loading many copies of the exact same image data to the GPU redundantly. Load your Texture one time and pass the same instance to all Entities, and don't forget to dispose of it in your dispose() method. Don't dispose of it in the Entity's dispose method, since it would then be disposed multiple times (the Entity doesn't "own" the Texture since it didn't instantiate it).