Changing Material Parameters at Runtime

by Antone Franich

Type:Material, UScript
Level:Intermediate

Download:
File Planet
GameFront

Summary:

This tutorial will teach you how to set up materials so that their color and size can be changed at runtime. The example provided here is only scratching the surface of what can be done using the SetVectorParameterValue() and SetScalarParameterValue() function in the material instance class. For instance, in my current project I'm using it to allow players to custom paint their ships and vehicles, but I'm sure there are a million other uses for it.
This tutorial assumes a basic understanding of Uscript and the UDK's Material Editor.



1: Setting up the Material

I've included a simple texture for use with this tutorial. However when creating textures that will later have a color applied to them it is important to start out a texture that is as close to greyscale as possible. This is done to ensure that the applied color appears true and is not offset by the color in the source texture. The texture I've included only makes use of the red and green channels, treating them each like a black and white texture.

1a.Create a new material based off the texture.

1b. Create a Scalar Param Node, a TexCoord Node, and a Multiply Node.

1c. Change the name of the Scalar Param Node to 'Scale' and set it's value to 1.

1d. Plug both of these into the Multiply node and plug the Multiply node into the Texture Sample's UV input.

1e. Create 2 Vector Param Nodes, 2 Multiply Nodes, and an Add Node.

1f. Connect the Texture Sample's Red Output to one of the Multiply Nodes, and it's Green Output to the other Multiply Node.

1g. Name one of the Vector Param nodes 'GridColor', set it's color to white, and connect it to the Multiply Node that the Texture's Green Channel is linked to.

1h. Name the other Vector Param Node 'BGColor', set it's color to black, and connect it to the Multiply Node that the Texture's Red Channel is linked to.

1i. Connect both multiply nodes to the Add Node, and connect the Add Node to the material's Diffuse input.




2: Creating an Actor Class

Now we need to set up a placeable actor class for this tutorial. The majority of this code doesn't have anything to do with changing the parameters of our Material. It deals mostly with setting up a placeable actor and making color and size transitions smooth. The important bit of code is colored red to make it easier to identify.

2a. Declare the new actor subclass and make it placeable.

class Tut_Actor extends Actor
placeable;



2b. Declare all of the variables we'll need.

var LinearColor GridColor;
var LinearColor BGColor;
var float Scale;
var LinearColor DesiredGridColor;
var LinearColor DesiredBGColor;
var float DesiredScale;
var float Speed;



2c. Declare defaultproperties, lighting and static mesh to be used on this actor. Assign the static mesh's component to our MeshComp variable.

defaultproperties
{
Begin Object class=DynamicLightEnvironmentComponent Name=Lighting
bDynamic=FALSE
bCastShadows=FALSE
End Object

Begin Object class=StaticMeshComponent Name=NewMesh
StaticMesh=StaticMesh'TutorialMats.Tut_Mesh'
LightEnvironment=Lighting
scale = 1;
End Object

Components.Add(NewMesh);
Components.Add(Lighting);
PlanetComp = NewMesh;
}



2d. Create the PostBeginPlay function, so that all variables are set to their initial values, and the timer for the Update() function is started.

simulated function PostBeginPlay()
{
Super.PostBeginPlay();

GridColor.R = 1;
GridColor.B = 1;
GridColor.G = 1;
DesiredGridColor.R = 0;
DesiredGridColor.B = 0;
DesiredGridColor.G = 0;

BGColor.R = 1;
BGColor.B = 1;
BGColor.G = 1;
DesiredBGColor.R = 0;
DesiredBGColor.B = 0;
DesiredBGColor.G = 0;

Scale = 1;
DesiredScale = 1;
Speed = 0.025;

self.SetTimer(0.1,true,'Update');
}


2e. Create a function to return a random LinearColor. LinearColor is a variable type where Red, Green, Blue, and Alpha are represented as values between 0.0 and 1.0. It the variable type accepted by a material's vector parameters.

function LinearColor RandomColor()
{
local LinearColor NewColor;

NewColor.R = RandRange(0,255)/255;
NewColor.B = RandRange(0,255)/255;
NewColor.G = RandRange(0,255)/255;
NewColor.A = 1.0;

return NewColor;
}



2f. Now we need to set up our Update() function that we told to run 10 times a second in PostBeginPlay(). The first part of the function will just run through each value of BGColor and GridColor and see if they need to be increased or decrease to reached the desired value as specified in DesiredBGColor and DesiredGridColor. If all values in BGColor or GridColor match the desired values, then a new desired color is generated using RandomColor(). The Last line of the function calls out ApplyChanges() function that we'll create in the next step.

function Update()
{
if(Abs(GridColor.R-DesiredGridColor.R) < Speed
&& Abs(GridColor.G-DesiredGridColor.G) < Speed
&& Abs(GridColor.B-DesiredGridColor.B) < Speed)
{
DesiredGridColor = RandomColor();
}
else
{
if(Abs(GridColor.R-DesiredGridColor.R) > Speed)
{
if(GridColor.R < DesiredGridColor.R)
GridColor.R += Speed;
else if(GridColor.R > DesiredGridColor.R)
GridColor.R -= Speed;
}

if(Abs(GridColor.B-DesiredGridColor.B) > Speed)
{
if(GridColor.B < DesiredGridColor.B)
GridColor.B += Speed;
else if(GridColor.B > DesiredGridColor.B)
GridColor.B -= Speed;
}

if(Abs(GridColor.G-DesiredGridColor.G) > Speed)
{
if(GridColor.G < DesiredGridColor.G)
GridColor.G += Speed;
else if(GridColor.G > DesiredGridColor.G)
GridColor.G -= Speed;
}
}

if(Abs(BGColor.R-DesiredBGColor.R) < Speed
&& Abs(BGColor.G-DesiredBGColor.G) < Speed
&& Abs(BGColor.B-DesiredBGColor.B) < Speed)
{
DesiredBGColor = RandomColor();
}
else
{
if(Abs(BGColor.R-DesiredBGColor.R) > Speed)
{
if(BGColor.R < DesiredBGColor.R)
BGColor.R += Speed;
else if(BGColor.R > DesiredBGColor.R)
BGColor.R -= Speed;
}
if(Abs(BGColor.B-DesiredBGColor.B) > Speed)
{
if(BGColor.B < DesiredBGColor.B)
BGColor.B += Speed;
else if(BGColor.B > DesiredBGColor.B)
BGColor.B -= Speed;
}

if(Abs(BGColor.G-DesiredBGColor.G) > Speed)
{
if(BGColor.G < DesiredBGColor.G)
BGColor.G += Speed;
else if(BGColor.G > DesiredBGColor.G)
BGColor.G -= Speed;
}
}
if(Abs(Scale-DesiredScale) < Speed)
{
DesiredScale = RandRange(0,50)/10;
}
else
{
if(Scale < DesiredScale)
Scale += Speed;
else if(Scale > DesiredScale)
Scale -= Speed;
}

ApplyChanges();
}



2g. Now we need to create the ApplyChanges() function that we just called in Update(). This is really the important part of this whole tutorial. This function creates an instance of the material we created in section 1 of the tutorial, and them applies the values of our Scale, BGColor, and GridColor variables to the appriopriate parameters in our material. Lastly it applies this new changes material instance to our Actor's mesh.

function ApplyChanges()
{
local MaterialInstanceConstant MyMat;

MyMat = new(None) Class'MaterialInstanceConstant';
MyMat.SetParent(Material'TutorialMats.Grid2_Mat');
MyMat.SetVectorParameterValue('BGColor',BGColor);
MyMat.SetVectorParameterValue('GridColor',GridColor);
MyMat.SetScalarParameterValue('Scale',Scale);
MeshComp.SetMaterial(0, MyMat);
}


Note: If the Material created in section 1 of this tutorial is named something different that the material refrenced in
MyMat.SetParent(Material'TutorialMats.Grid2_Mat');
Then the material will need to have it's name changed to the material created in section 1.

Last Notes

That should do it. Now if you compile your code, you will be able to place our Tut_Actor in a level. It should show up as a box with our grid material on it. This material will change it's shape and color. Ultimately, this is just one simple example of what you can be done by changing a material's parameters at runtime. I hope this was helpful. :)

As always, if you have any questions feel free to email me at potens at franich.com.