Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
(1 edit)

Tonight we've been working on the weapon switching mechanic. The goal is to implement it so that:

  • When the player releases their finger (touchUp) we go into an "options" or "passive" mode that allows the player a chance to switch to another available weapon
  • When in "options" mode everything is dramatically slowed down, but not fully paused.
  • When the player touches back down, if they touch one of the open weapons, the player's weapon is reset
  • When the player touches down if it's not on one of the weapon selects, the game resumes and goes back to full speed
  • The weapon selections will be plant pods that close/open when selected or deselected (not implemented yet)

For this we have a single System: WeaponChangeSystem, and a global flag for whether the app is slowed or not App.isSlowed().

To achieve our goal we need WeaponChangeSystem to extend EntitySystem, and implement InputProcessor

public class WeaponChangeSystem extends EntitySystem implements InputProcessor {
...

When the system is added to the Engine we need to perform our setup: Creating the entities needed to represent the weapon switching buttons, and register the system with the game's inputMultiplexer. (NOTE: this code shows some basic examples of generating entities with the Kitten2d Ashley Extension components). In our game we also keep a static accessor to our root Game object. This is fragile and frowned upon in a larger game, but effective for easy access to globally managed resources like the inputMultiplexer and game state with a small game like this.

@Override
public void addedToEngine(Engine engine) {
    super.addedToEngine(engine);
    App.game.multiplexer.addProcessor(this);
    PooledEngine pEngine = (PooledEngine)engine;
    if(seedSelect == null){
        seedSelect = pEngine.createEntity();
        seedSelect.add(TransformComponent.create(pEngine)
            .setPosition(App.W/4f, 0f, Z.weaponSelect)
            .setHidden(true));
        seedSelect.add(BoundsComponent.create(pEngine)
            .setBounds(0f, 0f, 2f, 2f));
        seedSelect.add(TextureComponent.create(pEngine));
        seedSelect.add(StateComponent.create(pEngine).setLooping(false).set("DEFAULT"));
        seedSelect.add(AnimationComponent.create(pEngine)
                .addAnimation("DEFAULT", Animations.getSeedPod())
                .addAnimation("SELECTED", Animations.getSeedPodOpening()));
        pEngine.addEntity(seedSelect);
    }
    if(helicpoterSelect == null){
        helicpoterSelect = pEngine.createEntity();
        helicpoterSelect.add(TransformComponent.create(pEngine)
                .setPosition(2f * (App.W / 4f), 0f, Z.weaponSelect)
                .setHidden(true));
        helicpoterSelect.add(BoundsComponent.create(pEngine)
                .setBounds(0f, 0f, 2f, 2f));
        helicpoterSelect.add(TextureComponent.create(pEngine));
        helicpoterSelect.add(StateComponent.create(pEngine).setLooping(false).set("DEFAULT"));
        helicpoterSelect.add(AnimationComponent.create(pEngine)
                .addAnimation("DEFAULT", Animations.getHelicopterPod())
                .addAnimation("SELECTED", Animations.getHelicopterPodOpening()));
        pEngine.addEntity(helicpoterSelect);
    }
    if(auraSelect == null){
        auraSelect = pEngine.createEntity();
        auraSelect.add(TransformComponent.create(pEngine)
                .setPosition(3f*(App.W/4f), 0f, Z.weaponSelect)
                .setHidden(true));
        auraSelect.add(BoundsComponent.create(pEngine)
                .setBounds(0f, 0f, 2f, 2f));
        auraSelect.add(StateComponent.create(pEngine).setLooping(false).set("DEFAULT"));
        auraSelect.add(AnimationComponent.create(pEngine)
            .addAnimation("DEFAULT", Animations.getAuraPod())
            .addAnimation("SELECTED", Animations.getAuraPodOpening()));
        auraSelect.add(TextureComponent.create(pEngine));
        pEngine.addEntity(auraSelect);
    }
}

Now that we have our system setup when the engine is added we need to handle when the player touches or stops touching. On touchUp we want to setup our "options" mode state. This is very basic right now, and will be expanded to activate the different animation states of the weaponSelect buttons, but for now it just shows them all and sets the "isSlowed" flag on the application.

@Override
public boolean touchUp(int screenX, int screenY, int pointer, int button) {
    if(App.getState() != GameState.GAME_OVER && !App.isSlowed()) {
        App.setSlowed(true);
        K2ComponentMappers.transform.get(seedSelect).setHidden(false);
        K2ComponentMappers.transform.get(auraSelect).setHidden(false);
        K2ComponentMappers.transform.get(helicpoterSelect).setHidden(false);
    }
    return false;
}

The next thing we need to do is capture touchDown and either switch to the new weapon or resume into normal playing mode. To do this we simply unproject the touchpoint, and check if it falls within the bounds of any of our buttons. (notice we aren't creating a new Vector3 object here, but reusing a class-level instance to reduce additional garbage collection over time):

@Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
    if(App.isSlowed()){
        touchPoint.set(screenX, screenY, 0f);
        this.cam.unproject(touchPoint);
        BoundsComponent seedBounds = K2ComponentMappers.bounds.get(seedSelect);
        BoundsComponent helicopterBounds = K2ComponentMappers.bounds.get(helicpoterSelect);
        BoundsComponent auraBounds = K2ComponentMappers.bounds.get(auraSelect);
        if(seedBounds.bounds.contains(touchPoint.x, touchPoint.y)){
            switchWeapon(WeaponType.GUN_SEEDS);
        }else if(helicopterBounds.bounds.contains(touchPoint.x, touchPoint.y)){
            switchWeapon(WeaponType.HELICOPTER_SEEDS);
        }else if(auraBounds.bounds.contains(touchPoint.x, touchPoint.y)){
            switchWeapon(WeaponType.POLLEN_AURA);
        }else{
            App.setSlowed(false);
        }
    }
    return false;
}

The last thing we need to do is make sure our render method knows to throttle the detlaTime sent to the Engine when App.isSlowed() is true. So in our SpaceScreen we update the update() method like so:

@Override
protected void update(float deltaChange) {
    float deltaToApply = Math.min(deltaChange, App.MAX_DELTA_TICK);
    if(App.isSlowed()){
        if(App.isSlowed()) {
            deltaToApply *= App.SLOW_SCALE;
        }
    }
    engine.update(deltaToApply);
...

With these changes in place, we get the effect you can see in this vine of gameplay. All of the enemies and animations slow down while in "options" mode, and you can switch weapons by clicking/tapping on one of the weapon select 'buttons'. (I was unaware of the background conversation at the time, but it oddly fits..)