Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

Simulating a gas leak

Here is the situation : Major Tom’s spaceship is completely wrecked because it entered a very dense region of the asteroid belt, thus it was hit by uncountable micrometeoroids. In some places, tubes carrying oxygen a various gases were punctured, causing gas leak.

In the absence of gravity, these gas leaks will propel anything that crosses the gas spray.

Here is an animation showing what happens when Major Tom pushes floating boxes in a gas leak :


Let's see the code of Leak.java :

public class Leak extends Obstacle{

    private Set<Fixture> fixtures;
    private Vector2 leakForce, leakOrigin;
    private float force, leakSize;
    
    public Leak(World world, OrthographicCamera camera,    MapObject rectangleObject) {
        super(world, camera, rectangleObject);
        
        body.getFixtureList().get(0).setSensor(true);
        body.getFixtureList().get(0).setUserData("Leak");
        body.setUserData("Leak");
        
        fixtures = new HashSet<Fixture>();
        
        //Leak force
        if(rectangleObject.getProperties().get("Force") != null){
            force = Float.parseFloat(rectangleObject.getProperties().get("Force").toString()) * GameConstants.DEFAULT_LEAK_FORCE;
        }
        else
            force = GameConstants.DEFAULT_LEAK_FORCE;
        
        //Leak direction and leak origine
        if(rectangle.width > rectangle.height){
            leakForce = new Vector2(force, 0);
            leakSize = rectangle.width * GameConstants.MPP;
            
            if(force > 0)
                leakOrigin = new Vector2(posX - width, posY);
            else
                leakOrigin = new Vector2(posX + width, posY);
        }
        else{
            leakForce = new Vector2(0, force);
            leakSize = rectangle.height * GameConstants.MPP;
            
            if(force > 0)
                leakOrigin = new Vector2(posX, posY - height);
            else
                leakOrigin = new Vector2(posX, posY + height);
        }
    }
    
    public void addBody(Fixture fixture) {
        PolygonShape polygon = (PolygonShape) fixture.getShape();
        if (polygon.getVertexCount() > 2) 
            fixtures.add(fixture);
    }

    public void removeBody(Fixture fixture) {
        fixtures.remove(fixture);
    }
    
    public void active(){
        for(Fixture fixture : fixtures){
            float distanceX = Math.abs(fixture.getBody().getPosition().x - leakOrigin.x);
            float distanceY = Math.abs(fixture.getBody().getPosition().y - leakOrigin.y);
            
            fixture.getBody().applyForceToCenter(    
                                                    leakForce.x * Math.abs(leakSize - distanceX)/leakSize, 
                                                    leakForce.y * Math.abs(leakSize - distanceY)/leakSize,
                                                    true
                                                );
        }
    }
}

About this code :

  • Leak extends Obstacle
  • Leak is a sensor, so there are no physical collisions with a leak. But it still detects collisions.
  • Then we set up an HashSet called fixtures, where we’ll gather and manage all the fixtures that enter and exit gas spray.
  • We then read the properties of the rectangle we drew in Tiled to get the speed.
  • The following bunch of code lines automatically deduct the position of the leak origin and the direction of the gas spray.
  • Then we have addBody() and removeBody() functions that will be called in the GameScreen each time a body enters or exits the gas spray.
  • Finally, in the active() function we apply a force to all the bodies that are in the gas spray. Note that the force decreases as you are farther from the leak origin.
The TiledMapReader.java needs to recognize the leak. No surprises for that, it’s always the same thing, you only need to add few code lines in the main for loop :
for (RectangleMapObject rectangleObject : objects.getByType(RectangleMapObject.class)) {
            if(rectangleObject.getProperties().get("Type") != null){
                ...

                //Leaks
                else if(rectangleObject.getProperties().get("Type").equals("Leak")){
                    Leak leak = new Leak(world, camera, rectangleObject);
                    obstacles.add(leak);
                }

                ...
            }
}

In the GameScreen.java, we'll use beginContact and enContact functions of the ContactListener to add or remove bodies from the gas spray :

public void beginContact(Contact contact) {
                Fixture fixtureA = contact.getFixtureA();
                Fixture fixtureB = contact.getFixtureB();
                
                if(fixtureA.getUserData() != null && fixtureB.getUserData() != null) {
                    //Leak
                    if (fixtureA.getUserData().equals("Leak") && fixtureB.getBody().getType() == BodyType.DynamicBody) {
                        for(Obstacle obstacle : mapReader.obstacles){
                            if(obstacle.body.getFixtureList().get(0) == fixtureA){
                                Leak leak = (Leak) obstacle;
                                leak.addBody(fixtureB);
                            }
                        }
                    } 
                    else if (fixtureB.getUserData().equals("Leak") && fixtureA.getBody().getType() == BodyType.DynamicBody) {
                        for(Obstacle obstacle : mapReader.obstacles){
                            if(obstacle.body.getFixtureList().get(0) == fixtureB){
                                Leak leak = (Leak) obstacle;
                                leak.addBody(fixtureA);
                            }
                        }
                    }                 
                }          
            }


            @Override
            public void endContact(Contact contact) {
                Fixture fixtureA = contact.getFixtureA();
                Fixture fixtureB = contact.getFixtureB();
                
                if(fixtureA.getUserData() != null && fixtureB.getUserData() != null) {
                    //Leak
                    if (fixtureA.getUserData().equals("Leak") && fixtureB.getBody().getType() == BodyType.DynamicBody) {
                        for(Obstacle obstacle : mapReader.obstacles){
                            if(obstacle.body.getFixtureList().get(0) == fixtureA){
                                Leak leak = (Leak) obstacle;
                                leak.removeBody(fixtureB);
                            }
                        }
                    } 
                    else if (fixtureB.getUserData().equals("Leak") && fixtureA.getBody().getType() == BodyType.DynamicBody) {
                        for(Obstacle obstacle : mapReader.obstacles){
                            if(obstacle.body.getFixtureList().get(0) == fixtureB){
                                Leak leak = (Leak) obstacle;
                                leak.removeBody(fixtureA);
                            }
                        }
                    }
                }
            }

And that's it ! Another feature for the level design !