Left 4 Dead 2

Left 4 Dead 2

34 ratings
Material Proxies for Modders : Part 1 : Basics
By Ellie
This is the first part of a planned huge guide that aims to explain everything Modders of Source Engine needs to know about Material Proxies. Material Proxies are small bits of "programming" that are contained in the texture parameters, they have the advantage of being accepted on Official Servers, and are therefore the only way to build texture effets that can be read on official (or any) server, since they are client-side.
Hello everyone. First of all, this guide is a guide for modders, on Source Engine, if you are not a modder, you won't be interested by its content. However, if you are, this Guide might interest you because it's one of the very few ressources on the Internet that explains how Material Proxies work.

Q : What are Material Proxies ?
Material Proxies are chunks of programming that can be included in texture parameter files (VMT) to modify the original values of the variables contained in the VMT. For example, modifying the $frame value with Material Proxies will result in another frame (so another texture) to be displayed.

Q : What basic stuff can we do with Material Proxies ?
We can do a lot of stuff. Basically, Random Textures (frames), Random Transparancy, Blinking Textures, Bliking Eyes, Textures that depends the player Speed, Texture Rotation, Texture Translation, Animated Textures.

Q : What advanced stuff can we do with Material Proxies ?
Using certain properties that were not designed for it and sometimes with the help of 3D Tools, it's possible to create Infected Radars, Cutscene triggers (!), Items or Players that get more and more damaged as they go through the map... it's also possible to dissociate certain MDL files or to bypass the 64Mb VTF limit.

Q : Is it hard ?
What's really cool with Material Proxies is that you can make cool mods at each step of the learning curve. So even with the most easy stuff, you can already build nice mods that will be unique, since only a few people use Material Proxies. So at first, no it's not hard, but it requires a bit of "programming" logic.

Q : What are the skills required before starting that guide ?
You need to know what a VMT is and how to modify it, you also need to have some logic and not be afraid of High School level Math. Being able to code in a programming language is a very good plus

So, this being said, I am happy to welcome you, fellow modder, in that guide where I hope you'll learn some new useful stuff ;).
I thought it would be a cool idea to give you other references than this Guide. I myself have learned a lot by myself but I couldn't have done it without the help of some other "websites". Here they are :
[Part 1.1] : Variables Syntax
When you open a VMT file, you usually see some stuff like that (minus the colors) (file = hulkwallglow.vmt) :
Here, you have 5 variables : $baseTexture, $translucent, $decal, $detailscale and $proxTeam.
$proxTeam (in red), unlike the others, is a custom defined variable. It is used only to store data while the proxies are exexuted.

To write a custom variable, you must, in the beginning of a new line, write $ then the name of your variable, then a space and then its initial value.

Examples :
$arandomnamethatworks 1 $proxTeam 1.0 "$zero" 0 "$two" "2" $initialarray "[0 0 0]" //

As you have seen, I used quotes in my examples. Quote marks are necessary only for the value when you make an array and when you set the value as a decimal number, using the form 0.25 instead of .25. (See, for example, the $detailscale variable uses them)
$nn "0.99" // Correct $nn 0.99 // NOT correct $nn ".99" // Correct $nn .99 // Correct

If you code with proxies, you will need to declare custom variables all the time. Some stuff I coded used not less than 40 different custom declared variables !
[Part 1.2] : Proxies Syntax
VMT's that contain proxies are always separated in different parts.

In blue, you have the global VMT beginning and end. It always starts with the Draw Method (UnlitGeneric / VertexLitGeneric / LightMappedGeneric...) and then you have an opening { that matches a closing } at the very end.
Variables must be written under the first {. The Variable Block is here in green.

In red, you have the Proxies Block. Under your variables, you must write Proxies and then an opening { that mathes a closing } at the end of your Proxies Block (both are shown in red)

In orange (3x), you have the Proxies themselves. In that example, there are 3 : Sine, PlayerTeam and Multiply. To write a proxy, you must first write it's name (i. e. "Sine"), then for each line, write the proxy parameter (i. e. "sineperiod") and then define a variable or a value for it (i. e. ".2").
The complete list of the proxies parameters can be found here.

It's important to note that when you have to write a var (i.e. when it says "srcVar1", you MUST put a variable)

// This is WRONG $value 10 Proxies { Add { "srcVar1" "$value" "srcVar2" "1" "resultVar" "$frame" } }
// This is RIGHT $value 10 $one 1 Proxies { Add { "srcVar1" "$value" "srcVar2" "$one" "resultVar" "$frame" } }

You can also note that since $frame is a game created variable, you do not need to define it to do operations on it. You can define it if you want but it has no purpose except for (1) : Debugging and (2) Using the patch method (where you do not have to define it)

I guess you are already fed up with Syntax explanations from the 2 past points. Because a example is worth a thousand words and that I don't like to write thousands of dumb theoretical ♥♥♥♥, almost all this guide will be filled with examples that you can download.

When you see a section with a number like this : ❻, it means that it introduces you to a new proxy and how to use it. Example (see below) can spread on multiple following "Introductions to Proxies", but here, you're in the "Sine" section, so we'll talk about Sine

Practical Example START : A Pikachu that blinks and open his mouth when we move fast
The goal here will be to see how to add "Blinking Eyes" on a Pikachu Gnome Replacement that I did, and how to make Pikachu Open his mouth when we carry him (in first person) and that we move (he like the speed to he smiles)

First of all, the different "stages" of the eye (open, closing and closed) have to be contained in a Multi-Frame VTF (because what we want to do is to change the texture frame, by changing the value of the game defined $frame with our proxies).

The VTF I used for Pikachu contains 3 frames : frame 0 = eyes open (default frame), frame 1 = eyes closing and frame 2 = eyes closed.

In case you don't know, to make a Multi-Frame VTF, you just have to select multiple images at once (have to be the same size) and press ENTER, VTFEdit will turn them into different frame and will sort them in the alphabetical order

So, this being said, let's take a look at Pikachu's Eyes VMT :
"VertexLitGeneric" { "$baseTexture" "models\pika\PikachuEye" "$halflambert" "1" }

We want to add proxies in that, so let's add the Proxies block :
"VertexLitGeneric" { "$baseTexture" "models\pika\PikachuEye" "$halflambert" "1" Proxies { } }
Now, to make the eyes blink, we will have to create a "Sine" proxy :

The "Sine" proxy is a proxy that generates a Sinus Wave that keeps looping. You can set the minimal value, the maximial value, the time between peaks and (optional) the initial value

Example :
Proxies { Sine { sinemin "0" sinemax "2" sineperiod "1" // Time in sec between peaks timeoffset "1" resultVar "$frame" } }
  • sinemin : Minimal value of the wave
  • sinemax : Maximal value of the wave
  • sineperiod : Time it takes in seconds to "look" (= to go from max value to min value and back to max value)
  • timeoffset : Initial value of the wave
  • resultVar : Where will the result of the proxy be stored
So the above code generated a sine wave that takes 1 second to go from 0 to 2 and way-back, starting at 1, and injects the result of the wave in the $frame variable ; there's no need to define the $frame variable because it's an "in-game" that's authomatically defined.
The above code would create a Sine Wave that would look like this (approximately) :

Sine waves main "basic" purpose is to make things blink. Most commonly, the result of the Sine Wave is linked to $frame or $selfillumtint. Here's an example or "real" code, that's used to make a LED Blink :
// EMISSIVE $selfillum 1 $selfillummask "yog/pipebomb/pipebomb_e" // LIGHT ANIMATION $sine 0 Proxies { Sine { sineperiod 1.2 sinemin -1.8 sinemax 1.5 resultVar $sine } Clamp { min 0 max 1 srcVar1 $sine resultVar $sine } Equals { srcVar1 $sine resultVar "$selfillumtint[1]" } Equals { srcVar1 $sine resultVar "$selfillumtint[2]" } Equals { srcVar1 $sine resultVar "$selfillumtint[0]" } }

Here are some mods that use a Sine Wave Proxy :

So, back to our Pikachu. What we want to do is that the frame 0 (eyes open) stay for, let's say 10 seconds, then that the game changes it very quickly to frame 1 (eyes semi-open), then to frame 2 (eyes closed) and then back to frame 1 and to frame 0.

So, given these informations, our code will, in a first time, look like this :
Proxies { Sine { sinemin "0" // STILL needs to be changed, see later sinemax "2.99" // SEE below why it's 2.99 and not 2 sineperiod "10" // We want that the time between each peak is 10sec resultVar "$frame" } }

You will have noticed that, in the code shown above, I put 2.99 for sinemax and not 2. Why this ? The reason is simple, if I had put 2, the frame N°2 would only have been shown for a ridiculously small amount of time.
If $frame = 1.78, it's the frame ONE that will be displayed, since the game takes only the natural part of the number into account to set the frame.

Here are two graphics for you to see why it's not ok to set sinemax to 2 :

With sinemax = 2, we never get to see the frame N°2

With sinemax = 2.99, we get to see frame N°2

Now, the problem that we still have is that, with these settings, like it's shown in the above graphics, the Pikachu eye would just keep blinking and at a very slow rate, so what we want to do is something "like" this :

Here, we can see that the game will very quickly change the frames, that will create the illusion of the eye blinking. In this configuration, sinemin is way below 0 (at -20 here), but you will need way less to make the eye blink in a realistic way

At that point, our code would look like this :
Proxies { Sine { sinemin "-1000" // To create a very steep peak. Cf. above graphic sinemax "2.99" sineperiod "10" resultVar "$frame" } }

But once again, we have a problem : when the $frame goes below 0 (cf. above graphic), it won't work ! (frames -1 to -1000 don't exist) ; this will just display an all-black frame (and spam console with errors). So what we need to do is to force the $frame to stay between 0 and 2.99. And here we'll need a 2nd proxy : Clamp

The "Clamp" proxy forces a variable to stay in defined boundaries. If the value is above or below the set boundaries, the Clamp proxy will set it to min, resp. max

Example :
$frametoclamp 5 Proxies { Clamp { min "0" max "2.99" srcVar1 "$frametoclamp" resultVar "$frame" } }
  • min : Minimal value of resultVar
  • max : Maximal value of resultVar
  • srcVar1 : Input variable
  • resultVar : Output variable ; it can either be the same as srcVar1 or a different one. If it's a different one, the Clamp won't affect the var written in srcVar1
The above code takes the custom variable $frametoclamp and keeps it in the min and max boundaries. The above code will result in $frame to become 2.99 (because $frametoclamp is above 2.99), when $frametoclamp will still be equal to 5

Back to our Pikachu, we need to use the Clamp Proxy to keep the value of the Frame between 0 and 2. So we need our min and max to be 0 and 2.99, and apply this to the $frame variable

The final code of the Pikachu Eyes Bliking would look like this :
The numbers are used for a quick re-explaination below

// PIKACHU BLINKING EYES FINAL CODE Proxies { Sine { sinemin "-1000" // N°1 sinemax "2.99" // N°2 sineperiod "10" // N°3 resultVar "$frame" // N°4 } Clamp { min "0" // N°5 max "2.99" // N°6 srcVar1 "$frame" // N°7 resultVar "$frame" // N°8 } }
  • N°1 : We create a sine wave, the minimal value has to be way below 0 to make the eyes blink fast. If it was -100, it would take Pikachu 10 times longer to achieve an eye blink
  • N°2 : The max value of the sine wave is the N° of the last frame +0.99. If there wasn't that 0.99, the frame 2 would be display only a microsecond. (+ if $frame = 2.45 (i.e.), it's frame 2 that will be displayed)
  • N°3 : We want Pikachu to blink every 10 seconds, so the time between the peaks has to be 10 (seconds)
  • N°4 : We inject the result of the Sine directly into $frame (so doing this, we do not need custom declared variables). $frame is a game-defined variable and changing it will effectively change the frame (so texture) of the material in game
  • N°5 : We Clamp $frame. The minimal is 0, we do not want the frame to go below 0 because it doesn't exist (and would become full black)
  • N°6 : We Clamp $frame. The maximal is 2.99. It could in fact be any number above this since the Sine wave can't go above 2.99.
  • N°7+8 : The result variable is here the same as the input variable. We want to do the Clamp on $frame, so resultVar has to be $frame, and the input variable also has to be $frame. srcVar1 and resultVar are different only when we want to keep srcVar1 unclamped.

Here's the mathematical curve that is produced by the Proxies and animate Pikachu's eyes by changing the $frame

The "PlayerSpeed" proxy returns the speed the local player (the character or infected you are controlling) and is able to scale this value if needed

Example :
$myspeed "0" Proxies { PlayerSpeed { scale "1" resultVar "$myspeed" } }
  • scale : Factor to multiply the result of the variable. If set to 1, does not do anything
  • resultVar : Output variable. It's where the result of the proxy will be stored
The above code stores the player speed in a custom declared variable called "$myspeed"

For the next part of our Pikachu (open his mouth if we move while carying him), we then need to get the player speed with a code exactly similar to the one in the example. In my code, I just used a variable named "$playerspeed" insted of the "$myspeed" here.

So at that point, the VMT of our Pikachu Mouth looks like this :
"VertexLitGeneric" { "$baseTexture" "models\pika\PikachuMouth" "$halflambert" "1" $playerspeed "0" Proxies { PlayerSpeed { scale "1" resultVar "$playerspeed" } } }
The "LessOrEqual" proxy is an extremely powerful proxy that is most of the time the key element to everything. This proxy compares 2 variables (N°1 and N°2) and sets the value of a 3rd variable to a 4th one if the variable N°2 is greather than the variable N°1, otherwise, it sets the 3rd variable to a 5th one.

Example :
$zero "0" $one "1" $six "6" $ten "10" Proxies { LessOrEqual { LessEqualVar "$zero" greaterVar "$one" srcVar1 "$six" srcVar2 "$ten" resultVar "$frame" } }
  • LessEqualVar : resultVar will be set to this var if srcVar1 is not greater than srcVar2
  • greaterVar : resultVar will be set to this var if srcVar1 is greater than srcVar2
  • srcVar1 : input variable
  • srcVar2 : variable to compare the input variable with
  • resultVar : the variable to change in either LessEqualVar or greaterVar depending on the result of the comparison between srcVar1 and srcVar2
    The above code compares the variable $six to the variable $ten. Since $six is not greater than $ten, the code will store $zero intro $frame

This Proxy is the most important of them all. This proxy is the key to programming since it's (almost) the only one that can be used to create a condition ("if").

In programming language, this proxy gives this result :

if (srcVar1 > srcVar2) { resultVar = greaterVar } else { resultVar = LessEqualVar }

If you know programming language, you can know that it's possible to turn this, using math, into an "or" (||), an "and" (&&), an "<" and also an "equal" (==), but this requires mathematical manupulation and I am planning to explain that in the 3rd Guide about Proxies (Programming with Proxies). For the Basic part here, this is enough ;).

[Part 1.3] : Basic Conditions (If)
First of all, if you haven't read the previous sections (about LessOrEqual & Player Speed), do it. In this section, I'll take once again the example of the Pikachu to show you how to build a condition to "Make Pikachu open his mouth if the player that carries him is walking").

✦ What we want to do with words is : if the player is moving, make pikachu open his mouth.
✦ What we want to do technically is : if the player speed is over 50 unites per second, change the texture frame to display the open mouth
✦ The condition we want to write is therefore : if player speed > 50 units per second, $frame = 1, and else, $frame = 0
✦ And "Proxies Speaking" : Compare the player speed to 50. If it's over, store 1 into $frame, otherswise store 0 into $frame

So here under is how you can use the LessOrEqual proxy to write that condition :

LessOrEqual { LessEqualVar "$zero" // ❸ ... if player speed if less or = 50, store 0... greaterVar "$one" // ❸ ... if player speed if over 50, store 1... srcVar1 "$playerspeed" // ❶ Compare the player speed... srcVar2 "$triggerspeed" // ❷ ... to 50 ... resultVar "$frame" // ❹ ... into $frame. }

What you need to note is that it is not possible to "Stop a Proxy", so the LessOrEqual proxy will keep doing its stuff over and over again. It won't just apply when the Pikachu appears, witch can seem good, but sometimes, for other projects, it can be problematic because you'll have to artificially make it stop ;).

So the full code for the Pikachu Mouth is the following :

"VertexLitGeneric" { "$baseTexture" "models\pika\PikachuMouth" $one "1" $zero "0" $playerspeed "0" $triggerspeed "50" Proxies { PlayerSpeed { scale "1" resultVar "$playerspeed" } LessOrEqual { LessEqualVar "$zero" greaterVar "$one" srcVar1 "$playerspeed" srcVar2 "$triggerspeed" resultVar "$frame" } } }

At that point, I'll resume with words (once again) how we built the above VMT :
● We started by making a 2 frames VTF, one is frame 0, witch is closed mouth, and one is frame 1, witch is open mouth. This VTF is PikachuMouth.VTF
● We created the Proxies section by putting Proxies and the opening and closing {}
● We created a PlayerSpeed proxy with the required elements and defined a custom variable $playerspeed that will contain the result of the proxy
● We declared a custom $triggerspeed variable with the speed we want Pikachu to open his mouth at
● We created a LessOrEqual proxy that will act as a condition (if), it compares $playerspeed to $triggerspeed and sets the frame ($frame) to either $zero or $one depending if $playerspeed is below $triggerspeed or not
● We declared $one and $zero

I know that I didn't really explained "HOW" to create a condition. It's mainly because it's hard to explain in a simple way, and a lot easier to learn with practice.

What I can say is that you need 2 things to compare, one of these 2 things at least being non-constant (the speed in out example) and something you want to change as a result. Then, simply modify the values of the LessOrEqual. I'll use a LOT more examples in the future guides (if I have the courage to make them because people are interested) that will show you the wide range of possible conditions.

< >
Ellie  [author] Aug 25, 2016 @ 8:43am 
@J-Muse[ネプギア] : Yes
The Pineapple Lord Aug 24, 2016 @ 3:49pm 
Is there a way to make eyes closed only when billed by a boomer? (For future reference)\
Erica May 6, 2016 @ 6:55am 
Oh no I can't make it x( It just blinks between two frames every time period and it still is a REAL RNG! Could you write the correct codes for me when you are free? I need a correct example to know how it works x(
Erica May 6, 2016 @ 4:41am 
Oh it was so embarrassing that I fogot to explain what I said was all about RNG special infected,my last sentence is so confusing xD My intention was not about items but,it's okay! xD Thank you for the guidance,I'm trying x)
Ellie  [author] May 6, 2016 @ 3:37am 
@桜丿呚 : Oh yeah, it's totally possible to make some kind of RNG that makes the items in hand match the ones in world and that the thing changes each X seconds, or that they change when you don't look at them or other shit (depends what items though).
What you can do is copy a True RNG code (Take if from the RNG Aion Katana), then add a Sine Wave proxy from -1 to 1000 and make a LessOrEqual proxy that changes the $frame to 1000 if the sine wave is under 0. If the $frame resets to 1000, it will change again to another random frame next time you encounter an item.
There's also maybe the possibility to use (if it works on items) the PlayerView proxy to force this to happen when you don't look at the thing. I might try to code that if you want. The problem is that it will take a lot of ressources in game and will likely make it crash if there are too much mods that use this.
Erica May 6, 2016 @ 3:13am 
Since I read this gudiance only I get to know what these codes are xD I can hardly find any chinese tutorials about proxies,It seems few people use it here :( But yours helps a lot! Thank you for your detailed introduction! Please keep up! xD
Actually I'm still thinking of a way to solve the unmatching problem xD Is there a RNG method which can generate random numbers every xx seconds? I try using the animated function to make the frames change every 45 seconds(the interval of special infected respawn in campaign),when playing they match and it seems nothing is strange! xD But the order of the frames is constant you konw,the chance of each frame in display is not balance.Through you guidance I think it is possible to realize,could you guide me how to write the codes?And if the interval can match the respawn time in all kinds of mutations,it can also be used in versus perfectly(This function looks not possible xD )!
MrUnkn0wnB3y0nd Apr 27, 2016 @ 11:35am 
Gotcha, Thanks!
Ellie  [author] Apr 27, 2016 @ 10:06am 
@Mr.Unknown : RNG is with textures. You do not need to know how to use a 3D Tool to get RNG Working, but when I meant "texture" I mean stuff like enhancing mod (especially HD Mods). I always suggest HD Mods because it's most of the time easy to do and have great popularity ;).
MrUnkn0wnB3y0nd Apr 27, 2016 @ 9:16am 
Texture mods? Is that like the graphic enhancing mod or your RNG mods?
Ellie  [author] Apr 27, 2016 @ 12:58am 
@Mr.Unknown : Start with at least some "texture" mods first (only VMT and VTF). Model Replacement is the hardest to learn and switching from Sound to MDL is imo a too big step if you are not familiar with the required tools (Blender, Maya or else) already :P