Jump to content

[SOLVED]Changing Skin Client Side Only


Recommended Posts

Greetings, fellow Torque-goers!


Recently I've been trying to update the skin of a shape only for a specific client, and I'm finding that more difficult than I'd envisioned it would be. After a few failed attempts at this, I figured I'm way overthinking this and overlooking an existing method. Hopefully someone here can clue me in. :mrgreen:


First, a little backstory to make clear what I am trying to accomplish. Basically I have shapes that are doors with a blue color. When a client meets certain requirements, I want that client to see that door as green as a visual indicator the door is now passable. However, calls to setSkin() for TSStatics or setSkinName() for StaticShapes is changing the color for all connected clients. Here are a couple things I've tried so far:


Client Side Object

I had the notion that I would just create the objects client side only, so off I went implementing a client-side only object. I was successful in this venture, and indeed the objects created were for the client only and not ghosted across the network. However, this leads to ghost mismatching between the client & server and sort of undermines the ghosting system in the first place.


Custom Shader

I tried creating a shader to accomplish the color change yet this presented a new problem: I couldn't find any way to toggle the shader on/off during runtime. I was trying to do something along the lines of: The client sees a blue shape, if they meet the requirements turn on this shader effect on the material to blend it to green. Anyhow a lerp or multiply shader was simple enough to implement, but unable to figure out how to toggle the shader(similar to a PostEffect) I haven't been able to apply it.


After attempting all of this, which seems like a whole lot of work for a simple client-side color change, I figured I have got to be approaching this in the wrong way. Logic = NULL lol. I've had a couple other ideas like doing some client-side check on the object's unpackData() or even in the onRender() but before diving that deeply into it again I figured I'd ask here.


Summary

Essentially I've got an object that needs to show a different skin for each client based on their current status. I was hoping someone knows what I need to do to get this working.

Edited by TorqueFan
Link to post
Share on other sites

setSkin/setSkinName are indeed networked but the actual retexturing work is done clientside by the reSkin() method. The only reason calling setSkin/setSkinName applies the change to all clients is because those functions set a netmask (SkinMask) which sends the mSkinNameHandle string across the wire. Clients then see that 'SkinMask' is set, read in the new mSkinNameHandle, and then call reSkin() on the object in question.


I would make a new function based on reSkin() (let's call it reSkinLocal()) that just accepts a string as the name of the skin instead of using the networked mSkinNameHandle variable. Expose the reSkinLocal() method to the scripting system so you could then call commandToClient() to tell specific clients to call reSkinLocal() on their (ghosted) mesh. It'd work something like this:

 

// serverside door.cs script
function Door::unlock(%this, %clientToNotify)
{
    commandToClient(%clientToNotify, 'UnlockDoor', "green_skin", %this.getId());
}
 
// clientside commands.cs script
function clientCmdUnlockDoor(%skin, %doorServerID)
{
    // need to convert the serverside ID to the ghost ID on this client
    %obj = ServerConnection.getGhostID(%doorServerID);
 
    // retexture the object
    %obj.reSkinLocal(%skin);
}
Link to post
Share on other sites

Wow, that was a quick reply! Yes, all of that makes sense about the 'SkinMask'. As a matter of fact, I did try to do what you are saying once but must have goofed up the 'getGhostID()' script stuff on the client side.


Hey, thanks, I'll write up a reSkinLocal() real quick and give it another go! I really appreciate your input @Happenstance

Link to post
Share on other sites

@Happenstance


I still hadn't been able to get this to work. I believe I understand why it didn't work when I tried it before, and still doesn't. Torque's client/server architecture still requires those masks to be set, even if the object was just in a singleplayer game on a single machine. If there isn't any mask bit set, the material won't update, even if it's just a local object. Any attempts to do so will crash the client because the local ghost's data(the skin) won't match with the server. You can look at any SceneObject and it will be setting a mask bit for its material. Don't use that mask bit and that material will never show, even locally. You can see this in the RenderMeshExample. To the best of my understanding, mask bits are required for any data to be changed on an object that is ghosted(singleplayer or otherwise). Therein lies the quandary - it's harder than it first appears to change the local skin on a ghosted object...because every object is always ghosted local or not.


Here's what I've done so far:


Tracing the call to TSStatic::_setFieldSkin(), I found that the TSStatic calls on setSkinName() prior to calling reSkin(). In that method, since it's called server-side, there is a check to be sure it isn't a ghost. I duplicated that method and named it setSkinNameLocal(), only removing the !isGhost() check and the setting of the mask bit.


Added in tsStatic.h:

void setSkinNameLocal(const char *name);

 

Added in tsStatic.cpp:

void TSStatic::setSkinNameLocal(const char *name)
{
	if(name[0] != '\0')
	{
		// Use tags for better network performance
		// Should be a tag, but we'll convert to one if it isn't.
		if(name[0] == StringTagPrefixByte)
			mSkinNameHandle = NetStringHandle(U32(dAtoi(name + 1)));
		else
			mSkinNameHandle = NetStringHandle(name);
	}
	else
		mSkinNameHandle = NetStringHandle();
}

 

So here I'm assuming that's a local update to the mSkinNameHandle. Next order of business, add an EngineMethod to try and reskin the TSStatic from script:


Added in tsStatic.cpp:

DefineEngineMethod(TSStatic, reSkinLocal, void, (const char* skin), (""), "")
{
	object->setSkinNameLocal(skin);
	object->reSkin();
}

 

Here I set a new mSkinNameHandle using the method just created and then call the existing TSStatic::reSkin() method. Now at this point I'm assuming we're good. Calling that on the object from script, however, does nothing. :shock:


Again I'm not sure that anything could ever render for ANY class if a mask bit wasn't set to send that material's information to the client. Strangely enough, we're assuming that we are calling this 'on the client' so that shouldn't be necessary. Torque's networking infrastructure creates that sort of problem, and that is exactly what I'm attempting to overcome here. I have never been able to update any object visually in T3D without setting some sort of mask bit. Please correct me if you can get this to work otherwise!

Link to post
Share on other sites

Whoa whoa whoa! Hold the phone, this IS updating the local texture for the host machine(so far). I believe the ultimate problem here is with how I am using the script functions to resolve the ghostIDs.


Here's what I've got working on the host machine so far:

 

function clientCmdreSkinLocal(%id)
{
	if(ServerConnection.getAddress() $= "local")
	{
		%obj = serverToClientObject(%id);
		%obj.reSkinLocal("Green");
	}
	else
	{
		// ...
	}
}

 

Now I've(hopefully) just got to fill in the 'else' block above for connecting clients, where I'd resolve the local ghostIDs.


Calls to getGhostId() and/or resolveGhostID() do not seem to be returning the proper ID's, as I'm getting console errors that the object trying to call 'reSkinLocal' doesn't have that method. Also one such console entry says the Camera doesn't have that method lol, so that's a sure sign I'm not resolving those GhostID's properly on the client side. I'll keep pecking at this, feels like it's close! :D

Link to post
Share on other sites

@Happenstance Hey, thanks again for getting my head back in the game. I solved this, and it did have to do with the resolving of ghostID's after all. It sort of feels like I went around my elbow to get to my @$$hole, but alas it 'feelsgoodman'! LOL!


Here's the script I ended up with, in case someone else stumbles into this post and needs a little help resolving ghostIDs:


On the server:

if(%client.getAddress() $= "local")
	commandToClient(%client, 'reSkinLocal', %obj.getId());
else
	commandToClient(%client, 'reSkinClient', %client.getGhostId(%obj));

 

On the client:

// For local host:
function clientCmdreSkinLocal(%id)
{
	%obj = serverToClientObject(%id);
	%obj.reSkinLocal("Green");
}
 
// For client:
function clientCmdreSkinClient(%id)
{
	%obj = ServerConnection.resolveGhostID(%id);
	%obj.reSkinLocal("Green");
}
Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...