I don't need to test my programs. I have an error-correcting modem.
Legacy:Assault Mapping Tutorial/Creating A Destroyable Objective
This page is a section of the Assault Mapping Tutorial. If you have arrived here from some other place, this page will likely make more sense if you start at the above link and read the tutorial through from beginning to end.
Previous Section: Legacy:Assault Mapping Tutorial/Getting Started
Creating a Destroyable Objective
As you might notice from the map creation directions, or from looking at the map itself, this base isn't that hard to get into. It doesn't even have a door! Our first objective in this tutorial, therefore, is going to be a destroyable door, that will (try to) protect the entrance to the stronghold.
There are a couple of different ways we can do destroyable objectives, but the one which is almost always what we'll want to use is a DestroyableObjective_SM, which uses the flexibility of static meshes to automatically adjust its representation within the world it inhabits.
The first step is to Add an Actor>>NavigationPoint>>JumpDest>>JumpSpot>>GameObjective>>DestroyableObjective>>DestroyableObjective_SM. This actor combines several different functions in one convenient package:
- It's a GameObjective. GameObjectives do several things:
- They have text strings which display on the HUD directing people to attack or defend them when the time comes.
- They automatically draw the little flashing HUD displays indicating where they are, how close to completion they are, and whether they're under attack at the moment.
- Bots automatically know when GameObjectives need to be attacked or defended, and will act accordingly.
- The DestroyableObjective_SM shows a static mesh, just like a StaticMesh actor does. Moreover, when this objective is the active one, this mesh will flash on the screen in addition to the HUD icon, showing players where they should be shooting.
- When the objective is destroyed, the DestroyableObjective_SM will also automatically change its static mesh to another one (generally, one that looks like a blown-up version of whatever it's supposed to be).
As you can see, the actor already does most of the work for us, there are only a few things we'll need to fiddle with. First, we'll deal with the properties which apply to any objective you might place in your map. Open the properties window for the new actor and set the following properties:
- Events->Tag = BaseDoor
- This property is used in many places, so you should generally make it something descriptive and easy to remember.
- GameObjective->ObjectiveName = the Base Door
- This is used to print location messages, so make it something that makes sense to a human.
- GameObjective->LocationPrefix = Near
- This is the text that's printed in front of the ObjectiveName when describing where somebody is (i.e. "Near the Base Door").
- GameObjective->LocationPostfix =
- This is the text that's printed after the ObjectiveName in location descriptions (for example, we might put "area" for "the Base Door area"). We'll just leave it at the default empty string.
- GameObjective->Score = 10
- How many points are awarded to the attacker who completes this objective?
- Assault->DrawDistThresHold = 0
- How close does an attacker have to be for the game to show the crosshairs and health bar of this objective (when it's visible) instead of the triangular icon? (0 means it's always shown as soon as the objective becomes visible, no matter how far away it is).
- Assault->Objective_Info_Attacker = Penetrate the Base's Outer Defenses
- The message shown on the screen to attackers when this is the active objective.
- Assault->Objective_Info_Defender = Prevent the Attackers from Infiltrating the Base
- The message shown to defenders when this is the active objective.
- Assault->ObjectiveDescription = Destroy the door to enter the base
- A brief message of what people need to do to accomplish this goal.
- GameObjective->DestructionMessage = Base Outer Defenses Breached!
- The message displayed to both sides when the objective has been completed.
Now for the bits which are specific to the DestroyableObjective_SM class. You've probably already noticed that this actor starts out with a default untextured box mesh, which we will want to change. For our purposes, we'll just borrow from RobotFactory, which has a nice door mesh we can use (and more importantly, a destroyed version we can use for the destroyed mesh). This mesh can be found in the Plutonic_BP2_static package as Door.FactoryGateDoor, so go into the DestroyableObjective_SM actor's properties and set the resource property Display->StaticMesh to Plutonic_BP2_static.Door.FactoryGateDoor.
As you might notice, the door from RobotFactory is a bit large for our purposes, so we'll scale it down to about half size by setting the Display->DrawScale property to 0.55. Now we can position it into place over the entrance to the base. It's still a bit taller than we need, but that's ok. Just move it down so the top of the door meets the top of the door frame, and we'll let the extra stay hidden under the floor.
Next, we need to set the destroyed version. The destroyed version of the factory door from RobotFactory is Plutonic_BP2_static.Factory.door_destroyed, so in the DestroyableObjective_SM Properties, simply set the resource property DestroyableObjective_SM->DestroyedStaticMesh to Plutonic_BP2_static.Factory.door_destroyed. That was easy, wasn't it?
Now would be a good time to check to make sure that our door will look right when it's been blown up. When the destroyable objective is destroyed, it simply changes the Display->StaticMesh it's using to the value of DestroyableObjective_SM->DestroyedStaticMesh instead, so we can do the same thing in the editor to check how it will look. Just temporarily set Display->StaticMesh to Plutonic_BP2_static.Factory.door_destroyed and take a look. The particularly astute here will notice that something doesn't look right. The destroyed door actually doesn't line up the same way as the normal one does.
Did we do something wrong? Nope. Believe it or not, Epic screwed up, and if you look closely at RobotFactory while you're playing it, it has the same problem. The origin of the destroyed door mesh is at the bottom, but the origin of the regular door mesh is in the center, so they don't line up. This illustrates an important point when designing meshes for destroyable objectives: Make sure their origins match up, because you can't adjust this in the actor. Also note that since the actor just changes the setting for the Display->StaticMesh property when it gets destroyed, all of the other settings will be applied to the different mesh as well. This means, for example, that any skins you use in Display->Skins to change the appearance of the original mesh will also be applied to the destroyed mesh when it gets put in place, so if you're going to use your own skins, you'll need to make sure that the two meshes map their textures in compatible ways.
In any case, the glitch with the destroyed door mesh is annoying, but we'll leave it for now. For a more polished level, you'll probably want to make sure things like this are fixed (by creating your own meshes correctly, or by loading up the broken mesh in a 3D modeller, adjusting the origin, and saving the fixed version in your own package to use instead (Remember: as tempting as it may be, Do Not Alter Default Packages)). Anyway, moving along...
The last thing we need to do is to tell the game how much punishment our door will be able to take before finally giving up the ghost. This is done by setting the DestroyableObjective->DamageCapacity property to the amount of damage it can take. For our purposes, let's set it to about 2000, which should be enough to make somebody with a rocket launcher work a bit, but still not be impossibly tough to get through.
In general, there are a couple of other properties one may want to fiddle with as well:
- This determines where bots will aim when trying to shoot this objective, relative to its origin. For our purposes here, (0,0,0) is fine, since that's roughly the middle of the door, but if the origin of your static mesh isn't actually in the mesh, or there's something else covering that part of it, you may want to ajust this so the bots know to shoot some other part of it instead. (Note that the crosshairs displayed to players when they approach the objective are always shown at (0,0,0), and as far as I know that can't be changed, so you will generally want to put the mesh's origin where you want people to shoot anyway, so that it looks right to human players)
- These allow you to set a threshold at which the objective will trigger an event once it has taken a certain amount of damage, but before it actually gets to the point of blowing up. This can be used, for example, to trigger an "almost destroyed" warning, or to make things look more damaged as the attackers get closer to actually breaking through.
There, we now have a door which can be blown up by the attackers as one of the objectives for the assault. Go ahead and fire up the game and give it a try!
Ok, so we've got our door, and we can blow it up, and the game takes care of all the proper objectivey things like announcing it and showing it on the HUD and so forth, but there's still something missing. When the door gets blown up, sure, the mesh changes to a blown up version, but that's it. In the words of Marvin the Martian, "Where's the kaboom?! There's supposed to be an earth-shattering kaboom!"
Obviously, we're going to want to make some things happen when the door goes kablooey. All GameObjectives will trigger their Events->Event when they have been completed (in this case, when the door's been destroyed), so go into the properties for the DestroyableObjective_SM object, and set the Events->Event property to some appropriate name, let's say "DoorDestroyed". Now we'll use a ScriptedTrigger to perform a few different actions for us when that event gets triggered. Add an Actor>>Keypoint>>AIScript>>ScriptedSequence>>ScriptedTrigger to the map near the door (so it's easy to find), bring up its properties, and add an inline object or three to the AIScript->Actions property to define what this ScriptedTrigger should do. We'll want something along the following lines:
- ExternalEvent = DoorDestroyed
- Event = DoorExplosion
- SoundEmitterActorTag = BaseDoor
- SoundVolumeScale = 2.0
- SoundRadiusScale = 1.0
- SoundPitchScale = 1.0
This will trigger the explosion event, and play the explosion sound. Note that the SoundEmitterActorTag in the ACTION_PlayExplosionSound action is the same as the tag we gave to the door objective earlier. This means that the explosion will sound like it's coming from that object. Now, for the actual explosion, we'll do two things: We'll need to set up an Emitter to show the visual explosion, and we'll also create a ViewShaker actor to shake the camera a bit, to give it a bit of "oomph". But wait a minute, there's a problem here..
We could just create the Emitter and the ViewShaker, and set their tags to "DoorExplosion", so they get triggered by the ScriptedTrigger. In fact, this would work fine, in single-player mode. Unfortunately, this doesn't work in network play. It turns out that in client-server mode, ScriptedTriggers only run on the server. Things like Emitters and ViewShakers, on the other hand, only run on the clients, so we need some way for the ScriptedTrigger on the server to trigger an event on the clients to show the explosion. The answer to this is a NetworkTrigger.
NetworkTriggers are very easy to use. Simply add an Actor>>NetworkTrigger to the map (doesn't matter where, but let's keep it close to the ScriptedTrigger), and set its Events->Tag to "DoorExplosion" (the event that happens on the server). Then, set its Events->Event to "DoorExplosionClient" (an event we want to happen on the client side). Now whenever the server triggers a DoorExplosion event, the NetworkTrigger will trigger a DoorExplosionClient event on the client side of things.
Now add an Actor>>Emitter and place it just a little bit in front of the door. Open up its properties, and set its Events->Tag to "DoorExplosionClient". Now, go to Emitter->Emitters and add an inline object of a new SpriteEmitter. In the new SpriteEmitter, you'll want to set the following properties:
- Color->Opacity = 0.4
- Fading->FadeOut = True
- Fading->FadeOutStartTime = 0.5
- Local->Disabled = True
- Local->RespawnDeadParticles = False
- Location->StartLocationShape = PTLS_Sphere
- Location->SphereRadiusRange = (Min=-300, Max=300)
- Size->UseSizeScale = True
- Size->UseRegularSizeScale = False
- Size->SizeScale = (RlativeSize=10, RelativeTime=1)
- Spawning->InitialParticlesPerSecond = 10
- Texture->Texture = Texture'VMParticleTextures.TankFiringP.CloudParticleOrangeBMPtex'
- Texture->TextureUSubdivisions = 4
- Texture->TextureVSubdivisions = 4
- Texture->UseRandomSubdivision = True
- Time->LifetimeRange = (Min=1, Max=1)
This will create a visual blast when the door is destroyed. Obviously, we could get a lot more sophisticated than this, including making a few smaller explosions around the area, varying the colors, making smoke, and throwing door bits around with a MeshEmitter if we wanted to, but this isn't a tutorial on Emitters, so we'll leave that as an exercise for another time.
Now we need to add an Actor>>Triggers>>ViewShaker. Place it next to the emitter, open its properties, and set its Events->Tag property to "DoorExplosionClient". We could also customize what sort of shaking to produce by modifying the other properties under the ViewShaker section of the properties window, but for now the defaults will work fine.
We now have a door that goes "Boom!" like any respectable door should when it gets blown up.
Proceed to the next section: Legacy:Assault Mapping Tutorial/PlayerStarts And The PlayerSpawnManager
This tutorial was originally written and contributed to the Wiki community by Foogod. Questions and comments are welcome! Please either leave them on my personal page or at the bottom of the relevant tutorial page. If you see something which is wrong or unclear and you think needs some correction, please feel free to edit the text directly (that's what the Wiki is for!), but please try to keep the tutorial flow intact (no thread discussions in the middle of the tutorial text, please).
Note: To reduce clutter, comments on the tutorial pages will be periodically moved to the Legacy:Assault Mapping Tutorial/Discussion page. Look there for older comments and thread discussions.