This is a proof-of-concept prototype I did for my last game, "Destroy All Humans: Big Willy Unleashed," for the Wii.
Here is the full-size version.
I’ll append the source code to the bottom of the page. It’s pretty ugly. If you resize the browser in the full size version, you can see a bunch of objects I just dragged on to the stage and linked in Action-Script. I even have components (the sliders) that have Action Script attached to them in clear violation of good coding standards. It really was just slapped together over the course a a couple of nights. Anyone who has followed some of my early experiments will also recognize some reused sound effects, and a bit of re-used code from early unrelated projects.
In other words, the perfect prototype. I saw Chris Hecker give a talk at GDC a couple of years ago, and he said the great thing about prototypes is that they end arguments. You build them fast, and they either work, show promise or suck. In any case, issue that were hazy before come into sharp focus.
Compare and contrast with the illustration from the design document:

Which one better communicates the idea? I have to go with the prototype.
In this case, the engineering department was unsure they wanted to commit resources to the feature based on a short text description and the above illustration. I had a clear picture in my head of what I wanted, so I spent some time on my own dime to put together this demo.
The demo clinched the deal, because everyone saw exactly what the design intention was. The implementation was more complex, as the whole thing had to be billboarded to characters in the 3D world-space, but the scope was suddenly clear, and major questions: Is it more fun than button mashing (the feature it was replacing), and minor questions: Can we restrain the particles to a certain volume of the screen? were answered.
Here’s the source:
ball_mc.gotoAndStop(10);
Target_01_mc.gotoAndStop(10);
_root.mySlider.onEnterFrame=function(){
_global.myvelocMod = _root.mySlider.ratio;
ratio.text=_root.mySlider.ratio;
}
_root.mySlider2.onEnterFrame=function(){
_global.myLimit = _root.mySlider2.ratio;
ratio2.text=_root.mySlider2.ratio;
}
_root.mySlider3.onEnterFrame=function(){
_global.interval = _root.mySlider3.ratio;
ratio3.text=_root.mySlider3.ratio;
}
_root.mySlider4.onEnterFrame=function(){
_global.clusterDist = _root.mySlider4.ratio;
ratio4.text=_root.mySlider4.ratio;
}
// Create a mouse listener object
var mouseListener:Object = new Object();
// Every time the mouse cursor moves within the SWF file, update the position of the crosshair movie clip instance on the Stage.
mouseListener.onMouseMove = function() {
targeting_mc._x = _xmouse;
targeting_mc._y = _ymouse;
};
mouseListener.onMouseDown = function() {
_root.shootSound.start(0,1)
if (targeting_mc.hitTest(ball_mc_0)){
ball_mc_0._visible = false;
_root.Boomer._x = ball_mc_0._x -35;
_root.Boomer._y = ball_mc_0._y -60;
_root.Boomer.gotoAndPlay(1);
_root.pongSound.start(0,1);
}
if (targeting_mc.hitTest(ball_mc_1)){
ball_mc_1._visible = false;
_root.Boomer._x = ball_mc_1._x -35;
_root.Boomer._y = ball_mc_1._y -60;
_root.Boomer.gotoAndPlay(1);
_root.pongSound.start(0,1);
}
if (targeting_mc.hitTest(ball_mc_2)){
ball_mc_2._visible = false;
_root.Boomer._x = ball_mc_2._x -35;
_root.Boomer._y = ball_mc_2._y -60;
_root.Boomer.gotoAndPlay(1);
_root.pongSound.start(0,1);
}
if (targeting_mc.hitTest(ball_mc_3)){
ball_mc_3._visible = false;
_root.Boomer._x = ball_mc_3._x -35;
_root.Boomer._y = ball_mc_3._y -60;
_root.Boomer.gotoAndPlay(1);
_root.pongSound.start(0,1);
}
if (targeting_mc.hitTest(ball_mc_4)){
ball_mc_4._visible = false;
_root.Boomer._x = ball_mc_4._x -35;
_root.Boomer._y = ball_mc_4._y -60;
_root.Boomer.gotoAndPlay(1);
_root.pongSound.start(0,1);
}
}
Mouse.addListener(mouseListener);
//__Load Sounds___________________________________________________________
pongS ound = new Sound(pong_mc);
pongSound.attachSound("boomSound");
shootSound = new Sound(shoot_mc);
shootSound.attachSound("pong");
wallSound = new Sound();
wallSound.attachSound("badGoal");
var exclusionTest = 0;
var TgoalExclusion = 0;
var BgoalExclusion = 0;
var LgoalExclusion = 0;
var RgoalExclusion = 0;
var hitCount = 0;
//var Grav = .05;
var Grav = _global.myGrav;
var pX = 400;
var pY = 400;
var pMass = 5000000;
var bMass = 9999999999;
var speed = .6;
var checkTime = 0;
var switchTime = 30;
var speedClip = 9;
//Loose
var targetX:Array = [400,264,536,318,481];
var targetY:Array = [231,323,323,500,500];
var slopeNorm:Array = [0,0,0,0,0];
var slopeX:Array = [400,224,576,318,481];
var slopeY:Array = [231,323,323,500,500];
for (i=0; i <=4; ++i){
tempX = slopeX[i] - 400;
tempY = slopeY[i] - 400;
slopeNorm[i] = 1/(Math.abs(tempX) + Math.abs(tempY));
}
// Start points for planets
var sX:Array = [400,400,400,400,400];
var sY:Array = [400,400,400,400,400];
var velocityX:Array = [0,0,0,0,0];
var velocityY:Array = [0,0,0,0,0];
var pX:Array = [0,0,0,0,0];
var pY:Array = [0,0,0,0,0];
this.onEnterFrame = function() { //_________________________________Main Loop_________________
Mouse.hide();
//prevent explosion clip from loping
if (Boomer._currentframe == 9) {Boomer.stop();}
// Switch Gravity Wells Every Second______________________________________________
checkTime += 1;
switchTime = _global.interval;
if (checkTime >= switchTime) {
// Blinker
if (int1._x < 426){
int1._x = 426;
} else {
int1._x = -150;
}
for (n = 0; n <= 4; ++n){
randTarg = random(5);
pX[n] = targetX[randTarg];
pY[n] = targetY[randTarg];
checkTime = 0;
}
}
//____Interactively change the radius of the gravity wells______________
// clusterDist is controlled by a slider
for (i=0; i <=4; ++i){
targetX[i] = 400 + (((slopeX[i]-400)* slopeNorm[i]) * clusterDist);
targetY[i] = 427 + (((slopeY[i]-400)* slopeNorm[i]) * clusterDist);
// the 430 is me fudging the center of the gravity well cluster
}
// Update the position of each Gravity Well Marker
// The hard way, becuase I suck at Flash
bg0._x = targetX[0]-13;
bg0._y = targetY[0]-13;
bg1._x = targetX[1]-13;
bg1._y = targetY[1]-13;
bg2._x = targetX[2]-13;
bg2._y = targetY[2]-13;
bg3._x = targetX[3]-13;
bg3._y = targetY[3]-13;
bg4._x = targetX[4]-13;
bg4._y = targetY[4]-13;
//____________________________________________________________________ _____
// Calculate the Acceleration, add it to the current velocity, update position
for (n = 0; n <= 4; ++n){
// grab interactive values from sliders
velocMod = _global.myvelocMod;
speedClip = _global.myLimit;
// Call the gravity function
accelArray = getAccel(pX[n],pY[n],sX[n],sY[n]);
// Add the acceleration to the current velocity
velocityX[n] += accelX;
velocityY[n] += accelY;
// If the velocity of a particle is above a certain threshold, clip it
if (velocityX[n] > speedClip) {velocityX[n] = speedClip}
if (velocityX[n] < -(speedClip)) {velocityX[n] = -speedClip}
if (velocityY[n] > speedClip) {velocityY[n] = speedClip}
if (velocityY[n] < -(speedClip)) {velocityY[n] = -speedClip}
// Add the velocity to the current position, applying the Acceleration Mod first
sX[n] = sX[n] + (velocityX[n] * velocMod);
sY[n] = sY[n] + (velocityY[n] * velocMod);
}
// Update the position of each particle
// The hard way, becuase I suck at Flash
ball_mc_0._x = sX[0];
ball_mc_0._y = sY[0];
ball_mc_1._x = sX[1];
ball_mc_1._y = sY[1];
ball_mc_2._x = sX[2];
ball_mc_2._y = sY[2];
ball_mc_3._x = sX[3];
ball_mc_3._y = sY[3];
ball_mc_4._x = sX[4];
ball_mc_4._y = sY[4];
}
stop();
// Function Calculates the acceleration based on gravity
// Simplified some redundant terms, apologies to Issac Newton
function getAccel (planetX,planetY,bodyX,bodyY) {
// Distance = sqrt( (pX-sX)^2 + (pY-sY)^2 )
dist = Math.sqrt( Math.pow((planetX - bodyX),2) + Math.pow((planetY - bodyY),2));
// Base Accelleration from gravity = gravitationalConstant * (pMass / distance^2)
// baseAccel = gravity * (planetMass*bodyMass/Math.pow(dist,2));
baseAccel = (1/Math.pow(dist,2));
// Normalize accelleration for each axis
normalizer = speed/(Math.abs(planetX - bodyX) + Math.abs(planetY - bodyY));
accelX = (planetX - bodyX) * normalizer;
accelY = (planetY - bodyY) * normalizer;
return accelX;
return accelY;
}











June 16th, 2008 at 7:15 pm |
[…] There’s a more detailed discussion of it over here. […]