Game Development Community

dev|Pro Game Development Curriculum

Dynamic changeable player textures

by Peter Simard · 02/28/2006 (6:58 pm) · 14 comments

Player.h Changes

[b]struct PlayerData: public ShapeBaseData {[/b]
....
   StringTableEntry  baseTextureName;
....
}

[b]class Player: public ShapeBase {[/b]
....

  // Make a list of the slots that can change here
  // You define where the coordiates are in the constructor
   enum LayerNames
   {
	chest,
	legs,
	hands,
	feet
   };

   enum TextureLayersConsts
   {
              TEXTURELAYERS = 10
   };

   enum MaskBits {
    ....
	  layerTextureMask  = Parent::NextFreeMask << 7, // <-- Replace 7 with your next free mask #
    ....
   };
	        TextureHandle     texture;
		StringTableEntry  layerTextureName[TEXTURELAYERS];
		Point2I                 pointsList[TEXTURELAYERS];
		F32                      rotationList[TEXTURELAYERS];
	        static bool           blit(GBitmap* src, GBitmap* dst, Point2I pt, F32 rot );
                void	                 setLayerTexture(U32 layer, const char* texture);
                bool                     generateTexture();

....
}


Player.cc Changes

[b]PlayerData::PlayerData()[/b]
{
   baseTextureName = NULL;
   ....
}

[b]void PlayerData::initPersistFields()[/b]
{
   addField("baseTexture",   TypeString,   Offset(baseTextureName,     PlayerData));
   ....
}

[b]void PlayerData::packData(BitStream* stream)[/b]
{
   Parent::packData(stream);

   stream->writeString( baseTextureName );
   ....
}

[b]void PlayerData::unpackData(BitStream* stream)[/b]
{
   Parent::unpackData(stream);

   baseTextureName = stream->readSTString();
   ....
}

[b]Player::Player()[/b]
{
   for( U32 i = 0; i < TEXTURELAYERS; i++ )
   {
     pointsList[i].set( -1, -1 );
   }
   dMemset( layerTextureName, NULL, sizeof( layerTextureName ) );
   
   // Set the offset here for each slot.
   // For example here the chest texture will be pasted in at 440, 450
   // and the legs will be at 100, 50
   pointsList[chest].set(440 , 450);
   pointsList[legs].set(100 , 50);

   texture = NULL;
   ....
}

[b]ConsoleMethod( Player, setLayerTexture, void, 4, 4, "")[/b]
{
	object->setLayerTexture(dAtoi(argv[2]), argv[3]);
}

[b]void Player::setLayerTexture(U32 layer, const char* textureName)[/b]
{
	char textureFullName[128];
	dSprintf(textureFullName, sizeof(textureFullName), "starter.fps/data/shapes/player/%s", textureName);
	GBitmap* bitmap = (GBitmap*)ResourceManager->loadInstance( textureFullName );
	if(!bitmap)
		return;

	if(layer < 0 || layer > TEXTURELAYERS)
		return;

	layerTextureName[layer] = StringTable->insert(textureFullName);
	//Con::printf("SETLAYER TO: %s", layerTextureName[layer]);
	setMaskBits(layerTextureMask);
}

[b]bool Player::blit(GBitmap* base, GBitmap* layer, Point2I pt, F32 rot )[/b]
{
	
  const U32 halfWidth  = 0; //layer->getWidth() / 2;
  const U32 halfHeight = 0; //layer->getHeight() / 2;
  
  // TODO: add condition to check to make sure it is ok with rotation taken into account
  if( pt.x + halfWidth > base->getWidth() ||
      pt.x - halfWidth < 0 ||
      pt.y + halfHeight > base->getHeight() ||
      pt.y - halfHeight < 0 
    )
    return false;
  //Con::printf("Blitting base X %i Y %i", base->getWidth(), base->getHeight());
  for ( U32 w = 0; w < layer->getWidth(); w++ )
    for ( U32 h = 0; h < layer->getHeight(); h++ )
    {
      U8* basePixel;

      if( rot == 0 )
      {
        basePixel = base->getAddress( pt.x - halfWidth + w, pt.y - halfHeight + h );
      }
      else
      {
        F32 angle = mDegToRad( rot );

        const S32 x = (S32)(((S32)w - (S32)halfWidth)*cos(angle) - ((S32)h - (S32)halfHeight)*sin(angle)) + pt.x;
        const S32 y = (S32)(((S32)w - (S32)halfWidth)*sin(angle) + ((S32)h - (S32)halfHeight)*cos(angle)) + pt.y;

        basePixel = base->getAddress(x,y);
      }

      U8* layerPixel = layer->getAddress( w, h );

      const float alpha = layerPixel[3]/255.0;
      
      basePixel[0] = (U8)(alpha*layerPixel[0] + (1-alpha)*basePixel[0]);
      basePixel[1] = (U8)(alpha*layerPixel[1] + (1-alpha)*basePixel[1]);
      basePixel[2] = (U8)(alpha*layerPixel[2] + (1-alpha)*basePixel[2]);
    }

  return true;
}

[b]bool Player::generateTexture()[/b]
{
	if(!isGhost())
		return false;
  GBitmap* baseBitmap;
  
  if( mDataBlock->baseTextureName )
  {

    baseBitmap = (GBitmap*)ResourceManager->loadInstance( mDataBlock->baseTextureName );
   
    if( baseBitmap == NULL )
    {
      Con::errorf(ConsoleLogEntry::General, "TextureLayersData(%s)::onAdd: baseTextureName (%s) invalid file", getName(), mDataBlock->baseTextureName);
      return false;
    }

    if( baseBitmap->getFormat() != GBitmap::RGB )
    {
      Con::errorf(ConsoleLogEntry::General, "TextureLayersData(%s)::onAdd: baseTextureName (%s) not RGB", getName(), mDataBlock->baseTextureName);
      return false;
    }
  }
  else
  {
    Con::errorf(ConsoleLogEntry::General, "TextureLayersData(%s)::onAdd: baseTextureName invalid", getName());
    return false;
  }

    for( U32 i = 0; i < TEXTURELAYERS; i++ )
  {
    if( layerTextureName[i] != NULL )
    {
      GBitmap* layerBitmap = (GBitmap*)ResourceManager->loadInstance( layerTextureName[i] );

      if( layerBitmap == NULL )
      {
        Con::errorf(ConsoleLogEntry::General, "TextureLayersData(%s)::onAdd: layerTextureName (%s) invalid file", getName(), layerTextureName[i]);
        continue;
      }

      if( layerBitmap->getFormat() != GBitmap::RGBA )
      {
        Con::errorf(ConsoleLogEntry::General, "TextureLayersData(%s)::onAdd: layerTextureName (%s) not RGBA", getName(), layerTextureName[i]);
        continue;
      }

      if( !blit( baseBitmap, layerBitmap, pointsList[i], rotationList[i] ) )
      {
        Con::errorf(ConsoleLogEntry::General, "TextureLayersData(%s)::onAdd: unable to blit layerTextureName (%s)", getName(), layerTextureName[i]);
        continue;
      }
      
      delete layerBitmap;
    }
  }
  texture.set(NULL,baseBitmap,BitmapTexture); 
  return true;
}

[b]U32 Player::packUpdate(NetConnection *con, U32 mask, BitStream *stream)[/b]
{
    U32 retMask = Parent::packUpdate(con, mask, stream);

	if(stream->writeFlag(mask & layerTextureMask))
	{
		for(int x = 0; x < TEXTURELAYERS; x++)
		{
			stream->writeString(layerTextureName[x]);
			//Con::printf("WRITING TEX #%i TO %s", x, layerTextureName[x]);
		}
	}
        ....
}

[b]void Player::unpackUpdate(NetConnection *con, BitStream *stream)[/b]
{
    Parent::unpackUpdate(con,stream);

	if(stream->readFlag())
	{
		for(int x = 0; x < TEXTURELAYERS; x++)
		{
			layerTextureName[x] = stream->readSTString();
			//Con::printf("SET TEX #%i TO %s", x, layerTextureName[x]);
		}
		if(isProperlyAdded())
			generateTexture();
	}
      ....
}

[b]void Player::renderImage(SceneState* state, SceneRenderImage* image)[/b]
{
      ....
      if(texture)
            mShapeInstance->setOverrideTexture(texture);

      mShapeInstance->setupFog(fogAmount,state->getFogColor());
      mShapeInstance->animate();
      mShapeInstance->render();
     
       if(texture)
	  mShapeInstance->clearOverrideTexture();
       ....
}

Usage

%slotNumber = 1;
    %textureName = "pants.png";
    %player.setLayerTexture(%slotNumber, %textureName);

As I said at the top of the resource, sometimes setting the texture layer will not work until a new player is created, so hopefully someone can find the problem. Also, make sure you change the path to you textures in the setLayerTexture method.

#1
03/01/2006 (2:31 am)
Sorry to whine, but could you please bold your code and put some context around where this code is supposed to be going? I'm having a very, very hard time following.

Looks like a great resource though!
#2
03/01/2006 (5:15 am)
Is the referenced multi-texture resource needed as a base?
#3
03/01/2006 (12:43 pm)
1. Not sure I get it, how is your resource different from the one you mentioned at HERE?

2. Does that other resource have the same issue yours does (if related)?

3. Looks like for the other resource, realtime changes was/is something to be done, does yours hand this?

Thanks,
John
#4
03/01/2006 (7:54 pm)
I added some bold tags to show where a function starts. Look for the elipses to know where the rest of the code show go.

The main difference between this resource and the original is it can change dynamicly. This means if the player picks up a new chest piece and equips it, it will update it in real time. You do not need the old resource for this.
#5
03/08/2006 (2:32 am)
#6
03/29/2006 (12:54 am)
Im sorry, but I really cant understand this resource, please include better inplementation instructions, such as find this inside whatever function, after it place this, somthing a little easier than just the function bolded, and periods for where it ends? I dont understand how to implement it...
#7
03/29/2006 (8:21 am)
Hey Tristan, this is already included in the MMORPG Enhancement Kit (which you already own), although I never did manage to figure out the little bug he mentioned.
#8
05/23/2006 (6:08 pm)
Even when this resource is great, and it solves the problem.

I have been looking this code and investigating the engine for more than 2 days, and im almost sure this line if(texture) mShapeInstance->setOverrideTexture(texture);
simply kills the cloacking feature, is that right?

Im trying to do this same thing in a more simple way, i try to check all the resources before adding em, and im sure there must be a better way to handle multitexturing.

Maybe its just my paranoia hehe.
I opened this post to talk about the subject
http://www.garagegames.com/mg/forums/result.thread.php?qt=44630
#9
08/19/2006 (10:41 pm)
When i am compiling I have error :
'Player::setLayerTexture': cannt acces protected member declared in class 'Player'

I am using tge 1.4
#10
09/14/2006 (12:37 am)
Was there a bug fixed found for this resource?
Cheers in advance
#11
10/03/2006 (5:08 pm)
Try using SkinMask instead of adding the custom mask, I've noticed a random bug with any new masks that are added in that they sometimes randomly fail to fire properly. I've never had this same problem when re-using existing masks.

Regards,
Dreamer
#12
01/02/2007 (6:46 pm)
Noticed that every once in a while, in Player::unpackUpdate, the generateTexture() is not being called because isProperlyAdded() returns False. Not sure why it returns False.
Also wanted to remind anyone else using this awesome resource, that the BaseTexture has to be RGB format. If you decide to use a non-RGB texture and comment out the lines that prevent you from using non-RGB, your textures will have "side-effects."

Happy New Years.
#13
11/08/2007 (5:30 pm)
Hi. New here but having a blast.
Just wondering, does this method work for non-skin tight armor?
Thanks.
#14
11/22/2007 (12:37 am)
Has anyone seen the random bug Peter mentioned? If so did you manage to fix it?