Game Development Community

dev|Pro Game Development Curriculum

TGB Crowd Test pt. 2

by Brian Wilson · 05/19/2008 (2:51 pm) · 3 comments

Ok, time for an update on the TGB Crowd Test Generator. Last time I unveiled my first run at my crowd generator. At the time I created several art assets to handle color variation along with 2 variants of the shirt and pants graphic to mix things up a bit.

I've updated the system to use t2dSceneObject::setBlendColor() on a simple white template for each object. So now there is just 1 graphic for each art asset. This results in 7 different assets instead of 29 and and an exponential increase in runner variants from the previous 2178 unique possibilities:

www.soulsofthebanished.com/images/torque/CrowdTest002/runner_assets.jpg

And here a new movie with the updated code and an added background manager (again, 1 art asset using some tricks with setBlendColor(); )

www.youtube.com/watch?v=bFa0ROaEAhs
UPDATE: Vimeo version (much better) - www.vimeo.com/1214676


And a couple of screenshots, this of the new game running:
www.soulsofthebanished.com/images/torque/CrowdTest002/runner_ss2a.jpg
and a close-up of the action:
www.soulsofthebanished.com/images/torque/CrowdTest002/runner_ss2.jpg
And now for the code. First a run-through of the variables used:
$UNITLAYERMIN = 5; // the closest layer of the runner anchor
$UNITLAYERMAX = 25; // the furthest layer of the runner anchor

$CLOSELIMIT = 32.5; // the X coord for the closest runner range
$FARLIMIT = -12.5; // the Y coord for the furthest runner range

$UNITWIDTHBASE = 10; // set the width base for runners
$UNITWIDTHVARIANT = 20; // set the +/-  width variation percentage
$UNITHEIGHTBASE = 10; // set the height base for the runners
$UNITHEIGHTVARIANT = 20; // set the +/- width variation percentage

$UNITSPEEDBASE = 10; // set the velocity base for the runners
$UNITSPEEDVARIANT = 20; // set the +/- velocity variantion percentage

$UNITSCALE = 25; // the degree of scale between distance variations

$thescenegraph = sceneWindow2D.getSceneGraph(); // global for the scenegraph

$STREAKERBOTTOM = 5; // percentage probability of runner having no pants
$STREAKERSHOES = 25; // percentage probability of runner having no shoes
$STREAKERTOP = 15; // percentage probablity of runner having no shirt

// vars for random clothing style
// using a naming convention of:
// <clothingtype><variant#>
// i.e. "shirt1"

$CLOTHINGPANTVAR = 2; // there are 2 pant graphic variants
$CLOTHINGSHIRTVAR = 2; // there are 2 shirt graphic variants

And the generator:
function newRunner()
{
   %thislayer = getRandom($UNITLAYERMIN,$UNITLAYERMAX);
   %rangeY = $CLOSELIMIT-$FARLIMIT;

   // algo to determine runners location in the depth field
   // 5% - Closest Quarter
   // 13% - 2nd Closest Quarter
   // 15% - 3rd Closest Quarter
   // 66% - Furthest Quarter
   %rangeFinder = getRandom(1,100);
   if(%rangeFinder>95){
     %rangeYhigh = $CLOSELIMIT;
     %rangeYlow = (%rangeY/4)*3;
     %speedmod = 1.50;
   } else if(%rangeFinder>82) {
     %rangeYhigh = (%rangeY/4)*3;
     %rangeYlow = (%rangeY/4)*2;
     %speedmod = 1.25;
   } else if(%rangeFinder>67){
     %rangeYhigh = (%rangeY/4)*2;
     %rangeYlow = (%rangeY/4);
     %speedmod = 1;
   } else {
     %rangeYhigh = (%rangeY/4);
     %rangeYlow = $FARLIMIT;
     %speedmod = 0.75;
   }
   %coordY = getRandom(%rangeYlow, %rangeYhigh);   
   
   // set the runners height and width /w variant factored
   // and then scale it based on location in depth field
   %sizebaseX = getRandom($UNITWIDTHBASE-($UNITWIDTHVARIANT/$UNITWIDTHBASE),
                        $UNITWIDTHBASE+($UNITWIDTHVARIANT/$UNITWIDTHBASE));
   %sizebaseY = getRandom($UNITHEIGHTBASE-($UNITHEIGHTVARIANT/$UNITHEIGHTBASE),
                        $UNITHEIGHTBASE+($UNITHEIGHTVARIANT/$UNITHEIGHTBASE));
   %sizescale = (%coordY+50)/($UNITSCALE+100);
   %size = %sizebaseX*%sizescale@" "@%sizebaseY*%sizescale;
   
   // spawn the root layer of the runner
   %runner = new t2dAnimatedSprite() {
      scenegraph = $thescenegraph;
      imageMap = "runnerBaseImageMap"; // 
      canSaveDynamicFields = "0";
      Position = "-76 " @ %coordY; //spawn on left side offscreen
      class = "runnerClass";
      size=%size;
      WorldLimitCallback = "1";
      WorldLimitMax = "75 100";
      WorldLimitMin = "-85 -100";
      WorldLimitMode = "KILL";
   };
   %runner.playAnimation(runnerBaseAnimation);
   %runner.setLayer(%thislayer);
   // set the speed based on base modded by variant range and depth field loc
   %runner.setLinearVelocityX(getRandom(($UNITSPEEDBASE-
                        ($UNITSPEEDVARIANT/$UNITSPEEDBASE))*%speedmod, 
                        ($UNITSPEEDBASE+($UNITSPEEDVARIANT/$UNITSPEEDBASE))
                        *%speedmod));

   // spawn the skin layer and mount it behind the root   
   %runnerSkin = new t2dAnimatedSprite() {
      scenegraph = $thescenegraph;
      imageMap = "skinImageMap";
      canSaveDynamicFields = "0";
      class = "runnerClass";
      size=%size;
      WorldLimitCallback = "1";
      WorldLimitMax = "75 100";
      WorldLimitMin = "-85 -100";
      WorldLimitMode = "KILL";
   };
   // generate the skin shade darkness
   %shade=getRandom(441, 1000)/1000;
   // generate the skin pigment and modify it by the shade
   // my starting color for skin was R252G228B186. Based on a shift of
   // +/- 20 degrees hue and +/- 50 lightness (what looked good in my tool)
   // I shifted the skin to +20deg hue and +50 lightness to figure out what
   // 100%/100%/100% would be for my top color, which ended up being
   // R254G253B221. I then shifted from my base
   // color to -20%deg Hue and +50 lightness to figure out my low hue shift of
   // R254G231B221. Now, knowing that R and B do not shift in my hue range,
   // I only needed to shift G from 91.3% to 100%. I took my base skin color
   // again and shifted it to -20% hue, -50% lightness to get my darkest pigment
   // which was R126G103B093 or R49.6%G40.7%B42.1%. I averaged these percentages
   // to 44.1%. So once I  apply my green shift, then apply the shading of
   // 44.1% to 100%.
   %runnerSkin.setBlendColor(%shade@" "@
                        (getRandom(913, 1000)/1000)*%shade@" "@%shade@" 1");
   %runnerSkin.playAnimation("skinAnimation");
   %runnerSkin.setLayer(%thislayer+1); // place behind root
   %runnerSkin.mount(%runner, "0.000 0.000"); // mount to center of root


   // spawn the shoe layer and mount it (if runner isn't streaking)
   if(getRandom(1,100)>$STREAKERSHOES){
      %shoes = "shoes1";
      %runnerShoes = new t2dAnimatedSprite() {
         scenegraph = $thescenegraph;
         imageMap = %shoes@"ImageMap";
         canSaveDynamicFields = "0";
         class = "runnerClass";
         size=%size;
         WorldLimitCallback = "1";
         WorldLimitMax = "75 100";
         WorldLimitMin = "-85 -100";
         WorldLimitMode = "KILL";
      };
      %runnerShoes.setBlendColor((getRandom(25, 100)/100)@" "@
                           (getRandom(25, 100)/100)@" "@(getRandom(25, 100)
                           /100)@" 1");
      %runnerShoes.playAnimation(%shoes@"Animation");
      %runnerShoes.setLayer(%thislayer-1);
      %runnerShoes.mount(%runner, "0.000 0.000");
   }

   // spawn the pants layer and mount it (if runner isn't streaking)
   if(getRandom(1,100)>$STREAKERBOTTOM){
      // there are 2 pant variants to chose from
      %pants = "pants"@getRandom(1,$CLOTHINGPANTSVAR);
      %runnerPants = new t2dAnimatedSprite() {
         scenegraph = $thescenegraph;
         imageMap = %pants@"ImageMap";
         canSaveDynamicFields = "0";
         class = "runnerClass";
         size=%size;
      };
      %runnerPants.setBlendColor((getRandom(25, 100)/100)@" "@
                           (getRandom(25, 100)/100)@" "@(getRandom(25, 100)
                           /100)@" 1");
      %runnerPants.playAnimation(%pants@"Animation");
      %runnerPants.setLayer(%thislayer-2);
      %runnerPants.mount(%runner, "0.000 0.000");
   }


   if(getRandom(1,100)>$STREAKERTOP){
      // there are 2 shirt variants to chose from
      %shirt = "shirt"@getRandom(1,$CLOTHINGSHIRTVAR);
      %runnerShirt = new t2dAnimatedSprite() {
         scenegraph = $thescenegraph;
         imageMap = %shirt@"ImageMap";
         canSaveDynamicFields = "0";
         class = "runnerClass";
         size=%size;
         WorldLimitCallback = "1";
         WorldLimitMax = "75 100";
         WorldLimitMin = "-85 -100";
         WorldLimitMode = "KILL";
      };
      %runnerShirt.setBlendColor((getRandom(25, 100)/100)@" "@
                           (getRandom(25, 100)/100)@" "@(getRandom(25, 100)
                           /100)@" 1");
      %runnerShirt.playAnimation(%shirt@"Animation");
      %runnerShirt.setLayer(%thislayer-3);
      %runnerShirt.mount(%runner, "0.000 0.000");
   }
 }

My next steps are to do one more version of the generator demo, and also to strip the thing down completely to bare bones for a simple benchmarking tool for release (~2.5MB download).

#1
05/19/2008 (5:33 pm)
Thats a horrible FPS rate lol.
#2
05/19/2008 (11:16 pm)
@Neill
Yeah, I had the saturation at 1000k runners (~5000 objects), which is right after the point where things turn south on performance. I get ~38 FPS at 900 runners (~4500 objects), then ~55 FPS at ~800 runners. The final release will have proper benchmarking and hopefully a performance increase.
#3
05/19/2008 (11:54 pm)
I did a somewhat similar test a couple of years back (nothing near as cool--just some boxes bouncing about), and I found that when things got really busy in the scene (so many objects), the actual process of creating the objects one at a time caused quite a bit of performance hit.

It appears from what I can see that you are killing off the runner's object when they hit the world limit, and then allocating a new one to replace--you might want to look at simply setting them to hidden and moving them back to the "starting line" instead of going through the performance hit of deletion/recreation--effectively recycling them instead of kill/create.

IIRC, I just used a SimSet to store "expired" objects, and my spawn code would check to see if one was available before actually creating a new one.