Procedural Death Jam, Day 5+: Gibby's Greebles
by Gibby · 03/18/2014 (8:51 pm) · 4 comments
So, I didn't finish a game in seven days...

Odds were against me, considering what I was trying yet I was, at a point, poised to actually pull it off...
Aye Acaster, comprendo tu dolor Pues!
In the pre-dawn hours between Day 4 and Day 5, I'd dropped in a handful of UAISK bots using the mech and player models I was using, and even without auto-updating navmeshes or any of my planned alien invasion, I had a pretty immersive Procedural Death Arena. All I needed was to fill the scene with 'greebles' to give the appearance of a battlefield and I'd have a cool little package. I knew I wasn't going to finish a playable demo of what I'd planned, mostly due to work calls hundreds of miles away, so I decided instead to pull a 'James T. Kirk' and change the rules of an unwinnable game. I figured I'd try to push the limit of how much proceduralism I could achieve in one week using my version of T3D.
I began by making a list of all the various ways I could randomize the level, whether drastic or subtle. I then made a spreadsheet of 96 different 'greeble elements' I could add. Most of these were models of some sort but others were particles, lights, decals, sounds and scripted events.

I then went on a marathon of speed-rigging. I didn't want ot use anything I was planning for TolTech so I leeched off of Open Game Art and TurboSquid most of what I needed, and re-worked some old-school TGE bits to fill in the gaps. All in all I rigged and/or reskinned, as well as created .dds materials for, 48+ greeble models in addition to 20 buildings within the 7 days of the jam. This included three new weapons, three recycled ones, and modifcations of the GG alien weapons with custom AFX effectrons. In the process, I added team/damage/zombie skins and functionality to the player and mech models. In addition to assets, I also created or hacked up another 8 random effect scripts, most of them using AFX effectrons, to generate the feel of random battle chaos.
The Greebles:

Energy Spores:

LFA's 'Destroyed Walls' hacked into pieces:

Zombie Marine:

The first and simplest proceduralism were the Precipitation, Groundcover and ShapeReplicator tools. I set these up in scripts so they could be called dynamically:
The plague is rampant when the player enters the arena

but gone if they can turn the tide of battle

In the process of doing this I also started work on some environmental spells for AFX but back-burnered them for the sake of time. The challenge of course is that the replicators and ForestObject are intended to be used in a level that is setup once and not changed. To account for this I setup a set of scripts I dubbed the 'Greeblizer' that could generate additional layers of proceduralism. The concept here was to treat each 'level' of proceduralism similar to a 'layer' in Photoshop. My base buildings and tiles are the background layer, detail buildings and props are the colors and effects the 'grunge' layer on top. First was a generic StaticShape datablock setup from which I could quickly create markers for each layer of greebles. I left 'hooks into them so that I could later access and manipulate them, even if I added functionality after the prefabs were built, adding to my extensibility:
All very basic TorqueScript, nothing fancy at all. Notice the action happens in the initializeObjective function, since onAdd is usually too early. I created a total of eight different types of 'GreebleMarkers' using a similar structure. Next I needed to randomly choose and deploy them. I'd hacked together a random tile generator with considerable help on the forums and my bro Sean Rice, but it was too hard-wired and in the spirit of extensibility I threw it out in favor of a newer version. This one creates the needed numbers sequentially into a 'source' array, and then randomly add it to a second 'target' array, deleting each entry in the process. This guaranteed me a random shuffle every time.
This is still TorqueScript 101, but here's the caveat: I needed to be able to add items such as health, AI markers and weapons to the prefab tile pieces and later be able to access which ever ones were chosen for that mission. This nessecitated a simple script that creates SimGroups and/or SimSets for the greebles, then does something with them:
Once I had the basis for setting up the prefab tile pieces I created a new 'bench jig' level, in which I could rapidly construct the tile pieces:

At this point, I now had the assets and scripts created to procedurally populate and animate my world, as well as a rough hard-wired tile generator and a dozen or so prefabs finished. What I didn't have was any more time to get my entry in. In a 10-day Jam I'd have pulled it off - the irony is that in failing to complete my jam game on time, I succeded in creating a very useful little script applet and considerable content to share with everyone. For now I have enough roughed in tiles to satisfy my 16-tile, hard-wired generator, and with a couple nights' work setting up AI, skinning the GUIs and tightening up a few things could have a playable server up.

I'm giving my brain and wrists a couple days' break and will then finish this phase up enough to host network test session. The great thing about this 'tile' system is that I can get it all working and test gameplay/AI with rough placeholder tiles, and then go back and superdetail the tiles at a less frantic pace later. As I package things up, I'll upload the sharable content in the appropriate places.
Stay Tuned!

Odds were against me, considering what I was trying yet I was, at a point, poised to actually pull it off...
Aye Acaster, comprendo tu dolor Pues!
In the pre-dawn hours between Day 4 and Day 5, I'd dropped in a handful of UAISK bots using the mech and player models I was using, and even without auto-updating navmeshes or any of my planned alien invasion, I had a pretty immersive Procedural Death Arena. All I needed was to fill the scene with 'greebles' to give the appearance of a battlefield and I'd have a cool little package. I knew I wasn't going to finish a playable demo of what I'd planned, mostly due to work calls hundreds of miles away, so I decided instead to pull a 'James T. Kirk' and change the rules of an unwinnable game. I figured I'd try to push the limit of how much proceduralism I could achieve in one week using my version of T3D.
I began by making a list of all the various ways I could randomize the level, whether drastic or subtle. I then made a spreadsheet of 96 different 'greeble elements' I could add. Most of these were models of some sort but others were particles, lights, decals, sounds and scripted events.

I then went on a marathon of speed-rigging. I didn't want ot use anything I was planning for TolTech so I leeched off of Open Game Art and TurboSquid most of what I needed, and re-worked some old-school TGE bits to fill in the gaps. All in all I rigged and/or reskinned, as well as created .dds materials for, 48+ greeble models in addition to 20 buildings within the 7 days of the jam. This included three new weapons, three recycled ones, and modifcations of the GG alien weapons with custom AFX effectrons. In the process, I added team/damage/zombie skins and functionality to the player and mech models. In addition to assets, I also created or hacked up another 8 random effect scripts, most of them using AFX effectrons, to generate the feel of random battle chaos.
The Greebles:

Energy Spores:
LFA's 'Destroyed Walls' hacked into pieces:
Zombie Marine:
The first and simplest proceduralism were the Precipitation, Groundcover and ShapeReplicator tools. I set these up in scripts so they could be called dynamically:
The plague is rampant when the player enters the arena

but gone if they can turn the tide of battle

In the process of doing this I also started work on some environmental spells for AFX but back-burnered them for the sake of time. The challenge of course is that the replicators and ForestObject are intended to be used in a level that is setup once and not changed. To account for this I setup a set of scripts I dubbed the 'Greeblizer' that could generate additional layers of proceduralism. The concept here was to treat each 'level' of proceduralism similar to a 'layer' in Photoshop. My base buildings and tiles are the background layer, detail buildings and props are the colors and effects the 'grunge' layer on top. First was a generic StaticShape datablock setup from which I could quickly create markers for each layer of greebles. I left 'hooks into them so that I could later access and manipulate them, even if I added functionality after the prefabs were built, adding to my extensibility:
//****************************************************************
// "Gibby's Greebles"
// weaponGreeble.cs
// procedural generation of weapon placement
//****************************************************************
//echo("weaponGreeble.cs->exec'd here ->->");
//****************************************************************
// weaponMarker Datablock
//****************************************************************
datablock StaticShapeData(weaponMarker)
{
//Mission editor category, this datablock will show up in the
//specified category under the "shapes" root category.
category = "GreebleMarker";
//Basic Item properties
shapeFile = "core/art/shapes/octahedron.dts";
visible = "false";
};
//****************************************************************
// weaponMarker add/remove/initialize
//****************************************************************
function weaponMarker::onAdd(%this, %obj)
{
echo("weaponGreeble.cs->weaponMarker::onAdd this: "@%this@" obj: "@%obj);
//reset the object
%obj.initialized = 0;
// Place into correct group
if( !isObject(WeaponMarkerGroup) )
{
echo("greebles.cs->generateGreebleMarkers creating WeaponMarkerGroup");
%grp = new SimGroup(WeaponMarkerGroup);
if(!isObject(MissionCleanup))
{
new SimGroup(MissionCleanup);
// Make the MissionCleanup group the place where all new objects will automatically be added.
$instantGroup = MissionCleanup;
}
MissionCleanup.add(%grp);
}
else
WeaponMarkerGroup.add(%obj);
}
function weaponMarker::initializeObjective(%this, %obj)
{
//Gibby ideally, this would be a list of weapons in a text string file that would be parsed
//For now, we'll hard-wire them
if(!isObject(%obj))
return;
%weaponType = %obj.weaponType;
if(%weaponType $= "")
%weaponType = "random";
echo (">>>>weaponMarker::initializeObjective called by %obj: " @%obj@ " type: "@%weaponType);
switch$ ( %weaponType )
{
case "sniper":
%rndSniper = getRandom(1,2);
switch(%rndSniper)
{
case 1:
%block = "sniperRifle";
case 2:
%block = "Kraad";
}
case "heavy":
%rndHeavy = getRandom(1,2);
switch(%rndHeavy)
{
case 1:
%block = "VolcanoGun";
case 2:
%block = "rocketLauncher";
case 3:
%block = "Kralmok";
}
case "assault":
%rndAssault = getRandom(1,2);
switch(%rndAssault)
{
case 1:
%block = "grenadeLauncher";
case 2:
%block = "shotGun";
}
case "random":
%rndWeapon = getRandom(1,8);
switch(%rndWeapon)
{
case 1:
%block = "grenadeLauncher";
case 2:
%block = "shotGun";
case 3:
%block = "VolcanoGun";
case 4:
%block = "rocketLauncher";
case 5:
%block = "sniperRifle";
case 6:
%block = "rocketLauncher";
case 7:
%block = "Kraad";
case 8:
%block = "Kralmok";
}
}
%pos = posFromTransform(%obj.getTransform());
%rot = "0 0 1 "@ (getRandom() * 360);
%xfm = (%pos @ %rot);
echo (">>>>weaponMarker::initializeObjective block: " @%block@" %xfm: "@%xfm);
%item = new Item()
{
datablock = %block;
rotation = %rot; //"0 0 1 "@ (getRandom() * 360);
position = %pos;
};
MissionCleanup.add(%item);
%item.setTransform(%xfm);
echo (">>>>weaponMarker::initializeObjective weapon item: " @%item@", a "@%block@" added at: "@%xfm);
%ammoBlock = %block.image.clip;
if(!isObject(%ammoBlock || %ammoBlock $= ""))
%ammoBlock = %block.ammo;
if(!isObject(%ammoBlock || %ammoBlock $= ""))
return;
echo (">>>>weaponMarker::initializeObjective ammo block is: " @%ammoBlock);
%ammo = new Item()
{
datablock = %ammoBlock;
rotation = "0 0 1 "@ (getRandom() * 360);
count = %amount;
};
MissionCleanup.add(%ammo);
%ammo.setTransform(%xfm);
echo (">>>>weaponMarker::initializeObjective weapon item: " @%item@", a "@%block@" added.");
%item.applyKick( 1, 2, "foward" );
return;
}
//****************************************************************
// weaponMarker collision/damage
//****************************************************************All very basic TorqueScript, nothing fancy at all. Notice the action happens in the initializeObjective function, since onAdd is usually too early. I created a total of eight different types of 'GreebleMarkers' using a similar structure. Next I needed to randomly choose and deploy them. I'd hacked together a random tile generator with considerable help on the forums and my bro Sean Rice, but it was too hard-wired and in the spirit of extensibility I threw it out in favor of a newer version. This one creates the needed numbers sequentially into a 'source' array, and then randomly add it to a second 'target' array, deleting each entry in the process. This guaranteed me a random shuffle every time.
//***********************************************************************
// "Gibby's Greebles"
// greebles.cs
// procedural generation of level entity placement
//***********************************************************************
function generateRandomNumberSequence(%range, %count, %offset, %verbose)
{
if(%verbose = 1)
echo("greebles.cs->generateRandomNumberSequence range:" @%range@", count: "@%count@", offset: "@%offset);
//%range is total amount of source numbers
//%count is the amount of numbers needed
//%offset is the first number if the count doesn't start at 0
//%verbose turns off the spam
//create the source array
%sourceArray = new ArrayObject() {};
MissionCleanup.add(%array);
for(%key = 0;%key < %count;%key++ )
{
%value = %key + %offset;
%sourceArray.add(%key, %value);
%check = %sourceArray.getValue(%key);
if(%verbose = 1)
echo("greebles.cs->generateRandomNumberSequence sourceArray key[" @%key@"] is: "@%check);
}
if(%verbose = 1)
%sourceArray.echo();
//Now create the target arrray
%targetArray = new ArrayObject() {};
MissionCleanup.add(%array);
%sourceCount = %sourceArray.count();
for (%targetKey = 0;%targetKey < %count; %targetKey++)
{
%randomKey = getRandom(0,%sourceCount-1);
%targetValue = %sourceArray.getValue(%randomKey);
%targetArray.add(%targetKey, %targetValue);
%sourceArray.erase(%randomKey);
%sourceArray.sortka();
%sourceCount = %sourceArray.count();
if(%verbose = 1)
{
%check = %targetArray.getValue(%targetKey);
echo("greebles.cs->generateRandomNumberSequence %targetArray key[" @%key@"] is: "@%check);
%sourceArray.echo();
%targetArray.echo();
}
}
%sourceArray.delete();
return %targetArray;
}This is still TorqueScript 101, but here's the caveat: I needed to be able to add items such as health, AI markers and weapons to the prefab tile pieces and later be able to access which ever ones were chosen for that mission. This nessecitated a simple script that creates SimGroups and/or SimSets for the greebles, then does something with them:
//***********************************************************************
//generate SimGroups for each greeble/gamePlay marker
//***********************************************************************
function generateGreebleGroups()
{
if( !isObject(WeaponMarkerGroup) )
{
//echo("greebles.cs->generateGreebleMarkers creating WeaponMarkerGroup");
%grp = new SimGroup(WeaponMarkerGroup);
MissionCleanup.add(%grp);
}
if( !isObject(HealthMarkerGroup) )
{
//echo("greebles.cs->generateGreebleMarkers creating HealthMarkerGroup");
%grp = new SimGroup(HealthMarkerGroup);
MissionCleanup.add(%grp);
}
if( !isObject(DebrisMarkerGroup) )
{
//echo("greebles.cs->generateGreebleMarkers creating DebrisMarkerGroup");
%grp = new SimGroup(DebrisMarkerGroup);
MissionCleanup.add(%grp);
}
if( !isObject(CraterMarkerGroup) )
{
//echo("greebles.cs->generateGreebleMarkers creating CraterMarkerGroup");
%grp = new SimGroup(CraterMarkerGroup);
MissionCleanup.add(%grp);
}
if( !isObject(JunkMarkerGroup) )
{
//echo("greebles.cs->generateGreebleMarkers creating JunkMarkerGroup");
%grp = new SimGroup(JunkMarkerGroup);
MissionCleanup.add(%grp);
}
if( !isObject(ScrapyardMarkerGroup) )
{
//echo("greebles.cs->generateGreebleMarkers creating ScrapyardMarkerGroup");
%grp = new SimGroup(ScrapyardMarkerGroup);
MissionCleanup.add(%grp);
}
if( !isObject(ZombieMarkerGroup) )
{
//echo("greebles.cs->generateGreebleMarkers creating ZombieMarkerGroup");
%grp = new SimGroup(ZombieMarkerGroup);
MissionCleanup.add(%grp);
}
if( !isObject(MechWreckMarkerGroup) )
{
//echo("greebles.cs->generateGreebleMarkers creating MechWreckMarkerGroup");
%grp = new SimGroup(MechWreckMarkerGroup);
MissionCleanup.add(%grp);
}
// poll through the MissionGroup
%n = MissionGroup.getCount();
//echo(">>greebles.cs->generateGreebleMarkers number of entities in MissionGroup:" @ %n);
for (%i = 0; %i < %n; %i++)
{
//echo(">>greebles.cs->generateGreebleMarkers total objects: "@%n@" index: "@%i);
//Get the object
%obj = MissionGroup.getObject(%i);
if(!isObject(%obj)||%obj.dataBlock $= "")
continue;
if(%obj.getType() & $TypeMasks::StaticShapeObjectType)
{
//%objBlock = %obj.getDatablock();
//if(%objBlock !$= "")
if(%obj.dataBlock !$= "")
%name = %obj.dataBlock;
//echo(">>greebles.cs->generateGreebleMarkers valid object at index: "@%i@" obj:" @ %obj @" dataBlock name: "@%name);
//Place the marker in its group if it isn't already there
switch$ (%name)
{
case "weaponMarker":
if (!WeaponMarkerGroup.isMember(%obj))
{
//echo(">>greebles.cs->generateGreebleMarkers adding " @ %obj @" datablock: " @ %obj.dataBlock);
//add to the WeaponMarkerGroup
WeaponMarkerGroup.add(%obj);
}
case "healthMarker":
if (!HealthMarkerGroup.isMember(%obj))
{
//echo(">>greebles.cs->generateGreebleMarkers adding " @ %obj @" datablock: " @ %obj.dataBlock);
//add to the HealthMarkerGroup
HealthMarkerGroup.add(%obj);
}
case "debrisMarker":
if (!DebrisMarkerGroup.isMember(%obj))
{
//echo(">>greebles.cs->generateGreebleMarkers adding " @ %obj @" datablock: " @ %obj.dataBlock);
//add to the DebrisMarkerGroup
DebrisMarkerGroup.add(%obj);
}
case "craterMarker":
if (!CraterMarkerGroup.isMember(%obj))
{
//echo(">>greebles.cs->generateGreebleMarkers adding " @ %obj @" datablock: " @ %obj.dataBlock);
//add to the CraterMarkerGroup
CraterMarkerGroup.add(%obj);
}
case "junkMarker":
if (!JunkMarkerGroup.isMember(%obj))
{
//echo(">>greebles.cs->generateGreebleMarkers adding " @ %obj @" datablock: " @ %obj.dataBlock);
//add to the JunkMarkerGroup
JunkMarkerGroup.add(%obj);
}
case "scrapyardMarker":
if (!ScrapyardMarkerGroup.isMember(%obj))
{
//echo(">>greebles.cs->generateGreebleMarkers adding " @ %obj @" datablock: " @ %obj.dataBlock); //add to the ScrapyardMarkerGroup
ScrapyardMarkerGroup.add(%obj);
}
case "zombieMarker":
if (!ZombieMarkerGroup.isMember(%obj))
{
//echo(">>greebles.cs->generateGreebleMarkers adding " @ %obj @" datablock: " @ %obj.dataBlock);
//add to the ZombieMarkerGroup
ZombieMarkerGroup.add(%obj);
}
case "mechWreckMarker":
if (!MechWreckMarkerGroup.isMember(%obj))
{
//echo(">>greebles.cs->generateGreebleMarkers adding " @ %obj @" datablock: " @ %obj.dataBlock);
//add to the MechWreckMarkerGroup
MechWreckMarkerGroup.add(%obj);
}
}
}
}
}
//***********************************************************************
//'generate' functions for each type of greeble marker
// where possible/convenient, hooks have been left for future functions
// these can be exec'd at mission load, or dynamically in-game
//***********************************************************************
//***********************************************************************
// generateWeapons
// these are placed in prefabs and 'randomly' placed in the level
// based on type (asasault,sniper, etc.)
//***********************************************************************
function generateWeapons(%range, %count, %verbose)
{
//%range is total number of available weaponMarkers
//%count is number of weapons to be used
if(%verbose = 1)
echo( " >> greebles.cs->generateWeapons input values are range: " @ %range @ " count: " @%count);
if( !isObject(WeaponMarkerGroup) )
{
if(%verbose = 1)
echo("greebles.cs->generateWeapons no WeaponMarkerGroup");
return;
}
if (%range $= "")
%range = WeaponMarkerGroup.getCount();
if(%range == 0)
{
if(%verbose = 1)
echo( " >> greebles.cs->generateWeapons MARKER GROUP COUNT IS 0!");
return;
}
if(%count > %range || %count == 0)
%count = %range;
%seqArray = generateRandomNumberSequence(%range, %count, 0, 1);
for (%key = 0; %key < %count; %key++)
{
//initialize the marker
%markerIndex = %seqArray.getValue(%key);
if(%verbose = 1)
echo( " >> greebles.cs->generateWeapons seqArray key: " @ %key @ " WeaponMarkerGroup index: " @%markerIndex);
%greeble = WeaponMarkerGroup.getObject(%markerIndex);
if(!isObject(%greeble))
{
if(%verbose = 1)
echo (">>>>greebles.cs->generateWeapons SNAP! %greeble "@%greeble@" doesn't exist!");
continue;
}
else if(%greeble.initialized != 1)
{
%greeble.initializeObjective();
%greeble.initialized = 1;
if(%verbose = 1)
echo (">>>>greebles.cs->generateWeapons %greeble: "@%greeble@" initialized");
}
}
}Once I had the basis for setting up the prefab tile pieces I created a new 'bench jig' level, in which I could rapidly construct the tile pieces:

At this point, I now had the assets and scripts created to procedurally populate and animate my world, as well as a rough hard-wired tile generator and a dozen or so prefabs finished. What I didn't have was any more time to get my entry in. In a 10-day Jam I'd have pulled it off - the irony is that in failing to complete my jam game on time, I succeded in creating a very useful little script applet and considerable content to share with everyone. For now I have enough roughed in tiles to satisfy my 16-tile, hard-wired generator, and with a couple nights' work setting up AI, skinning the GUIs and tightening up a few things could have a playable server up.

I'm giving my brain and wrists a couple days' break and will then finish this phase up enough to host network test session. The great thing about this 'tile' system is that I can get it all working and test gameplay/AI with rough placeholder tiles, and then go back and superdetail the tiles at a less frantic pace later. As I package things up, I'll upload the sharable content in the appropriate places.
Stay Tuned!
#2
03/19/2014 (7:13 am)
Nice write up, shame you ran out of time but that deadline was a killer. ;)
#3
Gracias! 'Tis true, 7 days is a killer - I think a 10-day Jam is more realistic as there's extra time in there for things like one's life, etc.
03/19/2014 (7:45 am)
@Steve y Demolishun:Gracias! 'Tis true, 7 days is a killer - I think a 10-day Jam is more realistic as there's extra time in there for things like one's life, etc.
#4
VERY honorable (and dare I say MONUMENTAL) effort and a GREAT read. Wish I could crank out content like that. Sorry you did not make the deadline but, hey you are in a great position for the next time something like this rolls around!
03/19/2014 (11:50 am)
Gibby, VERY honorable (and dare I say MONUMENTAL) effort and a GREAT read. Wish I could crank out content like that. Sorry you did not make the deadline but, hey you are in a great position for the next time something like this rolls around!

Torque Owner Demolishun
DemolishunConsulting Rocks!