Updates from May, 2012 Toggle Comment Threads | Keyboard Shortcuts

  • Nick 6:53 am on May 17, 2012 Permalink | Reply  

    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);
            }
        }
    }
    
     
  • Nick 10:54 am on March 7, 2012 Permalink | Reply  

    Cosmic Crossfire Update 

    I’ve made some good progress on my two-player face-to-face game for iPad build with the Corona SDK tentatively titled ‘Cosmic Crossfire’ – inspired by the classic 90s tabletop action game Crossfire. For this early prototype, I was also inspired by the clean, sharp look and feel of the vector graphics / Vectrex games of the early 80s.

    In Cosmic Crossfire, two players sit at opposite ends of the iPad and ‘swipe’ to fire pellets at large geometric shapes in the middle of the screen, attempting to knock them into the other player’s ‘goal zone’. To calculate the direction of the pellets while maintaining a constant ‘total’ firing force, I took the distance from the point where the player lifted his/her finger and compared that to the point where they first began their ‘swipe’ gesture.

    Unfortunately, as Corona does not yet support vector math or contain a built-in function for normalizing a force with a unit vector, I had to write a little snippet to recreate the functionality. Note the hacky workarounds for a coordinate system where Y-values are inverted.

    -- (Note, this runs when a finger is 'lifted' from the screen)
        
        -- Hacky workaround to account for negative distance values and inverted y-coord system
        
        if (event.x - event.xStart < 0) then
            multX = -1;
        end
        if (event.x - event.xStart >= 0) then
            multX = 1;
        end
        if (event.y - event.yStart < 0) then
            multY = -1;
        end
        if (event.y - event.yStart >= 0) then
            multY = 1;
        end
        
        -- Get the absolute value of the distance between where the swipe ended and where it began
        
        magX = math.abs(event.x - event.xStart);
        magY = math.abs(event.y - event.yStart);
        
        -- print("magX is " .. magX);
        -- print("magY is " .. magY);
           
        -- Get the angle of the shot by taking the inverse tangent of the y 'distance' over the x 'distance' (TOA = Tangent: Opposite over Adjacent!)
        
        local angle = math.atan(magY/magX)
        
        local constantForce = 500;
        
        -- Normalize the force using cosine and sine and multiplying by the desired constant force. Also, multiply by multX and multY, my hacky 'direction' handlers.
        
        local xPower = math.cos(angle)*constantForce*multX
        local yPower = math.sin(angle)*constantForce*multY
            
        -- print("angle is... " .. angle);
       
        -- If the distance of y is zero (user taps rather than swipes), angle can return 'undefined' and crash the program. Only fire if the distance between y-values is not zero. (Note that '~=' in Lua is the equivalent of '!=' in other languages).
        
        if (magY ~= 0) then 
            shootFrom(1,event.x, xPower, yPower, totalTime) 
        end
    

    Although I plan to add all sorts of complex shapes and odd alien bodies and physics behaviors, the only one I’ve implemented so far is the ‘OctoOrb’. Although I’ve heard from passers-by that he/she/it resembles everything from the michelin man to a piece of candy, I was really going for some sort of weird top-down jellyfish-like creature as inspired by the various octopus villains from the old Sega Alex Kidd games and the Legend of the Mystical Ninja and the like. The OctoOrb is a sphere connected to two smaller spheres via revolute joints which are in turn connected to two tiny orbs. The OctoOrb’s ‘pulse’ is a momentary force applied to the center sphere in a random direction.

                    octoOrb.myJointR = physics.newJoint( "pivot", octoOrb, rightOrb, xLoc + 50,550 )
                    octoOrb.myJointR.isLimitEnabled = true -- (boolean)
                    octoOrb.myJointR:setRotationLimits( -60, 60 )
     
                    myJointRsm = physics.newJoint( "pivot", rightOrb, rightSmOrb, xLoc+100,550 )
                    myJointRsm.isLimitEnabled = true -- (boolean)
                    myJointRsm:setRotationLimits( -60, 60 )
     
                    myJointL = physics.newJoint( "pivot", octoOrb, leftOrb, xLoc-50,550 )
                    myJointL.isLimitEnabled = true -- (boolean)
                    myJointL:setRotationLimits( -60, 60 )
     
                    myJointLsm = physics.newJoint( "pivot", leftOrb, leftSmOrb, xLoc-100,550 )
                    myJointLsm.isLimitEnabled = true -- (boolean)
                    myJointLsm:setRotationLimits( -60, 60 )
    

    I had a whale of a time dealing with the logistics of getting the game on the device which unfortunately kept me from implementing my idea for an ‘attractor’ a small capsule that travels in a sine wave across the screen unless struck by a player’s pellet, in which case in begins to attract all the large bodies toward it (and, presumably, toward the opposing player’s goal). Oh well, see you at the next version – code available on GitHub.

     
  • Nick 2:46 am on February 22, 2012 Permalink | Reply  

    Otherworldly Crossfire 

    For my Nature of Code class, I’ve decided to try to create a two-player iPad game inspired by the classic tabletop action game Crossfire, albeit with otherworldly physics and powerups. Here’s a little code written in Lua, intended for use with the Corona SDK. At this stage, the example does not do much except create a physics object (a triangle) in the middle of the screen and create listeners for the two purple “hot zones” at each end of the iPad. When touched, these hot zones fire pellets from the corresponding side of the screen toward the other player.

    ----------------------------------------------------------------------------------
    --
    -- game.lua
    -- Game Screen for 'Crossfire'
    --
    ----------------------------------------------------------------------------------
     
    local storyboard = require( &quot;storyboard&quot; )
    local scene = storyboard.newScene()
     
    -- Called when the scene's view does not exist:
    function scene:createScene( event )
        local group = self.view
    end
    
    -- Function that shoots a pellet - takes two arguments, which player fired, and where they fired from.
    local function shootFrom(whichPlayer, loc)
        local group = scene.view;
        local ball
    
        -- If the first player fired ...
        if (whichPlayer == 1) then
    
            ball = display.newCircle( 100, 100, 10 )
            physics.addBody( ball, { density=10.0, friction=0.8, bounce=0.3, radius = 10 } )
            ball.x = loc
            ball.y = 10
            ball:setFillColor(255,30, 30, 255)
            
            -- Apply a force downward on the ball
            ball:applyForce( 0, 1000, ball.x, ball.y )
            ball.isBullet = true;
            group:insert(ball);
        
        end
    
        -- If the second player fired...
        
        if (whichPlayer == 2) then
      
            ball = display.newCircle( 100, 100, 10 )
            physics.addBody( ball, { density=10.0, friction=0.8, bounce=0.3, radius = 10 } )
            ball.x = loc
            ball.y =  display.contentHeight - 10;
            ball:setFillColor(30,30, 255, 255)
            
            -- Apply a force upward on the ball
            ball:applyForce( 0, -1000, ball.x, ball.y )
            ball.isBullet = true;
            group:insert(ball);
    
        end
    
        -- This function eventually removes a pellet from memory if it has been on the screen too long
        -- (In the future add a 'fade out')
        
        local function removeBall()
            print(&quot;ball removed&quot;);
            ball:removeSelf();
            ball = nil
        end  
        
        -- In 10 seconds, remove the ball.
        timer.performWithDelay(10000, removeBall )
    end
    
    -- These are the event listeners for tapping in the 'fire zones'
    
    local function firePlayer1(event)
    
        -- Note how pellet fires when the finger is lifted. Later, I will implement a 'charge up' that will change the size of the pellet based on how long the finger has been on the screen.
        
        if (event.phase == &quot;ended&quot;) then
            print (event.x);
            shootFrom(1, event.x)
        end
    end
    
    local function firePlayer2(event)
    
        -- Note how pellet fires when the finger is lifted. Later, I will implement a 'charge up' that will change the size of the pellet based on how long the finger has been on the screen.
        print (event.x);
        if (event.phase == &quot;ended&quot;) then
            shootFrom(2,event.x)
        end
    end
     
    -- Note that other event.phases could be &quot;began&quot;, &quot;moved&quot;, &quot;stationary&quot;, &quot;cancelled&quot;
    
    -- Called immediately after scene has moved onscreen:
    function scene:enterScene( event )
        local group = self.view
        
        -- Create two goal zones
        
        local zone1 = display.newRect( 0, 0, display.contentWidth, 150 )
    	zone1:setFillColor(153, 0, 153, 100)
    	group:insert(zone1)
    	
    	-- Register an event listener for touching the first goal zone
    	
    	zone1:addEventListener(&quot;touch&quot;, firePlayer1)
    	
    	local zone2 = display.newRect( 0, display.contentHeight - 150, display.contentWidth, 150 )
        zone2:setFillColor(153, 0, 153, 100)
        
        -- Register an event listener for touching the first goal zone
        
        zone2:addEventListener(&quot;touch&quot;, firePlayer2)
        
        -- Everything must be added to the local group to be handled on scene changes.
        
        group:insert(zone2)
        group:insert(zone1)
    	
    	-- Define a function to place the initial objects on screen. For now, it's just a triangle.
    	
    	local function placeObjects()
    
            -- Draw a triangle
            local triangle = display.newLine(100,100, 300,100);
            triangle:append(200,0, 100,100);
            triangle.width = 3;
            triangle:setColor(100,100,100,255);
            
    
            -- Add a custom physics body shape based on the triangle shape
            triShape = {0,0, 100,-100, 200,0, 0, 0}
            physics.addBody( triangle, { density=3.0, friction=0.8, bounce=0.3, shape = triShape } )
            triangle.x = 200;
            triangle.y = 500;
            triangle.isSleepingAllowed = false
            triangle.linearDamping = 1
            triangle.angularDamping = 1;
    
            -- Everything must be added to the local group to be handled appropriately on 'scene changes'
            group:insert(triangle);
        end
        
        -- Place objects on screen
        
        placeObjects();
    end
     
     -- Called when scene is about to move offscreen:
    function scene:exitScene( event )
            local group = self.view
    end
     
    -- Called prior to the removal of scene's &quot;view&quot; (display group)
    function scene:destroyScene( event )
            local group = self.view
    end
     
    
    ---------------------------------------------------------------------------------
    -- Listeners required for Storyboard API.
    ---------------------------------------------------------------------------------
     
    -- &quot;createScene&quot; event is dispatched if scene's view does not exist
    scene:addEventListener( &quot;createScene&quot;, scene )
     
    -- &quot;enterScene&quot; event is dispatched whenever scene transition has finished
    scene:addEventListener( &quot;enterScene&quot;, scene )
     
    -- &quot;exitScene&quot; event is dispatched before next scene's transition begins
    scene:addEventListener( &quot;exitScene&quot;, scene )
     
    -- &quot;destroyScene&quot; event is dispatched before view is unloaded, which can be
    -- automatically unloaded in low memory situations, or explicitly via a call to
    -- storyboard.purgeScene() or storyboard.removeScene().
    scene:addEventListener( &quot;destroyScene&quot;, scene )
     
    ---------------------------------------------------------------------------------
     
    return scene
    
    ----
    
    
    
    

     
c
Compose new post
j
Next post/Next comment
k
Previous post/Previous comment
r
Reply
e
Edit
o
Show/Hide comments
t
Go to top
l
Go to login
h
Show/Hide help
shift + esc
Cancel