Game Development Community

dev|Pro Game Development Curriculum

Setting object skins from the scripts

by Joel Baxter · 03/27/2002 (5:55 pm) · 25 comments

***** IMPORTANT NOTE -- START *****

The code below was written for and tested with the Release_1_1_1 version of TGE.

Later versions of TGE handle tagged strings differently, and the code shown below WILL NOT WORK with later versions.

I've modified the current version of TGE (as of 5/5/02) to include this code, with the necessary changes to deal with the new style of tagged strings. So if you checkout or update from CVS now you should have this functionality available to you.

You can still use some of the scripting ideas described in the article and comments below, but there are two important differences that you should be aware of:

1) The function that you invoke on the object-to-be-reskinned is now called setSkinName, rather than setSkin.

2) You do not need to send the strings over ahead of time to "prefetch" them any more.

Otherwise the reskinning code in the latest TGE codebase should behave like the code described here.

As always, if there are problems, let me know in the comments or in email.

***** IMPORTANT NOTE -- END *****


Desired functionality

When setting the team skin for a particular object, we only want to be required to supply some team identifier. We shouldn't be required to know the name of the current skin texture, or what path the textures are located at, or even what type of object we are skinning (beyond the fact that it must be derived from ShapeBase). We also want the capability to specify that some skin textures change when the team changes, and some skin textures do not.


Naming convention

To help achieve these objectives, we'll use a Tribes-like nomenclature where the root of a skin texture filename consists of a team identifier and an object identifier, separated by a period. So, for example, a player object might have skin textures (PNG graphics) named red.player.png, aliens.player.png, and swat-team.player.png, if "red", "aliens", and "swat-team" identify the teams that those skins are for. A flag object might have skins red.flag.png, aliens.flag.png, and swat-team.flag.png. Etc. (The current TGE code looks like it's trying to work with that sort of nomenclature, but it has some problems, particularly when dealing with file paths.)

A single model could use multiple skin textures to cover various parts of the model, of course. Instead of aliens.player.png for covering the entire player with the aliens team skin, the model might use aliens.player-head.png and aliens.player-body.png for skinning two different sections. No problem.

Along with the identifiers for the specific teams of objects that may be used in our game, there's also a special identifier called "base". The "base" identifier marks a texture that should be changed when a new team identifier is specified for the object's skin. The textures with the "base" identifier are the ones that are actually used when creating the model.

A model can also use a texture name that does not start with any identifier at all; that texture will remain constant regardless of team.

Let's go back to the example of the player model that uses two textures for its head and body sections. If the texture player-head.png is used to skin the head, then the head appearance will remain constant across team changes, since that texture does not start with an identifier. If the body texture, on the other hand, was base.player-body.png, then it would be changed on team changes; i.e. if we specified "aliens" as the new team identifier for that player's skin, then its body texture would change to aliens.player-body.png.

The "base" textures should actually exist, for use in those occasions when an object has not had a team identifier set for its skin. Also, each set of team skin textures must reside in the same folder as its "base" texture.

(If you want a string of characters other than "base" to be used as the special identifier, then that's easily changed at a couple of locations in the code.)


Engine code changes

Here's the implementation, file by file. When describing which lines to change, I'm referring to TGE version 1.1.1 (don't know if any of this code has changed since then). I should mention that we don't currently have any preferences that override the skinning behavior, so our code doesn't use ShapeBase::sUsePrefSkins or its scripting counterpart $pref::usePrefSkins. If you want to have some preference setting that changes the skinning behavior you'll need to add it to the code shown here.


*** platform*/*Strings.cc

1) We need to add a support function, dStrncat, so let's get that out of the way first. You'll see later where this is used (dealing with texture file pathnames). Add this same code to platformMacCarb/macCarbStrings.cc, platformWin32/winStrings.cc, and platformX86UNIX/x86UNIXStrings.cc, right below Strcat:
char* dStrncat(char *dst, const char *src, U32 len)
{
   return strncat(dst,src,len);
}
2) Also add a prototype for this function below the one for Strcat in platform/platform.h.
extern char*       dStrncat(char* dst, const char* src, U32 len);
3) You can remove the existing dStrncat prototype in platformX86UNIX.h.


*** ts/tsShapeInstance.h

1) There was some reskinning-related code in ShapeBase; we're going to move it into TSShapeInstance instead, and change and expand it. So add this to the public signature of TSShapeInstance (I put it right below the declaration of ownMaterialList):
void reSkin(const char *newBase);

*** ts/tsShapeInstance.cc

1) And now let's add the implementation of that. I added this under the cloneMaterialList function:
static bool makeSkinPath(char* buffer, U32 bufferLength, const char* resourcePath,
                         const char* oldSkin, const char* oldRoot, const char* newRoot)
{
   bool replacedRoot = true;

   U32 oldRootLen = 0;
   char* rootStart = NULL;

   if (oldRoot == NULL) {
      // Not doing any replacing.
      replacedRoot = false;
   }
   else {
      // See if original name has the old root in it.
      oldRootLen = dStrlen(oldRoot);
      AssertFatal((oldRootLen + 1) < bufferLength, "makeSkinPath: Error, skin root name too long");
      dStrcpy(buffer, oldRoot);
      dStrcat(buffer, ".");
      rootStart = dStrstr(oldSkin, buffer);
      if (rootStart == NULL) {
         replacedRoot = false;
      }
   }

   // Find out how long the total pathname will be.
   const U32 oldLen = dStrlen(oldSkin);
   U32 pathLen = 0;
   if (resourcePath != NULL) {
      pathLen = dStrlen(resourcePath);
   }
   if (replacedRoot) {
      const U32 newRootLen = dStrlen(newRoot);
      AssertFatal((pathLen + 1 + oldLen + newRootLen - oldRootLen) < bufferLength, "makeSkinPath: Error, pathname too long");
   }
   else {
      AssertFatal((pathLen + 1 + oldLen) < bufferLength, "makeSkinPath: Error, pathname too long");
   }

   // OK, now make the pathname.

   // Start with the resource path:
   if (resourcePath != NULL) {
      dStrcpy(buffer, resourcePath);
      dStrcat(buffer, "/");
   }
   else {
      buffer[0] = '[[6281f467969b7]]';
   }
   if (replacedRoot) {
      // Then the pre-root part of the old name:
      U32 rootStartPos = rootStart - oldSkin;
      if (rootStartPos != 0) {
         dStrncat(buffer, oldSkin, rootStartPos);
      }
      // Then the new root:
      dStrcat(buffer, newRoot);
      dStrcat(buffer, ".");
      // Then the post-root part of the old name:
      dStrcat(buffer, oldSkin + rootStartPos + oldRootLen + 1);
   }
   else {
      // Then the old name:
      dStrcat(buffer, oldSkin);
   }

   return replacedRoot;
}


void TSShapeInstance::reSkin(const char *newBase)
{
#define NAME_BUFFER_LENGTH 256
   static char pathName[NAME_BUFFER_LENGTH];

   if (newBase == NULL) {
      return;
   }

   // Make our own copy of the materials list from the resource
   // if necessary.
   if (ownMaterialList() == false) {
      cloneMaterialList();
   }

   const char* resourcePath = hShape.getFilePath();

   // Cycle through the materials.
   TSMaterialList* pMatList = getMaterialList();
   for (U32 j = 0; j < pMatList->mMaterialNames.size(); j++) {
      // Get the name of this material.
      const char* pName = pMatList->mMaterialNames[j];
      // Bail if no name.
      if (pName == NULL) {
         continue;
      }
      // Make a texture file pathname with the new root if this name
      // has the old root in it; otherwise just make a path with the
      // original name.
      bool replacedRoot = makeSkinPath(pathName, NAME_BUFFER_LENGTH, resourcePath,
                          pName, (const char*)"base", newBase);

      if (!replacedRoot) {
         // If this wasn't in the desired format, set the material's
         // texture handle (since that wasn't copied over in the
         // cloning) and continue.
         pMatList->mMaterials[j] = TextureHandle(pathName, MeshTexture, false);
         continue;
      }

      // OK, it is a skin texture.  Get the handle.
      TextureHandle skinHandle = TextureHandle(pathName, MeshTexture, false);
      // Do a sanity check; if it fails, use the original skin instead.
      if (skinHandle.getGLName() != 0) {
         pMatList->mMaterials[j] = skinHandle;
      }
      else {
         makeSkinPath(pathName, NAME_BUFFER_LENGTH, resourcePath, pName, NULL, NULL);
         pMatList->mMaterials[j] = TextureHandle(pathName, MeshTexture, false);
      }
   }
}
BTW, the hardcoded string "base" in TSShapeInstance::reSkin is one of the two locations where that appears, if you're interested in changing the code to use something other than "base" as the special identifier. The other instance is in ShapeBase::checkTags below.


*** game/shapeBase.h

1) Replace these lines
U32 mSkinTag;
   U32 mSkinPrefTag;
with these lines:
U32 mSkinLocalTag;
   U32 mSkinRemoteTag;
   U32 mSkinDesiredRemoteTag;
You can also remove the declaration of sUsePrefSkins if, like us, you're not using it.

These variables will hold the tags for the tagged strings that are our team identifiers. mSkinRemoteTag is the tag for the team identifier string received by the client from the server, and mSkinLocalTag is the tag for the team identifier string according to the local process (client or server). mSkinDesiredRemoteTag holds the tag for the most recent team identifier sent over, for a skin change request that may or may not have happened yet.

2) We also need to add a ghost update mask bit so that we can indicate when the server should propagate a skin change to the client, so replace this line:
SoundMaskN      = Parent::NextFreeMask << 7,       // Extends + MaxSoundThreads bits
with these lines:
SkinMask        = Parent::NextFreeMask << 7,
      SoundMaskN      = Parent::NextFreeMask << 8,       // Extends + MaxSoundThreads bits
3) Final change to this header file is to add this under the prototype of checkTags:
void setSkinTag(S32 skinTag) { if (mSkinLocalTag != skinTag) { mSkinLocalTag = skinTag; setMaskBits(SkinMask); } }
That's the function we'll use to request the skin change on the server and set things in motion.


*** game/shapeBase.cc

1) Again, if you're not going to be implementing any code to deal with "skin prefs", you might want to remove any lines that reference sUsePrefSkins (there are two).

2) Then, in the constructor for ShapeBase, replace these lines:
mSkinTag = 0;
   mSkinPrefTag = 0;
with these lines:
mSkinLocalTag = 0;
   mSkinRemoteTag = 0;
   mSkinDesiredRemoteTag = 0;
3) Remove the reSkin function that existed in this file, as we're going to be using our new TSShapeInstance member function for that instead.

4) In ShapeBase::onNewDataBlock, replace these lines:
if (isGhost() && mSkinTag)
    {
       reSkin(mShapeInstance, gNetStringTable->lookupString(mSkinTag),
              mSkinPrefTag ? gNetStringTable->lookupString(mSkinPrefTag) : "");
       mSkinHash = (_StringTable::hashString(gNetStringTable->lookupString(mSkinTag)) ^   
                    _StringTable::hashString(mSkinPrefTag ? gNetStringTable->lookupString(mSkinPrefTag) : ""));   
    }
with these:
if (isGhost() && mSkinLocalTag && mShapeInstance) {
      const char* nameString = gNetStringTable->lookupString(mSkinLocalTag);
      mShapeInstance->reSkin(nameString);
      mSkinHash = _StringTable::hashString(nameString);
   }
(remember, we're not using "skin prefs").

5) ShapeBase::checkTags will need some changes as well, but rather than treat those individually, here's the whole function:
void ShapeBase::checkTags()
{
   NetConnection *tosv = NetConnection::getServerConnection();
   if (!tosv) {
      return;
   }
   bool repost = false;
   
   // Resolve the object's name
   if (mNameTag) {
      if (U32 localTag = tosv->translateRemoteStringId(mNameTag)) {
         mShapeName = gNetStringTable->lookupString(localTag);
      }
      else {
         repost = true;
      }
   }
   else {
      mShapeName = 0;
   }

   // Check for skin update.
   if ((mSkinRemoteTag != mSkinDesiredRemoteTag) && mSkinDesiredRemoteTag) {
      if (U32 localTag = tosv->translateRemoteStringId(mSkinDesiredRemoteTag)) {
         mSkinLocalTag = localTag;
         mSkinRemoteTag = mSkinDesiredRemoteTag;
         const char* skinName = gNetStringTable->lookupString(mSkinLocalTag);
         if (mShapeInstance && skinName) {
            mShapeInstance->reSkin(skinName);
            mSkinHash = _StringTable::hashString(skinName);
         }
      }
      else {
         repost = true;
      }
   }

   // Now check the images
   for (int i = 0; i < MaxMountedImages; i++) {
      MountedImage& image = mMountedImageList[i];
      if (image.dataBlock && image.skinTag != image.desiredTag) {
         const char *newBase = NULL;
         if (image.desiredTag) {
            U32 localTag = tosv->translateRemoteStringId(image.desiredTag);
            if (localTag) {
               newBase = gNetStringTable->lookupString(localTag);
            }
            else {
               repost = true;
            }
         }
         else {
            newBase = "base";
         }

         // Have a new tag, so we need to re-skin the object
         image.skinTag = image.desiredTag;
         if (image.shapeInstance) {
            image.shapeInstance->reSkin(newBase);
         }
      }
   }

   // Try again later if we didn't get them all
   if (repost) {
      Sim::postEvent(this, new CheckSkinEvent, Sim::getCurrentTime() + 1000);
   }
}
Note the other occurrence of the hardcoded string "base" above.

6) Now we need to modify slightly the conditions under which checkTags will be called. In ShapeBase::packUpdate, replace these lines:
if(!stream->writeFlag(mask & (NameMask | DamageMask | SoundMask |
         ThreadMask | ImageMask | CloakMask | MountedMask | InvincibleMask |
         ShieldMask)))
      return retMask;
with these:
if(!stream->writeFlag(mask & (NameMask | DamageMask | SoundMask |
         ThreadMask | ImageMask | CloakMask | MountedMask | InvincibleMask |
         ShieldMask | SkinMask)))
      return retMask;
I.e., all you're doing is adding SkinMask to the set of update conditions there.

7) Similarly, replace these lines from that function:
// Group some of the uncommon stuff together.
   if (stream->writeFlag(mask & (NameMask | ShieldMask | CloakMask | InvincibleMask))) {
with these:
// Group some of the uncommon stuff together.
   if (stream->writeFlag(mask & (NameMask | ShieldMask | CloakMask | InvincibleMask | SkinMask))) {
8) Even further down in that same function, below the block of code that sends out mInvincibleTime and mInvincibleSpeed, add this block:
if (stream->writeFlag(mask & SkinMask)) {
         stream->writeInt(mSkinLocalTag, NetStringTable::StringIdBitSize);
         con->checkString(mSkinLocalTag);
      }
9) Then in ShapeBase::unpackUpdate, those values need to be read. So, below the block of code that reads the invincibility info (the block marked with the "// InvincibleMask" comment), add this block:
if (stream->readFlag()) {  // SkinMask
         S32 skinTag = stream->readInt(NetStringTable::StringIdBitSize);
         checkTagsNeeded = true;
         if (mSkinDesiredRemoteTag != skinTag) {
            mSkinDesiredRemoteTag = skinTag;
         }
      }
10) That completes the code necessary to make the right things happen when the server calls setSkinTag. So our final addition to the engine code is a script access point for using that function. Down among the console hooks defined at the bottom of the file (I put this just after the definition for setShadowDetailLevel), add this code:
ConsoleMethod(ShapeBase, setSkin, bool, 3, 3, "obj.setSkin(skinTag);")
{
   const char* skinName = argv[2];

   // If they sent us a legal skin tag, grab it.
   if (skinName[0] == StringTagPrefixByte) {
      ShapeBase* shape = static_cast<ShapeBase*>(object);
      S32 skinTag = dAtoi(skinName + 1);
      shape->setSkinTag(skinTag);
      return true;
   }

   return false;
}

Reskinning non-ShapeBase objects

As I mentioned before, and as the code indicates, this solution is for reskinning objects that are derived from ShapeBase. However, most of the interesting code is in TSShapeInstance, so any object that is displayed using a TSShapeInstance could be reskinned if you add code to that object's class similar to the code I added to ShapeBase; i.e., setting a team skin tag on the server, transmitting it to the client ghost, and calling reSkin appropriately.


Using this in the scripting

In my player creation code, I have this:
%player.setSkin(Game.teamSkin[%player.team]);
where %player is an identifier for a newly created Player object, and Game.teamSkin is an array I constructed elsewhere that contains the team skin identifiers as tagged strings. With this team skin setup, you must use a tagged string as an argument to setSkin. A tagged string is enclosed in single quotes rather than double quotes, which associates it with an identifier that will be transmitted over the server/client connection rather than having to transmit the whole string.

One slightly annoying problem is that the tagged strings are sent to the server on demand. So, the first time you use a given skin, there will be a one second delay before it is applied to the object, as the server has to send the tagged string to the client first. This delay is adjustable (it's the "1000" value at the end of checkTags), but if you're a little clever you won't have to have any delay at all. What you can do is send over the tagged strings for team skin identifiers ahead of time, before any team skinning happens. In serverCmdMissionStartPhase1Ack, before sending the CRC value, I do
// Send any net strings that the client will need right away.
   %client.sendNetStrings();
This calls the following function I created:
// Called after the client gets the mission file, to send any important
// net strings immediately.
function GameConnection::sendNetStrings(%this)
{
   for (%i = 1; %i <= Game.numTeams; %i++) {
      commandToClient(%this, 'PrefetchNetString', Game.teamSkin[%i]);
   }
}
Game.numTeams is set elsewhere to the number of teams in the mission. The PrefetchNetString command means that this function gets invoked on the receiving client:
function clientCmdPrefetchNetString(%string)
{
   // Does nothing, just exists to get the strings sent.
}
The side effect of transmitting the tag for its string argument is all that I need out of this command, so the function doesn't do anything else.

Other objects besides players can have team skins. In our project, if an object is to be "teamed", it is placed in a SimGroup that has a name indicating which team number it belongs to. Just before the mission starts, our scripting calls setTeam on each such SimGroup, with its team number as an argument. That executes SimGroup::setTeam, which can also call setTeam on other types of objects; the relevant code:
// "Set team" functions for types of objects.
// SimObject:: just set the team field.
function SimObject::setTeam(%this, %team)
{
   %this.team = %team;
}
// SimGroup: call setTeam on each contained object.
function SimGroup::setTeam(%this, %team)
{
   Parent::setTeam(%this, %team);
   for (%i = 0; %i < %this.getCount(); %i++) {
      %obj = %this.getObject(%i);
      %obj.setTeam(%team);
   }
}
// ShapeBase: set the team skin, if the object has one.
function ShapeBase::setTeam(%this, %team)
{
   Parent::setTeam(%this, %team);
   %this.setSkin(Game.teamSkin[%team]);
}
So, if you call setTeam on a SimGroup, it will call setTeam on each contained object. Any object that has setTeam called on it will set its ".team" field to the indicated team number (which isn't related to skinning BTW, just a useful thing to have). ShapeBase objects also get their team skins set, if they have them.

I think that about covers it... I'd be interested in hearing what happens if anyone else gives this a try. I've tried to reconstruct all the necessary changes by checking our CVS logs; I think I got everything. Fingers crossed. :-)
Page «Previous 1 2
#1
03/27/2002 (6:52 pm)
With this code, could I change the skin on any object, say a specific instance of a vehicle ... ?
#2
03/27/2002 (10:13 pm)
wow i could see the potential for this in a singleplayer game also :), right now im learning to build map objects, but i will get to this sooner or later. thanks
#3
03/27/2002 (10:58 pm)
Ken: Yep. A vehicle is derived from ShapeBase, so it should work, although personally I've only tried it on players and items.
#4
03/28/2002 (6:13 am)
finest kind
#5
03/28/2002 (7:27 am)
Please could you make an example of a tagged string?
for example for this line of code:

%this.setSkin(Game.teamSkin[%team]);

how does Game.teamSkin[%team] look like?
#6
03/28/2002 (1:32 pm)
A tagged string is a string enclosed by single quotes instead of double quotes.

So, in our scripts, Game is the name of an object, and one of its fields is an array named teamSkin. You could initialize the array like this (to use the example team identifiers from the resource):

Game.teamSkin[1] = 'aliens';
Game.teamSkin[2] = 'swat-team';

(I'm not setting the zeroth element of the array in this example because I use "team zero" to represent not being on a team... more on that later.)

So if you called that setTeam script function shown above on some ShapeBase-derived object, you would change its skin to the aliens skin for a setTeam argument of 1 (for example).

You don't have to indirect through an array indexed by team number like we do; you can just directly call setSkin on some object using any tagged string as an argument, as long as that tagged string is a valid team identifier for the skin textures available for that object.

I've gotten a few questions on that issue, so I dunno, maybe I'm just confusing things by speaking in generalities. Our particular scripting setup for teams is kinda incidental to the whole skinning issue, so I didn't really get into it in the article, but here's a few more details about it in case this helps to clear things up.

Again taking a cue from Tribes, our mission files each have a SimGroup named Teams contained in the MissionGroup, and within that Teams group, more SimGroups named Team1, Team2, etc. Each of those numbered-teams SimGroups can contain goal items, groups of player spawnpoints, and anything else that needs to be associated with a particular team for purposes of gameplay functionality or skinning. A team-numbered SimGroup can also have a field named teamProps which is a string that specifies the team properties; if the teamProps field is not explicitly specified then it is assumed to be "default_1" for Team1, or "default_2" for Team2, etc.

Those teamProps names are used to execute scripts that define the team properties by setting certain global variables (may not be the best way to do this, but it's our current setup). I've created another folder named teamProps under fps/server/scripts to contain those scripts. There must exist a default_0.cs script for setting the default properties, which will apply for any object not set to a team, or may partially apply to a teamed object if that object's team properties are not completely specified. Right now the only team properties we have are the team name and the team skin identifier, so our default_0.cs looks like this:
$teamName = 'default';
$teamSkin = 'base';
Note the use of tagged strings, which is particularly important for the team skin identifier.

The teamProps folder also contains the default team properties scripts for the various team numbers, for example default_2.cs:
$teamName = 'the Phoenix Sand Vipers';
$teamSkin = 'blue';
And it can contain other team definitions too, for non-default teams specified via teamProps in the mission file, so it could have scripts named assassins.cs, or chickenheads.cs, or whatever. The idea is that content creators should be able to add teams to the game just by making the skins or whatever else is necessary to create the team, and then creating a tiny script naming the team properties in the teamProps folder.

OK, well, after the mission loads, our scripts call setUpTeams on our Game object; we have Game as a ScriptObject with either a class or superclass of DefaultGame, which means that this function will get executed:
// Initialize team-related information.
function DefaultGame::setUpTeams(%this)
{
   // Sanity check.
   %group = nameToID("MissionGroup/Teams");
   if (%group == -1) {
      return;
   }

   // Count and process teams.
   %this.numTeams = 0;
   exec("./teamProps/default_0.cs");
   %this.teamName[0] = $teamName;
   %this.teamSkin[0] = $teamSkin;
   %team = nameToID("MissionGroup/Teams/Team" @ %this.numTeams + 1);
   while (%team != -1) {
      %this.numTeams++;
      if (%team.teamProps $= "") {
         %propsPath = "./teamProps/default_" @ %this.numTeams @ ".cs";
      }
      else {
         %propsPath = "./teamProps/" @ %team.teamProps @ ".cs";
      }
      exec("./teamProps/default_0.cs");
      if (isFile(%propsPath)) {
         exec(%propsPath);
      }
      %this.teamName[%this.numTeams] = $teamName;
      %this.teamSkin[%this.numTeams] = $teamSkin;
      %this.teamScore[%this.numTeams] = 0; 
      %team.setTeam(%this.numTeams);
      %team = nameToID("MissionGroup/Teams/Team" @ %this.numTeams + 1);
   }
}
So that initializes Game.numTeams to the proper value, initializes the teamName, teamSkin, and teamScore array fields of the Game object, and also calls setTeam on each team SimGroup with the effects that I've discussed before (setting the team number field and skinning appropriate objects). As mentioned above, note that personally I use the zeroth element of the arrays for the non-team, and if the mission file contained teams numbered 1, 2, and 3, then Game.numTeams would be set to 3, not 4. Nothing magical about that, just the way I'm doing things.

This doesn't skin the players, since player objects have not yet been created; players are skinned as they are created and enter the mission.
#7
03/29/2002 (7:27 am)
well i can't get it to work, i thought i was doing something wrong.. but im just doing %player.setSkin('red'); in the game.cs file after the player datablock is created. I also made a base.player.png texture and deleted the player.png texture and it wont even skin it... i displays it white.
#8
03/29/2002 (10:37 am)
Make sure that your player model is made so that it references the base.player.png file.

If you're just using the default player model that comes with the TGE SDK, it won't work, since that model is not built using the necessary skin naming convention.

If you want to experiment with this using the default player model, what I would recommend is that you hex-edit the player.dts file and replace "player" with "base.1". (Have to use "base.1" rather than something more descriptive like "base.player", because when hex-editing this particular string you have to maintain the same string length.) Then create some other textures like "red.1.png" and experiment with changing the skins.
#9
03/29/2002 (2:25 pm)
ahhh that's it joel :)
thanks
#10
03/29/2002 (5:27 pm)
Glad it worked! :-)
#11
04/07/2002 (9:29 pm)
So, Xavier noticed some failures when he was trying to swap the skins several times in a row. Turns out that there's a bug in my code that will cause it to fail except 1) if you set the skin as soon as the object is created or 2) if there are other updates pending. If the object has been around for a while and you're wanting to swap the skin around, with no other update-causing changes going on, then the code won't notice that it's supposed to ghost the skin change to the client. Oops!

The fix is easy. In ShapeBase::packUpdate in shapeBase.cc, change this:
// Group some of the uncommon stuff together.
   if (stream->writeFlag(mask & (NameMask | ShieldMask | CloakMask | InvincibleMask))) {
to this:
// Group some of the uncommon stuff together.
   if (stream->writeFlag(mask & (NameMask | ShieldMask | CloakMask | InvincibleMask | SkinMask))) {
Thanks for staying on top of things Xavier. :-)

I'll update the resource text above to include this necessary change.
#12
04/08/2002 (9:20 am)
no problem joel :)
thanks to you for your code... and im happy it was fixed ... i thought it was my stupid scripting which caused the problem :D
#13
06/10/2002 (8:17 am)
Joel, How much of this is in the HEAD?
For example, in game/shapebase.h, I don't find any of the variables (either new or old) you mention above:

*** game/shapeBase.h

1) Replace these lines
U32 mSkinTag;
U32 mSkinPrefTag;

with these lines:
U32 mSkinLocalTag;
U32 mSkinRemoteTag;
U32 mSkinDesiredRemoteTag;

Unless I'm blind and the search tool is broken :)
#14
08/11/2002 (1:55 pm)
Hey, i am having a Problem with the new version of this code that came with the engine release 1.1.2

For some reason i can't get it to set the skin to what i want...
i have this line in the game.cs file
under GameConnection::createPlayer(%this, %spawnPoint)

%player.setSkinName(%this.skin);

now orginaly with the old code i had

setSkin(%player, %this.skin);

it just not working right i am just geting a model in the game with no skin at all...
i am geting no errors at all... i even tryed changing %this.skin with 'path/of/skin' and it still didn't work any ideas?

Thanks
#15
09/26/2002 (6:11 pm)
Same problem here - and it produces no error when I do: %player.setSkinName('~/data/shapes/Players/ufo/ufo.png'); or if I use a variable. Nothin' happens. I've posted elsewhere about the problem, and ain't seen a resolution to it either. Any new ideas on the problem?
#16
12/19/2002 (8:20 pm)
I don't understand.. I went thru the SDK and some things in this snipit seem to be added.... but some parts AREN'T.

?????
#17
12/30/2002 (10:05 am)
Just tried this with 1.1.2 and it worked great. Make sure you are calling setSkinName on the server object: you can even try it in the console and it works. The tag you should use is not the full path to the texture - just the name of the texture that is different from 'base'.

So you should have at least two textures:
base.foo.png
red.foo.png

Save the model using base.foo.png. Add a string somewhere in your server scripts like so:

$testSkinName = 'red'.

Now in the console if you can find the object's id, you can just call

%id.setSkinName('red'); to change it to red, and %id.setSkinName('base'); to change it back.
#18
12/31/2002 (7:13 am)
This looks very useful, and thanks for the heads up on hex editors. Very handy little tool that I wasn't aware of.

Edit:
Wow, I didn't realize this was already working in the HEAD so well. Damn this is sweet. Nice work.
#19
02/15/2003 (2:42 pm)
coooool

this is so coooooool
#20
03/10/2003 (8:52 am)
It looks like most of this has been merged into the HEAD at this point. This may be a little redundant,
but here's a step-by-step for newbies like me to follow:

In order to get skin switching to work for my demo app here's what I had to do.

1) Hex-edit player.dts and changed the word "player" at the end to "base.1". I like UltraEdit for this kind of work.

2) Create a new texture, painted it red, and called it red.1.png

3) Rename player.png to base.1.png

4) Modified game.cs

function GameConnection::createPlayer(%this, %spawnPoint)

	// skip down and add this in the player setup area.

 	if (%this.team.teamId == 1) {
		%player.setSkinName('red');
 	}
 	if (%this.team.teamId == 2) {
 		%player.setSkinName('base');
 	}

You'll also need to do your own teamId code or implement this one:

www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=2312
Page «Previous 1 2