Game Development Community

dev|Pro Game Development Curriculum

Damage Texture with Transparency

by Derk Adams · 08/03/2009 (9:37 am) · 2 comments

Title:
Damage Texture with Transparency

Purpose:
This resource adds a single texture to the shapeBase object that is a second render with transparency based on damage level.

Background:
A damage layer has been a feature lacking from TGE. As such many types of solutions have been developed. Since none provided exactly what I needed, I created this resource.

Discussion:
A second texture file using the same UV map as the object's original texture is needed. It should represent the completely destroyed object. Two variables and associated functions are added to ShapeBase, one to load the texture and another to track the level of transparency. It is not intrinsically tied to the damage level in the engine, but could be.

The damage texture is a second render and as such, may slow screen draws. It should be minimal unless there are many double rendered objects on the screen. I tried to implement the ability as high as possible in the rendering pipeline so I didn't have to figure out as much of the rendering engine. It should be possible to implement this ability as a single rendering pass using openGL blending and I'd be interested if someone is able to do that.

Acknowledgements:
The ideas come from far and wide, and although I ended up modeling after the cloak texture, here are some resources that I explored before doing it myself.
Combining Multiple Texture Layers
Blended texture layer on models
Plastic Gem #33: Damaged/Bloody Weapons

Key:
I use the same indication of modifications as a patch file. A line beginning with a "-" is to be removed and a line beginning with a "+" is to be added. Be sure to remove the "+" when you actually put the line into your code. A few lines around the changes are shown to give context to the changes. The triple dot "..." is showing that information has been removed for brevity purposes.

Development Environment:
July 2009
Head 1.5.2
Win32
Single Player

Implementation:

EngineFile: /game/shapebase.h
struct ShapeBaseData : public GameBaseData {
...
public:
...
   StringTableEntry  shapeName;
   StringTableEntry  cloakTexName;
+   StringTableEntry  damageTexName;
...

class ShapeBase : public GameBase
{
...
 protected:
...
   /// @name Cloaking
   /// @{
   bool mCloaked;
   F32  mCloakLevel;
   TextureHandle mCloakTexture;
   bool mHidden; ///< in/out of world
   /// @}
+
+   /// @name Damaged
+   /// @{
+   F32  mDamagedLevel;
+   TextureHandle mDamagedTexture;
+   /// @}
...
public:
...
   /// Returns level of cloaking, as it's not an instant "now you see it, now you don't"
   F32 getCloakLevel();
   /// @}

+   /// @name Damaged
+   /// @{
+   /// Sets the percent of damage on this object.
+   void setDamagedLevel(F32 damage);
+
+   /// Returns level of damage
+   F32 getDamagedLevel();
+   /// @}
...
}
...
inline F32 ShapeBase::getCloakLevel()
{
   return(mCloakLevel);
}
+
+inline F32 ShapeBase::getDamagedLevel()
+{
+   return(mDamagedLevel);
+}
...

EngineFile: /game/shapeBase.cc
ShapeBaseData::ShapeBaseData()
{
...
   shapeName = "";
   cloakTexName = "";
+   damageTexName = "";
...
void ShapeBaseData::initPersistFields()
{
...
   addGroup("Render");
   addField("shapeFile",      TypeFilename, Offset(shapeName,      ShapeBaseData));
   addField("cloakTexture",   TypeFilename, Offset(cloakTexName,      ShapeBaseData));
+   addField("damageTexture",   TypeFilename, Offset(damageTexName,      ShapeBaseData));
   addField("emap",           TypeBool,       Offset(emap,           ShapeBaseData));
   endGroup("Render");

...
void ShapeBaseData::packData(BitStream* stream)
{
...
   stream->writeString(shapeName);
   stream->writeString(cloakTexName);
+   stream->writeString(damageTexName);
...
void ShapeBaseData::unpackData(BitStream* stream)
{
...
   shapeName = stream->readSTString();
   cloakTexName = stream->readSTString();
+   damageTexName = stream->readSTString();

...
ShapeBase::ShapeBase()
{
...
   mCloaked    = false;
   mCloakLevel = 0.0;
+   mDamagedLevel = 0.0;
...
bool ShapeBase::onAdd()
{
...
   if (isClientObject())
   {
      if(mDataBlock->cloakTexName != StringTable->insert(""))
        mCloakTexture = TextureHandle(mDataBlock->cloakTexName, MeshTexture, false);
+      if(mDataBlock->damageTexName != StringTable->insert(""))
+        mDamagedTexture = TextureHandle(mDataBlock->damageTexName, MeshTexture, false);
...
void ShapeBase::setDamageLevel(F32 damage)
{
   if (!mDataBlock->isInvincible) {
      F32 store = mDamage;
      mDamage = mClampF(damage, 0.f, mDataBlock->maxDamage);

      if (store != mDamage) {
         updateDamageLevel();
         if (isServerObject()) {
            setMaskBits(DamageMask);
            char delta[100];
            dSprintf(delta,sizeof(delta),"%g",mDamage - store);
            Con::executef(mDataBlock,3,"onDamage",scriptThis(),delta);
         }
      }
   }
}
+
+void ShapeBase::setDamagedLevel(F32 damage)
+{
+    mDamagedLevel = mClampF(damage, 0.f, 1.f);
+    if (isServerObject())
+       setMaskBits(DamageMask);
+}
...
void ShapeBase::renderImage(SceneState* state, SceneRenderImage* image)
{
...
      mShapeInstance->setupFog(fogAmount,state->getFogColor());
      mShapeInstance->animate();
      mShapeInstance->render();

+      // If damaged do a second render with transparency
+      if (mDamagedLevel != 0.0) {
+         glMatrixMode(GL_TEXTURE);
+         glPushMatrix();
+
+         glMatrixMode(GL_MODELVIEW);
+
+        F32 oldAlpha = mShapeInstance->getAlphaAlways();
+         mShapeInstance->setAlphaAlways(mDamagedLevel);
+         mShapeInstance->setOverrideTexture(mDamagedTexture);
+         mShapeInstance->render();
+         glMatrixMode(GL_TEXTURE);
+         glPopMatrix();
+
+         mShapeInstance->clearOverrideTexture();
+         mShapeInstance->setAlphaAlways(oldAlpha);
+	  }
+
      mShapeInstance->setEnvironmentMapOn(false, 1.0);
...
U32 ShapeBase::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
{
...
   if (stream->writeFlag(mask & DamageMask)) {
      stream->writeFloat(mClampF(mDamage / mDataBlock->maxDamage, 0.f, 1.f), DamageLevelBits);
      stream->writeInt(mDamageState,NumDamageStateBits);
      stream->writeNormalVector( damageDir, 8 );
+      stream->writeFloat( mDamagedLevel, 8);
   }
...
void ShapeBase::unpackUpdate(NetConnection *con, BitStream *stream)
{
...
   if (stream->readFlag()) {
      mDamage = mClampF(stream->readFloat(DamageLevelBits) * mDataBlock->maxDamage, 0.f, mDataBlock->maxDamage);
      DamageState prevState = mDamageState;
      mDamageState = DamageState(stream->readInt(NumDamageStateBits));
      stream->readNormalVector( &damageDir, 8 );
      if (prevState != Destroyed && mDamageState == Destroyed && isProperlyAdded())
         blowUp();
      updateDamageLevel();
      updateDamageState();
+      mDamagedLevel = (stream->readFloat(8));
   }
...
ConsoleMethod( ShapeBase, setDamageLevel, void, 3, 3, "(float level)")
{
   object->setDamageLevel(dAtof(argv[2]));
}
+
+ConsoleMethod( ShapeBase, setDamagedLevel, void, 3, 3, "(float level)")
+{
+   object->setDamagedLevel(dAtof(argv[2]));
+}
...
Script

In your object datablock, add the line:
damageTexture = "location of your file";
In your damage script add something like:
%level = %obj.damagelevel / %obj.originallevel;
%obj.setDamagedLevel(%level);


Credits:
Derk Adams - adamsderk@hotmail.com


#1
08/03/2009 (9:53 am)
Awesome, good to know such useful resources are still coming out on TGE1.5.2!

Thanks Derk.
#2
08/03/2009 (10:00 pm)
interesting resource. will have to look at possibly porting it to tgea.

Good to see resources still coming out for TGE.