Game Development Community

Assigning skins to player models dynamically..

by Robert Blanchet Jr. · in Torque Game Engine · 12/09/2001 (4:18 am) · 13 replies

I know in Tribes 2 they used setTargetSkin(%client, %skin); where %skin was the prefix to the filename of the skins.

I've looked and looked and it doesnt look like setTargetSkin is in the engine code anymore.

The only thing I could really find in reference to setting skins in torque script was this:

%client.skin = addTaggedString( "base" );

Any got any code they care to share that might be able to do this? :)

#1
12/09/2001 (8:37 am)
I'm not sure how tribes 2 did it, but here's what I did.

First make a method to change mSkinTag from ShapeBase and a console function setSkin(%object, %skinName) in shapebase.cc:
// all this does is set mSkinTag (which is just a string id)
void ShapeBase::setSkinTag(S32 skinTag)
{
	mSkinTag = skinTag;
}

ConsoleFunction(setSkin, bool, 3, 3, "setSkin(objectid, skintag);")
{
	ShapeBase * obj = NULL;
	U32 skinStringTag;

	// note: add 1 to arg so we can skip the prefix byte of a string id
	skinStringTag = dAtoi(argv[2]+1);
	obj = static_cast<ShapeBase*>(Sim::findObject(argv[1]));

	if (!obj)
	{
		Con::errorf("Unable to find object");
		return false;
	}

	// set mSkinTag
	obj->setSkinTag(skinStringTag);

	return true;
}

Then in ShapeBase packUpdate() and unpackUpdate() send mSkinTag during initial update mask:
U32 ShapeBase::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
{

...

	if(!stream->writeFlag(mask & (NameMask | DamageMask | SoundMask |
             ThreadMask | ImageMask | CloakMask | MountedMask | InvincibleMask |
             ShieldMask)))
		return retMask;

// INSERT
	// Send initial update
	// send skin tag (this should be rearranged or something.  too bad we can't have a SkinMask?)
	if (stream->writeFlag(mask & InitialUpdateMask))
	{
		// i should probably send a flag for skintag
		if (stream->writeFlag(mSkinTag))
		{
			stream->writeInt(mSkinTag, NetStringTable::StringIdBitSize);
			
			// check to make sure the client has this string,
			// or else send a new string event
			con->checkString(mSkinTag);
		}

	}
// INSERT END

...

void ShapeBase::unpackUpdate(NetConnection *con, BitStream *stream)
{
	Parent::unpackUpdate(con, stream);
	mLastRenderFrame = sLastRenderFrame; // make sure we get a process after the event...

	if(!stream->readFlag())
		return;

// INSERT
	// moved this var to top from below
	bool checkTagsNeeded = false;
	
	// get initial update
	if (stream->readFlag())
	{
		// get skin tag
		if (stream->readFlag())
		{
			mSkinTag = stream->readInt(NetStringTable::StringIdBitSize);

			if (mSkinTag)
				checkTagsNeeded = true;
		}
	}
// INSERT END

Once the client receives the skintag it calls checkTags() in unpackUpdate to convert the stringtag to a usable string for it to be able to reskin the object:
void ShapeBase::checkTags()
{
   NetConnection *tosv = NetConnection::getServerConnection();
   bool repost = false;

// INSERT
	const char * skinName = NULL;

	// check if object needs new skin
	if (mSkinTag)
	{
		// we need to get the local string id from the one sent by the server
		U32 localString = tosv->translateRemoteStringId(mSkinTag);
		if(!localString)
		{
			// this is really stupid but....
			// local string tag hasnt arrived yet (or possibly doesn't exist?!) so schedule
			// a checkSkinEvent to call this function again at a later time
			repost = true;
		}
		else
		{
			// a local version of the string exists
			skinName = gNetStringTable->lookupString(localString);
			// lets reskin it
			if (skinName)
				reSkin(getShapeInstance(), skinName, "");
		}
	}
// INSERT END
...

The shapebase method that does the actual reskinning of the object is reSkin(), but it seems to have a bunch of weird file naming schemes and stuff we should comment out:
static void reSkin(TSShapeInstance *instance, const char *newBase, const char *prefBase)
{
   if (instance->ownMaterialList() == false)
      instance->cloneMaterialList();

   TSMaterialList* pMatList = instance->getMaterialList();

   for (U32 j = 0; j < pMatList->mMaterialNames.size(); j++) 
   {
      const char* pName = pMatList->mMaterialNames[j];
      if (pName == NULL)
         continue;

      const U32 len = dStrlen(pName);

// get rid of this stuff
//      if (len < 6)
//         continue;
//      const char* pReplace = dStrstr(pName, (const char*)"base.");
//      if (pReplace == NULL)
//         continue;

//      char newName[256];
      AssertFatal(len < 200, "ShapeBase::checkSkin: Error, len exceeds allowed name length");
//      TextureHandle test;
//      if(ShapeBase::sUsePrefSkins && prefBase[0])
//     {
//         dStrncpy(newName, pName, pReplace - pName);
//         newName[pReplace - pName] = '[[4ba69b77dfc90]]';
//         dStrcat(newName, prefBase);
//         dStrcat(newName, ".");
//         dStrcat(newName, pName + 5 + (pReplace - pName));
//         test = TextureHandle(newName, MeshTexture, false);
//         test = TextureHandle(prefBase, MeshTexture, false);
//      }
//      if(test.getGLName() == 0)
//      {
//         dStrncpy(newName, pName, pReplace - pName);
//         newName[pReplace - pName] = '[[4ba69b77dfc90]]';
//         dStrcat(newName, newBase);
//         dStrcat(newName, ".");
//         dStrcat(newName, pName + 5 + (pReplace - pName));
//         test = TextureHandle(newName, MeshTexture, false);
//      }
// INSERT
	// just use the given skin name to see if texture exists
         test = TextureHandle(newBase, MeshTexture, false);
// INSERT END

      if (test.getGLName() != 0) 
         pMatList->mMaterials[j] = test;
      else
         pMatList->mMaterials[j] = TextureHandle(pName, MeshTexture, false);
   }
}

Then in the script, use:
setSkin(%player, %client.skin);
immediately after you create the player (such as in GameConnection::createPlayer()). This assumes %client.skin is set to something like this:
%client.skin = addTaggedString("fps/data/shapes/player/myskin");
or this:
%client.skin = 'fps/data/shapes/player/myskin';

The above code only changes the skin during the initial pack update, but you could easily change it to update whenever you want. One problem is that the string is sent after receiving mSkinTag so there's a short delay before the first reskin (it schedules checkTags() again at a later time). I guess one way to prevent this is to send the string to the client beforehand. Another minor problem is that it assumes there's only one whole skin texture per object (which is kinda the point).

Sorry, I haven't tested it (just copy/pasted straight from code) so I don't know if I left anything out. But it should give you the basic idea of how it's done. As you can see, the reskin function is already in torque. All you need to do is set mSkinTag with the skin name, send it to the client in packupdate(), and then call reSkin() on the client.
#2
12/09/2001 (8:27 pm)
This doesn't seem to be working for me. No errors at all, the skin just doesn't change.

Any suggestions?

And, can this be used to change the skin at any time, or just when the player is created?


Dark
#3
12/09/2001 (8:38 pm)
Ya, same problem. It still doesnt change the skin.

But like he stated above this will only change when a packet sends an update, which would probably be when the object is destroyed and recreated. Therefore I think the problem might be is that we have to create the object before we can set its skin, therefore the skin is always set to the dts default and reskin never gets a chance to actually work.
#4
12/09/2001 (8:49 pm)
Ahh, got it to work. I moved setSkin to *just* after doing the new Player() { }; stuff and it worked fine.
#5
12/09/2001 (11:26 pm)
That's where mine's at, hmm, I'll have to play with it some more.


Dark
#6
03/20/2002 (10:09 pm)
I've moved the contents of this post to a resource; post any comments/corrections there please.
#7
03/20/2002 (10:26 pm)
Word of warning on the off chance that someone copied this code as soon as I posted it...

Originally, I posted this code in makeSkinPath:
if (oldRoot == NULL) {
      // Not doing any replacing.
      replacedRoot = true;
   }
It should have been like this:
if (oldRoot == NULL) {
      // Not doing any replacing.
      replacedRoot = false;
   }
(and now it is).
#8
03/20/2002 (11:11 pm)
Could this be used to change the skin on staticShapes as well? Create tree teams for example? And is there any limit on the total number of teams?
#9
03/20/2002 (11:33 pm)
There's no limit on the number of teams.

I wouldn't be surprised if you could do something very similar for TSStatic objects, since they have a TSShapeInstance and that's really what's at the core of this code. But the current code as shown above only applies to objects derived from ShapeBase.

For various reasons I may need to look into TSStatic reskinning in the future, but it hasn't bubbled to the top of my To Do list yet. If someone wants to beat me to it, that's cool. :-)

EDIT: Oops, I misread your question. Yes, it will work on StaticShape objects. StaticShape is derived from ShapeBase.
#10
03/25/2002 (10:07 pm)
Made a change to this... seems more elegant to have reSkin as a member function of tsShapeInstance, so I did that and made the necessary changes. Also moved makeSkinPath to tsShapeInstance.cc along with it, and removed the getResource member function that I had added since reSkin can now access hShape directly.

Anyway I'll probably tidy this up and post it as a code snippet resource soon. Is anyone planning on trying this? If so I'll wait to see what happens. :-)

EDIT: BTW the changes mentioned here are not reflected (yet) in the code posted above.
#11
03/25/2002 (10:25 pm)
Well, I'd love to see this as a nice packaged tutorial - but I guess your posting does the job for now, too... very detailed! Maybe I'll try it out this week!
Anyhow, thanks for sharing it!
#12
03/26/2002 (1:42 am)
I've tidied up my post some, made the change of moving reSkin into TSShapeInstance, and dumped the entire thing into a submitted resource. If there are any comments, corrections, whatever specific to that code rather than to the topic of this thread in general, then please tack them onto that resource.
#13
03/26/2002 (3:50 am)
Very nice. Clean, works perfectly. I made some modifications (to your first version) because I need personalized skins and not team skins, but it was obvious enough. I'll take a peek at your new resource tonight.