Sunday, November 21, 2010

Collision System Revisited

After giving a lot of thought to Professor Keenan's suggestions about callbacks and the decoupling of colliding objects, I have decided to completely redesign my collision system and generalize it to the point that it could become a central part of my MHFramework game engine.

Here are the elements of the system as I am currently envisioning it:

Collidable Interface

  • public boolean isColliding(Collidable);
  • public CollisionGeometry getCollisionGeometry();
  • public void collideWith(Collidable);
  • public int getType();


CollisionPoint Class

  • private Point center;
  • private float radius;
  • public boolean isColliding(CollisionPoint);


CollisionGeometry Class

  • private ArrayList collisionPoints;
  • public boolean isColliding(CollisionGeometry);
  • public CollisionPoint getCourseBounds();

Wednesday, October 20, 2010

Shield System UML Diagrams, Iteration 1

The shield system for the Space Invaders project is somewhat hierarchical in nature; not just through inheritance, but through the system of instantiated objects as well.  The class diagram below shows the classes that make up this subsystem.



Both the shield class and the individual bricks that comprise it derive from the basic sprite class because they have similar needs -- rendering, collisions, changes in state, etc.  The shield itself maintains an array of brick objects so that each brick can have its own state and damage effects.

The damage effect is accomplished by the dynamic creation of a separate image object.  When a brick is destroyed, it generates a "damageEffect" image.  The appearance of crumbling is attained by randomly drawing ovals onto the "damageEffect" image, which is then superimposed over the normal shield brick image.  So the bricks don't actually go away.  Instead, they change state which changes their collision response (nothing counts as a collision) and their rendering behavior (the damage effect is drawn over the brick image).

Collision detection and response happens in the game's update phase.  The game screen calls to the collision processor to process all relevant collisions.  Part of that process involves checking the shields for collisions with projectiles, and another part checks for collisions with the invaders.  Here's a rough, highly generalized form of the sequence diagram.

Thursday, October 14, 2010

Space Invaders Class Diagram, Iteration 2

Here's a revamp of the class diagram that more accurately reflects the actual implementation up to this point.  To keep the diagram clean and readable, not all associations are represented here.

Saturday, October 9, 2010

Brainstorming a "Simple" Design Doc

I'm a little disappointed by the cancellation of the individual project, but mostly, it's a relief.  I was very concerned about my ability to devote an appropriate amount to time to it.  But Space Invaders -- now that can be done.

So now we need a game design document .  Normally, that's the first step in any game project, but in this case, a standard game design document seems almost like overkill for a game like this.  The question is, what sections would be appropriate to be in a design document for as simple a game as Space Invaders?  I'm going to brainstorm here 'cause it's a good place to keep a record of my thoughts.
  • Overview
  • Rules and Objectives
    • Game Play Overview
    • Victory/Loss Conditions
    • Scoring
  • User Interface and Controls
    • Key Commands
    • HUD Elements
  • Multimedia Assets
    • Graphics
    • Audio

Friday, October 1, 2010

Debating the Project

When I first found out that we'd be doing an individual game project, I was thrilled.  It's just the excuse I've been looking for to get a small, simple game done.  My first overwhelming urge was to make the isometric platformer that I've been wanting to do ever since I added two new detail layers to my isometric tile map system.  However, the realist in me eventually spoke up with some reasonable objections:

  1. With my heavy load and long hours at work right now, I don't have the time to make the assets required for such a project.  
  2. With Space Invaders being written in parallel to this project, I don't want to take on something with that much uncertainty.

A few days ago, I decided that I had to think of something more feasible for the time allowed.  Then, just today, out of the blue, I was hit with a brilliant idea:  I need to remake the old CTG game that my friends and I made twelve years go.  Why?  Because:

  • The first version (from 1998, in C++) was abandoned because of issues we had with the rigidity, deployment, and licensing of the wrapper on the graphics API.  It was playable, but never finished, lacking essentially EVERY feature aside from a two-player deathmatch that let the players shoot and destroy each other.
  • I tried to remake the game about eight years ago in Java, but that was before I had started serious work on my game engine.  There were a lot of graphics and threading issues that were later solved with the engine itself, but I had once again abandoned the project because I didn't feel like refactoring all the work that I had just done.
  • Most importantly, the art assets and sound effects are already made, saving a huge amount of time in developing this game.

So this is what I'm going to propose:  A complete redesign and rewrite of my first "major" game...which wasn't that major, but it seemed like it at the time.  If this proposal is rejected for any reason, I have a few more ideas in mind, but I'm going to assume that this'll be it...as soon as I get Space Invaders out of the way, of course.

Thursday, September 30, 2010

Space Invaders Class Diagram, Iteration 1

I am beginning the Space Invaders project the same way I begin every project -- with a rough class diagram to start getting some ideas down. It needs a lot of refinement, but it's a start.  At this point, I foresee uses of the Factory, Facade, Observer, and possibly the Template Method pattern, with probably more to come.

A first attempt at a class diagram for the Space Invaders project.

Monday, September 27, 2010

PA2...I think.

A first (and maybe final) draft of PA2.
I'm uncertain of the precise instructions for PA2.  From what I remember from last week's lecture, we were to have three sprites, each of different shapes.  One was to move horizontally, one vertically, and one diagonally.  All three were to bounce off the screen borders.

So, my implementation so far has a blue rectangle that bounces horizontally, a red rounded rectangle that bounces vertically, and a green oval that bounces diagonally.

Also, I wasn't sure if we were to create the entire program from scratch or if it was OK to take advantage of some reusable code from the past.  So to save some time, I used my own framework to handle the UI and video mode stuff.  I did, however, create the sprites from scratch since that was the focus of the assignment.

Friday, September 24, 2010

My Game Loop

In my last post, I said I would follow up with a listing of my game loop showing the use of the new metrics calculation class.  Here it is!


Note:  The metrics calculator object is called "timer" in this code.

// Loop until the program is over
while (!programOver)
{
    // Record the starting time of the loop
    timer.recordStartTime();

    // Update the screen data
    screenManager.advance();

    // Draw the updated screen
    screenManager.render(MHDisplayModeChooser.getGraphics2D());
            
    // Record the ending time of the loop
    timer.recordEndTime();

    // Calculate how long it took to run this loop and
    // use that value to see how long we should wait
    // (or "sleep") before starting the next iteration
    timer.sleep();

    // Separate UPS from FPS to maintain a  better frame rate.
    while (timer.shouldUpdate())
        screenManager.advance();
} // while (!programOver) . . .

A Slightly Improved FPS/UPS/Sleep Time Calculator

Irritated by the monolithic structure provided by the book's examples, I decided to separate out the metrics calculations into a separate class. I'm not sure if this is the best possible solution, but it does provide certain advantages over the author's examples.  To name a few:

  • The metrics calculation logic is separated out from the game logic, greatly improving the game class' cohesion.
  • This new metrics class is directly reusable and stands on its own integrity.
  • The metrics calculations are all encapsulated neatly into private methods, eliminating the need for a programmer to understand them and reducing the odds of making an error in reproducing this functionality.
  • Several variables in the original code were assigned but then never referenced.  This code eliminates them.
  • This code is documented with comments.  Not completely or perfectly, but much better than the textbook examples.

I will follow up in a separate post to show what my game loop using this class looks like.

public class MHRuntimeMetrics
{
    // Constants to make the math easier and less error-prone.
    private static final long ONE_SECOND_IN_MILLI = 1000L; 
    private static final long ONE_MILLI_IN_NANO = 1000000L; 
    private static final long ONE_SECOND_IN_NANO  = ONE_SECOND_IN_MILLI * ONE_MILLI_IN_NANO;
    
    /** The frame rate we're trying to achieve. */
    public static final short TARGET_FPS = 50; // 28 = average FPS for Japanese anime
    
    /** Average period in nanoseconds required to achieve the target frame rate. */
    public static final long PERIOD = ONE_SECOND_IN_NANO/TARGET_FPS;

    /** Maximum number of renders to skip when compensating for long loop iterations. */
    static final short MAX_FRAME_SKIPS = 2;
    
    /** Nanoseconds between metrics calculations. */
    private static final long MAX_STATS_INTERVAL = ONE_SECOND_IN_NANO;
    
    /** Number of measurements to track for calculating an average. */
    private static final int SAMPLE_SIZE = 10;

    private int frameCount = 0;        // Number of frames elapsed.
    private int statsInterval;         // Time since last metrics calculation.
    private long gameStartTime;        // Approximate time that game started.
    private long prevStatsTime = 0;    // Time that metrics were last calculated.
    private long totalElapsedTime = 0; // Total time elapsed in game loop.
    private int totalFramesSkipped;    // Total renders skipped due to long loop iterations.
    private int framesSkipped = 0;     // No. renders skipped since last calculation.
    private int statsCount = 0;        // Index into fpsStore and upsStore arrays.
    private double averageFPS;         // Calculated average frames per second.
    private double averageUPS;         // Calculated average updates per second.
    private int[] fpsStore, upsStore;  // Arrays for storing calculation results.
    private long startTime, endTime;   // Start and end times for current loop iteration.
    private long excess = 0;           // Extra time acquired from short loop iterations.
    

    /****************************************************************
     * Default constructor.
     */
    public MHRuntimeMetrics()
    {
        gameStartTime = System.nanoTime();
        prevStatsTime = gameStartTime;
        
        fpsStore = new int[SAMPLE_SIZE];
        upsStore = new int[SAMPLE_SIZE];
    }
    
       
    /****************************************************************
     */
    public void recordStartTime()
    {
        startTime = System.nanoTime();
    }

    
    /****************************************************************
     */
    public void recordEndTime()
    {
        endTime = System.nanoTime();
        storeStats();  // Update the stored metrics.
        Toolkit.getDefaultToolkit().sync(); // Sync the display (for Linux users).
    }
    
        
    /****************************************************************
     */
    private int nanoToSec(long nano)
    {
        return (int)(nano / ONE_SECOND_IN_NANO);
    }
    
    
    /****************************************************************
     */
    private long nanoToMilli(long nano)
    {
        return nano / ONE_MILLI_IN_NANO;
    }
    
    /****************************************************************
     */
    private void storeStats()
    { 
      frameCount++;
      statsInterval += PERIOD;

      if (statsInterval >= MAX_STATS_INTERVAL) 
      {
        long timeNow = System.nanoTime();

        long realElapsedTime = timeNow - prevStatsTime;   // time since last stats collection
        totalElapsedTime += realElapsedTime;

        totalFramesSkipped += framesSkipped;
        
        int actualFPS = 0;     // calculate the latest FPS and UPS
        int actualUPS = 0;
        if (totalElapsedTime > 0) 
        {
          actualFPS = (frameCount / nanoToSec(totalElapsedTime));
          actualUPS = ((frameCount + totalFramesSkipped) / nanoToSec(totalElapsedTime));
        }

        // store the latest FPS and UPS
        fpsStore[ statsCount%SAMPLE_SIZE ] = actualFPS;
        upsStore[ statsCount%SAMPLE_SIZE ] = actualUPS;
        statsCount += 1;

        double totalFPS = 0.0;     // total the stored FPSs and UPSs
        double totalUPS = 0.0;
        for (int i=0; i < SAMPLE_SIZE; i++) 
        {
          totalFPS += fpsStore[i];
          totalUPS += upsStore[i];
        }

        if (statsCount < SAMPLE_SIZE) // obtain the average FPS and UPS
        { 
          averageFPS = totalFPS/statsCount;
          averageUPS = totalUPS/statsCount;
        }
        else 
        {
          averageFPS = totalFPS/SAMPLE_SIZE;
          averageUPS = totalUPS/SAMPLE_SIZE;
        }

        framesSkipped = 0;
        prevStatsTime = timeNow;
        statsInterval = 0;   // reset
      }
    }  // end of storeStats()

    
    /****************************************************************
     * Calculate how long the application thread should sleep based on the time
     * it took to run the game loop.
     */
    public void sleep()
    {
        long sleepTime = PERIOD - (endTime - startTime);

        if (sleepTime > 0)
        {
            try
            {
                Thread.sleep(nanoToMilli(sleepTime));
            } 
            catch (final InterruptedException e)
            {
            }
        } 
        else
        {
            excess -= sleepTime; // store excess time value
            Thread.yield(); // give another thread a chance to run
        }
    }
       
    
    /****************************************************************
     */
    public int getFramesPerSecond()
    {
        return (int) averageFPS;
    }


    /****************************************************************
     */
    public int getUpdatesPerSecond()
    {
        return (int) averageUPS;
    }

    
    /****************************************************************
     */
    public int getTimeSpentInGame()
    {
        return nanoToSec(System.nanoTime() - gameStartTime);
    }


    /****************************************************************
     */
    public boolean shouldUpdate()
    {
        boolean updateNeeded = (excess > PERIOD) && (framesSkipped < MAX_FRAME_SKIPS);
        
        if (updateNeeded)
        {
            excess -= MHRuntimeMetrics.PERIOD;
            framesSkipped++;
        }
        
        return updateNeeded;
    }

Tuesday, September 21, 2010

I got Worms, but do I GET Worms?

Much better than my previous score of -100...I think.
The Worms game is a little fun while a little annoying.  Likewise, the code for the game is also a little fun while a little annoying.  The gameplay is pretty original, and the logic behind the workings of the worm itself is pretty clever.  However, the code itself is full of issues.

The first major problem that I noticed is the utter lack of cohesion in the WormChase class.  The UI, the game logic, the metrics, and just about everything (other than the worm and the obstacles) is all crammed together into one poorly planned class.  Admittedly, I violate the single responsibility principle myself from time to time.  We probably all do.  But for a program that is intended to serve as a clear example, this one leaves a bit to be desired.

The only complaint I have about the Worm class is the way it handles the directions of movement, the Point2D instantiations, and the probabilities for changing direction.  As I already explained in a message board post, every part that I just mentioned could be encapsulated into an enumeration, which would help greatly to simplify the code.

The Obstacles class, in my humble opinion, is well done.  Everything is synchronized to make it safe for use between the game thread and the event dispatch thread, it encapsulates its functionality neatly, and completely fulfills its well-defined purpose.

I do feel a little disappointed by the FPS/UPS example.  I was hoping for a nice, clean, easy-to-read implementation of UPS, but instead we have an unstructured, poorly documented tangle of variables thrown loosely about this monolithic WormChase class.  I've seen plenty of clearer examples of FPS calculations than the one presented here, so I suppose I shouldn't be surprised by the complexity and confusion in this particular UPS example.  Nevertheless, I am going to try to extract the essentials and implement them into my own code to get the experience of doing so.  If I succeed, I'll post some code snippets in a future blog entry.

Sunday, September 12, 2010

Set up and ready to go!

The first week's homework is now finished and out of the way.  If I've done everything correctly, this means that my environment is ready to roll...and so am I.

For the most part, everything went pretty smoothly.  I already had up-to-date versions of the JDK and Eclipse, of course.  I already had the KGPJ sample code as well.  (I've loved this book for awhile and have referred to it several times.)  Downloading and installing Java 3D was no problem, and the Perforce installation was fast and smooth as well.

I couldn't figure out where to get HelloUniverse, so I Googled it, found a couple of different versions, and took the most recent one.  Compiling it caused a few problems with Java 3D features being unavailable due to restrictions, but that was fixed by telling Eclipse to ignore those issues.  Then the code compiled and ran just fine.

The only other issue I had was with Perforce "forgetting" that I have a workspace associated with my connection.  The issue hasn't happened since the second time I fixed it, so maybe this time it will stay fixed.  If it doesn't, at least I know what to do now.

Saturday, September 11, 2010

Getting Started in SE456

This blog will be devoted to my work in SE456:  Architecture of Computer Games.  Software architecture, especially when applied to game development, has always been one of my very favorite topics, and I am eagerly looking forward to getting into some serious game-making action.  What's even more exciting is that the class uses Java, which is without a doubt the very best game development technology yet devised.  (I will fight to the death defending my reasons for using it to develop my MHFramework API.)

My only concern is about the amount of time I can devote to the class.  My work load is especially heavy right now, and won't lighten up until this class is nearly over.  Nevertheless, I am excited and eager to work on some great new games, and will do my best to give them all the time and attention I possibly can.

This is gonna be fun!