• Register
Post tutorial Report RSS Unreal Learning #4: Advanced Weapons (part 2) - Material Switching

This tutorial is part of a series of three will show you how to create complex materials, and how to program a weapon which can alter them, and a physics gun.

Posted by on - Basic Server Side Coding

This is the second part of the advanced weapons tutorial. You should have a custom mesh, and a custom material all set up ready for some coding - if you don't, you may want to step back and read the first part of the tutorial before you get started. Once again, let's start with the power cube.

The Power Cube

Let's create a class called PowerCube. We're going to want it to be a static mesh with physics properties. We're in luck, there's already a class which has just that for us - kActor. Even better, there's a class derived from that called kActorSpawnable, which we can create while the game is running if we want to - perfect.

class PowerCube extends KActorSpawnable;

DefaultProperties
{
}

Best of all, we don't need to write any real code - everything we want is in the default properties, and we just need to do some tweaking. We set bWakeOnLevelStart to true, meaning that when the game begins, the object will start to act under world physics. We don't want magically floating boxes that unpredictably fall back down to earth. We also add a static mesh component. I set the mesh to 'TutorialStuff.PowerCube' for the time being. And that's it!

class PowerCube extends KActorSpawnable;

DefaultProperties
{
Begin Object Name=StaticMeshComponent0
StaticMesh=StaticMesh'TutorialStuff.PowerCube'
Scale3D=(X=1.0,Y=1.0,Z=1.0)
WireframeColor=(R=0,G=255,B=128,A=255)
BlockRigidBody=true
RBChannel=RBCC_GameplayPhysics
RBCollideWithChannels= (Default=TRUE, GameplayPhysics=TRUE, EffectPhysics=TRUE)
End Object

bWakeOnLevelStart = true
}

The Charge Rifle

This is where things get a little tricker. We know this is going to be difficult, so how about we think about what we need before we start?

We want a weapon, so extending the Instagib Rifle could be a good start as it already has mesh and doesn't have a weapon group, which saves us a lot of hassle. The weapon needs to also contain material and colour information, so that we can change the visible appearance of the power cube when we want to. Well, that gives us some direction! I hope you remembered your colour choices and materials from the last part of the tutorial, because they're mighty handy right now.

class ChargeRifle extends UTWeap_InstagibRifle;

enum Colour
{
eRed,
eBlue,
eNeutral
};

var Colour Charge;
var bool Hard;
var linearcolor RedCube, BlueCube, NeutralCube;
var texture2d WeakMat, StrongMat;
var MaterialInstanceConstant NewMaterial;

DefaultProperties
{
RedCube = (R = 100, G = 5, B = 5, A = 1)
BlueCube = (R = 5, G = 5, B = 100, A = 1)
NeutralCube = (R = 100, G = 100, B = 5, A = 1)
WeakMat = texture2d'Envy_Effects.Energy.Materials.T_EFX_Energy_Swirl_03'
StrongMat = texture2d'Envy_Effects.Energy.Materials.T_EFX_Energy_Tile_01'
Hard = false
}

Next up, we need some logic. We need to be able to both colourise and apply new materials to our power cube. Let's write a function to do each.

We'll start with recolouring, as this should be nice and easy. I decided that our firing function should decide what colour we want the cube to be, so this function takes that colour, as well as the actor that has been shot by the weapon. We quickly check that our actor is indeed a power cube, and if it is, we create and set a new material instance from the material it already had, and find the PowerColour parameter - then we change it with our chosen colour.

function ReColour(LinearColor NewColour, Actor HitActor)
{
if (HitActor.IsA('PowerCube'))
{
NewMaterial = PowerCube( HitActor ).StaticMeshComponent.CreateAndSetMaterialInstanceConstant( 0 );
NewMaterial.SetVectorParameterValue('PowerColour', NewColour);
}

}

Materials wise, we only have two options and we're using the boolean 'Hard' to decide which one we need, so we'll give that function just the actor that the weapon hit, and decide the rest. We work out whether the weapon is currently set to hard or not, then create and set a new material instance based on that again, this time looking for the BaseTexture and RippleEffect parameters from before, and applying different textures to them. Very snazzy.After we've done that, we then make sure that the materials are the right colour, so we call recolour with the correct colour.

function ReMaterial(Actor HitActor)
{
if (Hard == true)
{
NewMaterial = PowerCube( HitActor ).StaticMeshComponent.CreateAndSetMaterialInstanceConstant( 0 );
NewMaterial.SetTextureParameterValue('BaseTexture', WeakMat);
NewMaterial.SetTextureParameterValue('RippleEffect', StrongMat);

Hard = false;
}
else
{
NewMaterial = PowerCube( HitActor ).StaticMeshComponent.CreateAndSetMaterialInstanceConstant( 0 );
NewMaterial.SetTextureParameterValue('BaseTexture', StrongMat);
NewMaterial.SetTextureParameterValue('RippleEffect', WeakMat);

Hard = true;
}

switch(Charge)
{
case eRed:
ReColour(RedCube, HitActor);
break;
case eBlue:
ReColour(BlueCube, HitActor);
break;
case eNeutral:
ReColour(NeutralCube, HitActor);
break;
}
}

Only one thing left! Our weapon use what colour we're firing, and what material we're firing - but it can't actually fire properly! We need a StartFire() function.

In this case, we just lifted and modified the CalcWeaponFire() function in the Weapon class. It doesn't deal any damage, or have any fancy effects, but it does send out a trace, and determine what we hit, which is enough for us. I decided that our first fire mode would cycle through the available colours, and our second would toggle the different materials.

To summarise what is commented in the code, we send out a trace and determine what we have hit. If we have hit a power cube, and our fire mode is the first, we change the colour, otherwise if the fire mode is not the first, we change the material. Additionally, we nicked and left in some code from CalcWeaponFire() that handles subsequent hits and portals. We'll leave that in for now because you never know, it might be useful later.

simulated function StartFire(byte FireModeNum)
{
local vector HitLocation, HitNormal, Dir;
local Actor HitActor;
local TraceHitInfo HitInfo;
local ImpactInfo CurrentImpact;
local PortalTeleporter Portal;
local float HitDist;
local vector StartTrace, EndTrace;
local array ImpactList;

StartTrace = InstantFireStartTrace();
EndTrace = InstantFireEndTrace(StartTrace);

// Perform trace to retrieve hit info

HitActor = GetTraceOwner( ).Trace(HitLocation, HitNormal, EndTrace, StartTrace, TRUE, vect(0,0,0), HitInfo, TRACEFLAG_Bullet);

// If we didn't hit anything, then set the HitLocation as being the EndTrace location
if( HitActor == None )
{
HitLocation = EndTrace;
}

// Convert Trace Information to ImpactInfo type.
CurrentImpact.HitActor = HitActor;
CurrentImpact.HitLocation = HitLocation;
CurrentImpact.HitNormal = HitNormal;
CurrentImpact.RayDir = Normal(EndTrace - StartTrace);
CurrentImpact.HitInfo = HitInfo;


// Add this hit to the ImpactList
ImpactList[ ImpactList.Length ] = CurrentImpact;

// check to see if we've hit a trigger.
// In this case, we want to add this actor to the list so we can give it damage, and then continue tracing through.
if( HitActor != None )
{
if (!HitActor.bBlockActors && PassThroughDamage( HitActor ))
{
// disable collision temporarily for the trigger so that we can catch anything inside the trigger
HitActor.bProjTarget = false;
// recurse another trace
CurrentImpact = CalcWeaponFire(HitLocation, EndTrace, ImpactList);
// and reenable collision for the trigger
HitActor.bProjTarget = true;
}
else
{
// if we hit a PortalTeleporter, recurse through
Portal = PortalTeleporter(HitActor);
if( Portal != None && Portal.SisterPortal != None )
{
Dir = EndTrace - StartTrace;
HitDist = VSize(HitLocation - StartTrace);
// calculate new start and end points on the other side of the portal
StartTrace = Portal.TransformHitLocation( HitLocation );
EndTrace = StartTrace + Portal.TransformVector( Normal( Dir ) * ( VSize( Dir ) - HitDist) );
//@note: intentionally ignoring return value so our hit of the portal is used for effects
//@todo: need to figure out how to replicate that there should be effects on the other side as well
CalcWeaponFire(StartTrace, EndTrace, ImpactList);
}
}

if (HitActor.IsA('PowerCube'))
{
if (FireModeNum == 0)
{
switch(Charge)
{
case eRed:
ReColour(BlueCube, HitActor);
Charge = eBlue;
break;
case eBlue:
ReColour(NeutralCube, HitActor);
Charge = eNeutral;
break;
case eNeutral:
ReColour(RedCube, HitActor);
Charge = eRed;
break;
}
}
else
{
ReMaterial( CurrentImpact.HitActor );
}
}
}
}

You might want to give that a good read through, and make sure you understand exactly what is going on. It's fortunately not too complicated at the end of the day.

So we're finished! Well, not quite. We have power cubes, and we have a very basic weapon that will change them. What we don't have are cubes and weapons in the game. I wrote a quick summon function that I can call from the console to drop one straight into the game for testing. I decided to use the Weapon Replacement mutator that is already in the game to test, but you could easily write your own mutator to replace the default inventory - just like we did in Unreal Learning #1. You can access the console in the game, by default pressing the tab button. Typing in the name of the function will allow it to happen - this is what the exec function is for. Note that because the function is in the weapon code, it will only happen when the weapon is drawn!

exec function SpawnCube()
{
local class CubeClass;

CubeClass = class(DynamicLoadObject("Tutorial4.PowerCube", class'Class'));

Spawn( CubeClass,,, );
}

We're definitely now done! Compile the code, and give it a try. You should be able to spawn a power cube using the console command (doesn't that look odd but funky in game?), and use the super shock rifle (ChargeRifle) primary fire to change it's colour, and secondary colour to change it's material.

In the final part of this tutorial, we will look at how the power cube physics can be altered - and we'll add a physics gun to let us throw it around.

Until next time, happy modding!

Post comment Comments
Varsity
Varsity - - 1,044 comments

That font size makes my eyes hurt. :-(

Reply Good karma Bad karma+1 vote
ambershee Author
ambershee - - 865 comments

It was larger (it was formatted the same way as all the others). It must have been resized when authed 0_o

Reply Good karma+2 votes
ambershee Author
ambershee - - 865 comments

In fact no, it shrink purely at random when saved. God this text editor is buggy...

Reply Good karma+2 votes
p3gamer
p3gamer - - 10 comments

I am trying to compile. Getting error: "ChargeRifle.uc<119> Error, Missing '<' in 'array' "
1. I am copying and pasting the code. 2. There is no code on line 119.
3. I have incrementally commented out lines of code and am still getting this error. Any ideas? Can you upload the source files?

Thanks,
new member

Reply Good karma Bad karma+1 vote
ambershee Author
ambershee - - 865 comments

Sorry I didn't get back to you sooner.

The text editor here has a merry habit of eliminating < > tags and treating it like a html tag, regardless of what you do.

Take a look at the StartFire function - I've spotted the issue right away. It should have an array of impactinfos;

local array<ImpactInfo> ImpactList;

Reply Good karma+1 vote
p3gamer
p3gamer - - 10 comments

Thank you, but I am getting a new error:

"Error, Type mismatch in Call to 'Spawn', parameter 1"

I found that Spawn() can take 5 parameters and that the first parameter is of type class. Here is my code for the SpawnCube function:

local class CubeClass;

CubeClass = class (DynamicLoadObject ("PowerCube.PowerCube", class 'Class'));

Spawn( CubeClass,,,, );

Am I missing something with my class declaration or something else?

Reply Good karma Bad karma+1 vote
ambershee Author
ambershee - - 865 comments

Looks like the text editor strikes back again. It has a habit of killing lines of code that use the angled brackets.

The lines should be:

exec function SpawnCube()
{
local class<PowerCube> CubeClass;

CubeClass = class<PowerCube>(DynamicLoadObject("Tutorial4.PowerCube", class'Class'));

Spawn( CubeClass,,, );
}

Reply Good karma+1 vote
p3gamer
p3gamer - - 10 comments

Thanks again ambershee.

I, unfortunately have another issue.

"Begin Object Name = StaticMeshComponent()"

I must specify valid name for subobject/component.

Is StaticMeshComponent a data type and not a function, similiar to this:
"Begin Object Class=StaticMeshComponent Name=......."


Reply Good karma Bad karma+1 vote
ambershee Author
ambershee - - 865 comments

I'll look into it - I'm a little busy.

I'll also be updating and moving a lot of tutorials soon, so we'll have a proper help forum, and a have all those editor issues fixed, thanks to the changes Intense made :)

Reply Good karma+1 vote
qweasdzxc
qweasdzxc - - 2 comments

I am also trying to compile. But the error is: "ChargeRifle.uc<159> Error, Type mismatch in'=' "
the function is : simulated function StartFire(byte FireModeNum)

156:{
157:ReMaterial( CurrentImpact.HitActor );
158:}
159:}
160:}
161:}

i don't know where the '=' is in

Reply Good karma Bad karma+1 vote
Handshakes
Handshakes - - 286 comments

I think the UT compiler takes out any empty spaces, so <159> in your text editor may not be <159> to the compiler.

Reply Good karma Bad karma+1 vote
Handshakes
Handshakes - - 286 comments

By the by, thanks for all the tutorials Ambershee. A+ stuff.

Reply Good karma Bad karma+1 vote
purplerainbo
purplerainbo - - 2 comments

Ok i think I fixed it, i compiled it and got no errors. I did get 2 warnings in PowerCube but i will worry about those later. The fix is

CubeClass == class(DynamicLoadObject("Tutorial4.PowerCube", class'Class'));

Reply Good karma Bad karma+1 vote
Tikithehut
Tikithehut - - 2 comments

Hello!

Thanks much for the tutorial, I feel like I'm getting a better grasp on the script. But I am coming up with some issues on this one. Overall it's just not working, I've tried it in published and unpublished.

The script compiles perfectly accept for the two warnings purplerainbo was talking about. But when I go into the game and try the console command "SpawnCube" nothing happens.

Also, when I go to use the weapon replacement mod to set the charge rifle, it's not one of the options available.

I'm also noticing the compiling process doesn't write a ".ini" file for the powercube. I don't know if this is because it's not actually supposed to be a mutator (that was my assumption). But to be on the safe side I went and wrote my own, including a bit in there about recognizing the charge rifle. This didn't work either, although it was funny to watch all the bots run around with instagib rifle models that wouldn't fire.

I'm pretty certain I followed the tutorial to the letter, and am comfortable in both the material editor and generic browser. Was wondering if anyone knew possible directions I might look to go about fixing this. Thanks for taking the time to write these!

p.s. I noticed the end of the charge rifle code has the ("Tutorial4.PowerCube", class'Class'));
Is this supposed to be the same package the cube is located within the Unreal Editor? If so should it be "TutorialStuff" like in part one of this tutorial? I could just be confused

Reply Good karma Bad karma+1 vote
purplerainbo
purplerainbo - - 2 comments

yea i still have not worked it out. I gave-up after trying to fix it, im giving it another go. I ended up removing the "Tutorial4.PowerCube" and replaced it with my own path. Ill let you know if i learn more.

Reply Good karma Bad karma+1 vote
Tikithehut
Tikithehut - - 2 comments

So I figured something out! At least in regards to the warnings.

This is specifically for anyone coming across the "unresolved reference" or similar error. This is caused by the package (where your static mesh for the power cube is located) being in a place the compiler/editor does not like. By default these packages seem to like to be created in the unpublished directory. However when compiling, the package needs to be copied over to the UTGame\Published\CookedPC directory.

Once I copied my packages over these warnings vanished when compiling. Good luck all!

Reply Good karma Bad karma+1 vote
Modham
Modham - - 1 comments

Ambershee kind of left everyone hanging.. I guess we can thank him for taking us half way there on this one.

Reply Good karma Bad karma+1 vote
Y2Rimmer
Y2Rimmer - - 2 comments

Well I've finally got this tutorial working. To fix the issue with the cube not spawning, I had to edit the line:

CubeClass = class<PowerCube>(DynamicLoadObject("PowerCube.PowerCube", class'Class'));

Notice I'm using "PowerCube.Powercube" instead of "Tutorial4.PowerCube". PowerCube is the name of my mod (change this to your script's folder name). The spawn command should work fine now, assuming you've managed to equip the Change Rifle. I made an ini file for this, so it can be changed via the Weapon Replacement mod.

Now the cube has spawned you'll notice another problem. It doesn't have the texture you made. You can edit the mesh in Unreal Editor so it uses it. If anyone's still interested I don't mind giving further details.

Reply Good karma Bad karma+1 vote
Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: