Post tutorial Report RSS Coding Tutorial 7: Adding a New Powerup

Tutorials using Unreal Script specific to POSTAL 2.

Posted by on - Advanced Client Side Coding

Tutorial originally released on Running With Scissor's Postal website. Was taken down more than 10 years ago. Mirrored here for archival purposes.

Adding a New PowerUp

to POSTAL 2

Code Tutorial 7

You must be running the new Share the Pain version of POSTAL 2 for any of this to be relevant. Using these tutorials with older code may cause you problems.

These tutorials are to be completed in succession with one another. If you jump ahead to a later tutorial, I will be discussing things from earlier tutorials that you may not recognize. Following each one in order should greatly improve your learning experience.

Even though we’ve already added a whole new weapon in the past three tutorials, I thought it’d be fun and useful to add a new powerup pickup. I call it a pickup, but the basics of this could easily be followed for adding a new errand pickup also.

The new item we’ll be making is an aerosol can of spray paint or the SprayPaint for short. Again, all the art assets used here will be from the game even though some of them won’t make perfect sense. If you want better assets you’ll have to make them! There will be two parts to this object—the pickup and the inventory item. These two will be intimately connected.

Making the Pickup

We start by making a new file. Let’s copy something simple like Inventory\Classes\BookPickup.uc. We’re not going to extend from this file—we’ll extend from its parent, but it’s a good simple template to start from.

Paste it into SuperPack\Classes and rename it to SprayPaintPickup.uc. Start by changing the class definition like so:

///////////////////////////////////////////////////////////////////////////////
// SprayPaintPickup
///////////////////////////////////////////////////////////////////////////////

class SprayPaintPickup extends OwnedPickup;

As you can see, it still extends from OwnedPickup. For the most part all powerup pickups extend from this. Here’s the hierarchy of our new powerup:

Engine.Pickup> Postal2Game.P2PowerupPickup> Postal2Game.OwnedPickup> SuperPack.SprayPaintPickup

Let’s move on to the default properties. Here’s what we have from BookPickup:

///////////////////////////////////////////////////////////////////////////////
// Default properties
///////////////////////////////////////////////////////////////////////////////
defaultproperties
          {
          InventoryType=class'BookInv'
          PickupMessage="You picked up your Library Book."
          DrawType=DT_StaticMesh
          StaticMesh=StaticMesh'stuff.stuff1.LibraryBook'
          BounceSound=Sound'MiscSounds.PickupSounds.BookDropping'
          }

Since we’re making this a spray paint can, let’s use a model already from the game. I found this by simply looking through the static meshes in the various packages that come with POSTAL 2. Let’s change the static mesh of the our new pickup to this:

StaticMesh=StaticMesh'Timb_mesh.janitorial_supply.aerosol_timb'

Next we need to link our new pickup to the new inventory item like this:

InventoryType = class’SprayPaintInv

Also let’s make the pickup text message more appropriate:

PickupMessage="You picked up<strong>a Spray Paint can</strong>."

And finally, let’s pick a different dropping noise. A ‘book dropping’ noise is so specific, we can probably pick something more generic or better fitting.

BounceSound=Sound'MiscSounds.Props.karmaLamp02 '

Wer’e going to only have one pickup equal one spray paint can. So when you pick up a single can, you’ll see one in your inventory. But the MoneyInv is different. You get 10 bucks by default with each pickup. The variable to change in the pickup defaults is AmountToAdd. Jack that up if you want more cans per pickup.

That’s about it. Save SprayPaintPickup.uc.

Now, let’s move on to the inventory item.

Making the Inventory item

Copy Inventory\Classes\BookInv.uc over to SuperPack\Classes and rename it to SprayPaintInv.uc.

Change the class extension to the following:

          ///////////////////////////////////////////////////////////////////////////////
          ///////////////////////////////////////////////////////////////////////////////
class SprayPaintInv extends OwnedInv;

We don’t really want any functions or states from BookInv so go ahead and cut everything out from below the class defintion down to the default properties. Let’s look at those:

          PickupClass=class'BookPickup'
          Icon=Texture'HUDPack.Icon_Inv_LibraryBook'
          InventoryGroup =105
          ExamineAnimType="Book"
          ExamineDialog=Sound'DudeDialog.dude_ithinkineedthat'

Clearly, the first one needs to link back to our new Pickup like this:

PickupClass=class'SprayPaintPickup'

Next is the HUD icon texture. That’s what appears in the upper right to show the player what item he has. This is the one art asset that we won’t be able to supply properly. We’ll have to use something as a placeholder that doesn’t make sense. Just like the grenades in first-person for the Party Bomb we’ll leave the library book icon as the icon for the spray paint can. It’s pretty simple to draw something that small to be the placeholder for the spray paint can. Just make sure to make the icon the exact same size as the other icon textures in the package HUDPack.

Next is a very important value that is easy to mess up. The InventoryGroup for each item needs to be unique. Obviously keeping the same 105 then would be a mistake. I have no great method for keeping track of the last item that I added (other than writing it down). I would suggest doing a search on “InventoryGroup =1”. That should narrow things down for you. Then look at all the items and see what the highest value is. Then increment it for your new item. Let’s do that now.

It looks like the highest value right now is in FlavinInv.uc at 134. We should probably make ours 135 like so:

InventoryGroup =135

The next two Examine lines aren’t currently used, so just go ahead and cut them out.

For the moment, that should do it!

Now build SuperPack and place a few of the SprayPaintPickups in your test level. (They should be under Pickup>P2PowerupPickup>OwnedPickup)

Now, run the game and make sure you can pick them up. Cool! It should show up in your inventory as the library book icon, but when you pick it up, you should see it say you grabbed a ‘spray paint can’.

Alright! You’ve officially made your own stand alone new powerup! Congrats! Hmmmm… why do you feel so unfulfilled then? So hollow… perhaps it’s because your new powerup doesn’t power anything up…

Let’s change that! I just wanted to make sure you can make your own, empty powerup first. The code above is the best place to start for any new powerup. It’s all empty and ready to be filled with coolness—so let’s add something new for the SprayPaintCan!

Adding the Graffiti

What do we want to add? Well we basically need the primary functionality plus any supporting programming. When you use the spray paint can powerup, you would spray graffiti in front of you, onto whatever was there. It would work just like a bullet hole splat, and it would be fun to just run around the game as a player and dirty the world up.

Let’s get to work. The first thing we should probably do is make the splat that will be the graffiti. There is already plenty of graffiti textures in the game. I figure this will randomly pick one of those each time you use a can. Each paint can will represent one sprayed on graffiti texture to keep things nice and simple.

Let’s start with the splat. A splat in POSTAL 2 is a flat texture that is projected onto any surface. Not all surfaces can support these. People will not work well with them. Neither will Karma objects or powerups, etc.

Here is the hierarchy for splats in POSTAL 2:

Engine.Projector> BaseFx.Splat

Projector does all the work and Splat sets it up for the game to use more easily.

Copy Postal2Game\Classes\BloodMachineGunSplat.uc over to SuperPack\Classes. Rename it to SprayPaintSplat.uc. Change the class definition at the top to the following:

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
class SprayPaintSplat extends Splat;  

Next, I’m going to crack open the editor and look for the graffiti textures I’ve seen in the game in the various texture packages. It could take a little while, but the results will be worth it (in terms of saving time from having to actually make the art from scratch).

Alright, I found some good ones in Josh-textures.projection. I’m just going to pick five of the more generic ones for our class here.

Rename BloodSprites in SprayPaintSplat.uc to PaintSprites. Then add in five graffiti textures(you’ll see the ones I picked in the default properties). You should end up with the following:

const SPLAT_NUM          =          5;
var() Texture PaintSprites[SPLAT_NUM];
var float sizerange;
var float sizestart;

simulated function BeginPlay()
{
            local int usecount;
            local float usescale; 

            Super.BeginPlay();

             // pick a random texture from the four as one to display
            usecount = Rand(ArrayCount(PaintSprites));
            ProjTexture = PaintSprites[usecount];

            // Randomly pick size
            // Can only do this in single player since SetDrawScale can't be called on
            // the client
            if(Level.Game != None
                        && Level.Game.bIsSinglePlayer)
            {
                        usescale = (sizerange*FRand() + sizestart);
                        SetDrawScale(usescale);
            }
}

defaultproperties
{
            PaintSprites(0)=Texture'Josh-textures.projection.bleh-fin-JL'
            PaintSprites(1)=Texture'Josh-textures.projection.bullseye'
            PaintSprites(2)=Texture'Josh-textures.projection.hindu'
            PaintSprites(3)=Texture'Josh-textures.projection.JTT'
            PaintSprites(4)=Texture'Josh-textures.projection.mcIcePac'
ProjTexture=Josh-textures.projection. projection.bleh-fin-JL
            Lifetime=7.0
            sizerange=0.25
            sizestart=0.2
            DrawScale=0.4
}

That should be fine for testing for the moment. We may have to come back to this file to tune the sizes. If you noticed in the PostBeginPlay function, the sizes are randomly scaled through a range set in the default properties. Note that these are just Textures and not FinalBlends. And be sure you fill in the ProjTexture also, this will be the fall back texture for the splat if anything goes wrong.

Now that we have the graffiti, we need to make the can spray it.

Open SprayPaintInv.uc

Think of items in the game. When you use them, they go into an Activated state. Let’s pick a simpler powerup like the FastFoodInv.

Open the file Inventory\Classes\FastFoodInv.uc for reference. You can see that once activated, the EatIt function is immediately called. For the moment, to make it simple, we’ll do something very similar. Copy the whole Activated state there over into our SprayPaintInv.uc file.

Rename the function EatIt to SprayNow. What will happen here is very similar to what happens when a bullet hole gets put on a wall. For a bullet, a collision test is done for the bullet and if hits a wall, a bullet hole splat is placed on the wall. For us, we need to do a collision test forward and then place the spray paint on the wall—if we hit anything. We’ll decide on a certain distance forward that the paint won’t work. If it’s too far, then the can won’t be used. But if it is close enough, we’ll place a splat, and use up a can.

Let’s look at the code now:

///////////////////////////////////////////////////////////////////////////////
// Active state: this inventory item is armed and ready to rock!
///////////////////////////////////////////////////////////////////////////////
state Activated
{
                function bool SprayNow()
                {
                                local vector StartPt, EndPt;
                                local vector X, Y, Z;
                                local vetor HitPos, HitNormal;

                                const SPRAY_DIST = 400;
 
                                // Find our start and end points
                                StartPt = Instigator.Location;
                                // Find out the direction the player&rsquo;s looking
GetAxes(Instigator.GetViewRotation(),X,Y,Z);
                                EndPt = StartPt + SPRAY_DIST *X;
 
                                // if we actually hit something, the allow a can to be used
                                if(Trace(HitPos, HitNormal, EndPt, StartPt, false) != None)
{
                spawn(class&rsquo;SprayPaintSplat&rsquo;, Owner, , StartPt, Rotator(X));
                ReduceAmount(1);
return true;
}
                                return false;
                }
Begin:
                SprayNow();
                GotoState('');
}

What’s going on here? We’ll let’s look at the more complicated things.

                            const SPRAY_DIST = 400;

                        // Find our start and end points
                        StartPt = Instigator.Location;
                        // Find out the direction the player&rsquo;s looking
GetAxes(Instigator.GetViewRotation(),X,Y,Z);
                        EndPt = StartPt + SPRAY_DIST *X;

This fancy block determines the two points between which we’ll do a collision test. We’re testing forward from the Dude’s position to a point out in space 200 units away. We first find the Dude’s position and store it. Then we find the direction he’s looking and put that into three vectors X, Y, and Z. Those are orthogonal axes. That means each one is 90 degrees off of the other two. The one that points forward for us, X, is the one we want. Conveniently, they’re also normalized. This means they just mean a direction and have a length of 1.0. What good is that? We’ll, we can simply take the X and multiply by a distance and then add it to our starting position… exactly like do right there in the code.

Pretty cool, huh?

                                // if we actually hit something, the allow a can to be used
                                if(Trace(HitPos, HitNormal, EndPt, StartPt, false) != None)

Do look at more examples of Trace. It’s a powerful function used a lot. It looks for collisions between those two points. The false at the end means it won’t stop on non-static actors (like pawns). That’s good for our present use. Also, we’re comparing it to None. We basically just want to know if it hit anything solid. If it’s not None, then we know it did, and we’re good to try to make our projector.

spawn(class’SprayPaintSplat’, Owner, , StartPt, Rotator(X));

This is the actual creation of the graffiti. We use StartPt because we know it’s the Dude’s position. Think of the splat like a movie projector. We don’t place it directly on the wall, we pull it back a little to make sure it can cast the image correctly. The Rotator(X) is sending in the direction. That will make it have a nice angle when we spray the paint obliquely on the wall.

Let’s build SuperPack and run our level to test.

Pick up a Spray Paint can and use it (Enter) near a wall. What happens? Well… it doesn’t exactly work as we wanted.

Problems:

  1. Seems like it’s got a colored diamond shape around it.
  2. Clips strangely sometimes on the side.
  3. Doesn’t seem to match the wall well.

These are all easily fixable, so let’s start knocking them down.

Problem 1 is because we’re using a Texture instead of a FinalBlend like before in the blood splats. We could either make a new package and make all the textures we want to use into FinalBlends. Or we could just change the blending mode that the projector gets drawn with. Add this line to the default props of SprayPaintSplat.

FrameBufferBlendingOp=PB_AlphaBlend

The next two problems are related so we’ll address them all at once. Add this line:

MaxTraceDistance=500

Splat was setting this for us and we never changed it. When extending from classes it’s important to know what it does and what it sets for you.

Build SuperPack.u and test the game. Everything should be pretty rockin’ at this point. You should be able to pick up the paint and use it near a wall and get a random graffiti image on the wall. You should be able to look up and down and cast it at crazy angles on the wall. Of course if you’re too far away from the point that you’re aiming at, it won’t let you spray it. You should be able to move a long way from the wall and try to use it, but nothing happens, and you don’t use up a can.

You should notice though, that after a while, the splat goes a way. That’s because it inherits from the same actors as the blood splats and the bullet holes. If you’ll recall from the game, they go away too after a while. I think for our cool paint powerup, because you only get one spray with each can, we should make the splats stay as long as you’re in that same level. Of course when you change levels and come back they won’t still be there, but we won’t destroy them after a time like the blood and bullet holes. How we change that? Well, if you look at the PostBeginPlay in Splat.uc, you’ll see that it calls SetTimer. If you then look at Timer you’ll see that it gets destroyed. What we need to do is handle the setup properly, but then not call the timer. Let’s add a PostBeginPlay to SprayPaintSplat.uc like this:

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
simulated function PostBeginPlay()
{
          AttachProjector();
          AbandonProjector();
}

Let’s rebuild again and test. Make some graffiti and then stare at it for a while to see if it goes away. It should stay there forever.

Fancier Code for the Graffiti

This next part is just more polish for the item. Right now you simply use the item and instantly graffiti appears. I think we should add a small delay to incorporate some sound effects.

Open SprayPaintInv.uc.

Near the top, just under the class defs, add a few more variables:

var Sound BallSound;

var Sound SpraySound;

In the default props fill these sounds in like so:

BallSound=Sound’MiscSounds.Props.doorknob_jiggle03’

SpraySound=Sound’MiscSounds.Props.TVStatic’

Again, I found those sounds just by digging through the sound packages for a while.

I was figuring that prior to the actual spray, it’d be cool if it sounded like the Dude was shaking the can up, ready to spray it. I found a sound that hopefully will sort of sound like the metal ball getting shaken in the paint can. Also, I thought it would be nice to hear an actual spraying noise when he makes the paint.

First, let’s add in the ball noise. We’ll add this in the begin section of the Activated state.

Begin:

          Instigator.PlaySound(BallSound, SLOT_Misc, 1.0,false,300,1.0);
          Sleep(2.0);
          SprayNow();
          GotoState(&lsquo;&rsquo;);

Save, build and test the game.

Well… it’s pretty cool… at least the noise gets played. But it doesn’t sound like the vigorous shaking that one usually does to prime a paint can. How about we play the sound 3 times and pitch it up some to sound faster? Add this variable to the top of the file with the sounds:

          var Sound BallSound;
          var Sound SpraySound;
          var int ShakeCount;

Now, add some code to the state like so:

Begin:

          ShakeCount=0;
MakeShake:
          Instigator.PlaySound(BallSound, SLOT_Misc, 1.0,false,300,2.0);
          Sleep(0.3);
          ShakeCount++;
          If(ShakeCount<3)
                   goto(&lsquo;MakeShake&rsquo;);

This resets the counter, marks the code where to start each time, plays the sound at a higher pitch, waits for 1/3 of a second, increments the counter, then repeats if the counter is not high enough. (I’ve completely removed the Sleep(2.0); line—at least, it’s turned into the Sleep(0.3); line.)

Let’s save, build and test again.

Cool! Sounds much better now. All we have to do now is add in the spraying noise.

That too will go in the same state block. We’ll put it after the shaking noises of course though.

This plays the spraying noise and waits a split second afterwards before throwing out the graffiti itself, just to make it feel better.

Again, save, build, and test it in the level.

Hmm…seems pretty good but the sound is a little too loud and not low enough pitch. With problems like this it’s probably better to change the sound itself. In our case though, because we’re using another sound for the job we’ll change it from the code side.

Here’s the new line:

Instigator.PlaySound(SpraySound, SLOT_Misc,0.5,false,300,2.0);

I jacked up the pitch and lowered the volume. Let’s test it again.

Cool! It all sounds and works pretty well. Realize that with the ‘shaking noise delay’ near the beginning, you could be walking, hit the Use key, and the graffiti won’t come out right when you used the item, it will be delayed by the sound. I like this behavior but you may want to change things.

At this point you have a neat new powerup with actual powers. You could add other affects to it or modify the affects we’ve created. I’d like to continue forward for those would like to add even more affects to the spray paint.

Advanced Enhancements to the Spray Paint

This could be another entire tutorial, but I’ll try to keep it under control and stop soon.

What if as you’re using the graffiti and a cop sees you, he arrests you? I like the sound of that. It’s not too bad to add, so let’s pop that in there.

All we really need to do is have the SprayPaintInv use the proper TimedMarker when spray the graffiti. We’ve encountered those before with the PartyBomb in Code Tutorial 5 so this should be a piece of cake.

We’re going to call the static NotifyControllers in TimedMarker instead here. If you look in P2Weapon it gets used in several places. It’s simply becase we don’t need to keep this object around, we really only want to use it right now.

In SprayPaintInv.uc inside the SprayNow function let’s add this function call:

                                if(Trace(HitPos, HitNormal, EndPt, StartPt, false) != None)
{
                spawn(class&rsquo;SprayPaintSplat&rsquo;, Owner, , StartPt, Rotator(X));
                class&rsquo;PawnBeatenMarker&rsquo;.static.NotifyControllersStatic(
                                Level,
                                Class&rsquo;PawnBeatenMarker&rsquo;,
                                FPSPawn(Instigator),
                                None,
                                Class&rsquo;PawnBeatenMarker&rsquo;.default.CollisionRadius,
                                StartPt);
                ReduceAmount(1);
return true;
}

It may look intimidating, but we need to send all those variable to this function simply because it’s a static and doesn’t have access to them like normal functions do. This uses the PawnBeatenMarker settings for telling pawns around it. The Baton, shovel, and foot use this in the game. Basically, if anyone sees you do it, they’ll react, but if they don’t, they’ll never no the difference. This works great for cops with this. If they see us do it, they’ll arrest us, otherwise, they’ll never turn around. The only weirdness is that bystanders that see us do it may run away screaming. But at least they’ll notice you doing it so it’s pretty cool.

That should be about it—all the work has already been done in that function and in the AI. Let’s save, build SuperPack.u.

Next, add a cop to your test level. Save and exit.

Now, test the new code in your level. Grab the spray paint and wait till you know the cop will be walking towards you and then spray it—he should get mad and try to arrest you! There’s a distance limit so he won’t notice you if you’re too far away.

Pretty cool, huh?

More Stuff for You

Now we’ve got a fully functioning spray paint can. I’m sure there’s much more you could do with it though. Here are a few random ideas that pop into my mind:

  1. Add a cheat to be able to give yourself a bunch to use them in existing POSTAL 2 levels (like the Party Bomb from the last tutorial).
  2. Some particle effects that come out like the HealthPipe when you spray the paint.
  3. Scaling the graffiti image based on how far or close you are to the wall.
  4. Perhaps also you could make different powerups to spray different graffiti.
  5. Or maybe you could make it load a given texture file out a certain folder so people could make their own graffiti.

Enjoy!

Nathan Fouts

Post a comment

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