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:

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:

and a close-up of the action:

And now for the code. First a run-through of the variables used:
And the generator:
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).
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:

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:

and a close-up of the action:

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).
#2
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.
05/19/2008 (11:16 pm)
@NeillYeah, 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
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.
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.

Torque 3D Owner Neill Silva