Sunday, January 6, 2019

Ludum Dare 43

I realized I never made a post about my participation in Ludum Dare 43 where I finished my newest project, Slime Venture!

The theme for the latest Ludum Dare was "Sacrifices must be made" and what better thing to sacrifice than your own body or the bodies of similar creatures? I decided early on to stick with my theme of 2d platformers as I have become very familiar with the systems involved and I new I could get a functional prototype done quickly. I didn't dedicate the whole weekend to the compo so I wasn't willing to tackle something entirely new.

Credit to my wife for suggesting slimes, which were a great choice as I didn't have to put much effort into assets. Simple colored cubes while not much to look at were fully functional and believable. The bright colors also made it easy to differentiate the slimes from each other and the background.

JMonkeyEngine is of course my go to 3d engine with Java as the primary language. I decided midway through the project to also incorporate Groovy script to supplement Java and give me an easy way to test features without needing to compile. This also taught me that when Netbeans' Gradle plugin compiles a project with a sub-project containing your assets, those assets are copied and stored in a separate folder. After an hour of head scratching I figured this out and made a simple work around to access files directly for testing only.

I used dyn4j for the physics and collisions. Integrating the physics into JME is easy and consistent.

Zay-ES was used to take advantage of an entity-component system. While I don't feel as strongly about ECS as I used to (At one point I couldn't fathom making a game without one) I do still feel they are very useful and when implemented correctly greatly speed up prototyping. The art is in finding a balance between to many and to few components for any given situation and really fleshing out your systems on paper. Remember, your systems ultimately drive what components you make and not the other way around!

Lemur UI is my favorite GUI system for JME. It lacks some features but is so easy to work with that I rarely run into an issue I can't resolve.

The game loop is very simple: Every level has a spawn point that sets the player's avatar to a slime with a given size and color. Every level needs to specify a trigger to activate level end. The only trigger I used for the Jam was a simple detection zone that would detect the player entities id if the player was inside the zone. There was no visual editor for levels and they are all described via groovy script calling java functions to build the various set pieces.

Slimes all have a size (1-5) and a color (blue or green in the prototype). Slimes who are a larger size will consume slimes of a smaller size. This is true for slimes of the same color, so beware you can still be eaten by "friendly" looking slimes! If you consume a slime that is your color and smaller than you, you grow 1 size. If you consume a slime that is a different color and 1 size smaller than you, you shrink 1 size. Smaller slimes move faster and jump higher than big slimes. Most levels features obstacles that were to small for large slimes to pass through, so you would need to shrink yourself enough to squeeze through. Other levels would require you to eat slimes in a certain order to be the correct size to finish the level.

All in all this Ludum Dare was tons of fun. I didn't dedicate my entire weekend, and altogether probably spent 12 hours working on this project. By setting the scope very small and dedicating more time to interacting with my wife and kid I had a much easier time focusing on programming when I did sit down to work.

Monday, December 31, 2018

Happy New Years!

I hope you had a wonderful holiday season and are welcoming in the new year! Here's a little update on the state of my projects.

I worked a lot of overtime at my day job this month so I wasn't able to completely re-write Reindeer Rearing like I had hoped, but I was able to greatly reduce the ram usage by cutting out a largely redundant system I was using to handle sprites. I was able to touch on all of the mini games at least a little under the hood to make them better self contained and more reliable. I haven't experienced any crashes through testing but if you find any bugs don't be afraid to message me! Lastly I re worked how the toy builder mini game handles the different toys. What was before 7 textures and a data file has been reduced to a single texture! This isn't a texture sheet either, to add new toys is as simple as drawing their finished sprite. The game will automatically split this sprite into 3 pieces and color it black when it's loaded. The sprites are currently all hard-coded but in the future I'd like to support user files to add to a sprite list.

Now that the holiday season is wrapping up I look forward to the next item on my list, Project Gothic. Gothic is still the most viewed/downloaded game on my itch and it desperately deserves to be re-worked into a more functional and finished project. I have no time frame on when I plan to move updates for Gothic, except that I will be converting the project over to a Gradle project tomorrow and I'll start whittling away at it after that. Gothic is the focus until I deem it in a polished state!

While Gothic is my #1 focus I find I tend to work better if I have a side project going for when I hit roadblocks. The side project while I work on Gothic is going to be Slime Venture! I fully intend to polish Slime Venture into a finished project now that the Jam is over. It was well received and I believe has a very firm framework that held up very well during the short period I put it together. It won't be my primary focus for the next couple of months but I will be revisiting the level generation and menus to make it a better game in my spare time. I will also be making some finished assets for it to practice with Blender animations and prep for more detailed work that Gothic will require.

I wish you the happiest new year!

Sunday, November 4, 2018

Spooky Progress and More News

So I had originally hoped to have Spooky finished by Halloween. That didn't happen. I've actually become quiet invested in making a suite of 2d platforming tools within JME which has resulted in Spooky taking a LOT longer than originally anticipated, and also means the scope of Spooky is probably getting bigger than originally planned as well. Making games is fun!

I have the foundation for a much more robust Sprite system than I've done before, and I've implemented Tile Maps with a custom collision system that is looking very promising. The platforming aspects of Spooky are something I've wanted to do in games for a long time, but I always used pre-made physics systems. Using physics is great for some games, but in games where you want more deterministic responses to collisions and the environment they tend to make simple things more difficult. Hopefully this makes Spooky's controls much tighter and more responsive than Dungeon Hordes were.

The month of November will likely not see any major updates to Gothic as I had expressed last time. I still really want to sit down and work through all my poorly realized net code and incomplete understanding of ES systems to make Gothic the game it deserves to be. For now it has been moved from "Probably in November" to "Maybe in January". Not the best news but that's where it's at.

Reindeer Rearing will still very likely see updates come December. A large amount of work I am doing for Spooky should be directly applicable to Reindeer Rearing. I'm honestly excited about the chance to clean things up and really fix some of the more game breaking bugs I had left due to time constraints. (Reindeer Rearing was originally made for a Jam).

My current schedule looks something like this: Continue working on Spooky with the hopes of getting a vertical slice level by the end of the month. Once December starts wherever I am at with Spooky will go on hold until I can at least clean up Reindeer Rearing and tackle some of the bigger bugs as well as re-do the sprite system to be in line with Spooky's sprite system. Once RR has seen enough polish to warrant another update, go back to Spooky.

Next update will have some screenshots no matter how ugly they are!

Thursday, October 4, 2018

A Scheduling Update

Hello again! it's been a couple of months so I figured I should make a post saying what I've been up to and my current road map.

First off, I disappeared again after getting another job. Finding good ones is hard, but this one will likely stick for awhile. My hours are a lot better and I have had a LOT more time to work on things, of course I also have my kid, so that's where a lot of that time went!

I also built a new computer from scratch. This was a much needed upgrade though it did result in a couple of weeks where I didn't have access to my usual desktop environment. It also lead to me switching from Windows to Linux. I still have a copy of Windows 10 that I mostly use for playing games, I am about 90% on Linux Mint now. This means I have less distractions since most games are a reboot away so I can focus on tasks better.

I've been working on my programming projects so here's the quick rundown:

Project Dawn (from WAY back when I first tried making a game in unity) has been re-booted entirely and I've actually made a lot of progress. It's a 3rd person spaceship game somewhere in between the X games and EVE Online. I probably won't mention more than that for a long time since that's a very large project and even though I've been burning through my checklist it's still a very long ways from being playable.

Sheep Tutorial is kind of on hiatus for now. I lost some of my source files when I transferred (an issue with getting Linux to play nice with ntfs file systems caused by my relative lack of Linux experience) but it's not abandoned, just waiting for a long weekend where I can sit down and get back up to speed.

For the month of October I am working on a project called Spooky! It's planned to be a 2d platformer I am planning to launch on October 31st. More info will come as I make progress worth showing.

If all goes well with Spooky and I get relatively close to my release without any major issues, November will be dedicated to going back and really re-working Gothic. I've noticed it's probably my highest trafficked project and I really sort of dumped it without giving it a proper round of polish. I won't make any major promises outside of cleaning up the UI and re-working alot of systems that were largely hacked together.

Lastly, December will be Reindeer Rearing's month of love. I plan on thoroughly re-working it's systems and giving it a solid round of polish it definitely deserves.

Well that's pretty much all I have for right now. I hope to have some screenshots and better news to share shortly!

Tuesday, July 17, 2018

Simple Arcade Game 4: User Input and Spatial Controls

Welcome back to our simple arcade game tutorial where we look into what it takes to make a very simple, flock herding game using jMonkeyEngine. Today we are going to start adding logic to our game. We are going to start with a simple way to steer the white cube we made last time by reading user input, and applying the movements to our game objects via Controls.

First, let's look at our GameState class. Our GameState extends AbstractAppState and overrides the Initialize method. We also have a custom method to spawn our player object. We are going to add a few more lines in our Initialize method to handle user input.

First let's grab the SimpleApplications input manager. The input manager needs to have Mappings added which we will later listen for, so let's define some static mapping names so they are easy to find later. We then take our InputManager and add our mappings to specific key bindings. For keyboard keys we use a KeyTrigger and grab the keycode from the KeyInput class.

public class GameState extends AbstractAppState{
    //We make the input mappings public and static so they are easy to find later
    public static final String PLAYER_FORWARD = "Forward_Move";
    public static final String PLAYER_BACKWARD = "Backward_Move";
    public static final String PLAYER_LEFT = "Left_Move";
    public static final String PLAYER_RIGHT = "Right_Move";
    private Spatial player;
    private Node scene;

    @Override
    public void initialize(AppStateManager stateManager, Application app) {
        SimpleApplication application = (SimpleApplication)app;
        AssetManager am = application.getAssetManager();
        Node rootNode = application.getRootNode();
        scene = new Node("Game_Scene");
        rootNode.attachChild(scene);
        
        player = createPlayer(am);
        scene.attachChild(player);
        
        //configure user input
        InputManager input = application.getInputManager();
        input.addMapping(PLAYER_FORWARD, new KeyTrigger(KeyInput.KEY_UP));
        input.addMapping(PLAYER_BACKWARD, new KeyTrigger(KeyInput.KEY_DOWN));
        input.addMapping(PLAYER_LEFT, new KeyTrigger(KeyInput.KEY_LEFT));
        input.addMapping(PLAYER_RIGHT, new KeyTrigger(KeyInput.KEY_RIGHT));
    }
    
    ...
}

If you run the game now and start pressing the cursor buttons... nothing happens! We still need to register a listener for these mappings. Let's make GameState implement ActionListener so anything we want to do with player input can be done very easily.

The ActionListener interface has a single method we need to override, the onAction(String name, boolean isPressed, float tpf) method. When we register a listener to the key input, the onAction method gets called from ALL keybindings we register. Therefor, we need to sort through all possible inputs with if or switch statements. I prefer if statements because it is easier to assign blocking vs non blocking keybindings.

public class GameState extends AbstractAppState implements ActionListener{
    ...

    @Override
    public void initialize(AppStateManager stateManager, Application app) {

        ...
        
        //disable the default flycam
        application.getStateManager().getState(FlyCamAppState.class).setEnabled(false);

        //configure user input
        InputManager input = application.getInputManager();
        input.addMapping(PLAYER_FORWARD, new KeyTrigger(KeyInput.KEY_UP));
        input.addMapping(PLAYER_BACKWARD, new KeyTrigger(KeyInput.KEY_DOWN));
        input.addMapping(PLAYER_LEFT, new KeyTrigger(KeyInput.KEY_LEFT));
        input.addMapping(PLAYER_RIGHT, new KeyTrigger(KeyInput.KEY_RIGHT));
        //here we register GameState as a listener with the input manager and assign
        //it all the mappings we want to listen to
        input.addListener(this, PLAYER_FORWARD, PLAYER_BACKWARD, PLAYER_LEFT, PLAYER_RIGHT);
    }
    
    ...

    @Override
    public void onAction(String name, boolean isPressed, float tpf) {
        System.out.println(name+" was pressed");
        //We WANT the forward button press to block a backward button press, so we use if/else
        if(name.equals(PLAYER_FORWARD)){
            
        } else if(name.equals(PLAYER_BACKWARD)){
            
        }
        //we DON'T want the forward or backward buttons to block left and right buttons
        if(name.equals(PLAYER_LEFT)){
            
        } else if(name.equals(PLAYER_RIGHT)){
            
        }
    }
}

Now if you press play you can use the arrow keys to print the name of the binding you are currently pressing to the console. You may also notice the camera moving when you use the arrow keys, this is because SimpleApplication creates a FlyCam by default. It's easy enough to turn off by adding another line to our Initialize Method.

We have our user input registered with our input manager, now how do we get this to control our cube? The simple answer, Controls. Let's make a new package com.mrugames.unit and create a new class, Mob.java. Mobs in game design lingo are simply mobile entities, or anything that moves. That is exactly what our mob control is going to do, move whatever it is attached to at a specific speed every frame. Make our Mob class extend AbstractControl and override the 2 abstract methods controlUpdate(float tpf) and controlRender(RenderManager rm, ViewPort vp). We won't be using control render in our mob class, but it is a good place to put render specific code.

For our Mob class we want to store the maximum speed the mob can move as well as the current input direction for the mob. We will assume the input direction is a number between 0 and 1. Every frame that the input direction is greater than 0, we move the mob by it's maxSpeed*inputDirection*tpf (time per frame).

The Control class has a convenient "spatial" field which you can use to access the Spatial the control is attached to. Spatials also have a convenient "move" method which takes a Vector3f argument.

public class Mob extends AbstractControl{
    public float maxSpeed = 1f;
    public Vector2f dir = new Vector2f(0,0);

    @Override
    protected void controlUpdate(float tpf) {
        //every frame that dir is greater than 0, move in the direction of dir by
        //our max speed
        if(dir.lengthSquared() > 0){
            //spatial is a protected field inherited from AbstractControl
            spatial.move(dir.x*maxSpeed*tpf, dir.y*maxSpeed*tpf, 0);
        }
    }

    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {
        
    }
}

Now that we have our Mob control, we need to attach it to our player Spatial. We can then access our Mob control in our ActionListener to set the Mob.dir to match the players input.

public class GameState extends AbstractAppState implements ActionListener{    

    ...

    private Spatial createPlayer(AssetManager am){
        Mesh mesh = new Box(0.5f,0.5f,1f);
        Geometry geo = new Geometry("Player", mesh);
        Material mat = new Material(am, "Common/MatDefs/Misc/Unshaded.j3md");
        geo.setMaterial(mat);
        //Add a new Mob control to the player
        geo.addControl(new Mob());
        return geo;
    }    

    ...

    @Override
    public void onAction(String name, boolean isPressed, float tpf) {
        Mob playerMob = player.getControl(Mob.class);
        //We WANT the forward button press to block a backward button press, so we use if/else
        if(name.equals(PLAYER_FORWARD)){
            if(isPressed){
                //Called the frame the button was pressed down
                playerMob.dir.y =1 ;
            } else{
                //Called the frame the button was released
                playerMob.dir.y = 0;
            }
        } else if(name.equals(PLAYER_BACKWARD)){
            if(isPressed){
                playerMob.dir.y = -1;
            } else{
                playerMob.dir.y = 0;
            }
        }
        //we DON'T want the forward or backward buttons to block left and right buttons
        if(name.equals(PLAYER_LEFT)){
            if(isPressed){
                playerMob.dir.x = -1;
            } else{
                playerMob.dir.x = 0;
            }
        } else if(name.equals(PLAYER_RIGHT)){
            if(isPressed){
                playerMob.dir.x = 1;
            } else{
                playerMob.dir.x = 0;
            }
        }
    }

As you see there are some simple tricks we use to process the player input. The ifPressed variable will be true the frame that the player activates the mapping, and false on the frame that the mapping is deactivated. This makes on/off functionality easy. If you need to be notified every frame that a mapping is being activated, take a look at the AnalogListener class. We will cover the AnalogListener in more detail later.

Go ahead and press play! You'll see our little cube drift around the screen corresponding to your button presses.

Monday, July 2, 2018

Simple Arcade Game 3: Scenegraph

Hello again and welcome to another jMonkey Engine tutorial! Today we are going to take a detailed look at probably the most important aspect of jMonkey Engine, it's Scenegraph.

A great overview of how jme uses it's scenegraph is available here, but I'll run a brief overview for those who want to dive right in.

The Scene in jme is made up of Spatials, mostly Nodes and Geometries. Nodes do not have any visual elements, but can have children elements. Geometries have meshes and materials and will be rendered, these are the elements you actually see! All spatials must be connected to the RootNode, which is the top most node in the scene. You can easily make complex or large scale models with many moving parts by mixing Nodes and Geometries.

The most important thing to remember: DO NOT EXTEND NODE, GEOMETRY OR SPATIAL. Instead you should store your game data seperately, in the Spatial's UserData container, or as a Control which is attached to a spatial.

UserData can be any Object and is easily written by using Spatial.setUserData(String key, Object data), and later retrieved using Spatial.getUserData(String key).

Controls are a lot like AppStates, except they only work on the spatial they are attached to. We will go over Controls in a later tutorial, but if you are interested in making your own controls now they are easily attached to a spatial using Spatial.addControl(Control control), and retrieved by using Spatial.getControl(Class<T> controlType) where T extends Control.

We are going to make a new appstate for our game, let's call it "GameState". We need to override the "initialize" method as this is where we will be modifying our scene.


public class GameState extends AbstractAppState{
    private Spatial player;
    private Node scene;

    @Override
    public void initialize(AppStateManager stateManager, Application app) {
        SimpleApplication application = (SimpleApplication)app;
        AssetManager am = application.getAssetManager();
        Node rootNode = application.getRootNode();
        scene = new Node("Game_Scene");
        rootNode.attachChild(scene);
    }
    
}

Lets go ahead and cast the Application app to a SimpleApplication so we can access our RootNode and the AssetManager. We should also define a Spatial object that will reference the Players spatial so we can easily find it later. We also define a Node for the Scene. Create a new Node with a string argument for the node's name and attach it to the root node via the Node.attachChild() method. We will be attaching all the game objects to the game scene instead of the root node to keep them separated and easy to work with!

Now let's define a new method to create our player object. We will eventually be using a Dragon model, but for now let's simple spawn a box to represent our player. First we define a new method, createPlayer(AssetManager am). We are going to pass the assetManager from our Application object to this method to allow the use of pre-defined material definitions which really speed this process.

public class GameState extends AbstractAppState{

    @Override
    public void initialize(AppStateManager stateManager, Application app) {
        SimpleApplication application = (SimpleApplication)app;
        AssetManager am = application.getAssetManager();
        Node rootNode = application.getRootNode();
        scene = new Node("Game_Scene");
        rootNode.attachChild(scene);

        player = createPlayer(am);
        scene.attachChild(player);
    }

    private Spatial createPlayer(AssetManager am){
        Mesh mesh = new Box(0.5f,0.5f,1f);
        Geometry geo = new Geometry("Player", mesh);
        Material mat = new Material(am, "Common/MatDefs/Misc/Unshaded.j3md");
        geo.setMaterial(mat);
        return geo;
    }
}

Spawning a model is as simple as creating a geometry, giving it a mesh and a material. Here we use the Box shape to create our mesh. With 3 floating point arguments we construct a box that extends into the x, y and z axis by the given amount. This will give us a box that is actually 1x1x2. Simply pass the mesh to the Geometry as an argument along with a String name to attach the mesh to the geometry.

Next we create the material. Here we are using a Material Definition which is how jmonkey defines what pixel and vertex shaders control the material. We are using the built-in material definition for Unshaded objects by simply defining the path to the j3md file. You can view other material definitions included in the jme3 library in the jme3-core.jar/Common/MatDefs. Call Geometry.setMaterial() and we are finished.

In the initialize method add the 2 lines player = createPlayer(am) and scene.attachChild(player).

This is enough to have our GameState spawn a player object and attach it to the GameScene node, which in turn is attached to the RootNode and will be rendered. Now we need to attach the GameState to the AppStateManager, let's do this in our MainMenu state so in the future we can have a button start the game.

Open our MainMenu appstate and add the following lines.

public class MainMenu extends AbstractAppState {
    private AppStateManager stateManager;
    private float countDown = 3f;
    private BitmapText text;

    @Override
    public void initialize(AppStateManager stateManager, Application app) {
        super.initialize(stateManager, app);
        this.stateManager = stateManager;
        
        ...
    }
    
    @Override
    public void update(float tpf) {
        if((countDown -= tpf) <= 0){
            stateManager.detach(this);
            stateManager.attach(new GameState());
        }
    }
    
    ...
}

First we need to define 2 new fields, stateManager and countDown. The state manager is passed via the initialize() method when the AppState is attached to the AppStateManager. Go ahead and assign this via "this.stateManager = stateManager" so we can access it later.

Then in our update loop we are going to count down from 3. Once our countdown is equal to or less than 0, we will remove MainMenu from our AppStateManager and attach a new GameState. Do this by simple subtracting tpf from countDown once a frame and checking with a basic if statement. Remember, in a video game you NEVER want to use wait() as this will completely lock your thread from doing other things. Making simple timers like the one above is easy and guarantees that your other appstates have a chance to  run their updates while we wait.

Now if you press Play you will see our lovely Main Menu for approximately 3 seconds before being taken to our game state, and spawning in a simple, white cube!

Saturday, June 2, 2018

Universal Sound Board side project

I've been quiet the past month and a half, but I haven't stopped working. I'm still working on the tutorial series for jme3 and I'll hopefully get the next article up in the next couple of weeks. In the meantime I made a fun little soundboard app!

Universal Sound Board

Instead of making a keybinding for every sound file you want to play (ctrl-h for hello.wav, ctrl-g for goodbye.wav) with this soundboard you can make custom "key combos" that lead to your desired sound. Turn on the key-capture with a user defined key (ctrl-v by default) and enter the combo for your sound. This lets you make as many sounds as you want in easy to remember combos! ctrl-v, h for hello, ctrl-v ty for thank you. The possibilities are endless.

The only restriction is you cannot assign a sound "passed" another sound. If you have "h" as hello, you couldn't make "hg" as goodbye, since it'll start playing "hello" first! I suggest you make your combos at least 1 or 2 letters deep to prevent them from stepping on eachother. g-h and g-g could be "hello" and "goodbye" respectively, and you could still use "h" seperately, or as the start of even more combos.

I hope some of you find this interesting and make some fun soundboards. Install a virtual audio cable for twice the fun ;)