Hostinicon
GET HOSTING FROM $3.95/MO PLUS A FREE YEAR ON TUTS+ (RRP $180). HURRY OFFER LIMITED. Check it out
Advertisement

Make a Neon Vector Shooter With jME: Warping Grid

by
Gift

Get a free year on Tuts+ this month when you purchase a Siteground hosting plan from $3.95/mo

This post is part of a series called Cross-Platform Vector Shooter: jMonkeyEngine.
Make a Neon Vector Shooter With jME: Particle Effects

In the series so far, we've coded the gameplay, added enemies, and spiced things up with bloom and particle effects. In this final part, we will create a dynamic, warping background grid.


Overview

This video shows the grid in action:


We'll make the grid using a spring simulation: at each intersection of the grid, we'll place a small weight (a point mass), and we'll connect these weights using springs. These springs will only pull and never push, much like a rubber band. To keep the grid in position, the masses around the border of the grid will be anchored in place.

Below is a diagram of the layout.

jmonkeyengine-warping-grid

We'll create a class called Grid to create this effect. However, before we work on the grid itself, we need to make two helper classes: Spring and PointMass.


The PointMass Class

The PointMass class represents the masses to which we will attach the springs. Springs never connect directly to other springs. Instead, they apply a force to the masses they connect, which in turn may stretch other springs.

There are a few interesting points about this class. First, notice that it stores the inverse of the mass, 1 / mass. This is often a good idea in physics simulations because physics equations tend to use the inverse of the mass more often, and because it gives us an easy way to represent infinitely heavy, immovable objects by setting the inverse mass to zero.

The class also contains a damping variable, which acts to gradually slow the mass down. This is used roughly as friction or air resistance. This helps make the grid eventually come to rest and also increases the stability of the spring simulation.

The Update() method does the work of moving the point mass each frame. It begins by doing symplectic Euler integration, which just means we add the acceleration to the velocity and then add the updated velocity to the position. This differs from standard Euler integration in which we would update the velocity after updating the position.

Tip: Symplectic Euler is better for spring simulations because it conserves energy. If you use regular Euler integration and create springs without damping, they will tend to stretch further and further each bounce as they gain energy, eventually breaking your simulation.

After updating the velocity and position, we check if the velocity is very small and, if it is, we set it to zero. This can be important to performance due to the nature of denormalized floating-point numbers.

The IncreaseDamping() method is used to temporarily increase the amount of damping. We will use this later for certain effects.


The Spring Class

A spring connects two point masses, and, if stretched past its natural length, applies a force pulling the masses together. Springs follow a modified version of Hooke's Law with damping:

\[f = -kx - bv\]

  • \(f\) is the force produced by the spring.
  • \(k\) is the spring constant, or the "stiffness" of the spring.
  • \(x\) is the distance the spring is stretched beyond its natural length.
  • \(b\) is the damping factor.
  • \(v\) is the velocity.

The code for the Spring class is as follows:

When we create a spring, we set the natural length of the spring to be just slightly less than the distance between the two end points. This keeps the grid taut, even when at rest, and improves the appearance somewhat.

The Update() method first checks whether the spring is stretched beyond its natural length. If it is not stretched, nothing happens. If it is, we use the modified Hooke's Law to find the force from the spring and apply it to the two connected masses.

There is another class we need to create in order to display the lines correctly. The LineControl will take care of moving, scaling and rotating the lines:


Creating the Grid

Now that we have the necessary nested classes, we're ready to create the grid. We start by creating PointMass objects at each intersection on the grid. We also create some immovable anchor PointMass objects to hold the grid in place. We then link the masses together with springs:

The first for loop creates both regular masses and immovable masses at each intersection of the grid. We won't actually use all of the immovable masses, and the unused masses will simply be garbage collected at some point after the constructor ends. We could optimize by avoiding creating unnecessary objects, but since the grid is usually only created once, it won't make much difference.

In addition to using anchor point masses around the border of the grid, we will also use some anchor masses inside the grid. These will be used to very gently help pull the grid back to its original position after being deformed.

Since the anchor points never move, they don't need to be updated each frame. We can simply hook them up to the springs and forget about them. Therefore, we don't have a member variable in the Grid class for these masses.

There are a number of values you can tweak in the creation of the grid. The most important ones are the stiffness and damping of the springs. The stiffness and damping of the border anchors and interior anchors are set independently of the main springs. Higher stiffness values will make the springs oscillate more quickly, and higher damping values will cause the springs to slow down faster.

There is one last thing to be mentioned: the createLine() method.

Here, we basically create a line by specifying the vertices of the line and the order of the vertices, creating a mesh, adding a blue material, and so on. If you want to understand the process of the line creation exactly, you can always take a look at the jME tutorials.

Why does the line creation have to be so complicated—isn't it 'just' a simple line? Yes, it is, but you have to look at what jME intends to be. Usually, in 3D games, you don't have single lines or triangles in the game, but rather models with textures and animations. So while it is possible to generate a single line in jME, the main focus is on importing models that have been generated with other software, such as Blender.

Manipulating the Grid

In order for the grid to move, we must update it each frame. This is very simple, as we already did all the hard work in the PointMass and Spring classes.

Now, we will add some methods that manipulate the grid. You can add methods for any kind of manipulation you can think of. We will implement three types of manipulations here: pushing part of the grid in a given direction, pushing the grid outwards from some point, and pulling the grid in towards some point. All three will affect the grid within a given radius from some target point.

Below are some images of these manipulations in action:

Geometry Wars jMonkeyEngine tutorial
Wave created by pushing the grid along the z-axis.
Geometry Wars jMonkeyEngine tutorial
Bullets repelling the grid outwards.
Geometry Wars jMonkeyEngine tutorial
Sucking the grid inwards.

And here are the methods for the effects:

Using the Grid in Shape Blaster

Now it's time to use the grid in our game. We start by declaring a Grid variable in MonkeyBlasterMain and initializing it in simpleInitApp():

Then, we need to call grid.update(float tpf) from the simpleUpdate method:

Next, we need to call the effect methods from the right places in our game.

The first one, creating a wave when the player spawns, is pretty easy—we just extend the place where we spawn the player in simpleUpdate(float tpf) with the following line:

Note that we apply a force in the z-direction. We may have a 2D game but, since jME is a 3D engine, we can easily use 3D effects as well. If we were to rotate the camera, we'd see the grid bounce inwards and outwards.

The second and third effects need to be handled in controls. When bullets fly through the game, they call this method in controlUpdate(float tpf):

This will make bullets repel the grid proportionally to their speed. That was pretty easy.

It's similar with the black holes:

This makes the black hole suck in the grid with a varying amount of force. I reused the sprayAngle variable, which will cause the force on the grid to pulsate in sync with the angle it sprays particles (although at half the frequency due to the division by two). The force passed in will vary sinusoidally between 10 and 30.

In order to make this work, you must not forget to pass grid to BulletControl and BlackHoleControl.


Interpolation

We can optimize the grid by improving the visual quality for a given number of springs without significantly increasing the performance cost.

We will make the grid denser by adding line segments inside the existing grid cells. We do so by drawing lines from the midpoint of one side of the cell to the midpoint of the opposite side. The image below shows the new interpolated lines in red:

Geometry Wars jMonkeyEngine tutorial

We will create those additional lines in the constructor of our Grid class. If you take a look at it, you'll see two for loops where we link the point masses with the springs. Just insert this block of code there:

But, as you know, creating objects is not the only thing we need to do; we also need to add a control to them in order to get them to behave correctly. As you can see above, the AdditionalLineControl gets four point masses passed to so it can calculate its position, rotation and scale:


What's Next?

We have the basic gameplay and effects implemented. It's up to you to turn it into a complete and polished game with your own flavour. Try adding some interesting new mechanics, some cool new effects, or a unique story. In case you aren't sure where to start, here are a few suggestions:

  • Create new enemy types such as snakes or exploding enemies.
  • Create new weapon types such as seeking missiles or a lightning gun.
  • Add a title screen and main menu.
  • Add a high score table.
  • Add some power-ups, such as a shield or bombs. For bonus points, get creative with your power-ups. You can make power-ups that manipulate gravity, alter time, or grow like organisms. You can attach a giant, physics-based wrecking ball to the ship to smash enemies. Experiment to find power-ups that are fun and help your game stand out.
  • Create multiple levels. Harder levels can introduce tougher enemies and more advanced weapons and powerups.
  • Allow a second player to join with a gamepad.
  • Allow the arena to scroll so that it may be larger than the game window.
  • Add environmental hazards such as lasers.
  • Add a shop or leveling system, and allow the player to earn upgrades.

Thanks for reading!

Advertisement