882

Fabien Sanglard

  




Shoot 'Em Up with LWJGL.

A Java "Prototyp" tribute.


The actual stuff



Try it !
Browse the Source code (main function).
Download Eclipse project (4.4Mb).
Drop me a line.


History


I started to think about writing a Shoot 'Em Up back in 2005, after the winner of the "Horizontal Shooter with Boss" competition was kind enough to release his source code. This "Carmack" moves triggered the challenge ; Being a server-side programmer for a while, I had no freaking idea how do program something which was....moving. It was the perfect occasion to learn:    Thanks Mr "X-Out".

My objectives were:


It is usually wiser to jump into a new domain without adding the difficulty of learning a new language So I decided to use my favorite at this time: JAVA. For the rendition, I had two OpenGL binding available JOGL and LWJGL.

Why did I decide to go with LWJGL ? Because at this time it featured a lot more than openGL calls, there was everything I needed including: Inputs controls, Textures loading, Sounds, Timer, etc. LWJGL was clearly more game oriented.

One more thing.....

This is not a game and I never intended to program one, this is more like a proof of concept/challenge I felt like coding, a little bit to learn, a little bit to check how much juice could come out of JAVA+OpenGL. I took me a month of spare time and this is the result. I don't have the pretension to think this is good but I've learned so much reading people's webpage that I thought I should modestly return to the net a little bit of what it gave me.


World representation


The elementary unit of this Proof of Concept is the Entity object. Basically a sprite which has a position, a speed, a spin and is capable of updaing itself update() and also render itself render().

By default an Entity can only display one image but is subtyped to AnimatedEntity to perform animation (Note: I used an array of Texture to store all frame in an animation, I read later in GPU Gems Programming that you can use a 3D texture, this is much more clever).

In order to handle group of Entities easily (and also to improve collision detection but we'll talk about it later) I decided to go for the most intuitive design: A Layer pattern.

Prototyp has seven layer:

    static public Layer background = new Layer();
    static public Layer bullets = new Layer();
    static public Layer enemies = new Layer();
    static public Layer bonus = new Layer();
    static public Layer fx = new Layer();
    static public Layer frontground = new Layer();
    static public Layer text = new Layer();

The layer approach is very handy for several reasons:

Every visible Entity belong to a Layer. As a layer is capable of rendering itself on the screen or to update iteself, it is considered a high level Entity and you can start thinking "big".
Note that although it was very convenient to perfom distorsion and fading effect, I wouldn't use these static variables again but a LayerManager instead. It would make the entiere thing much more "engine" like.


Move the world!


Highslide JS

The main method of this Prototyp is trivial. It can summarized as the following:











      public void run()
      {
        init();
        while (gameOn)
        {
            Timer.tick();

            getEntries();
            updateEntities();
            checkCollisons();

            render();
            Display.update();

        }
      }


As you can see, it's only one thread, and one loop. Very Zen, just as I like it.


More on the Timer


Timer is a new concept I came across exploring Game Programming. In order to make our soft run consistently over machines with different speeds and different GPUs, you don't update an entity position on a "each frame" basis but rather on a "time elapsed since last rendition" basis. This technique allows the game to run at the same speed on every machine, the difference being only the frame rate and by extension the animation quality.

You have to express every speed in term of pixel/milliseconds (if your Timer return tick in millisecond unit). Updating your Entities state is called interpolation, it is based on your object's last position and it's speed.
So even though our Entity jumps from one position to an other, if the frame rate is high (>30) it will appear very smooth! If you check out the method of the abstract class Entity.update(), you will find interpolate():

     protected Vector2f interpolate(Vector2f old_position,Vector2f speed)
     {
          old_position.x = old_position.x + tick * speed.x;
          old_position.y = old_position.y + tick * speed.y;

          return old_position;
     }


It's not being too strong to say the Timer is the heart of your game, it's the metronome and it rules over time...and movement.
How to make a Pause in your game ? Just stop calling Timer.tick(), overwrite it (or even better, call Timer.pause() ;) ): Everything will stop moving. The later is actually the way it is implemented in "Prototyp".


Collision detection


Highslide JS

As we want to push this engine and animate 10000+ sprites, we need to perform collision detection fast. Again, something really simple is to use rectangles in order to represent the boundary area of an Entity.


Why do this ?






  public static boolean boxBoxOverlap(Entity entityA,Entity entityB)
  {

    if(entityA.position.x+entityA.width<entityB.position.x)
      return false;

    if(entityA.position.x>entityB.position.x+entityB.width)
      return false;

    if(entityA.position.y+entityA.height<entityB.position.y)
      return false;

    if(entityA.position.y>entityB.position.y+entityB.height)
      return false;

    return true;
}



OpenGL's corner


The is very little things to do if you want to use openGL as a 2D Shoot 'Em Up sprite renderer . The best thing to do is to remove the perspective (the thing that makes object further appear smaller), and use an orthogonal projection.

Here is a snippet how do to so:

    GL11.glMatrixMode(GL11.GL_PROJECTION);
    GL11.glLoadIdentity();
    GLU.gluOrtho2D(-(int)screenWidth/2,(int)screenWidth/2,(int)-screenHeight/2,(int)screenHeight/2);


And that's it, you are set to draw your GL_QUADS (rectangle). As we have many objects to render and we do so in sequence there is two roads you can choose:


I used to prefer the "fire and forget" GL11.glLoadIdentity() but it's very unefficient and if you want to go 3D eventually, it's better to make good habit right away: GL11.glPushMatrix() and GL11.glPopMatrix() .


Lightning


Highslide JS

Although we normally draw texture using the blending function GL11.glBlendFunc(GL11.GL_SRC_ALPHA,GL11.GL_ONE_MINUS_SRC_ALPHA), it's sometimes pretty cool to use an other type of composition.
Especially for light effects, use GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE) will allow texture overlapping each other to look brighter, simulating light intensity very convincingly.The more you blend the texture in the same area, the brighter effect you will get.


You can see on the right an example: An OrbBeam casted by the LightningOrb (The beam points are calculated using Bézier curve).











Distortion effect


Highslide JS

When you charge your orb or your main weapon, the background gets distorded:
This distortion effect is achieved in two steps:











User inputs


LWJGL includes a method to check if a key is pressed or not: Keyboard.isKeyDown(key) . It wasn't hard to write a little KeyListener in order to trigger events such as onKeyDown(), onKeyUp() or keyPressed().
KeyListener is then when used as an Anonymous Class. Here is an example from PlayerShip the class controling the player:

    KeyListener fire2KeyEvent = new KeyListener()
    {
        public void keyPressed()
        {
            if (orb != null)
                orb.setMove(Orb.ADJUDTING);
        };
        public void onKeyUp()
        {
            if (orb != null)
                orb.setMove(Orb.STICKED);
        };
    };

    EventManager.instance().addListener(fire2Key, fire2KeyEvent);

An to finish we make sure events are pooled from the EventManager singleton with the method: EventManager.instance().checkEvents() .


Manual


Highslide JS
Highslide JS

Dah.
















Bugs


When you decide to headbutt the screen along with the keyboard...

Highslide JS
Highslide JS
Highslide JS












Other sreenshot

Memories...

Highslide JS
Highslide JS
Highslide JS
Highslide JS
Highslide JS
Highslide JS

























Original c++ Prototyp


Here is the original c++ source code from X-Out: Browse or download.