Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles

GalaxSeed DevLog

A topic by Roaring Cat Games created Jun 15, 2016 Views: 316 Replies: 4
Viewing posts 1 to 5
Submitted(+1)

With a nice break after the initial Jam, we're ready to get back into full swing of development on GalaxSeed through the remaining weeks of Kentucky Fried Pixels. We'll be posting to this thread with updates on our development process and progress.

Submitted

Game: GalaxSeed

Jam Entry: Existing Project, expanding and polishing features.

Gameplay: It's an arcade shooter that would fall well into the "bullet hell" category. You control a ship that has various planting mechanisms (weapons) to spread life on the incoming asteroids and comets. You have to dodge your way through the obstacles. Successfully planting trees slows down fragments and subdues angry asteroids, but planted or not, you must avoid everything so navigate carefully.

Tools: libGDX (Java), Ashley (An entity component system built to work with libGDX), IntelliJ (IDE), Adobe Illustrator, Spriter

Team: @Barryrowe (code cat), @loilemix (art cat), Nathan Hutchens joining the team for this project on music and sound design

Background:

We started GalaxSeed a while back as part of the libgdx jam, and at the time we called it Planetary Planter. We had never participated in a month-long Jam before, and our end product was far less than complete. We procrastinated more than we worked on the game and it showed.

Since the end of libgdx jam, we put in quite a bit more effort to create at least a full play cycle experience (Menu, Start Game, Play, Game Over, Repeat). During this time we really added features and generic systems to a sibling project: Kitten2d Ashley Extensions. All of the documentation needs to be updated, and plenty of performance and general code clean-up needs to be done, but between January and Mid-May we made a lot of progress to make this extension library useful for jumpstarting a libGDX-Ashley based project.

We showed the game at an event in Louisville right as we had gotten the first draft ready to what we THOUGHT we were going to release. Based on feedback at the event, and that of other developers, we realized we really needed to re-think a lot of things. The game needed more variety, and needed to decide what it was going to be: Classic Shooter or full-on Bullet Hell?

With Kentucky Fried Pixels right around the corner we decided to take Planetary Planter, and re-work the majority of the game and use this jam as the time-box to finish those changes.

Features Planned for the Jam:

  • Implement multiple weapons instead of a single seed gun
  • Implement weapon upgrades
  • Design enemy spawn timings and paths rather than procedural generation
  • Focus on dodging, and make that fun
  • Implement "touch-up" menu/pause system based on passive mode of Squadron and Squadron 1945
  • Implement single-charge special weapon
  • All new music and sounds
  • Fix the player control for high DPI screens and allow switching between "normal" and "enhanced" movement

Kickoff Jam Accomplishments:

  1. We implemented mechanics and visuals for all three of the planned player weapons
  2. Implement all of the upgrades for each of the new weapons
  3. Began brainstorming music design
  4. Resolved a bug causing rotated textures to lose quality

Above: Shot of the new pollen aura weapon. It slows down anything that is inside of the field, and slowly pollenates it to grow trees. Slowed items are indicated with a yellow shade while they are being pollenated.

Above:Example of the difference between using Nearest and Linear openGL texture filters for our rotated textures.

Next Steps:

  • Weapon Selection and loss
    • Right now in the post-jam version, you switch weapons every-time a "touchDown" event fires (mouse-click or tap). What we want to implement is when you lift your finger on mobile (or release mouse on desktop) the game "slows-down" and you get a weapon-pod system where you can select which weapon you wish to switch to. This will also be where we can pause the game.
  • Design the introduction "levels" from Pluto up to Saturn
  • Implement a proper options and Credits screen
Submitted (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..)

Submitted

Over the last week we have been working on determining the bg music feel and designing out the weapon select UI.

The original plan for the weapon select UI was to have 3 little plant pods that would "bloom" when you selected one to activate a weapon. That sounded really good, but in practice it was pretty confusing. The biggest issue was that each of the pods would need to somehow indicate to the player which weapon they contained BEFORE they were selected and opened. We discussed using different colors, or somehow etching an icon on the outside, but nothing felt right.

On top of being confusing for the player, having to design a proper state transition to "bloom" and "un-bloom" the selections was becoming fairly annoying to keep track of with the animation system we currently have in place. (NOTE: this is a limitation of our animation and state system, and not really a problem with the approach)

Lastly, these blooms also lacked a way to indicate to the user how many upgrade levels each weapon has obtained. To stick with the aesthetic we would have to either place some colorful leaves around the bloom, or "grow" some small flowers below them. Nothing we could come up with felt like it would be a useful indicator to the player, and would just look like over-designed animation on the gui.

What we fell back to was a simple circular button system, with a three-section ring around each indicator. Each of the sections will "light-up" as the weapon is upgraded (no upgrades: all three grayed out, and each upgrade fills in a grayed-out section). To indicate the current weapon selection, we have a "rotating coin" style animation on the button so that it is clear which item is active.

Loi has only created assets for the Pollen Aura weapon, but here is a rough screenshot of the layout:

We are still working on the BG music but we've nailed down a good direction, and Nathan ( @TheLucidBard) has worked up a great draft for us. You can catch one of our favorite riffs from the track in the vine below (as well as seeing the weapon UI in action). We've been playing with the tempo on the track a little and found that the play style so far is nice with the speed throttled to about 70% tempo.

Submitted

Over the last few days we've been working on solidifying the weapon select experience, and in the meantime we ran into an unexpected bug that led me (@barryrowe) down a bit of a refactoring rabbit hole.

The bug had to do with converting touch events to world units properly. This is a common issue as its often better to work in "World Units" instead of pixels so that you can better manage units relative to your game world rather than whatever device might happen to be running the game. Even better, being a common practice, most engines and libraries provide a quick way to do this via your camera object(s). LibGDX is no exception and the Camera API provides both project() and unproject() methods

//Based on working in a Screen of 640x960 pixels
//Our desired width and Height in world units
private static final float WORLD_WIDTH = 20f;
private static final float WORDL_HEIGHT = 30f;

private Vector3 screenPoint = new Vector3(320f, 480f, 0f);
private Vector3 worldPoint = new Vector3(10f, 15f, 0f);

Camera cam = new OrthographicCamera(WORLD_WIDTH, WORLD_HEIGHT);
//World to Screen
cam.project(worldPoint); //worldPoint is updated to have values [320f, 480f, 0f] //Screen to World cam.unproject(screenPoint); //screenPoint is updated to have values [10f, 15f, 0f]

This is pretty straight forward, and so we were passing in the camera to our System(s) that needed to handle user input, and we just use camera.unproject() to get the world units, and from there find what was touched. Right? Well, we made the mistake of thinking this was all we would ever need to do, and didn't account for the viewport (Read up on libgdx viewports if you're interested). It turns out that if the viewport comes into play for resizing (we are using a FitViewport currently), the camera doesn't necessarily know about this adjustment when calling unproject(). Instead, we really needed to have access to the viewport object itself, and use Viewport.unproject(), otherwise we would end up with world units that don't match up to the adjusted viewport and we found that on odd device screens tapping to select a weapon was just completely broken.

The bigger problem this uncovered, was that we had written the kitten2d RenderingSystem to create the camera for the game. Looking back, this shouldn't have been the job of the rendering system. The RenderingSystem definitely needs a reference to the camera, but it shouldn't be responsible for creating it and exposing said camera to other systems/events that need it. Now in the future, it's probably the right call to make cameras an Entity with a CameraComponent, but for now we just moved this responsibility up to the Game implementation itself. We then make the Game implement IGameProcessor which can then expose the camera and viewport objects. We're free to pass the IGameProcessor reference to our Screens (which can provide the camera, viewport, gameProcessor, whatever is needed, on down to the systems that screen adds to its Ashley Engine.

You can see the bulk of this refactor in this commit (b4fbfb6a5). Feel free to laugh at how many cameras were being generated for some reason...it's kind of embarrassing.

Anyway, now that we knocked out this big adjustment, we're ready to start knocking out the final sets of features before the #kentuckyfriedpixels deadline.

Hopefully we finish strong and produce something at least a few people find fun.