My first XBox 360 Foray –

This semester I was able to return to trying to tackle game programming on the XBox 360. The organic behaviors and physics simulations learned in Dan Shiffman‘s Nature of Code class always screamed “Games! Enemy/AI behaviors!” to me and so I decided to choose a platform that would allow for the same OOP concepts employed by Java/Processing as well as a sensitive, tactile controller and a hefty graphics output. Enter the XBox 360, C# and the XNA framework.

During this project I feel like something finally “clicked” for me regarding object oriented programming. I’ve always understood OOP but never really used its capabilities (effectively, at least) to make my projects more efficient, organized, and expandable. While working on my XBox game (think Choplifter meets Geometry Wars) a magical thing started happening: as my project grew, my code wasn’t growing more bloated and complicated, it was growing leaner and more modular. It was a great feeling.

As an aside, I never thought I would enjoy developing on Windows or in a Microsoft-branded IDE (in this case, Visual Studio 2010 Express) – but I have to conclude that VS2010 Express is everything Eclipse wishes it could be. It highlights potential errors before they happen without trying to incorrectly autocomplete what I’m typing. I’m finding it easy to cover new ground without fear of breaking my code.

Here’s a short video of the game so far. I apologize for the quality but at the late hour of the night I could not devise a better way to capture the video output from my HDMI-enabled XBox 360 – which is pretty sad in this day and age, considering that just a few years ago I’d have a bevy of video equipment that could easily manipulate simple composite or S-Video output. A humorous photo of my workspace and photo-taking rig is included here.

The Ridiculous Documentation Rig

The Ridiculous Documentation Rig: iPhone atop Kleenex and game boxes.

By moving every non-player element (e.g. the background, the enemies, buildings, etc.) by the player’s velocity (should the player be either near the left or right third of the screen), I’m able to create the illusion of a scrolling background and a vast virtual 2D space.

What I’ve built so far is only the beginning: tanks fire homing missiles that use a basic seek behavior to follow the player. More interestingly, the geometric alien foes seek the player while attempting to maintain a distance from each other and (more pressingly) any bullet fired by a player. The result is a skittish yet ever-encroaching and inevitable threat against the player. I had to tweak the weights and the maximum forces applied to the squares quite a bit to get the desired effect. At first, the squares flew far away from the hunt when confronted with a bullet or a fellow square.

I’m hoping to have more time to advance the game this summer, using more sophisticated enemy behaviors and of course giving the game the needed polish to be truly visually immersive (dealing with dreaded sprite sheets to produce explosions and other animations is the first thing that comes to mind).

As a final aside: the XNA framework seems good at a lot of things. It is also puzzlingly bad at seemingly simple things like generating random numbers. Doing so apparently uses the system clock, which seems (at least the way I’m using them) to produce predictable patterns and results. I’m sure I can find a smarter way to produce a random-seeming number, but such a thing should not be so hard.

Here’s the class for the geometric shapes that seek and separate that is derived from Dan Shiffman’s ‘seek and separate’ examples in Processing.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace NightLifter
{
    class dodger
    {
        public bool alive;
        public Texture2D sprite;
        public Vector2 center; //center of the dodger
        public Vector2 velocity; //dodger's velocity
        public Vector2 acceleration; //dodger's acceleration
        public Vector2 position; //dodger's acceleration
        public bool active;
        public float rotation;
        public float rotationRate;
        public int direction;
        public float maxForce;
        public float maxSpeed;

        public dodger(Texture2D loadedTexture)
        {
            //This is the constructor and these values are assigned on load
            
            position.X = -6000; // For now, just place the object somewhere far, far away.
            position.Y = 300;
            maxSpeed = 3;
            maxForce = 0.1f;

            sprite = loadedTexture;
            center = new Vector2(sprite.Width / 2, sprite.Height / 2);
            alive = false;
            active = true;
            velocity.X = 0;
            velocity.Y = 0;
            acceleration.X = 0;
            acceleration.Y = 0;
        }
        
        public void checkActive(Vector2 playerOnePos) // checks if dodger is close to player. If so, make active.
        {
            if (Math.Abs(this.position.X - playerOnePos.X) < 2000)
            {
                this.active = true;
            }
            else
            {
                this.active = false;
            }
        }
        
        public void applyForce(Vector2 forceApplied)
        {
            acceleration = Vector2.Add(acceleration, forceApplied); 
        }
        
        public void applyBehaviors(dodger[] dodgerArray, bullet[] bullets, player thePlayer)
        {
            
            Vector2 separateFromEachOther = separate(dodgerArray);
            Vector2 separateFromBullets = separateBullets(bullets);
            Vector2 seekThePlayer = seek(thePlayer.position);

            seekThePlayer = Vector2.Multiply(seekThePlayer, 0.5f);
            separateFromEachOther = Vector2.Multiply(separateFromEachOther, 0.25f);
            separateFromBullets = Vector2.Multiply(separateFromBullets, 1);

            applyForce(separateFromBullets);
            applyForce(separateFromEachOther);
            applyForce(seekThePlayer);
        }
        
        Vector2 seek(Vector2 playerOnePos){
            
                //First, get a vector of the desired direction
                Vector2 desired = Vector2.Subtract(playerOnePos, this.position);
                
                desired.Normalize();
                desired = Vector2.Multiply(desired, maxSpeed);
                
                //Next, remember that steering force is desired vector minus velocity vector
                Vector2 steer = Vector2.Subtract(desired, this.velocity);

                steer.Normalize();
                steer = Vector2.Multiply(steer, maxForce);
                
                return(steer);

        }

       Vector2 separate(dodger[] dodgers){

            float desiredSeparation = 60;
            int count = 0;
            Vector2 sum;
            sum = new Vector2();
            
           foreach (dodger otherDodger in dodgers)
            {
                float theDistance = Vector2.Distance(this.position, otherDodger.position);
                if (theDistance > 0 &amp;&amp; theDistance < desiredSeparation)
                {
                    Vector2 diff = Vector2.Subtract(position, otherDodger.position);
                    diff.Normalize();
                    diff = Vector2.Divide(diff, theDistance);
                    sum = Vector2.Add(sum, diff);
                    count++;
                }
            }


            if (count > 0) {
                sum = Vector2.Divide(sum, count);
                //our desired vector is the average scaled to max speed
                sum.Normalize();
                sum = Vector2.Multiply(sum, maxSpeed);
       
                Vector2 steer = Vector2.Subtract(sum, velocity);
               
                float m = steer.Length();
                if (m > maxForce) {
                    steer.Normalize();
                    steer = Vector2.Multiply(steer, maxForce);
                }
               
            }
           return sum;
        }
        
       Vector2 separateBullets(bullet[] bullets)
       {

           float desiredSeparation = 100;
           int count = 0;
           Vector2 sum;
           sum = new Vector2();

           foreach (bullet bullet in bullets)
           {
               float theDistance = Vector2.Distance(this.position, bullet.position);
               if (theDistance > 0 &amp;&amp; theDistance < desiredSeparation)
               {
                   Vector2 diff = Vector2.Subtract(position, bullet.position);
                   diff.Normalize();
                   diff = Vector2.Divide(diff, theDistance);
                   sum = Vector2.Add(sum, diff);
                   count++;
               }
           }


           if (count > 0)
           {
               sum = Vector2.Divide(sum, count);
               //our desired vector is the average scaled to max speed
               sum.Normalize();
               sum = Vector2.Multiply(sum, maxSpeed);
               //Implement reynolds steering
               Vector2 steer = Vector2.Subtract(sum, velocity);

               float m = steer.Length();
               if (m > maxForce)
               {
                   steer.Normalize();
                   steer = Vector2.Multiply(steer, maxForce);
               }
               //steer = MathHelper.Clamp(steer, -maxforce, maxforce);
           }
           return sum;
       }
       
        public void update(float backgroundSpeed){ // backgroundSpeed is passed in to determine the relative velocity of the dodger.
        
            //if active? if alive? just assume always active for now
            this.velocity = Vector2.Add(this.velocity, this.acceleration);

            this.position = Vector2.Add(this.velocity, this.position);
            this.position.X = this.position.X + backgroundSpeed;

            float m = this.velocity.Length();
            
            if (m > maxSpeed)
            {
                this.velocity.Normalize();
                this.velocity = Vector2.Multiply(this.velocity, maxSpeed);
            }
            
            this.rotationRate = this.velocity.X *0.07f; // Rotate the dodgers according to their velocities.
            this.rotation += this.rotationRate;

            //reset accel to 0 each cycle
            this.acceleration = Vector2.Multiply(this.acceleration, 0);
        }
    }
}