Mostly Harmless

Legacy:ProjectileDestroyerVolume

From Unreal Wiki, The Unreal Engine Documentation Site
Jump to: navigation, search

This tutorial covers the creation of a custom class that involves some simple Replication. It forms part of a series of UnrealScript Lessons, and follows on from the Introduction To Replication.

Tarquin: Note – this tutorial is being written by a complete n00b when it comes to netcode – me. I would be very grateful if some experts could check this for acccuracy.

Before we begin, let's run down the behaviour we want from this class:

  • it's going to be a subclass of Volume (hence the name)
  • any projectile that enters the volume is destroyed
  • some sort of visual effect is created, such as a small explosion in the projectile's last spot

The obvious approach[edit]

A vital maxim when dealing with replication is:

Network-compatible code isn't "code plus netcode"

However, we're newbies, we don't know a thing, so let's begin with something basic that will work in a standalone game:

class ProjectileDestroyerVolume extends Volume;
 
event Touch( Actor Other )
{
  local xEmitter MyDestroyEffect;
 
  if( Projectile(Other) != None ) {
    MyDestroyEffect = Spawn(class'XEffects.SmallExplosion',self,,Other.Location);
    Other.Destroy();
  }
}

To use this script,

  1. Create A Subclass (you'll need to uncheck "show visible classes only" to expose the Brush >> Volume part of the hierarchy)
  2. Paste in the code
  3. Compile
  4. Create a room
  5. Add a volume of the new class – it should display in the right-click menu of the Add Volume in the toolbox
  6. Playtest!

What's this script doing? Well, we're using the Touch event, that is called whenever something touches the actor, in this case the brush shape of the volume. This event does the following:

  1. tries typecasting the touching actor to the class Projectile. If we don't get a 'None', then the cast has succeeded, and the actor is a Projectile or a child class
  2. In this case, we spawn a visual effect at the location of that actor. We're using a standard subclass of xEmitter. See creating actors and objects
  3. The incoming actor is destroyed.

Now you can try this script if you like. In a standalone botmatch game, it will work fine. But if you launch a local netgame, it won't – the puck disappears alright, but the puff of smoke simply won't show up.

Why? Well, it's the First Myth of Replication. The fact is that clients do mostly nothing. On the client side, the puck reaches the volume, but the volume's Touch() event isn't called. The volume actor just sits there, which is not very good. You see the puck being destroyed because it's destroyed on the server, and the server informs the clients of the destruction of actor and objects. You don't see the smoke because the server is not sending information about its creation – its RemoteRole is set to ROLE_None, which for the server means "don't replicate."

Simulation[edit]

We're now going to make a small change:

simulated event Touch( Actor Other )

It's important to be careful of Replication Myth Number 3: it doesn't mean that a function that runs on the server will be made to run on the client. In other words, this is NOT what happens: the server gets the Touch() event, and thinks "hey, I'd better tell the client to run this too!"

What the 'simulated' function syntax does is allow the event to run on a client, if the client finds the right conditions. Simulated functions can run in several places, but they might not be running at exactly the same time. Here's why:

  1. The player presses the fire button.
  2. Thanks to some other replication mojo, the client sends that information to the server.
  3. On the server, the translocator is fired.
  4. Still on the server, the translocator puck is spawned. That information is replicated to all clients which create a local "copy" of the puck for the players to see.
  5. There is now a translocator puck actor on the server and on the client. They are being moved independently, with the server keeping the client updated with location information from time to time (depending on bandwidth)
  6. Because of this, these two actors might not hit the volume at the same time!

This will work. However, there are two potential problems with is:

  • The explosion is being spawned in all cases. That includes a dedicated server – which is pointless.
  • The puck is destroyed in all cases. Destroying it on clients is redundant and possibly harmful. Redundant because when an actor is destroyed on a server, existing replication code ensures the clients destroy it too.

Wormbo: It's not really harmful. Only the authorative version of an actor can be destroyed. An exception are bNetTemporary actors, which actually have to be destroyed clientsidely as well.

Function Split[edit]

The answer to this problem is to test certain conditions about the machine we're running on in the Touch() event. The Netcode idioms page contains some hints of how to proceed.

This is what we want to happen:

Server Client
Destroy the puck Spawn a visual effect
The destruction of the actor is replicated to the client

Further development[edit]

There are more things you can do with this script. For example, you might want to try

  • adding sound
  • having it use the right kind of explosion for the projectile.

Recondite: maybe i'm missing something, but i would think that you would want the client to always destroy the puck locally at the same time that you spawn the explosion effect. otherwise, there could be lag issues with the puck destruction vs explosion. You _could_ put an if (Netmode != NM_DEDICATEDSERVER) then do the explosion. However, the projectile itself should be handling the destruction/effect–i.e. when u destroy an avril warhead, it spawns an explosion in the simulated destroy function. Otherwise, if u have some sort of fly-zapper volume with a custom effect, i would think u would want to force the destruction on the client at the same time as the explosion.

dataangel: The tutorial should probably start from getting it working and move up to working well, i.e. efficiently with no lag trouble ;) Although right now it has niether :P

Foxpaw: Recondite is right: the projectile should be destroyed locally and the explosion should be drawn normally as well. That is normal behaviour for projectiles (clients decide when it's destroyed and where to draw the effects, (otherwise it would look wierd if the explosion was somewhere other than where the projectile appeared to hit) and the server dishes out damage.

Most projectiles are bTearOff=true so the issue of the client destroying a projectile while it's still there on the server is somewhat moot. Only in cases of projectiles that are not predictable could this be a problem. Even then it likely wouldn't be a problem because the clients are BEHIND the server somewhat, so the projectile would be destroyed client side AFTER it had already been destroyed server-side. In fact, the server may have already destroyed the projectile by that point: put probrably not.

Also it might be better to call a "hitwall" event on the projectile instead of destroy, as that should cause the normal behaviour of the projectile when it hit a wall. (usually destruction and spawning of an explosion) You could check after the function returned to see if the projectile was not destroyed by the impact (ripper razors for instance) and then destroy it manually like the volume already does.

SuperApe: See also LimitationVolume.

Foxpaw: LimitationVolume would do the same thing, but this is a tutorial to show people how to create this for themself. I would think to code this like so, but it should be tested to see if I've slipped up:

class ProjectileDestroyerVolume extends Volume;
 
simulated event Touch(Actor Other)
{
  local xEmitter MyDestroyEffect;
 
  if(Projectile(Other) != None && !Other.bDeleteMe)
  {
    if (Level.Netmode != NM_DEDICATEDSERVER)
      MyDestroyEffect = Spawn(class'XEffects.SmallExplosion',self,,Other.Location);
 
    Other.Destroy();
  }
}

I added the bDeleteMe part because Touch will get called both on the volume and on the projectile, and it's possible (unlikely in this case) that the projectile destroyed itself in IT's touch event and hence might already be destroyed when we get here.

SuperApe: If this page is done, the Help Needed link to it should be removed from the Recent Changes page and any others out there.