Mostly Harmless

Legacy:Communication Between Objects

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

Objects in UnrealScript often need to be able to discover information about each other in order to actually interact with eachother in some way. For example.

  • A weapon needs to know how many charges are left in a powerup.
  • In a CTF the flag needs to know the team of the player that just touched it.
  • A player needs to know who fired the projectile that just killed them.
  • The guided redeemer shell needs to know the orientation of the player guiding it.

This is achieved by object references and will be better understood if we look at a few examples from the game itself.

The CTF Flag[edit]

Let's first take a look at how the CTF Flag in UT2004 uses references to figure out just who exactly is holding it and how it interacts with that person.

The Reference Itself[edit]

The code that tells the Flag who is holding it is not actually in the CTFFlag class at all. It is actually in a SuperClass called GameObject. In this class there are several important things that are essential for the CTF Flag to operate.

//The reference itself
var UnrealPawn      Holder;  //Holder has to be an UnrealPawn or any of it's subclasses
 
//Another reference, although not required, it will help out
var TeamPlayerReplicationInfo HolderPRI; //HolderPRI is simply the TeamPlayerReplicationInfo for the flags holder

These are the only variable-level references that are useful to this example so I won't bother going into detail with too many other aspects of the code.

Side Note

These references are also pretty useless unless we 'reference' an actual object that exists. What I mean by that is best explained by this:

var UnrealPawn MyPawn;
 
function PostBeginPlay()
{
    MyPawn.Health = 100;
}

Doing just this will actually create an Accessed None error in the log if it is executed by the engine. The reason for this is because declaring a variable as a reference is only good if you point that reference to something. Putting the following before trying to change the health of my Pawn in the previous pseudo-code does two things.

One: It will spawn an actor (in this case an UnrealPawn) and

Two: It will point MyPawn to the newly created actor.

This means that everytime you use MyPawn in the script, you're actually talking about the UnrealPawn that you spawned.

MyPawn = Spawn(class'UnrealGame.UnrealPawn');

Setting The Reference[edit]

Setting or Pointing the reference is what we need to do next, and the GameObject uses a SetHolder() function to do that.

When the SetHolder() function is called it will execute the following code:

function SetHolder(Controller C)
{
	local int i;
 
    //log(self$" setholder c="$c, 'GameObject');
    LogTaken(c);
    Holder = UnrealPawn(C.Pawn);
    Holder.DeactivateSpawnProtection();
    HolderPRI = TeamPlayerReplicationInfo(Holder.PlayerReplicationInfo);
    C.PlayerReplicationInfo.HasFlag = self;
	C.PlayerReplicationInfo.NetUpdateTime = Level.TimeSeconds - 1;
 
    GotoState('Held');
 
	// AI Related
	C.MoveTimer = -1;
	Holder.MakeNoise(2.0);
 
	// Track First Touch
 
	if (FirstTouch == None)
		FirstTouch = C;
 
	// Track Assists
 
	for (i=0;i<Assists.Length;i++)
		if (Assists[i] == C)
		  return;
 
	Assists.Length = Assists.Length+1;
  	Assists[Assists.Length-1] = C;
 
}

We will only concern ourselves with the first few lines of the code however. Firstly, the controller (the player) who took the flag will be logged. Then the Holder reference will be set with the following code.

Holder = UnrealPawn(C.Pawn);

Note that the Holder reference is being pointed to a Pawn that already exists, thus there is no need to spawn the pawn. If for some reason the pawn died when this function was called however, it would produce an error as the pawn in question would no longer exist.

If the Holder had spawn protection active, it would disable it when the flag is picked up allowing defenders to have a chance to kill the Holder of the flag. This is done by called the DeactivateSpawnProtection() method through the Holder reference.

Because Holder is pointed to UnrealPawn(C.Pawn), It actually calls that method within UnrealPawn(C.Pawn) so:

Holder.DeactivateSpawnProtection();

could very well be written as:

UnrealPawn(C.Pawn).DeactivateSpawnProtection();

They will do the same thing, but using references is more efficient, less messy and can also help with describing how your code works.

Next it will also assign HolderPRI to the PRI of the Holder in question. This is purely for efficiency and anti-mess purposes I would imagine.

If for whatever reason we wanted to add 100 health to the Holder of the flag when it is picked up, we could add something like the following before the end of the function.

Holder.Health = Holder.Health + 50; //Add 50 health to the Holders current health

A silly example[edit]

Using the BugEyedMonster class from Extending States again, let's suppose that we have a BugEyedQueen, and that when BugEyedMonster is very scared it runs towards her. We might have some sort of function RunToQueen() in BugEyedMonster's script.

The simplest thing might be to look for the Queen when we need to find her, with a foreach iterator: obtain a reference to the Queen, get her location and move towards it.

(example code here...)

This might be fine if the BUM only needs to do this once. However, if the Queen is also moving we have ourselves a case of the "pursuit problem" in maths (the one I could never solve – Tarquin), and BUM needs to know about her location more than once, maybe even every tick. Running a foreach iterator this frequently is too heavy for the engine, and there's a more efficient way of doing it:

Give the BUM a variable of type BugEyedQueen. This in fact means a variable that holds a reference to an object, and the class of the object must be BugEyedQueen, or a subclass. (Remember the Peppers And Pepper Grinders analogy: this variable does not indicate a class, it indicates an object of a certain class.) We could make it so the mapper has to set its value to point to the Queen object in the map:

var() BugEyedQueen MyQueen;

This requires the mapper to think, is bound to lead to confusion, recriminations and spilled milk. Docers often need to think for pammers ;-)

  • set the variable uneditable by declaring it with just var, no ()
  • in one of the begin play functions (PreBeginPlay, PostBeginPlay, I don't know which is appropriate (PostBeginPlay() gets my vote – EntropicLqd)), run the foreach code as above to find the Queen, and set MyQueen to point to it
  • whenever we need to find something out about the Queen, simply access local variable MyQueen, for example Destination = MyQueen.Location
I've just realised on reading the bit below that I essentially (and stupidly) rewrote it with BugEyedMonster above. I'll try and merge them later. I'll stick to the reactor example, it's by far the more sensible of the two – Tarquin
maybe a page on PreBeginPlay, PostBeginPlay would be useful, but I don't know what to call it. ­- Tarquin

º: have a look at the Creating Actors and Objects page. ("What exactly happens when I spawn an actor?") Is this what you were looking for or should there be more details? – Wormbo


Tarquin: snip from one of my forum posts:

The standard way round this is to only call ForEach once, like this:

Each actor that will need to call Bomb.AddTime gets a:

var bomb myBomb ;

(check my syntax, it might be var class<bomb> etc...)

Then one of the BeginPlay functions calls ForEach.AllActors and sets the local myBomb variable to point to the map's Bomb actor.

Then when you need to access the Bomb actor's function, grab it from myBomb.AddTime.

Comments[edit]

Tarquin Excellent idea, including this kind of information. Although, NOOBs will not have clue as to what you're referring to, and, how it would be implemented. I'll tell ya,...being a NOOB sucks. NFG

It's very vague for now, needs major work. :-) – Tarquin

Zedsquared chips in with a more general view:[edit]

If you want to have a master actor on your map that exchanges information with other slave actors which can be dynamically created during gameplay then the only way for those slaves to get a reference the master actor is for the slaves to scan through all the actors on the level when created ( i.e. run a foreach.allactors iteration in prebeginplay ) and identify the master actor (hopefully it will be the only one of it's type on the level or we're in trouble).

So, to put things in more concrete terms, imagine you have a mod where a reactor goes critical and will blow once the opposing team has pulled enough control rods out (meanwhile the defending team can push them back in).

Each player on has a small device (we'll call it an RCM or Reactor Core Monitor) which can display a reactor temperature which is going to be varying according to how many rods are in or out, this means that there's

no simple timer here, the state of the reactor depends on the actions of the players and this info has to come from the reactor

itself.

One way of doing this would be for the reactor object to scan all actors on the level each tick and each time it finds an RCM it would call an UpdateStatus function, like this:

var float temperature;
event tick( float deltatime)
 {
    local RCM monitor;
 
    modelheat(deltatime); // do calcs to see how hot we are
    if(temperature > critical_temperature) boom()
    else
      foreach allactors( class'RCM', monitor)
        monitor.UpdateStatus(temperature);
 }

Which is all well and good but somewhat inefficient as the foreach allactors iterator gets called every tick, this is slow and generally a Bad Thing.

Another way to approach this is to have the RCM's find out where the reactor is when they're created and then use that information to query the reactor themselves, like this:

  var myreactor reactor;
 
  function prebeginplay ()
  {
   // find the reactor and assign a pointer to it
   foreach allactors(class'myreactor', reactor);
  }
 
  event tick (float deltatime)
  {
   if(reactor !=none) UpdateStatus(reactor.temperature);
  }

The nasty slow iterator gets called only once per RCM actor (it'd probably be spawned as part of the players inventory on game login and that's when it scans to get a reference to the reactor) and all the reactor has to do is check how hot it's getting at regular intervals and set that value in it's temperature variable for the others to read... easy!

(and that mod ideas sounds like it might be fun... feel free to pinch it :-) )

Alternate + More Efficient Way[edit]

An alternative way that I have found that works is to have a reference in your 'slave' actors to the 'master' actor and when you spawn the slaves (the easiest way would be to spawn from the slave), set the 'master' reference in their properties to that of the 'master' actor on the map.

An Example:

//In the slave class
Class SlaveClass Extends SomeOtherActor;
var MasterActor MyMaster; //An empty reference when the slave is first created
//In the Master Class
Class MasterClass Extends SomeOtherActor;
 
function SpawnSlave()
{
   local SlaveClass MySlave;   
   MySlave = Spawn(class'YourPackage.SlaveClass) //plus whatever else you need to spawn >_<
   MySlave.MyMaster = Self //Set the empty reference MyMaster to reference the MasterClass    
}

I believe this to be far more efficient than going through the list of actors etc.


"Global" object references[edit]

class Foo extends Object;
 
var Object Bar;
 
event Whatever()
{
    if( default.Bar == None )
    {
        default.Bar = SpawnFindAssignWhatever();
        Bar = default.Bar;
    }
 
    if( default.Bar != None )
        default.Bar.Hello(self);
}
  • SpawnFindAssignWhatever(); will be called only once per game in the best case
  • All Foo's automatically gain access to Bar actor through default.Bar;
  • Newly created objects have their Bar variable initialized to what's in default.Bar;
  • Other objects can access Foo's bar through class'Foo'.default.Bar;

another example:

class Foo extends Object;
 
var Object Bar;
 
event Whatever()
{
    if( default.Bar != None )
        default.Bar.Hello(self);
}
class Bar extends Object;
 
event Whatever()
{
    if( class'Foo'.default.Bar == None )
        class'Foo'.default.Bar = self;
}
 
function Hello( Object o )
{
    //...
}

Related Topics[edit]

Comments[edit]

dataangel: I don't think this is correct:

"If you want to have a master actor on your map that exchanges information with other slave actors which can be dynamically created during gameplay then the only way for those slaves to get a reference the master actor is for the slaves to scan through all the actors on the level when created ( i.e. run a foreach.allactors iteration in prebeginplay ) and identify the master actor (hopefully it will be the only one of it's type on the level or we're in trouble)."

Couldn't you simply assign the slave's master when you create it? The same way game rules objects usually have a reference to the mutator that spawns them.

Fataloverdose yes, if it was the master that spawned the slave.But all the slaves would have to be spawned after the gameplay has started to be able get a valid refference to the master.

Ben

Well, what is the problem in the fact that the master didn't spawn the slaves ? Why don't we do like that:

The master has a list of all the slaves (empty at the begining) and a method "add_slave(slave newSlave)".

When a slave appears, in its prebeginplay() method, it find the reactor and do "pointorToTheReactorFound.add_slave(this)"

Like that the master update the slaves only when it changes.

mE: this is really confusing, and for some reason i think it's really important. Still it seems pretty simple, when i have my code working i may edit this down i think i know what i'm doing now.

Kartoshka: This page could use a good cleaning Category:Legacy Refactor Me. Also, one earlier question wasn't ever really answered. Which is a better place for the foreach iterator code – PreBeginPlay or PostBeginPlay?

MythOpus: I didn't really know there was a question like that... but here's my answer... I believe it's better it do any iterators in the PostBeginPlay as all the actors would have been created at that point so I believe it would be a better way of doing things. I will also look at refactoring this up later in the week as I have final's but am off thursday and friday. EDIT- I guess I'll also write up examples for the CTF Flag that the original author hasn't gotten around to yet heh.